120 95 14MB
German Pages 304 Year 2023
Janet Nagel
Optimierung von Energieversorgungssystemen Modellierung, Programmierung und Analyse
Optimierung von Energieversorgungssystemen
Janet Nagel
Optimierung von Energieversorgungssystemen Modellierung, Programmierung und Analyse
Janet Nagel INERI Berlin, Deutschland
ISBN 978-3-031-36354-2 ISBN 978-3-031-36355-9 (eBook) https://doi.org/10.1007/978-3-031-36355-9 Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über https://portal.dnb.de abrufbar. © Springer Nature Switzerland AG 2019, 2023 Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Jede Verwertung, die nicht ausdrücklich vom Urheberrechtsgesetz zugelassen ist, bedarf der vorherigen Zustimmung des Verlags. Das gilt insbesondere für Vervielfältigungen, Bearbeitungen, Übersetzungen, Mikroverfilmungen und die Einspeicherung und Verarbeitung in elektronischen Systemen. Die Wiedergabe von allgemein beschreibenden Bezeichnungen, Marken, Unternehmensnamen etc. in diesem Werk bedeutet nicht, dass diese frei durch jedermann benutzt werden dürfen. Die Berechtigung zur Benutzung unterliegt, auch ohne gesonderten Hinweis hierzu, den Regeln des Markenrechts. Die Rechte des jeweiligen Zeicheninhabers sind zu beachten. Der Verlag, die Autoren und die Herausgeber gehen davon aus, dass die Angaben und Informationen in diesem Werk zum Zeitpunkt der Veröffentlichung vollständig und korrekt sind. Weder der Verlag noch die Autoren oder die Herausgeber übernehmen, ausdrücklich oder implizit, Gewähr für den Inhalt des Werkes, etwaige Fehler oder Äußerungen. Der Verlag bleibt im Hinblick auf geografische Zuordnungen und Gebietsbezeichnungen in veröffentlichten Karten und Institutionsadressen neutral. Planung/Lektorat: Daniel Froehlich Springer Vieweg ist ein Imprint der eingetragenen Gesellschaft Springer Nature Switzerland AG und ist ein Teil von Springer Nature. Die Anschrift der Gesellschaft ist: Gewerbestrasse 11, 6330 Cham, Switzerland Das Papier dieses Produkts ist recyclebar.
Vorwort
Mit diesem Buch bleibe ich dem Thema Energie treu. Ich bin damit zu meinen Wurzeln der Energiemodellierung und Optimierung von Energieversorgungssystemen zurückgekehrt. Bereits vor über 20 Jahren habe ich mich das erste Mal mit dem Thema der Optimierung von Energieversorgungssystemen beschäftigt. Auch damals wurden je nach Fragestellung unterschiedlichste Modelle umgesetzt, manche mehr heuristisch, andere agentenbasiert bis hin zur klassischen linearen Optimierung. Im Zuge der Energiewende hat dieses Thema an Bedeutung weiter hinzugewonnen. Unzählige Fragestellungen weltweit warten darauf, beantwortet zu werden, um Lösungen für die zukünftige Gestaltung der Energieversorgung zur Deckung der Energienachfrage der Weltbevölkerung zu erhalten. Durch einen Zufall habe ich Anfang 2017 bei meinen Recherchen von dem spannenden Projekt bzw. Programmsystem „Open Energy System Modelling Framework“ (OEMOF) erfahren. Der Einsatz dieses Tools kann dazu beitragen, schneller Antworten auf die Vielzahl an Energie-Fragestellungen zu erhalten und damit die Umsetzung der Energiewende schneller voranzutreiben. Es handelt sich bei OEMOF um einen generischen Ansatz zur Modellierung und anschließenden Optimierung von Energieversorgungssystemen. Generische, also allgemeingültige Ansätze spielen in der objektorientierten Programmierung eine wichtige Rolle. Das generische Konzept findet sich bei OEMOF bspw. in der allgemeingültigen Beschreibung einzelner Klassen und Funktionen wieder. Hierdurch ist OEMOF auf verschiedenste Fragestellungen anwendbar. Bereits in der Vergangenheit, vor ca. 15 Jahren, habe ich mich mit generischen Ansätzen im Zusammenhang mit Datenbanken beschäftigt. Ich hatte den Gedanken, Datenbanken nicht jedes Mal, insbesondere bei Datenmodell-Anpassungen, neu zu programmieren, sondern generisch das Datenmodell anzupassen. Hinweise zu den Veröffentlichungen zu diesem Thema befinden sich unter http://www.risa.eu/de/safetyanalyses/publications.php. Das Projekt OEMOF wird ebenfalls von der Vision getragen, dass Optimierer von Energieversorgungssystemen zukünftig nicht mehr selbst Optimierungsmodelle programmieren müssen, sondern sich ausschließlich auf die Modellierung ihres betrachteten Energieversorgungssystems konzentrieren können. Noch befindet sich dieses Programmsystem im Aufbau. Da es sich hierbei um ein Open-Source-Projekt handelt, ist jeder eingeladen, daran mitzuentwickeln und das eigene Know-how einzubringen. V
VI
Vorwort
Das Programmsystem wird durch die Community ständig weiterentwickelt. Während der Zeit der Erstellung dieses Buches konnte ein wichtiger Schritt bei der Weiterentwicklung von OEMOF erreicht werden und ein neues Release v0.2.0 herausgebracht werden. Da die Veröffentlichung dieses Releases erst kurz vor Fertigstellung des Buches erschienen ist, konnten noch keine Erfahrungen damit gesammelt werden. Das Buch stellt einen ersten Schritt dar, um die im Internet vorhandene Dokumentation der OEMOF-Community zusammenzuführen und dem Anwender zugänglich zu machen. Als ich anfing, mich mit dem Projekt zu beschäftigen, war es mir wichtig, zu verstehen, wie die Programmierung hinter dem Modell erfolgt ist. Aus diesem Grund werden sowohl der Programmcode als auch Beispiele für die Anwendung von OEMOF vorgestellt. Die Entwickler von OEMOF, insbesondere jene, die das Projekt gestartet haben und immer noch dabei sind, haben sicherlich ihre eigene Sicht auf das Programmsystem OEMOF und wie darüber geschrieben werden sollte. Schließlich haben sie das Konzept für das Tool entwickelt und mit Leben gefüllt. Damit ist dieses Buch ein Buch „über“ OEMOF und nicht ein Buch „von“ OEMOF, also der OEMOF-Community. Und doch denke ich, dass es dabei hilft, den Einstieg in die Arbeit mit OEMOF zu erleichtern. In meiner eigenen Arbeit an der Hochschule habe ich erfahren, dass Modellierern, die sich bis dahin noch nicht mit Optimierungsfragestellungen beschäftigt haben, der Einstieg in die Arbeit mit diesem Tool schwerfällt. Auch, wenn vielleicht nicht alle Fragen beantwortet werden können, hoffe ich, dass mit diesem Buch ein leichterer Zugang zu dem Programmsystem OEMOF möglich ist. Bücher leben davon, dass Wissen von Experten zu bestimmten Themen mit eingebracht wird. Diese Erfahrung habe ich bei all meinen bisherigen Büchern gemacht. In diesem Zusammenhang ist zunächst die OEMOF-Community zu nennen. Durch interne Workshops, Diskussionen und Anmerkungen zu Ausschnitten des Manuskriptes war es möglich, viele Teile der Umsetzung von OEMOF zu verstehen und in dieses Buch einzubringen, wofür ich mich bedanken möchte. Ganz besonderes möchte ich an dieser Stelle Dr. Michael Stöhr der Firma B.A.U.M. Consult GmbH für seine aktive Unterstützung danken. Durch seine Anregungen und den konstruktiven Austausch konnten wertvolle Ausführungen zu dem gesamten Thema „Optimierung von Energieversorgungssystemen“ in das Buch eingebunden werden. Insbesondere seine Liebe zum Detail und den Wunsch, mathematische Zusammenhänge präzise darzulegen, führten dazu, dass ich mich mit vielen Themen noch tiefer auseinandersetzte. Dabei wissen wir alle, dass die Zeit im beruflichen Alltag extrem eng bemessen ist und trotzdem hat Herr Dr. Stöhr nicht gezögert, mir sein Expertenwissen zur Verfügung zu stellen. Es stand von Anfang fest, dass dieses Buch in englischer Sprache erscheinen soll. So ist Prof. Dr. Verena Jung der AKAD University neu hinzugekommen. Sie ist Professorin für Sprachwissenschaft und Übersetzungswissenschaft. Aufgrund ihres professionellen Sprachwissens konnte das Manuskript in die englische Sprache übersetzt werden und steht so einer größeren Leserschaft zu Verfügung. Auch ihr gilt mein besonderer Dank.
Vorwort
VII
Das Thema „Entwicklung von Szenarien“ spielt bei der Erstellung von Energiemodellen eine zentrale Rolle. In Diskussionen mit Detlef Schumann von BridgingIT GmbH erhielt ich im Rahmen unseres BDI Arbeitskreises „Internet der Energie“ Ansätze zur Gestaltung dieses Kapitels. Für die inspirativen Gespräche und Impulse möchte ich Herrn Schumann sehr danken. Da dieses Buch viel mit Programmcode in der Programmiersprache Python zu tun hat, habe ich Henrik Fahlke, Entwickler, als Spezialisten hinzugezogen. Er stand mir für spezifische Fragen zu Python zur Verfügung, ebenso wie zur Entwicklung eines eigenen kleinen Beispiels. Für seine Zeit und seinen persönlichen Einsatz möchte ich ihm sehr danken. Weiterhin hat mich wieder meine treue fachliche Begleiterin Dr. Silvia Porstmann, Geschäftsführerin der Seramun Diagnostica GmbH, bei der Fertigstellung des Buches unterstützt. Insbesondere das Lesen der englischen Übersetzung war bei diesem Buch von besonderer Wichtigkeit. Für ihre unbändige Neugier und ihren intensiven Einsatz möchte ich mich sehr herzlicher bedanken. Sie war bereits bei den letzten beiden Büchern beteiligt. Ein Buchprojekt lebt auch durch einen guten Verlag an der Seite. Im Springer Verlag habe ich einen sehr guten und kompetenten Partner zur Umsetzung meiner Buch-Idee gefunden. Für die Unterstützung aller Beteiligten möchte ich mich sehr herzlich bedanken. Besonders hervorheben möchte ich meine Familie. Neben der aktiven Unterstützung wurde großes Verständnis für die vielen Stunden am Rechner in meiner Freizeit aufgebracht. Ohne diesen Zuspruch wäre die Umsetzung all meiner Buchprojekte nicht möglich gewesen. Danke für die Kraft; die ihr mir gebt. Nun bleibt mir abschließend, allen interessierten Lesern, Energie-Modellierern und zukünftigen Entwicklern von OEMOF viel Spaß mit dieser Lektüre und einen guten Zugang zu dem Tool zu wünschen. Bringen wir gemeinsam die Energiewende nach vorne! Berlin, Deutschland April 2018
Janet Nagel
Vorwort 2023
Zu Zeiten des ersten Vorworts schien die Welt aus der heutigen Sicht noch ganz in Ordnung zu sein. Man versuchte, das Thema Energiewende in den Griff zu bekommen und beschäftigte sich vorrangig mit den damals verfügbaren bzw. sich in der Entwicklung befindenden Technologien. Fünf Jahre später sieht die Welt ganz anders aus. Eine Pandemie wurde weltweit durchlebt, ein Krieg zwischen Russland und der Ukraine ist entfacht und auch an vielen anderen Stellen der Welt schwelen neue Konflikte. Dabei steht das Thema Energie noch stärker als in der Vergangenheit im Fokus, heute verstärkt unter dem Aspekt der sicheren Versorgung. Denn nach dem Angriffskriegs Russlands gegen die Ukraine und den nachfolgenden umfassenden Sanktionen der westlichen Welt gegen Russland ist die Gasversorgung Deutschlands durch Russland nicht mehr gegeben. Dies hat zu einem drastischen Umdenken in Deutschland geführt, welches nun erst einmal versucht, die Abhängigkeiten von „alten“ Energielieferanten zu lösen und neue Versorgungsmöglichkeiten, bspw. auch für Wasserstoff, aufzubauen. Vor diesem Hintergrund steht die Modellierung und damit die Entscheidungsfindung über die Auswahl der besten Möglichkeiten einer sicheren, unabhängigen und trotzdem „sauberen“ Energieversorgung im Blickpunkt. Es haben sich neue Fragestellungen und Rahmenbedingungen ergeben, die neue Modelle und neue Berechnungen erforderlich machen. Das hier als Beispiel vorgestellte Programmsystem als Open Energy Modelling Framework (OEMOF) kann nach meiner festen Überzeugung in Zukunft einen großen Beitrag zur Bewältigung der vielfältigen und schweren Aufgabe leisten. Beschäftigt mit anderen Dingen, habe ich über ein weiteres Buch zur Optimierung von Energieversorgungssystemen nicht nachgedacht. Umso mehr erfreute es mich, als ich im Herbst 2022 einen Blick in mein E-Mail-Account warf und sich darin eine E-Mail des Springer Verlages mit der Idee der Übersetzung der englischen Ausgabe ins deutsche befand. Es sollte ein automatisiertes Übersetzungsprogramm erprobt und evaluiert werden. Da dieses Buch bereits im Original auf Deutsch bestand, war die Veröffentlichung einer deutschen Ausgabe auch ohne Übersetzung problemlos möglich. Eine größere Heraus forderung bestand nun darin, sich mit den neueren Entwicklungen von OEMOF auseinanderzusetzen. IX
X
Vorwort 2023
Schon bei der englischen Ausgabe war es nicht mein Ziel, OEMOF eins zu eins abzubilden. Vielmehr soll ein Verständnis für die Umsetzung eines generischen Ansatzes zur Abbildung von Optimierungsproblemen im Energiesektor mit der Programmierungssprache Python gegeben werden. Aus diesem Grund sollen sowohl durch die englische als die deutsche Ausgabe nur erste beispielhafte Einblicke in die Strukturen und Programmiermöglichkeiten in OEMOF geschaffen werden. Denn uns ist allen bewusst, dass sich Programmsysteme und Programmcodes insbesondere in open source Projekten sehr schnell ändern. Die Grundstrukturen bleiben jedoch gleich, sodass der interessierte Leser einen Überblick darüber bekommt, wie ein generischer Ansatz in OEMOF umgesetzt werden kann. Somit ist mit dieser deutschen Ausgabe der 2019 veröffentlichten englischen Version keine wesentliche Überarbeitung der Inhalte erfolgt. Auf Änderungen im Programmsystem OEMOF wird an den jeweiligen Stellen hingewiesen. Es wird jedoch kein Anspruch auf Vollständigkeit erhoben. Insbesondere der dargestellte Quellcode entspricht dem Stand der ersten englischen Ausgabe in 2019. Ich hoffe trotzdem, dass dieses Buch für den Leser hilfreich ist, um sich mit OEMOF und der Umsetzung in der Programmiersprache Python auseinanderzusetzen und erste Ideen für ein eigenes Energiemodell zu erhalten. Aktuelle Informationen können direkt der Internetseite von OEMOF entnommen werden, die durch die in diesem Buch dargestellten Beschreibungen und Inhalte dann leichter verständlich sein sollten. Dieses Buch soll somit dazu beitragen, sich intensiv mit der allgemeinen Struktur und Umsetzungsmöglichkeit zur Modellierung von Optimierungsproblemen in OEMOF auseinanderzusetzen. Ich wünsche dem interessierten Leser viel Spaß bei der Arbeit mit diesem Buch und hoffe, dass mit dem folgenden Energiemodell ein wichtiger Beitrag für eine sichere, unabhängige und trotzdem „saubere“ Energieversorgung unseres Landes geleistet werden kann. Ihre Janet Nagel Berlin, Deutschland Juni 2023
Janet Nagel
Inhaltsverzeichnis
1 Das Projekt OEMOF ���������������������������������������������������������������������������������������������� 1 1.1 Techniken zur Entwicklung von Strategien und Handlungsoptionen ������������ 5 1.2 Worauf basiert OEMOF?�������������������������������������������������������������������������������� 8 1.3 Linear Programming (LP) und Mixed-Integer Linear Programming (MILP) ������������������������������������������������������������������������������������ 13 1.3.1 Linear Programming (LP)������������������������������������������������������������������ 18 1.3.2 Beispiel „Biogasproduktion“�������������������������������������������������������������� 18 1.3.3 Formaler Aufbau LP �������������������������������������������������������������������������� 25 1.3.4 Mixed-Integer Linear Programming (MILP)�������������������������������������� 26 1.3.5 Sensitivitätsanalysen �������������������������������������������������������������������������� 29 Literatur�������������������������������������������������������������������������������������������������������������������� 29 2 Das generische Basismodell in OEMOF���������������������������������������������������������������� 31 2.1 Grundlagen in Python ������������������������������������������������������������������������������������ 32 2.1.1 Klassen, Objekte und Methoden �������������������������������������������������������� 34 2.1.2 Daten �������������������������������������������������������������������������������������������������� 36 2.1.3 Erstellen von Klassen, Objekten und Methoden�������������������������������� 38 2.1.4 Klassenvariablen �������������������������������������������������������������������������������� 41 2.1.5 Vererbung�������������������������������������������������������������������������������������������� 43 2.1.6 Ein eigenes Beispiel in Python umsetzen ������������������������������������������ 43 2.2 Strukturaufbau des generischen Models in OEMOF�������������������������������������� 49 2.3 Libraries in OEMOF �������������������������������������������������������������������������������������� 53 2.3.1 Bibliothek „oemof-network“�������������������������������������������������������������� 54 2.3.2 Bibliothek „oemof-solph“������������������������������������������������������������������ 55 2.3.3 Bibliothek „oemof-outputlib“ ������������������������������������������������������������ 60 2.3.4 Bibliothek „feedinlib“������������������������������������������������������������������������ 61 2.3.5 Bibliothek „demandlib“���������������������������������������������������������������������� 65 2.4 Zentrale Packages und Module in OEMOF���������������������������������������������������� 65 2.4.1 Module energy_system ���������������������������������������������������������������������� 66 2.4.2 Module groupings ������������������������������������������������������������������������������ 67 2.4.3 oemof.outputlib package�������������������������������������������������������������������� 69 XI
XII
Inhaltsverzeichnis
2.4.4 oemof.solph package�������������������������������������������������������������������������� 70 2.4.5 oemof.tools package �������������������������������������������������������������������������� 90 2.5 Vorgehen bei der Entwicklung eines eigenen Modells ���������������������������������� 91 Literatur�������������������������������������������������������������������������������������������������������������������� 97 3 Mathematische Beschreibung der Objekte������������������������������������������������������������ 99 3.1 Mathematisches Gleichungssystem im Modul „blocks.py“ �������������������������� 109 3.1.1 class Flow(SimpleBlock)�������������������������������������������������������������������� 110 3.1.2 class InvestmentFlow(SimpleBlock)�������������������������������������������������� 117 3.1.3 class Bus(SimpleBlock)���������������������������������������������������������������������� 121 3.1.4 class Transformer(SimpleBlock)�������������������������������������������������������� 122 3.1.5 class NonConvexFlow(SimpleBlock) ������������������������������������������������ 123 3.2 Mathematisches Gleichungssystem im Modul „components.py“������������������ 128 3.2.1 class GenericStorage(network.Transformer)�������������������������������������� 128 3.2.2 class GenericStorageBlock(SimpleBlock)������������������������������������������ 131 3.2.3 class GenericInvestmentStorageBlock(SimpleBlock)������������������������ 132 3.2.4 class GenericCHP(network.Transformer)������������������������������������������ 136 3.2.5 class GenericCHPBlock(SimpleBlock)���������������������������������������������� 138 3.2.6 class ExtractionTurbineCHP(solph_Transformer)������������������������������ 144 3.2.7 class ExtractionTurbineCHPBlock(SimpleBlock)������������������������������ 145 3.3 Mathematisches Gleichungssystem im Modul „constraints.py“�������������������� 148 3.4 Mathematisches Gleichungssystem im Modul „custom.py“�������������������������� 150 3.4.1 class ElectricalLineBlock(SimpleBlock)�������������������������������������������� 150 3.4.2 GenericCAESBlock(SimpleBlock)���������������������������������������������������� 151 3.5 Mathematisches Gleichungssystem im Modul „models.py“�������������������������� 158 Literatur�������������������������������������������������������������������������������������������������������������������� 159 4 Modellierung in OEMOF�������������������������������������������������������������������������������������� 161 4.1 Die Arbeit mit dem Framework von OEMOF������������������������������������������������ 161 4.1.1 Eine Vertiefung in Python Programmierung�������������������������������������� 162 4.1.2 Oemof-energy_system������������������������������������������������������������������������ 167 4.1.3 Oemof-groupings�������������������������������������������������������������������������������� 172 4.1.4 Oemof-network ���������������������������������������������������������������������������������� 177 4.1.5 Oemof-solph �������������������������������������������������������������������������������������� 180 4.1.6 Oemof-outputlib���������������������������������������������������������������������������������� 190 4.1.7 Oemof-feedinlib���������������������������������������������������������������������������������� 201 4.1.8 Oemof-demandlib ������������������������������������������������������������������������������ 215 4.2 Beschaffung von Daten ���������������������������������������������������������������������������������� 217 4.3 Das Programsystem Spyder���������������������������������������������������������������������������� 222 4.3.1 Spyder Editor�������������������������������������������������������������������������������������� 223 4.3.2 Variable Explorer�������������������������������������������������������������������������������� 225 Literatur�������������������������������������������������������������������������������������������������������������������� 226
Inhaltsverzeichnis
XIII
5 Getting startet�������������������������������������������������������������������������������������������������������� 229 5.1 Installation von OEMOF�������������������������������������������������������������������������������� 229 5.2 Mein erstes Modell ���������������������������������������������������������������������������������������� 238 5.3 Definition von Szenarien�������������������������������������������������������������������������������� 240 Literatur�������������������������������������������������������������������������������������������������������������������� 254 6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF������������������������������������������������������������������������������������������������������������ 257 6.1 Studie „Elektrifizierung von Landmaschinen und Einsatz von Photovoltaik“�������������������������������������������������������������������������������������������������� 257 6.1.1 Investitionsberechnung in OEMOF���������������������������������������������������� 258 6.1.2 Finanzmathemtische Erweiterung der Annuitätenmethode im Modul „economics_BAUM“ �������������������������������������������������������� 260 6.1.3 Der Quellcode des Moduls „economics_BAUM“������������������������������ 263 6.1.4 Die Applikation „GridCon_storage“�������������������������������������������������� 265 6.2 Modellierungsbeispiele aus OEMOF im Release v0.2.0�������������������������������� 280 6.2.1 Beispiel „simple_dispatch“���������������������������������������������������������������� 280 6.2.2 Beispiel Investment Modelle – Variante 1: Investitionskosten für Windkraft, PV und Speicher���������������������������������������������������������� 285 6.2.3 Beispiel Investment Modelle – Variante 2: Investitiotnskosten für Speicher���������������������������������������������������������������������������������������� 291 Literatur�������������������������������������������������������������������������������������������������������������������� 295
1
Das Projekt OEMOF
Die Welt rückt in vielen Fragestellungen, insbesondere zur zukünftigen Energieversorgung, immer näher zusammen. Früher wurden hauptsächlich Öl und Gas aus anderen Ländern importiert oder in andere Länder exportiert. Mit dem Ausbau der Erneuerbaren Energien stellt sich in der Welt ein völlig neues Bild dar. Die Energieerzeugung und -verteilung betrifft schon lange nicht mehr nur noch ein Land alleine. Vielmehr bestehen Abhängigkeiten zwischen den Ländern. Ein Beispiel ist Deutschland, als ein Land, welches an viele andere Länder Europas angrenzt. Es besteht ein reger Handel mit Energie zwischen den Ländern. Die Verflechtung der Volkswirtschaften zwischen den Ländern nimmt zu und wird immer komplexer. Dabei steht immer wieder die Frage im Mittelpunkt, wie die Energieversorgung bestmöglich unter bestimmten Restriktionen, wie geringe CO2-Emissionen, in der Zukunft aufgebaut oder weiterentwickelt werden kann. Jedoch sind nicht nur länderübergreifende Fragestellungen zur Entwicklung in der Zukunft von Interesse. Auch für ein einzelnes Land, wie Deutschland, oder noch weiter heruntergebrochen ein einzelnes Bundesland, wie Bayern, oder eine Region in einem Bundesland ebenso wie für ein einzelnes Versorgungsgebiet eines Stadtwerks ist die Frage nach der zukünftigen Gestaltung der Energieversorgung elementar. Wie komplex die Zusammenhänge in der Energiewirtschaft sind, zeigt Abb. 1.1. Weitere Informationen über Zusammenhänge der Energiewirtschaft in Deutschland und weltweit bestehenden techno lgischen Ansätzen finden sich unter anderem in (Nagel 2017). In Deutschland sind es Gemeinden selbst oder private Energieversorger aber auch Anwohner eines Wohngebietes, die sich zu einer Betreibergemeinschaft zusammenschließen oder Betreiber eines Industriestandortes, die für die Energieversorgung verantwortlich sind bzw. die Verantwortung übernehmen. Handelt es sich dabei um einen neuen Standort oder den Ausbau bzw. die Erneuerung eines bestehenden Standortes, beginnen Ingenieure mit der Planung und Auslegung der energieerzeugenden Anlagen.
© Springer Nature Switzerland AG 2023 J. Nagel, Optimierung von Energieversorgungssystemen, https://doi.org/10.1007/978-3-031-36355-9_1
1
2
1 Das Projekt OEMOF
Abb. 1.1 Abhängigkeiten und Einflüsse in der Energiewirtschaft
Dann gibt es noch übergreifende Betrachtungsweisen, bei denen ein gesamtes Land, wie Deutschland oder Frankreich, untersucht wird. Die hierbei betrachteten Fragestellungen sind globaler und umfangreicher. Dabei geht es oftmals nicht um die konkrete Auslegung eines Energieversorgungssystems sondern vielmehr darum, mithilfe der Ergebnisse Empfehlungen bzgl. Rahmenbedingungen oder Aussichten auf zukünftige Entwicklungen, wie der CO2-Emissionen, zu geben, die sich unter bestimmten Rahmenbedingungen einstellen könnten. Bei der Planung eines Energieversorgungssystems werden mehrere Ziele verfolgt: • Die Bedürfnisse der Bürger sollen mit einem ausreichenden Angebot gedeckt werden können. • Das Energieversorgungssystem soll die Energie möglichst effizient bereitstellen in der Form, dass die volkswirtschaftlichen Gesamtkosten so gering wie möglich sind. • Die Versorgung soll einen nachhaltigen Umgang der Ressource Umwelt ermöglichen. • Das Leben und die Gesundheit der Menschen sollen dadurch so wenig wie möglich beeinträchtigt werden. • Es soll ein nachhaltiges Wirtschaften mit allen Ressourcen erfolgen, sodass zukünftige Generationen in der Lage sein werden, ein nach ihren Wünschen gestaltetes Leben zu führen. Dies führt zu Zielkonflikten, die gelöst werden müssen, sodass die richtigen Entscheidungen und Planungen getroffen werden können. Antworten auf die zukünftige Gestaltung und Weiterentwicklung der Energieversorgung sowie Auswirkungen von Entscheidungen auf das bestehende Energieversorgungssystem, den Markt und die Umwelt suchen unter anderem die Politik, Verbände, Energieversorger,
1 Das Projekt OEMOF
3
Beratungsunternehmen wie auch Forschungsinstitute weltweit. Dabei steht die Entwicklung folgender typischer energiewirtschaftlicher Konzepte im Mittelpunkt: • Erstellung globaler, regionaler und kommunaler Energiekonzepte • Erstellung von Konzepten zur rationellen Energieanwendung in Gebäuden und Betrieben • Erstellung von Konzepten zur Minderung von Schadstoffemissionen (Klimaschutzkonzepte) Desweiteren gehören auch Planungsaufgaben dazu wie: • Planung des Ausbaus von Versorgungsnetzen (Elektrizität, Erdgas) • Planung des Ausbaus von Kraftwerksparks • Planung des Einsatzes von Kraftwerksparks und Raffinerien Mathematische Modelle helfen bei der Beantwortung der sehr unterschiedlichen Fragestellungen. Dabei müssen die vielfältigen Abhängigkeiten in einem Energieversorgungssystem zwischen Energiebedarf, Energieerzeugung, -verteilung, -speicherung, dem Markt mit seiner Preisbildung, der Umwelt mit den Auswirkungen von Emissionen und je nach Fragestellung vielen weiteren Faktoren abgebildet werden. Die Problemstellungen zeichnen sich als vieldimensional, komplex, mit langfristigem Horizont, durch große Tragweite und hohe Unsicherheit aus. Zielkonflikte erschweren Planungen und Entscheidungen. Aufgrund der sehr unterschiedlichen Fragestellungen mit den vielfältigen Zielstellungen, die dabei verfolgt werden, werden stark individualisierte mathematische Modelle in unterschiedlichen Programmier- oder Modellierungssprachen, wie GAMS (General Algebraic Modeling System) zur Optimierung von Energieversorgungssystemen, umgesetzt. Bereits seit vielen Jahren werden entsprechende Modelle aufgestellt. Weiterführende Literatur findet sich u. a. in (Forum 1999; König 1977; Pfaffenberg et al. 2004; Walbeck et al. 1988; Schultz et.al 2008; Kallrath 2013; Nagel 1998). Mit dem Projekt OEMOF wird ein neuer Weg beschritten. OEMOF steht für „Open Energy Modelling Framework“. Es handelt sich bei OEMOF um ein Werkzeug mit einer Toolbox zur Modellierung von Energiesystemen. Es können damit Einspeisezeitreihen, Lastprofile und ganze Energiemodelle erzeugt werden. Für die Entwicklung eines Energiemodells steht eine Modellbibliothek, oemof.solph, zur Verfügung. Die Modellbibliothek bildet das Kernstück für jeden Anwender (Modellierer). In der Modellbibliothek oemof.solph sind die mathematischen Gleichungen zur Abbildung der Komponenten eines Energieversorgungssystems hinterlegt. Mit Hilfe der Modellbibliothek wird eine mathematische Zielfunktion, wie z. B. Kosten der Energieversorgung, erzeugt. Wie es bei Optimierungsfragestellungen üblich ist, wird ein Maxi-
4
1 Das Projekt OEMOF
mum oder Minimum dieser Zielfunktion gesucht. Hier, in oemof.solph, wird ausschließlich eine Minimierungsfunktion z. B. der Kosten gebildet. Kosten können in der Zielfunktion als Fixkosten (zeitabhängige Kosten) wie auch variable Kosten (mengenabhängige Kosten) angegeben werden. Ebenso können Kosten für Emissionen oder andere Parameter definiert werden. Auch können in oemof.solph Investitionskosten und Abschreibungen den technischen Komponenten oder Gebäuden zugewiesen werden. Es handelt sich hierbei um Kostenmodelle. Dies ermöglicht dem Interessenten auf der Basis der Ergebnisse Investitions- bzw. Ausbauentscheidungen zu treffen. Es ist ebenso gut möglich, in oemof.solph statt der Kosten rein die Emissionen in die Zielfunktion zu nehmen, die dann minimiert werden sollen. Dies ermöglicht das Aufstellen von Klimaschutzmodell. In einem Kostenmodell können die Emissionen über Restriktion Eingang in das Modell finden. Wesentliches Charakteristikum von OEMOF ist der generische Aufbau des Programmsystems. Strom- und Wärmeerzeugeranlagen, Energieverteilsysteme, Kosten und vieles mehr, was in einem Energieversorgungssystem vorhanden ist, sind in diesem Modell in der Modellbibliothek oemof.solph bereits mathematisch beschrieben und können vom Modellierer entsprechend seiner Fragestellung beliebig miteinander verbunden werden. Der Modellierer ist damit in der Lage, sehr unterschiedliche Modelle zu entwickeln. Er selbst kann sich rein auf die Anpassung von Parametern im Modell konzentrieren, ohne sich mit den mathematischen Gleichungen auseinandersetzen zu müssen. Ein weiteres wichtiges Charakteristikum des Programmsystems OEMOF ist dessen freier Zugriff (Open Source). Als Open Source Energiemodellierungswerkzeug ist es jedem Anwender möglich, dieses zu nutzen und selbstständig weiterzuentwickeln. Darüber hinaus ist OEMOF als Gemeinschaftsprojekt organisiert, sodass Weiterentwicklungen der Modellierungsgemeinschaft wieder zur Verfügung gestellt werden können. Dem Programmsystem liegt ein methodischer Ansatz zugrunde, der nach einem Überblick über mathematische Modellierungen weiterführend vorgestellt wird. Das Projekt OEMOF wird unterstützt vom Reiner Lemoine Institut und vom Zentrum für nachhaltige Energiesysteme (ZNES). Das ZNES ist eine gemeinsame Einrichtung der Flensburg University of Applied Sciences und der Europa-Universität Flensburg. Gemeinsam haben sie das Projekt initiiert und das Werkzeug OEMOF in einer ersten öffentlichen Version entwickelt. Heute arbeiten an dem Projekt eine Vielzahl an Wissenschaftlern, wie bspw. der Technischen Universität Berlin, Deutschland und Anwender, wie bspw. der Unternehmensgruppe B.A.U.M. Eine ausführliche Liste der Anwender von OEMOF befindet sich auf der Webseite der openmod Initiative von OEMOF (https://wiki. openmod-initiative.org/wiki/Special:ListUsers). Zum Ende des Jahres 2017 wurde ein neues Release des Programmsystems OEMOF herausgegeben, das Release v0.2.0. Alle folgenden Ausführungen beziehen sich auf dieses Release. Im Jahr 2023 steht das Release oemof.solph v0.5. zur Verfügung. Einiges hat sich seit der ersten englischen Ausgabe an der Struktur und am Quellcode von OEMOF verändert.
1.1 Techniken zur Entwicklung von Strategien und Handlungsoptionen
5
Da hier nicht einfach der Quellcode von OEMOF eins zu eins abgebildet werden soll, sondern vielmer eine Auseinandersetzung mit dem Quellcode und damit ein Lernprozess erreicht und angestoßen werden soll, ist es zweitrangig, dass hier nicht der aktualisierte Quellcode dargestellt wird. Die prinzipielle Struktur des Modells bzw. des Modellierens sind gleichgeblieben. Ein paar Neuerungen werden trotzdem vorgestellt.
1.1 Techniken zur Entwicklung von Strategien und Handlungsoptionen Die Energiewende weltweit ist in vollem Gange und keiner kann heute sagen, wie die Energieversorgung zukünftig bspw. im Jahr 2050 aufgebaut sein wird. Um jedoch die für die Zukunft wichtigen Entscheidungen treffen und richtigen Schritte einleiten zu können, müssen Aussagen für die Zukunft gemacht und wichtige Einflussfaktoren herausgearbeitet werden. Hierfür braucht es Energiemodelle. Mit Hilfe eines Modells kann eine Analyse eines Energieversorgungssystems erfolgen und Strategien sowie Handlungsoptionen abgeleitet werden. Den Modellen liegt immer eine spezifische Fragestellung zugrunde. Beispielhaft könnte eine Fragestellung lauten: Bei welcher Preis- und Vergütungsstruktur ist es wirtschaftlich möglich, im Jahr 2050 aus biogenen Rest- und Abfallstoffen Biogas bzw. Biomethan zu erzeugen? Oder: Unter welchen Rahmenbedingungen können in ländlichen Regionen biogene Energieträger wirtschaftlich zur Wärmeversorgung eingesetzt werden (Nagel 1998)? In Energiemodellen werden für ein betrachtetes Energieversorgungssystem die Energiesysteme mit ihren Wechselwirkungen verschiedener Akteure auf dem Energiemarkt, ökonomischen Kenngrößen und umweltrelevanten Auswirkungen abgebildet. Die Zusammenhänge in diesem Energieversorgungssystem werden mit Hilfe mathematischer Gleichungen bzw. eines mathematischen Gleichungssystems beschrieben. Diese bilden dann das Energiemodell. Ein Modell basiert auf der Grundlage eines Zukunftsbildes. Um die Zukunft zu beschreiben, werden unterschiedliche Szenarien definiert, die in Storylines (narrative Szenarien) erzählend beschrieben werden (Krutzler et al. 2016). In Szenarien wird die Zukunft verstärkt in Zahlen abgebildet, wie bspw.: „Im Jahr 2050 werden 80 % der Weltbevölkerung eMobile fahren“. Zumeist wird nicht nur ein Szenario entwickelt. Vielmehr werden alternative zukünftige Entwicklungen beschrieben. Häufig werden formal die folgenden drei Szenarien definiert: • Best case Szenario: Dieses beschreibt eine Zukunft unter der Bedingung, dass sich alles, dem Wunsch entsprechend, positiv entwickelt. • Worst case Szenario: Im Worst case Szenario wird ein Bild gezeichnet, welches von den schlimmsten Befürchtungen ausgeht.
6
1 Das Projekt OEMOF
• Realistic szenario Hierin wird davon ausgegangen, dass es mit gewissen Wahrscheinlichkeiten Abweichungen von der Wunschvorstellung gibt. Szenarien beleuchten mögliche Entwicklungen und zeichnen ein Zukunftsbild. Sie geben keine Aussage darüber, wie wahrscheinlich deren Eintreten ist, sodass sie keine Prognosen darstellen. Sie beschreiben eine Variante der Entwicklungen der Rahmenbedingungen, die vom Entscheidungsträger nicht beeinflusst werden können. In den Szenarien in der Energiewirtschaft wird versucht, eine konsistente Weiterentwicklung energiewirtschaftlicher Leitparameter abzubilden. Ein Leitparameter kann bspw. die „Nutzung“ sein. Dieser Leitparameter könnte den Ausbau der erneuerbaren Energien und den CO2-Minderungs beitrag zum Klimaschutz beinhaltet. Anhand eines jeden entwickelten Szenarios können unterschiedliche Strategien und Handlungsoptionen zur Erreichung dieses Zukunftsbildes erarbeitet werden. Weitere Techniken zur Entwicklung von Strategien und Handlungsoptionen sind neben Szenarien bspw. Storylines und Modelle. Alle drei unterscheiden sich hinsichtlich ihrer Aussagekraft in ihrer Quantität und Qualität (s. Abb. 1.2). Zur Analyse von Energieversorgungssystemen können unterschiedliche Arten von Modellen Anwendung finden. Es wird zwischen den folgenden Arten von Modellen unterscheiden: • Prognosemodelle Diese haben die Aufgabe, die Zukunft vorauszusagen. Sie finden in der Energiewirtschaft bspw. zur Vorhersage von Sonnenscheinzeiten und -intensitäten Anwendung. Abb. 1.2 Qualität und Quantität von Storylines, Szenarien und Modellen
1.1 Techniken zur Entwicklung von Strategien und Handlungsoptionen
7
• Analysemodelle Unterschiedliche Parameter, wie bspw. Einwohnerzahl oder Gaspreis, werden hinsichtlich ihres Einflusses auf das betrachtete System untersucht und ihre Bedeutung bzw. Wichtigkeit herausgearbeitet. • Szenarienmodelle Hierin liegt der Schwerpunkt auf der Bildung mehrerer Szenarien, um eine Vielzahl an denkbaren Entwicklungen für die Zukunft und ihrer jeweiligen Implikaitonen darzustellen. • Simulationsmodelle Diese Modelle bilden die Wirklichkeit ab. Bspw. kann die Bewegung eines Windrades bei einer bestimmten Windstärke simuliert und damit die dann erzeugte Energie berechnet werden. • Optimierungsmodelle In diesen Modellen wird eine mathematische Zielfunktion, wie bspw. eine Kostenfunktion, definiert. Die Zielfunktion soll ein Maximum oder Minimum erreichen. Dazu wird mittels eines Lösungsalgorithmus in einem Lösungsraum nach dem absoluten Maximum oder Minimum der Funktion gesucht. All diese Modelle geben dem Anwender die Möglichkeit, Handlungsoptionen und Strategien abzuleiten. Die Wahl eines der Modelle hängt von der individuellen Fragestellung ab. OEMOF ist ein Optimierungsmodell mit bspw. einer Kostenfunktion als Zielfunktion. Diese soll minimiert werden. Es können verschiedene Parameter, wie bspw. Preise von Energieträgern, variiert und so deren Einfluss auf das System im Rahmen einer Sensitivitätsanalyse untersucht werden. Das Modell liefert unter den vorgegebenen Rahmenbedingungen, die in den Szenarien vorgegeben werden, ein Zukunftsbild. Es können bspw. Aussagen darüber gegeben werden, wieviel Windkraftanlagen gebaut werden müssen oder, ob eine Gasversorgung zukünftig wirtschaftlich ist. Auch können Aussagen darüber gemacht werden, welche Anlagentypen, bspw. Biogasanlagen mit Blockheizkraftwerk, mit welcher Leistung und betriebsweise gebaut werden sollen. Das Vorgehen zur Problemlösung lässt sich allgemein, wie in Abb. 1.3 abgebildet, darstellen. Abb. 1.3 Formales Vorgehen bei einer Problemstellung
8
1 Das Projekt OEMOF
1.2 Worauf basiert OEMOF? OEMOF bildet in der Modellbibliothek oemof.solph die Energiebereiche Wärme, Kälte und Strom sowie die am Markt bestehenden Technologien zu deren Bereitstellung wie Kraft-Wärmekopplung durch Blockheizkraftwerke oder Power-to-Gas durch die Umwandlung von Strom mittels Elektrolyse in Wasserstoff und weiter zu Methan ab. Ebenso wird der Mobilitätsbereich darin beschrieben. Dies ermöglicht eine sektorübergreifende Betrachtung und die Untersuchung von Sektorkopplungen. Die Modellbibliothek oemof.solph ist ein Modellgenerator. Sie erzeugt aus den vom Modellierer zusammengefügten Einzelteilen seines Energieversorgungssystems ein lineares oder auch gemischt-ganzzahliges lineares Optimierungsmodell. Weiterhin können mit Hilfe der Modellbibliothek mehrere Regionen flexibel verknüpft und in einem Modell abgebildet werden. So ist es möglich, bspw. Deutschland in seine 16 Bundesländer aufzuteilen, jedes einzeln in oemof.solph zu modellieren und diese dann miteinander zu verbinden. Der zeitliche Verlauf der Energieversorgung und -bereitstellung ist in energiewirtschaftlichen Fragestellungen von höchster Bedeutung. Denn es besteht das Ziel der Versorgungssicherheit. Dieses Ziel fordert, dass zu jedem Zeitpunkt so viel Energie bereitgestellt wird, die den bestehenden Bedarf deckt. Je kleiner die Zeitschritte in einem Modell gewählt werden, umso genauer erfolgt die Abbildung des Zukunftsbildes. In oemof. solph ist die Wahl der Zeitschritte frei. Die Verfügbarkeit von Daten ist u. a. ein Faktor, der die Einteilung der Zeitschritte bestimmt. Des Weiteren ist die Modellbibliothek oemof.solph modular aufgebaut. Was bedeutet dies? Für jede Komponente oder jedes Element, wie bspw. ein Blockheizkraftwerk oder Gasnetz, in einem Energieversorgungssystem gibt es ein Modul, welches die technologischen Zusammenhänge mathematisch in einem linearen oder gemischt-ganz zahlig linearen Gleichungssystem abbildet. Dabei kennzeichnet jede Komponente oder jedes Element eine bestimmte Anzahl an Parametern, wie bspw. Anlagenleistung oder Wirkungsgrad. Im Zuge der Modellbildung müssen diese Parameter durch den Modellierer festgelegt werden. Dies bedeutet, in oemof.solph sind die Komponenten erst einmal ganz abstrakt (generisch) programmiert. Es können dadurch die unterschiedlichsten Komponenten eines Energiesystems erstellt werden. Einige Beispiele sind nachfolgend aufgeführt: • Erzeugeranlagen, wie Blockheizkraftwerke, Windkraftanlage, Photovoltaikanlage, Diesel-Generatoren, KWK-Anlagen, Heizwerke • Elemente für den Transport von Energie, wie bspw. Strom-, Gas- oder Wärmenetze • Verbraucher jeglicher Art, wie Städte, Gebäude, Gewerbe und Industrie • Brennstoffe, wie Kohle, Öl, Gas oder Holz • Exporte und Importe, wie Gasimport, Kohleförderung oder Stromexport
1.2 Worauf basiert OEMOF?
9
Abb. 1.4 Generischer Ansatz in OEMOF
Basis der modularen Betrachtung ist die Abgrenzung eines Systems durch Systemgrenzen. Ein System stellt die Gesamtheit aller Elemente dar, die aufeinander bezogen sind. Betrachten wir dazu die Stadt San Diego an der Westküste der USA. In dieses Energieversorgungssystem gehören alle energiebezogenen Komponenten und Elemente von natürlichen Energieträgern, wie Holz, Wind oder Sonnenenergie, über die Umwandlung von Energie bspw. in einer Gasturbine, deren Verteilung über bspw. ein Stromnetz, die Speicherung bspw. in einer Batterie, bis zum Endverbrauch von Energie durch den Menschen, eine Maschine oder ein technisches Gerät als auch bspw. Staaten-spezifische Gesetzmäßigkeiten. Wodurch verfügt OEMOF über einen generischen Ansatz bei der Modellierung? Der Ansatz in OEMOF ist an zwei Stellen generisch aufgebaut (s. Abb. 1.4). Beginnen wir mit der oberen Ebene, die nicht ganz so abstrakt ist. Hier werden zum einen die zuvor vorgestellten Komponenten und Elemente eines Energieversorgungssystems in so genannten „components“ gruppiert. Es sind in OEMOF drei generische „components“ definiert. Dazu gehören Quellen (Source), Senken (Sink) und Wandler (Transformer). Diese können bspw. folgende Komponenten und Elemente repräsentieren: • Quelle (Source): Es gibt jeweils eine Quelle für bspw. Windkraftanlagen, Photovoltaikanlagen, Kohleförderung und Gasimport. • Senke (Sink): Eine jeweilige Senke ist bspw. ein Wärme- oder Stromverbraucher, der Export von Strom oder ein Elektroauto. • Wandler (Transfomer): Wandler können Kraftwerke oder Power2Gas Anlagen sein, ebenso wie eine Elektrolyseanlage, Wärmepumpe oder ein Nachheizer. Auch Stromoder Wärmeleitungen können als Wandler definiert werden. Komponenten und Elemente können auch anderen „components“ zugeordnet werden. Was genau eine „component“ darstellen soll, wird durch den Modellierer festgelegt. Die generischen „components“ sind durch bestimmte Eigenschaften charakterisiert: • Wandler (transformer): Dieser verfügt über beliebig viele In- und Outputs. Dies bedeutet, es kann jeweils nur einen In- bzw. Output geben oder, wie bei einem Heizkraftwerk, welches mit zweierlei Brennstoffen Strom und Wärme erzeugt, auch mehrere.
10
1 Das Projekt OEMOF
Ein Holzheizkraftwerk bekommt vom Bus des Typs „Holzbrennstoff“ seinen einzigen Input. Der eine Output geht in den Bus des Typs „Stromnetz“ und der andere in den Bus des Typs „Wärmenetz“. Busse werden nachfolgend beschrieben. Über Umwandlungsfaktoren, die sich aus entsprechenden Parametern, wie dem Heizwert des Holzes und der Art der Anlage, berechnen, werden In- und Outputs mathematisch in einem Gleichungssystem miteinander verbunden. Wandler können aber auch dazu genutzt werden, um damit eine Übertragungslinie, wie ein Stromnetz, und die bei der Übertragung auftretenden Verluste zu modellieren. • Senke (sink): Besitzt einen Input aber keinen Output. Mit der Komponente Senke kann bspw. ein Endverbraucher, wie ein Einfamilienhaus, modelliert werden. • Quelle (source): Hat keinen Input aber genau einen Output. Mit einer Quelle können bspw. Windkraftanlagen oder Photovoltaikanlagen aber auch die Förderung von Kohle oder der Import von Öl oder Erdgas modelliert werden. Es existieren in oemof.solph auch spezifische Komponenten, die nicht generisch sind, wie bspw. • Allgemeiner Speicher: dazu gehören bspw. Batterien oder Druckluftspeicher. • KWK-Anlage mit Entnahmekondensationsturbine (linearer Ansatz). • KWK-Anlage mit Entnahmekondensationsturbine (gemischt-ganzzahlig linearer Ansatz). Ein weiteres generisches Element ist der so genannte Bus. Ein Bus symbolisiert einen Bilanzknoten bzw. -raum. Es handelt sich damit nicht um einen physischen Bestandteil eines Energieversorgungssystems, wie eine Windkraftanlage oder ein Stromnetz, sondern rein um ein bilanztechnisches (virtuelles) Element. Ein Bus hat die folgende Eigenschaft: • Bus (bus): Er besitzt ein oder mehrere In- und Outputs. Ein Bus repräsentiert einen Bilanzknoten oder -raum, der in jedem Zeitschritt ausgeglichen sein muss. Durch diese Bedingung wird die Summe aller ein- und ausgehenden Flüsse in jedem Zeitschritt gleich Null. Zum anderen ist der generische Ansatz durch die in Abb. 1.4 dargestellte mittlere Ebene, der Graphentheorie, gegeben. Dies zeigt, dass ein Modell in oemof.solph über die Objekte Knoten (nodes) und Kanten (edges) abgebildet wird. Diese Knoten und Kanten sind ebenfalls abstrakte Objekte, die erst durch den Modellierer durch Festlegung von Parametern eine spezifische Bedeutung im Modell erhalten. Vertiefende Ausführungen zur Graphentheorie befinden sich in Kap. 3. Ein Knoten kann entweder ein Bus oder eine „component“ sein. Ein Knoten ist dadurch sehr variabel definierbar. Er kann bspw. eine Region abbilden oder auch unterschiedliche Spannungsebenen innerhalb einer Region oder unterschiedliche Temperaturen in einem
1.2 Worauf basiert OEMOF?
11
Fernwärmesystem repräsentieren. Der Modellierer mit seiner Parametrisierung legt fest, was ein Knoten repräsentieren soll. Die Kanten symbolisieren das Fließen der Energie im Modell. Über Pfeile wird die Fließrichtung der Energie vorgeben. Durch diese abstrakten Abbildungen eines Energieversorgungssystems verfügt OEMOF über einen generischen Modellierungsansatz, der es dem Modellierer erlaubt, für ein Energieversorgungssystem mit ein und denselben Komponenten und Elementen ganz unterschiedliche Modelle umzusetzen. Wie wird nun in OEMOF ein Energieversorgungssystem modelliert? Ausgangsbasis bildet das betrachte Energieversorgungssystem aus Abb. 1.5. Die Symbole in Abb. 1.5 haben eine bestimmte Bedeutung, die in der nächsten Abbildung (Abb. 1.7) beschrieben werden. Die einzelnen Komponenten in dem betrachteten Energieversorgungssystem zeichnen sich durch bestimmte Parameter aus. So hat das Blockheizkraftwerk (BHKW) einen bestimmten Wirkungsgrad, das Biogas verursacht unter Umständen variable Kosten und ist vielleicht in der Menge begrenzt. Der Strom- und Wärmebedarf zeichnet sich durch eine Zeitreihe (Lastgang) aus (s. Abb. 1.6).
Abb. 1.5 Exemplarisch betrachtetes Energieversorgungssystem
Abb. 1.6 Parameter der einzelnen Komponenten im System
12
1 Das Projekt OEMOF
Abb. 1.7 Generischer Ansatz im Programmsystem OEMOF (Pfeile = Kanten). (Nach oemof o. J.)
Aus der Struktur des betrachteten Energieversorgungssystems entwickelt der Modellierer anschließend das Optimierungsmodell. Die Modellierung in OEMOF erfolgt in oemof. solph. Hier werden „components“ über Kanten und Busse miteinander verbunden (s. Abb. 1.7). Dabei kann eine „component“ mit mehreren Bussen verbunden sein, ebenso wie ein Bus mehrere „components“ besitzen kann. Dazwischen liegen die Kanten, die den Energieinput oder -output einer „component“ abbilden. Kanten werden in OEMOF „flow“ genannt. Die Kanten („flow“) sind wichtig. Über diese kann der Energiefluss zwischen zwei Knoten z. B. über Kosten bewertet werden. Beispielhaft ist dies in Abb. 1.7 dargestellt. Dieser Ansatz stammt aus der Graphentheorie und findet bspw. bei Petri-Netzen Anwendung. Es handelt sich hierbei um einen bipatierten Graph. Dies ist ein mathematisches Modell, welches die Beziehungen zwischen Elementen zweier Mengen abbildet. Die Besonderheit des bipatierten Graph ist durch die fehlende Beziehung zwischen Elementen der gleichen Menge gekennzeichnet. In OEMOF ist die eine Menge die „components“ und die andere Menge die Busse. Weder können „components“ noch Busse direkt miteinander verbunden werden. Als Ergebnis liegt in OEMOF ein lineares oder gemischt-ganzzahlig lineares Optimierungsmodell vor, wie es auch bspw. mit dem Programmsystem GAMS (General Algebraic Modeling System) oder anderen Modellierungssprachen erstellt werden könnte. Dieses Gleichungssystem kann dann, wie bei den anderen Modellierungssystemen, an einen beliebigen Solver (Gleichungslöser) übergeben werden. Das Programmsystem OEMOF ist in der Programmiersprache Python umgesetzt. Diese Programmiersprache ist frei verfügbar und läuft auf sämtlichen Betriebssystemen. Bekannte Programmsysteme, wie bspw. Google oder YouTube, basieren teilweise auf dieser Sprache (Python o. J.). Die Sprache ist aufgrund ihrer Einfachheit und Übersichtlichkeit leicht zu erlernen. Python ist eine interpretierte höhere Programmiersprache. Sie ist universell für unterschiedlichste Softwareentwicklungen einsetzbar. Der Programmcode ist durch seine klare und übersichtliche Syntax gut lesbar. Python wird zu einer interpretierten Programmiersprache, da ein Computerprogramm, der Interpreter, den Programm-Quellcode zur Laufzeit des Programms übersetzt (auslesen und analysieren) und diesen dann direkt auf der jeweiligen Plattform ausführt (XOVI o. J.; bib o. J.).
1.3 Linear Programming (LP) und Mixed-Integer Linear Programming (MILP)
13
Höhere Programmiersprachen unterscheiden sich von einfachen Programmiersprachen durch die Zunahme der Abstraktion bei der Abfassung eines Computerprogramms. Komplexe logische Zusammenhänge können so vereinfacht ausgedrückt werden. Durch den Interpreter können die Befehle anschließend ausgelesen und ausgeführt werden. Da OEMOF in einer frei verfügbaren Programmiersprache umgesetzt wurde, war es nicht nur möglich, den Quellcode offenzulegen (Open Source), sondern dieser kann auch vom Modellierer frei ausgeführt werden. Dies ermöglicht jedem Anwender, das Programmsystem weiterzuentwickeln und der OEMOF-community wieder zur Verfügung zu stellen. Die Software kann kostenfrei im Internet heruntergeladen werden. OEMOF basiert auf einem generischen Ansatz, der frei nach den eigenen Bedürfnissen weiterentwickelt und angepasst werden kann. OEMOF ist dadurch vielseitig einsetzbar. Diese Vielfältigkeit spiegelt sich auch in der Verwendung unterschiedlicher Optimierungsansätze wieder. Es können sowohl lineare Probleme, wie bspw. wirtschaftliche Versorgungsmodelle, über den Modellierungstyp Linear Programming (LP) als auch gemischt-ganzzahlig lineare Problemstellungen, wie bspw. die Analyse der Betriebsweise von Versorgungsanlagen, über den Modellierungstyp Mixed-integer linear Programming (MILP) bearbeitet werden. Was sich hinter LP und MILP Modellen verbirgt, wird im folgenden Kapitel beschrieben.
1.3 Linear Programming (LP) und Mixed-Integer Linear Programming (MILP) Viele Fragestellungen in der Wirtschaft, Technik und Verwaltung aber auch im täglichen Leben sind Optimierungsprobleme. Bei den Fragestellungen steht eine Planungs- oder Entscheidungsaufgabe im Mittelpunkt. Beispiele für diese Art von Anwendungsproblemen basierend auf einem Optimierungsansatz sind: • Entscheidung über eine Standortwahl für bspw. Schulen, Industriegebäude, Lagerhäuser. Einfluss haben bspw. Entfernungen, Anbindung an öffentliche Verkehrsmittel oder Autobahnen, Einwohnerzahl und vieles mehr. • Tourenplanung für bspw. Reisen oder Logistikunternehmen. Hier sollen bspw. innerhalb kürzester Zeit bestimmte Punkte angefahren werden. • Lagerplanung für bspw. Pakete wie bei der Post oder Amazon. Für das Einlagern und Auslagern von Paketen soll der Mitarbeiter oder heute in vielen Fällen der Roboter den kürzesten Fahrweg nehmen und dabei so viele Pakete wie möglich einsammeln. • Gewinnmaximierung in einem Unternehmen. Bspw. muss in der Produktion entschieden werden, welches Produkt aus welchen Ausgangsstoffen zu welcher Zeit in welcher Reihenfolge der Produktionsschritte und welchem Preis produziert werden soll, um den Gewinn zu maximieren. • Optimierung der Aerodynamik wie bspw. beim Auto. • Gestaltung von Flugstrecken zur Minimierung von Fluglärm.
14
1 Das Projekt OEMOF
• Gestaltung der Energieversorgung einer Gemeinde. Dabei muss bspw. gelöst werden, welche Technologien mit welchem Energieträger und welcher Leistung eingesetzt werden sollen. Einflussfaktoren sind dabei bspw. Energiepreise oder Investitionskosten. Speziell bei bspw. Netzplanern und Stromversorgern werden Optimierungsmodelle eingesetzt. Deren Fragestellungen drehen sich bspw. um die Bestimmung der Typen an Stromerzeugungsanlagen zur Deckung des zeitlich abhängigen Strombedarfs mit den gleichzeitig niedrigsten Stromerzeugungskosten. Ebenso unterliegen die Anlagen der Forderung, die geringsten Emissionen zu verursachen und die wenigsten Energieressourcen zu verbrauchen. Eine weitere Bedingung kann durch die Limitierung der Produktionskapazität des bestehenden Erzeugerparks das Ergebnis beeinflussen. Mit einem Optimierungsmodell kann die bestmögliche Versorgungsvariante ermittelt werden. Es wird also nicht nach einem „Extremwert“ gesucht, sondern nach einem „Extremwert unter bestimmten Bedingungen“. Wird eine Sensitivitätsanalysen angeschlossen, können alternative Versorgungsvarianten herausgearbeitet werden. Sensitivitätsanalysen erfolgen durch die Variation bestimmter Parameter, wie bspw. Preise für Heizöl oder Gas. Dabei kann in den Szenarien angenommen werden, dass sich der Preis bspw. in den nächsten fünf Jahren einmal um 10 % und im anderen Szenario um 20 % erhöht. Es könnte auch eine Variation bspw. des Grenzwertes von CO2-Emissionen betrachtet werden. Die Suche nach dem richtigen Verfahren zur Lösung eines Optimierungsproblems ist oftmals schwierig. Es muss geklärt werden, ob ganzzahlige Variablen das Problem beschreiben. Dies ist bspw. der Fall, wenn berechnet werden soll, wie viele Energieerzeugungsanlagen eines bestimmten Typs an einem Standort errichtet werden sollen. In der Zielfunktion wie auch in den Randbedingungen können nichtlineare Terme auftauchen, was bei der Auswahl des richtigen Verfahrens berücksichtigt werden muss. Bspw. ist der Strombedarf eines Hauses über den Tag nicht konstant, sondern unterliegt Schwankungen. Dies führt zu nichtlinearen Gleichungen. Wenn möglich wird versucht, solche Nichtlinearitäten in lineare oder diskrete (integer) Zusammenhänge zu überführen. Um ein Optimierungsproblem einordnen zu können, wurden aus diesem Grund Problemklassen definiert (s. Tab. 1.1). In den Problemklassen spiegelt sich die mathematische Beschreibung des Optimierungsproblems wieder. Werden bspw. rein lineare Gleichungen formuliert, handelt es sich um eine lineare Optimierung. In der Mathematik spricht man auch von einem Programm oder englisch „program“ bzw. „programming“, wenn es sich um die Optimierungsmethode handelt. Für jede der Problemklassen braucht es spezielle Algorithmen zur Lösung des Gleichungssystems. Der Lösungsalgorithmus wird in einem so genannten Solver als eigenes mathematisches Programm abgelegt. Für lineare und gemischt-ganzzahlig lineare Optimierungsprobleme stehen dem Modellierer bereits gute Solver zur Verfügung. Bei der Verwendung spezieller Modellierungssysteme, wie bspw. GAMS oder OEMOF, steht dem Modellierer eine gewisse Auswahl an Solvern zur Verfügung, die direkt angewendet werden können. Die Frage nach Lizenzen bzw. der Verwendung lizenzfreier Solver muss vorab geklärt werden.
1.3 Linear Programming (LP) und Mixed-Integer Linear Programming (MILP)
15
Tab. 1.1 Einteilung von Optimierungsproblemen in Problemklassen Problemklasse Lineare Optimierung (Linear Programming) Ganzzahlige Optimierung (Integer Programming) Gemischt-ganzzahlige lineare Optimierung (Mixed-Integer Linear Programming) Nichtlineare Optimierung (Nonlinear Programming) Gemischt-ganzzahlige nichtlineare Optimierung (Mixed-Integer Nonlinear Programming) Globale Optimierung
Akronym LP IP MILP NLP MINLP
Abb. 1.8 Formales Vorgehen zur Entwicklung eines computergestützten Optimierungsmodells. (Nach Suhl et al. 2006)
Lineare Zusammenhänge lassen sich mathematisch einfacher beschreiben, als nichtlineare Zusammenhänge. Auch ist die Wahrscheinlichkeit, ein tatsächliches globales Optimum gefunden zu haben höher als bei nichtlinearen Optimierungsproblemen. Sollte es möglich sein, ein Optimierungsproblem auf ein lineares Gleichungssystem zurückzuführen, ist dies zu empfehlen. Wenn eine Transformation in eine lineare mathematische Beschreibung nicht möglich ist, sollte versucht werden, die Nichtlinearität durch diskrete Zusammenhänge zu beschreiben, wie dies bspw. beim Stromlastgang eines Hauses möglich wäre. Für diese Klassen an Problemstellungen stehen auch bereits sehr gute Solver zur Verfügung. Aus diesem Grund werden im Folgenden das Linear und Mixed-Integer Linear Programming ausführlicher vorgestellt. Wie erfolgt allgemein die Modellbildung? Das Vorgehen zur Lösung einer Problemstellung lässt sich formal wie in Abb. 1.8 darstellen. Dieses Vorgehen gliedert sich in sieben Schritte (nach Domschke et al. 2011): 1. Ganz zu Beginn steht das Erkennen und Analysieren eines Problems. Bspw. hat ein Energieversorger aufgrund von Marktveränderungen die Möglichkeit, neue Produkte
16
1 Das Projekt OEMOF
seinen Kunden anzubieten oder seinen Energieerzeugungspark durch neue Technologien zu erweitern oder bestehende Anlagen zu ersetzen und sich so auf den neuesten technologischen Stand zu bringen. Dazu müssen unter Umständen passende Standorte gefunden und die passenden Anlagenleistungen ermittelt werden. Es hat sich für den Energieversorger durch diese Veränderungen ein Entscheidungs- und Handlungsbedarf ergeben. 2. Im nächsten Schritt müssen die Handlungsmöglichkeiten herausgearbeitet und Ziele formuliert werden. Um die richtige Entscheidung fällen zu können, muss sich der Energieversorger vorab die Frage stellen, welches Ziel er mit seinen Maßnahmen verfolgen will. Soll bspw. die kostengünstigste Erweiterung des Energieerzeugungsparks gesucht werden oder sollen damit die CO2-Emissionen zukünftig minimiert werden? Zur Erreichung des Ziels sind oftmals alternative Handlungswege möglich, die ermittelt und voneinander abgegrenzt werden müssen. Da bei der Formulierung der Handlungsmöglichkeiten und der Ziele aus Gründen bspw. fehlender oder ungenauer Daten, Zeit- oder Budgetbegrenzungen, nicht alle Aspekte in die Betrachtung einbezogen werden können, ergibt sich ein vereinfachtes Abbild der Situation. Es entsteht ein so genanntes deskriptives Modell. Es geht darum, die Optimierungsfrage genau zu formulieren, um daraus abzuleiten, was genau entschieden werden soll. Dies führt zur späteren mathematischen Zielfunktion, die entweder maximiert oder minimiert werden soll. Die Handlungs- und Entscheidungsmöglichkeiten bilden in dem späteren mathematischen Modell die Variablen. Typischerweise werden die Variablen mathematisch in einem Vektor x ∈ Rn zusammengefasst. Bei der Lösung des Optimierungsproblems wird für jede Variable des Vektors x ein Wert gefunden. 3. Nun ist zu untersuchen, welche Bedingungen das betrachtete System eingrenzen. In unserem Fall können dies bspw. CO2-Emissionen sein, die nicht überschritten werden dürfen. Hierbei ist zu unterscheiden, dass es sich um eine Restriktion und nicht um einen Zielwert handelt, der in diesem Fall minimiert werden soll. Sämtliche Bedingungen gehen später als Nebenbedingungen in das mathematische Modell ein. Die Nebenbedingungen werden in Form mathematischer Gleichungen oder Ungleichungen formuliert. Wird nun für den Vektor x eine Lösung gefunden, müssen anschließend die berechneten Werte anhand der Nebenbedingungen überprüft werden. Sind die Nebenbedingungen für eine Lösung x erfüllt, wird von einer zulässigen Lösung gesprochen. 4. Ausgehend von dem deskriptiven Modell kann das mathematische Modell aufgestellt werden. Die zu optimierende Größe sowie deren Berechnung wurden ermittelt und die Nebenbedingungen festgelegt. 5. Die nun folgende Datenbeschaffung stellt manchmal einen schwierigen Schritt dar. Mitunter liegen die Daten nicht in der gewünschten Form vor oder bilden nicht den gewünschten Zeitraum ab. Manchmal müssen zusätzlich Methoden oder Modelle zur Erstellung von Prognosen hinzugenommen werden. 6. Zur Lösung des mathematischen Optimierungsmodells braucht es einen entsprechenden Algorithmus (Handlungsvorschrift zur Lösung eines Problems). Anhand des Algorithmus
1.3 Linear Programming (LP) und Mixed-Integer Linear Programming (MILP)
17
werden in Einzelschritten die zuvor erhobenen Eingabedaten in Ausgabedaten überführt. Handelt es sich dabei um Computerprogramme, in denen die Algorithmen abgelegt sind, wird von einem Solver (Löser) gesprochen. Ein Solver ist ein spezielles mathematisches Computerprogramm, welches mathematische Probleme numerisch löst. Der Solver ermittelt basierend auf der Zielsetzung eine besonders geeignete bzw. optimale Alternative. 7. Die gefundene Lösung muss abschließend bewertet werden. Dies geschieht meistens über einen Filter (b-wise GmbH o. J.). Filter können bspw. sein: Kann die gefundene Lösung technisch umgesetzt werden? Werden bei dieser Lösung irgendwelche Gesetze, Verträge, Regelungen oder die Unternehmenspolitik missachtet? Ist es eine faire Lösung, die sich auch mit meiner Selbstachtung vereinbaren lässt? Mit Hilfe der Bewertungskriterien kann die Lösung analysiert und als akzeptabel, modifizierungsbedürftig oder unbrauchbar identifiziert werden. Je nach Bewertungsergebnis kann es erforderlich sein, das Modell entsprechend anzupassen und neu zu lösen. Sind die sieben Schritte nacheinander durchlaufen und damit der Modellentwicklungsprozess abgeschlossen, erhält man als Ergebnis ein mathematisches Modell, mit welchem akzeptable Lösungen berechnet werden können. Als Methoden zur mathematischen Beschreibung der Modelle werden das Linear Programming und das Mixed-Integer Linear Programming vorgestellt. Welche der beiden Methoden zur Lösung der betrachteten Problemstellung anzuwenden ist, hängt von der Problemstellung und den Möglichkeiten zur mathematischen Abbildung des deskriptiven Modells ab. Auf youtube existieren unterschiedliche Einführungsvideos zum Thema Optimierung und Modellierung. Ein Video unter youtube konzentriert sich speziell auf die Optimierung und Modellierung von Energieversorgungssystemen. Das youtube Video trägt den Titel: „Energieversorgungssysteme optimieren“ und ist unter https://www.youtube.com/ watch?v=19-XD2E_obg&t=2s abrufbar. Das Video beschäftigt sich mit den Fragestellungen: 1 . Warum ist es so schwierig, Energieversorgungssysteme zu optimieren? 2. Was bedeutet ein „Modell“ erstellen? 3. Was heißt „optimieren“? 4. Wie bilde ich ein mathematisches Optimierungsproblem am Computer ab? 5. In welcher Programmiersprache kann ich dies umsetzen? 6. Wie kann ich ein Optimierungsproblem lösen? Das Video gibt einen schnellen und kurzen Einblick über das Modellieren von Energieversorgungssystemen und den mathematischen Ansätzen zur Optimierung der zugrunde liegenden Problemstellung wie Lineare Programming (LP) und Mixed-Integer Linear Programming (MILP).
18
1 Das Projekt OEMOF
1.3.1 Linear Programming (LP) Eine Klasse an Modellen für die Optimierung sind so genannte Linear Programming Modelle (LP Modelle). Diese werden oftmals als „botton-up“ Modelle bezeichnet, da sie detaillierte Informationen über Technologien und Kosten beinhalten (Claussen et al. 2001). Das Verbraucherverhalten wird bei diesen Modellen durch bspw. den Stromlastgang vorgegeben. Das Verbraucherverhalten kann aber auch zur Abbildung der Realität als nicht-quantitativer Faktor in das Modell eingehen. Dafür braucht es jedoch andere Modelle, wie bspw. Wahrscheinlichkeitsmodelle zur Simulation des Verbraucherverhaltens. Prinzipiell stellt die lineare Optimierung ein Teilgebiet der mathematischen Optimierung dar. Die mathematische Optimierung gehört zum Fachgebiet des Operations Research. Dieses Fachgebiet ist eine wissenschaftliche Disziplin, mit dem Hauptziel, unterschiedliche Methoden aus der Mathematik, Statistik, Wahrscheinlichkeitsrechnung sowie der Spieltheorie auf wirtschaftliche Fragestellungen anzuwenden, um Entscheidungsprozesse zu unterstützen. Die Methode des Linear Programming beruht auf der Formulierung eines linearen Gleichungssystems. Es ist ein mathematisches Optimierungsverfahren, welches den größten oder kleinsten Wert einer linearen Funktion, der Zielfunktion, berechnet. Diese Zielfunktion besitzt n Freiheitsgrade und wird durch Nebenbedingungen in Form von weiteren linearen Gleichungen und Ungleichungen eingeschränkt. Durch diese Nebenbedingungen wird das Gleichungssystem eingeschränkt. Nebenbedingungen können bspw. eingeschränkte Ressourcen in Form von Leistungsangaben von Technologien, wie Speicher, oder Ausstoß an CO2-Emissionen darstellen. Eine bekannte Problemstellung von linearen Optimierungsaufgaben ist das Travelling Salesman Problem (TSP). Dieses beinhaltet die logistische Fragestellung, eine definierte Anzahl an Orten auf der kürzesten Strecke zu besuchen. In Abb. 1.9 ist ein solches Problem bzw. dessen Lösung exemplarisch dargestellt. Aufgabe war es: „Finde die kürzeste Rute, um 24727 Stopps aufzusuchen, die auf der Webseite Pubs Galore – The UK Pub Guide“ gefunden wurden (HIM o. J.). Das Forschungsteam hatte also die Aufgabe, die kürzeste Laufroute zu ermitteln, um die Pubs in UK aufzusuchen. Zur Lösung des Problems wurde das TSP (Travelling Salesman Problem) Schnittebenenverfahren angewendet. Jeder Ort soll dabei genau einmal besucht werden. Das ist je nach Anzahl an Orten eine sehr komplexe Aufgabe. Bereits 1954 wurden zur Lösung dieser Problemstellung erste Ansätze entwickelt, die auf der Methode der linearen Programmierung beruhen (HIM o. J.; TSP 2015).
1.3.2 Beispiel „Biogasproduktion“ Bevor der formale Aufbau der Gleichungen eines linearen Programms vorgestellt wird, wird folgend für die Einführung zunächst ein konkretes Beispiel „Biogasproduktion“ bearbeitet:
1.3 Linear Programming (LP) und Mixed-Integer Linear Programming (MILP)
19
Abb. 1.9 Travelling Salesman Problem: kürzeste Laufroute durch die Pubs in UK, Ausschnitt London. (Nach HIM o. J., unter Verwendung von OpenStreetMap)
Ein Landwirt betreibt an zwei Standorten jeweils eine Biogas-Versuchsanlage, Anlage A und B. Dazu setzt der Landwirt die Einsatzstoffe Pferdeäpfel und Gülle ein. Das in Anlage A produzierte Biogas wird in einem BHKW zur Strom- und Wärmeerzeugung eingesetzt. Anlage B produziert Biogas, welches für den Mobilitätsbereich zu Biomethan aufbereitet wird. Um das Gleichungssystem nicht zu kompliziert werden zu lassen, betrachten wir nicht, wieviel Biogas benötigt wird, um Biomethan zu produzieren. Für den Verkauf von Strom und Wärme erhält der Landwirt einen Mischpreis von 75 € pro m3 Biogas und für Biomethan einen Festpreis von 50 € pro m3 Biomethan. Anlage A betreibt der Landwirt mit 8 m3 Pferdeäpfel und 2 m3 Gülle pro m3 Biogas. Anlage B fährt er mit 3 m3 Pferdeäpfel und 6 m3 Gülle pro m3 Biomethan. Das Biogas und das Biomethan werden jeweils in einem Gasspeicher zwischengespeichert. Dabei fasst der Gasspeicher für Biogas die doppelte Menge an Gas, wie der Gasspeicher für das Biomethan. Dies bedeutet für den Produktionsbetrieb, dass die produzierte Menge an Biomethan von Anlage B aus den Eingangsstoffen nicht größer sein darf als die doppelte Menge an Biogas von Anlage A. Außerdem beträgt der Materialvorrat der Einsatzstoffe derzeit nur 126 kg Pferdeäpfel und 84 kg Gülle. Für den Landwirt ergibt sich die Frage: Welche Mengen der beiden Energieträger (Anlage A: Biogas und Anlage B: Biomethan, jeweils in m3) soll er produzieren, um unter Einhaltung der Nebenbedingungen den Umsatz zu maximieren?
20
1 Das Projekt OEMOF
Das Ergebnis hat für den Landwirt Auswirkungen auf die zukünftige Auslegung der Anlagen. Die dargestellte Problemstellung muss nun zu dessen Lösung in ein mathematisches Gleichungssystem umformuliert werden. Die Lösung der Beispielaufgabe erfolgt in vier Schritten: Schritt 1: Dazu werden zunächst die Daten in eine Matrix überführt (s. Tab. 1.2): Schritt 2: Jetzt werden die mathematischen Gleichungen des Problems formuliert (s. Tab. 1.3). Dazu müssen zunächst die Entscheidungsvariablen festgelegt werden. Diese sind in den Gleichungen die Variablen x und y. Auf die Beispielaufgabe angewendet, stellen die Variablen die Menge an produziertem Gas (Biogas bzw. Biomethan) in m3 dar. Schritt 3: Eine grafische Herleitung der linearen Gleichungen in einem x,y- Koordinatensystem verdeutlicht den Lösungsansatz. Mathematische lineare Gleichungen stellen sich für gewöhnlich in der Form dar (s. Gl. 1.1): y = ax + b (1.1)
Der Faktor a repräsentiert die Steigung dieser Geraden und b den Achsenabschnitt auf der y-Achse. Zur Veranschaulichung wird der Steigung a beispielhaft der Wert 2 und dem Achsenabschnitt b der Wert 10 zugewiesen. Mit diesen Werten kann die Gleichung Gl. 1.1 konkretisiert werden zu (s. Gl. 1.2): y = 2 x + 10 (1.2)
Wir erhalten mit Gl. 1.2 eine konkrete Geradengleichung, die in ein x,y-Koordinatensystem eingezeichnet werden kann (s. Abb. 1.10). Tab. 1.2 Matrix Beispiel „Biogasproduktion“ Entscheidungsvariable Pferdeäpfel [kg/m3 Gas] Gülle [kg/m3 Gas] Verkaufspreis [€/m3 Gas]
A x (Biogas [m3]) 8 2 75
B y (Biomethan [m3]) 3 6 50
Vorrat [kg]
126 84
Tab. 1.3 Mathematische Formulierung des Problems Zielfunktion Nebenbedingungen des Weiteren gilt wegen Bedingung: y ≤ 2x Nichtnegativitätsbedingungen
Mathematische Formulierung z(x, y) = 75x + 50y → Max ! 8x + 3y ≤ 126 (NB1) 2x + 6y ≤ 84 (NB2) 2x − y ≥ 0 (NB3) x ≥ 0 (NB4) y ≥ 0 (NB5)
1.3 Linear Programming (LP) und Mixed-Integer Linear Programming (MILP)
21
Abb. 1.10 Darstellung der Steigung und des Achsenabschnitts im x,y-Koordinatensystem
Ausgehend von der beispielhaft dargestellten Geradengleichung Gl. 1.2 wird für das hier vorgestellte konkrete Beispiel aus Tabelle Tab. 1.1 überprüft, welche Gleichungen bzw. Ungleichungen nicht dieser Form genügen. Dies sind die beiden aufgeführten Nebenbedingungen NB1 und NB2. Diese müssen noch entsprechend umformuliert werden. Beide Nebenbedingungen werden nach y aufgelöst. Beginnend mit der ersten Nebenbedingung NB1 (s. Gl. 1.3 und 1.4):
3 y ≤ −8 x + 126 (1.3)
Daraus wird nach weiterer Umformung:
8 y ≤ − x + 42 3
(1.4)
Um die Ungleichungen als Gerade in ein Koordinatensystem einzeichnen zu können, werden sie als Gleichungen interpretiert (s. Gl. 1.5):
8 y = − x + 42 3
(1.5)
8 und dem Achsenabschnitt 3 +42, die in einem x,y-Koordinatensystem dargestellt werden kann (s. Abb. 1.11). Dies ist eine normale Geradengleichung mit der Steigung -
22
1 Das Projekt OEMOF
Abb. 1.11 Erste Nebenbedingung NB1 als Geradengleichung im x,y- Koodinatensystem
Die farbige Fläche in Abb. 1.11 stellt die Erfüllung der Ungleichung der ersten Nebenbedingung NB1 dar. Folgt nun die zweite Nebenbedingung NB2 (s. Gl. 1.6):
2 x + 6 y ≤ 84 (1.6)
Wir stellen auch diese Gleichung nach y um und formulieren daraus eine Geradengleichung (s. Gl. 1.7):
6 y ≤ −2 x + 84 (1.7)
Die Interpretation als Geradengleichung lautet (s. Gl. 1.8):
1 y = − x + 14 3
(1.8)
Ebenso kann diese Gleichung grafisch dargestellt werden (s. Abb. 1.12). Alle Punkte in der grünfarbenen Fläche erfüllen die Ungleichung der zweiten Nebenbedingung NB2. Gehen wir zur dritten Ungleichung NB3. Diese besagt, dass der Farmer aus den Eingangsstoffen an Anlage A maximal die doppelte Menge an Biogas (2 m3 Biogas) im Vergleich zu Biomethan (1 m3 Biomethan) produzieren darf. Mathematisch stellt sich das wie folgt dar (s. Gl. 1.9):
y ≤ 2 x (1.9)
Darstellung als Geradengleichung (s. Gl. 1.10):
y = 2 x (1.10)
Wird diese in ein x,y-Koordinaten überführt, erhalten wir Abb. 1.13. Nachdem alle Nebenbedingungen umgeformt und grafisch dargestellt wurden, können die Geraden und Flächen zusammengefügt werden. Es ergibt sich d ie in Abb. 1.14 dargestellte Schnittmenge. Die Geraden begrenzen den Bereich, für die die jeweiligen Nebenbedingungen gelten. Alle Punkte der blauen Fläche sind gültige Ergebnisse für das aufgestellte Ungleichungs
1.3 Linear Programming (LP) und Mixed-Integer Linear Programming (MILP)
23
Abb. 1.12 Zweite Nebenbedingung NB2 als Geradengleichung im x,y- Koordinatensystem
Abb. 1.13 Dritte Nebenbedingung NB3 im x,y- Koordinatensystem
Abb. 1.14 Schnittmenge der Ungleichungen im x,y-Koordinatensystem
ystem und stellen damit die zulässige Lösungsmenge dar. Die optimale Lösung muss jedoch erst noch ermittelt werden. Der Lösungsweg kann grafisch oder rechnerisch erfolgen. Im nächsten Schritt wird dazu der grafische Lösungsweg begangen. Dem folgt der rechnerische Lösungsweg. Schritt 4-a: Grafische Ermittlung der optimalen Lösung Nun kommt die Zielfunktion zum Einsatz, für die ein Maximum gesucht wird. Um diese in ein x,y-Koordinatensystem einzeichnen zu können, muss sie ebenfalls nach y auf-
24
1 Das Projekt OEMOF
gelöst werden. Weiterhin wird die Funktion als Gleichung dargestellt. Um die Zielfunktion im Nullpunkt des Koordinatensystems starten zu lassen, wird der Zielwert z auf den Wert Null gesetzt (s. Gl. 1.11).
z ( x,y ) = 150 x + 100 y = 0
(1.11)
In den nächsten beiden Schritten erfolgt die Umformung nach y (s. Gl. 1.12 und 1.13):
100 y = −150 x (1.12) y = −1, 5 x (1.13)
Jetzt kann die Zielfunktion Gl. 1.13 in unserer Abb. 1.14 mit dem Lösungsraum eingezeichnet werden (s. Abb. 1.15). Wird die eingezeichnete Gleichung nun parallel nach oben oder unten verschoben, erhält man entweder das grafische Maximum oder Minimum der Funktion. Da hier ein Maximum der Zielfunktion z gesucht wird, verschieben wir Gl. 1.13 parallel nach oben. Dies wird so lange gemacht, bis die Gerade den letzten Eckpunkt des Bereichs der Lösungsmenge berührt. Dieser Eckpunkt (roter Punkt) stellt die optimale Lösung dar (s. Abb. 1.16). Nachdem wir das Optimierungsproblem grafisch gelöst haben, folgt die rechnerische Lösung des Problems. Schritt 4-b: Für die rechnerische Lösung machen wir uns klar, dass für dieses Beispiel das Optimum auf irgendeinem der Eckpunkte der Lösungsmenge sitzen muss. Dies liegt darin begründet, dass der Optimalwert zum einen Teil der in Abb. 1.14 dargestellten Lösungsmenge sein muss. Auf der anderen Seite darf sich nur ein Punkt, das Optimum, ergeben. Dies bedeutet, wie wir das bei der grafischen Lösung gesehen haben, dass die Gleichung der Zielfunktion in gewissen Bereichen des Lösungsraums mehrere gültige Lösungen erzeugt, jedoch kein Optimum. Die x,y-Werte für die Eckpunkte können anhand Abb. 1.14 abgelesen werden: P(0;0), P(6;12), P(12;10), P(15,75; 0)
Abb. 1.15 Zielfunktion im Punkt 0 im x,y-Koordinatensystem
1.3 Linear Programming (LP) und Mixed-Integer Linear Programming (MILP)
25
Abb. 1.16 Grafische Lösung des linearen Optimierungsproblems
Zur Ermittlung des Optimums werden die Werte der Eckpunkte in die Zielfunktion eingesetzt (s. Gl. 1.14): z ( x, y ) = 150x + 100y
(1.14)
Es ergeben sich die folgenden Lösungen (s. Gl. 1.15, 1.16, 1.17 und 1.18):
z ( 0; 0 ) = 150·0 + 100·0 = 0 €
(1.15)
z ( 6;12 ) = 150·6 + 100·12 = 2.100 €
(1.16)
z (12;10 ) = 150·12 + 100·10 = 2.800 € ® Maximum
(1.17)
z (15,75;0 ) = 150·15,75 + 100·0 = 2.362,50 €
(1.18)
Der Landwirt bekommt als Antwort auf seine Frage: Welche Mengen der beiden Energieträger soll er produzieren, um unter Einhaltung der Nebenbedingungen den Umsatz zu maximieren? Er soll in Anlage A 12-mal 1 m3 Biogas und in Anlage B 10-mal 1 m3 Biomethan produzieren.
1.3.3 Formaler Aufbau LP Der formale Aufbau eines so genannten „Linear Programming Modells“ (LP Modell) zur mathematischen Formulierung eines Optimierungsproblems (einer Entscheidungsaufgabe) besteht aus den folgenden drei Bestandteilen (s. Tab. 1.4): Sind die Gleichungen bzw. Ungleichungen aufgestellt, folgt die Lösung des Gleichungssystems. Eine grafische wie auch rechnerische Lösung, wie hier dargestellt, ist für Problemstellungen im Bereich der Energietechnik oder Energiewirtschaft für gewöhnlich nicht möglich. Die Herausforderung bei der Lösung von LP Modellen ist die effiziente Ergebnissuche, bei der eine geringe Rechnerleistung und kurze Rechenzeit benötigt werden. Die Ergebnissuche erfolgt durch spezielle Algorithmen. Am Markt existieren bereits ei-
26
1 Das Projekt OEMOF
Tab. 1.4 Formaler Aufbau eines Linear Programming Modells (LP Modell) Zielfunktion Es ist das Ziel der linearen Programmierung eine lineare Funktion in Abhängigkeit von ein oder mehreren Variablen zu maximieren oder zu minimieren. Die zu optimierende lineare Funktion heißt Zielfunktion. Die in ihr auftretenden Variablen (x1, x2, …, xn) werden Entscheidungsvariablen genannt. Die Faktoren cn sind reelle Zahlen. Nebenbedingung (Restriktionen) Die Faktoren apn bis bp sind ebenfalls reelle Zahlen.
Nichtnegativitätsbedingung Bei vielen Entscheidungsaufgaben können die Entscheidungsvariablen keine negativen Werte annehmen. Es können bspw. nicht negative Mengen an Biogas produziert werden. Aus dem Grund macht es Sinn, eine Beschränkung der Entscheidungsvariablen auf Werte größer/gleich Null zu definieren.
Mathematische Formulierung z = z(x1, x2, … xp) = c1x1 + c2x2 + … + cnxn → Max !
a11x1 + a12x2 + … + a1nxn ≤ b1 a21x1 + a22x2 + … + a2nxn ≤ b2 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ap1x1 + ap2x2 + … + apnxn ≤ bp x1 ≥ 0 x2 ≥ 0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ xn ≥ 0
nige Standardverfahren, wie bspw. der Simplex-Algorithmus. Dieser Algorithmus ist der in der Praxis am häufigsten eingesetzte Ansatz. Das Simplex-Verfahren geht auf Dantzig zurück, der dieses 1947 vorstellte (Domschke et al. 2011). Der Simplex-Algorithmus ist komplex und wird hier nicht weiter vorgestellt. Die Grundidee des Simplex-Verfahrens ist das Absuchen des Randes des zulässigen Lösungsbereichs nach einem maximalen Zielfunktionswert startend in einer Ecke des Bereichs. Die Werte dieses Eckpunktes werden in die Zielfunktion eingegeben und die Lösung ermittelt. Der Algorithmus springt dann iterativ zur nächsten benachbarten Ecke. Dabei gilt die Bedingung, dass der Wert der benachbarten Ecke nicht niedriger als der aktuelle Wert sein darf. Ist ein neuer größerer Wert gefunden, geht die Iteration weiter zur nächsten benachbarten Ecke. Weist keiner der benachbarten Eckpunkte einen höheren Zielwert auf, wurde ein lokales Maximum gefunden und der Algorithmus endet. Dieses Ergebnis stellt gleichzeitig ein globales Maximum und damit die Lösung der Optimierungsaufgabe dar. Der Simplex-Algorithmus zählt zu den leistungsstärksten Verfahren zur Lösung von LP-Problemen (Domschke et al. 2011).
1.3.4 Mixed-Integer Linear Programming (MILP) Der Unterschied zwischen einem linearen Programm und einem gemischt-ganzzahligen Programm liegt in den zusätzlichen Restriktionen. Diese besagen, dass einige Variablen nur ganzzahlige Werte annehmen dürfen. Die wichtigsten Variablen sind binäre
1.3 Linear Programming (LP) und Mixed-Integer Linear Programming (MILP)
27
0-1-Variablen. Durch sie können Entscheidungen mathematisch formuliert werden. Bspw. kann damit modelliert werden, ob eine Windkraftanlage an einem Standort mit einer bestimmten Leistung gebaut werden soll oder nicht. Die Variable 0 bedeutet nicht bauen, die Variable 1 bauen. Die übrigen Variablen dürfen hingegen reelle Werte annehmen. In der Wirtschaft sind MILP Probleme für Analysten und Ingenieure von großer Bedeutung. Fragestellungen wie zur Standortplanung aber auch der Betriebsweise von Anlagen, wie bspw. die optimale Einsatzplanung von Kraftwerken mit dem Status „Ein/Aus“ (1 – Ein, 0 – Aus) in einem Stromnetz, können damit gelöst werden. Wichtige Themen in der Wirtschaft sind auch Ablauf- und Projektplanungen, zu denen mithilfe von MILP Modellen Entscheidungen getroffen werden können. Die Ganzzahligkeit ist für viele Fragestellungen sinnvoll. Bspw. kann an einem Standort nicht nur die Entscheidung gesucht werden, ob eine Windkraftanlage gebaut oder nicht gebaut werden soll (binäre 0-1-Variable), sondern die Frage geht weiter: Wie viele Windkraftanlagen sollen an einem Standort gebaut werden? Dies macht deutlich, dass die Anzahl an Windkraftanlagen nur durch eine ganze Zahl ausgedrückt werden kann. Zunächst wird ein lineares Optimierungsproblem mit ganzzahligen Variablen vorgestellt (IP Modell). Es handelt sich dabei um eine Investitionsplanung. An einem Standort soll eine für die Bürger optimale Energieversorgung aufgebaut werden, die deren Bedarf deckt. Dafür wurde ein Investor gesucht und gefunden. Dieser ist bereit, viel Geld zu investieren, um unter den gegebenen technischen Bedingungen das bestmögliche zu erreichen. Aus diesem Grund sollen entgegen des sonst üblichen Vorgehens die Kosten maximiert werden. Zur optimalen Versorgung der Bürger sollen x Energieumwandlungsanlagen des Typs 1 und y des Typs 2 beschafft werden. Die Anlagen des Typs 1 kosten 2 Mio. € und die des Typs 2 kosten 4 Mio. €. Daraus ergibt sich die Zielfunktion. Beide Anlagentypen setzen Strom und Prozessdampf ein. Die Anlage des Typs 1 benötigt 2 t Prozessdampf und die des Typs 2 benötigt 6 t. Der Vorratsbehälter an Prozessdampf fasst jedoch nur 14 t Dampf. Dies führt zur ersten Nebenbedingung. Jede Anlage des Typs 1 verbraucht 6 kWh Strom und die des Typs 2 verbraucht 4 kWh. Zusammen dürfen sie jedoch maximal 20 kWh Strom verbrauchen. Daraus lässt sich die zweite Nebenbedingung ableiten. Da immer nur ganze Anlagen beschafft werden können, müssen die Variablen x und y die Bedingung der Ganzzahligkeit und der Nichtnegativität erfüllen. Wie viele Anlagen können maximal beschafft werden? Die Zielfunktion kann wie folgt formuliert werden (s. Gl. 1.19):
z ( x,y ) = 2 x + 4 y ® max!
(1.19)
mit den Nebenbedingungen (s. Gl. 1.20, 1.21 und 1.22):
2 x + 6 y £ 14 (1.20)
6 x + 4 y £ 20 (1.21)
x, y ≥ 0 und ganzzahlig (1.22)
Grafisch lässt sich dieses Optimierungsproblem wie in Abb. 1.17 darstellen.
28
1 Das Projekt OEMOF
Alle zulässigen Lösungen des Optimierungsproblems, die sich aus den Nebenbedingungen ergeben, sind als weiße Punkte in Abb. 1.17 eingezeichnet. Zuvor, im Beispiel Biogas, hatten wir das Ergebnis aus dem Schnittpunkt der beiden Geraden der Nebenbedingungen erhalten. In diesem IP Beispiel bildet der Schnittpunkt aus den Geraden Gl. 1.20 und 1.21 keine zulässige Lösung, da die Restriktion der Ganzzahligkeit nicht erfüllt ist. Die optimale Lösung erhält man wieder durch verschieben der Zielfunktion (ZF). Der größte ganzzahlige Punkt liegt bei x = (1,2) mit dem Zielfunktionswert z(x*) = 10. Das Ergebnis ist in Abb. 1.18 dargestellt. Der größte ganzzahlige Punkt ist grün dargestellt. Bei einem gemischt-ganzzahligen Problem würde die Fragestellung anders lauten. Hier würde z. B. gefragt werden, ob in die Anlage des Typs 1 und/oder des Typs 2 investiert werden soll (x = 1 bzw. y = 1) oder nicht (x = 0 bzw. y = 0). Des Weiteren würde es andere Variablen (bspw. x3, x4) geben, die reelle Werte annehmen dürfen. Es könnte eine Nebenbedingung zu Emissionen geben, die besagt, dass jede Anlage maximal nur eine bestimmte Menge CO2 emittieren darf. Die Menge an CO2 könnte durch die Variable x3 abgebildet werden. Eine Nebenbedingung könnte bspw. eine maximale Betriebsdauer vorgeben. Die Zeit könnte durch die Variable x4 beschrieben werden. Abb. 1.17 Ganzzahliges Optimierungsproblem, GZ: Bedingung Ganzzahligkeit, NB: Nebenbedingung, ZF: Zielfunktion
Abb. 1.18 Ergebnis des ganzzahligen Optimierungsproblems
Literatur
29
1.3.5 Sensitivitätsanalysen Die in einem Optimierungsmodell eingesetzten Parameter verändern sich oftmals über die Zeit. So können bspw. Kosten für Windkraftanlagen, Stromverbräuche von Häusern, Preise für Energieträger bzw. Einsatzstoffe wie bei Biogasanlagen oder Erlöse aus Stromverkäufen ebenso politische Vorgaben zu Emissionsgrenzwerten steigen oder sinken. Manche Werte sind unter Umständen auch nur geschätzt, da keine verlässlichen Daten zum Erhebungszeitpunkt existierten oder nicht zur freien Verfügung standen. In einer Sensitivitätsanalyse finden diese variierenden Parameter Eingang, um Änderungen der optimalen Lösung zu untersuchen. Dies bedeutet, die Lösung eines Optimierungsmodells wird auf Reaktionen gegenüber Veränderungen der Ausgangsdaten, die in dem betrachteten System zum aktuellen Zeitpunkt oder in der Zukunft eintreten können, getestet. Dies betrifft im Optimierungsmodell die Koeffizienten der Zielfunktion cn sowie die rechte Seite bp und die Koeffizienten apn der Nebenbedingungen. Wird mehr als nur ein Parameter gleichzeitig im Modell variiert, wird dies als parametrische Sensitivitätsanalyse oder parametrische Optimierung bezeichnet (Domschke et al. 2011). Sensitivitätsanalysen geben einen guten Einblick, wie sich eine Lösung und damit eine Entscheidung oder Planung verändert, wenn ein Parameter im betrachteten System, wie bspw. die Änderung von Energiepreisen, steigt oder sinkt. Dieser Prozess hilft bei der finalen Festsetzung der Entscheidung bzw. Planung. Gerade im Energiesektor, bei dem jede Entscheidung langfristige Auswirkungen hat (z. B. Lebensdauer von Kraftwerken) und unvermeidbare Unsicherheiten (z. B. Ölpreis, Nachfrage, Importverfügbarkeit) bestehen, ist es besonders wichtig, genaue Analysen zu erstellen.
Literatur bib Bildungszentrum für informationsverarbeitende Berufe gGmbH: Programmiersprachen. Paderborn, o.J. https://www.bg.bib.de/portale/bes/Progentwicklung/Programmiersprachen/ Programmiersprachen-110.htm. Accessed on 03 Oct 2017 b-wise GmbH: Problemlösungsmethoden: Lösungen bewerten und entscheiden. Karlsruhe, o.J. https://www.business-wissen.de/hb/loesungen-bewerten-und-entscheiden/. Accessed on 04 Nov 2017 Claussen, E. et al. (Ed.): Climate change : science, strategies, & solutions. Leiden et.al., Brill Verlag, 2001, https://books.google.de/books?id=g85OJ76ufJoC&pg=PA157&lpg=PA157&dq=Linear+Programming+button+up&source=bl&ots=qAbgY4MjcF&sig=CVNMEoG__ AsXBO1Y1kJWfLaM-jk&hl=de&sa=X&ved=0ahUKEwjnvIecsNbWAhUEZVAKHUjKAZ0 Q6AEITjAJ#v=onepage&q=Linear20Programming%20button%20up&f=falseDatei:LinearProgramming.doc. Accessed on 06 June 2018 Domschke, W. et al.: Einführung in Operations Research. 8. Auflage, Springer Verlag, Heidelberg et al., 2011
30
1 Das Projekt OEMOF
Forum für Energiemodelle und Energiewirtschaftliche Systemanalysen in Deutschland (ed.): Energiemodelle zum Klimaschutz in Deutschland : Strukturelle und gesamtwirtschaftliche Auswirkungen aus nationaler Perspektive. Springer Verlag, Berlin (1999) HIM Hausdorff Research Institute for Mathematics: UK24727 : A shortest-possible walking tour through the pubs of the United Kingdom. Bonn, o.J. http://www.math.uwaterloo.ca/tsp/pubs/ index.html. Accessed on 04 Nov 2017 Kallrath, J.: Gemischt-ganzzahlige Optimierung: Modellierung in der Praxis, 2nd edn. Springer Spektrum, Wiesbaden (2013) König, C. (ed.): Energiemodelle für die Bundesrepublik Deutschland. Springer, Basel (1977) Krutzler, T. et al., Umweltbundesamt GmbH (Ed.): Szenario erneuerbare Energien 2030 und 2050. REP–0576, Umweltbundesamt GmbH, Wien, 2016. http://www.umweltbundesamt.at/fileadmin/ site/publikationen/REP0576.pdf. Accessed on 04 Oct 2017 Nagel, J.: Ein analytisches Prozesssystemmodell zur Bestimmung von Rahmenbedingungen für den wirtschaftlichen Einsatz biogener Energieträger im ländlichen Raum, dargestellt an einem Beispiel aus dem Bundesland Brandenburg. Diss., Fortschritt-Berichte VDI, Reihe 6, Nr. 403, VDI-Verlag GmbH, Düsseldorf (1998) Nagel, J.: Energie- und Ressourceninnovation : Wegweiser zur Gestaltung der Energiewende. Hanser Verlag, München (2017) oemof-developer-group: Using oemof. Berlin, o.J. http://oemof.readthedocs.io/en/stable/using_ oemof.html#feedinlib. Accessed on 02 Sept 2017 Pfaffenberg, W., et al. (ed.): Energiemodelle zum europäischen Klimaschutz : Der Beitrag der deutschen Energiewirtschaft. Umwelt- und Ressourcenökonomie, Band 22, LIT Verlag, Münster (2004) Python Software Foundation: Quotes about Python. Wilmington, USA, o.J. https://www.python.org/ about/quotes/. Accessed on 03 Oct 2017 Schultz, R. et.al. (Ed.): Innovative Modellierung und Optimierung von Energiesystemen. LIT Verlag, 2008 Suhl, L. et al.: Optimierungssysteme. Springer Verlag, Berlin et al., 2006 TSP: Queen of College Tours: http://www.math.uwaterloo.ca/tsp/college/index.html. Accessed on 04 Nov 2017 (2015) Walbeck, M., et al.: Energie und Umwelt als Optimierungsaufgabe : Das MARNES-Modell. Springer-Verlag, Berlin (1988) XOVI GmbH: Interpreter. Köln, o.J. https://www.xovi.de/wiki/Interpreter. Accessed on 03 Oct 2017
2
Das generische Basismodell in OEMOF
Wer mit dem Programmsystem OEMOF Energieversorgungsmodelle aufstellen und berechnen möchte, benötigt ein Grundwissen zur Programmiersprache Python sowie zum Aufbau und der Struktur von OEMOF. Neben vielen OEMOF spezifischen Bibliotheken, Packages und Modulen, kann auf mehrere Bibliotheken, lokale Module und Packages aus der Programmiersprache Python zurückgegriffen werden. Es handelt sich dabei um spezielle Python Programmeinheiten für wissenschaftliche Anwendungen wie bspw. Mathematische Optimierung, Netzwerk Analyse oder Daten Analyse. Wesentlicher Bestandteil von Simulations- und Optimierungsprogrammen sind Daten. Oftmals ist es eine große Datenmenge, die im Rahmen eines Rechenganges bearbeitet werden muss. Um die Daten kompakt an einem Ort zu haben, werden diese zumeist in einer gesonderten Datei ausgelagert. Ausgelagerte Daten müssen über spezifische Schnittstellen in das eigene Programmsystem eingelesen werden. Eine Möglichkeit ist das Einlesen über eine CSV-Datei. Auch können SQL-Datenbanken verwendet werden. Liegen GIS Daten zugrunde, kann eine Geodatenbank sinnvoll sein. Eine Geodatenbank kann bspw. über das Programmsystem PostgreSQL in Verbindung mit PostGIS gebildet werden. Das Programmsystem PostgreSQL ist ein objektrelationales open source Datenbankmanagementsystem. Das Programmsystem PostGIS wurde als Erweiterung für die objektrelationale Datenbank PostgreSQL entwickelt. Dieses Programmsystem enthält geografische Objekte und Funktionen. In Python können beide Programmsysteme eingesetzt werden (oemof 2014b). Für die Arbeit mit OEMOF ist darauf hinzuweisen, dass in den Texten auf den Internetseiten und auch hier in diesem Buch mit der vereinfachten Auszeichnungssprache „reStructuredText“ (reST) gearbeitet wird. Eine solche Sprache wird dazu eingesetzt, um Daten in Computertechnologien zu beschreiben (Maier 2015). Ihr Ziel ist es, Quellcode für den Anwender leicht lesbar zu machen. Um ein Beispiel zu nennen, sei folgender Auszug vorgestellt:
© Springer Nature Switzerland AG 2023 J. Nagel, Optimierung von Energieversorgungssystemen, https://doi.org/10.1007/978-3-031-36355-9_2
31
32
2 Das generische Basismodell in OEMOF
attr:`om.NonConvexFlow.status`: Das Wort „attr“ steht für Attribut. Dem folgt „om.NonConvexFlow.status“. Das eigentliche Attribut heißt „status“. Dieses befindet sich in der Klasse „NonConvexFlow“. Mit diesem Ausdruck wird also der Pfad angegeben, wo bspw. ein Attribut zu finden ist.
2.1 Grundlagen in Python Das Programmsystem OEMOF bietet ein Gerüst (framework) mit einer Toolbox zur Modellierung von Energieversorgungssystemen an. Dieses Gerüst mit seinen Werkzeugen ist in der Programmiersprache Python objektorientiert umgesetzt worden. Ein objektorientierter Ansatz beruht auf Klassen und Objekten. Objekte werden auch Instanzen genannt. Bei der Entwicklung des objektorientierten Ansatzes war es das Ziel, Daten und Funktionen, die auf die Daten zugreifen und bspw. Berechnungen durchführen, nach außen in so genannten Klassen zu kapseln und so vor dem Zugriff von außen zu sichern. Damit können Benutzer der Klassen, aber auch Methoden bzw. Funktionen fremder Objekte nicht auf diese zugreifen und die Daten verändern (Klein o. J.-a). Der Modellierer eines Programmsystems zur Optimierung eines Energieversorgungssystems wird bei der Arbeit feststellen, dass der Programmcode sehr schnell anwächst. Die Herausforderung ist daher, den Code übersichtlich und fehlerfrei zu halten. Nur durch Ordnung im eigenen Quellcode kann dies gelingen. Um dies zu gewährleisten, bietet sich das Konzept der Modularisierung an. In Python werden zwei Arten von Modulen unterschieden (Klein o. J.-b): • Bibliotheken (Libraries) Das Programmsystem Python stellt allen Anwendern allgemeine Funktionalitäten zur Verfügung. Dazu gehören unterschiedliche Funktionen, wie bspw. „SageHallo“, und das Definieren unterschiedlicher Datentypen, wie bspw. Ganzzahlen (Integer), Fließkommazahlen (floating point numbers) oder Zeichenketten (Strings). Diese sind in so genannten Bibliotheken programmtechnisch umgesetzt. Zu den Bibliotheken gehören: –– eine umfangreiche Standardbibliothek –– eigene, selbst programmierte Bibliotheken –– fremde Bibliotheken von Drittanbietern • Lokale Module Dies sind eigene Programmteile, die nur für ein spezielles Programm verfügbar sind. Hier sind Module relevant, die in Python umgesetzt wurden. Sie werden als eigenständiges Programm zur Verfügung gestellt und tragen als Dateinamen die Endung: „.py“. In lokalen Modulen werden bspw. Klassen und Funktionen eines speziellen Programms beschrieben. Allgemein werden Module durch eine Import-Anweisung in den eigenen Quellcode eingebunden. Um den Code übersichtlich zu halten, empfiehlt es sich, die Import-Anweisung
2.1 Grundlagen in Python
33
an den Anfang des Quellcodes zu schreiben. Es können auch mehrere Module durch Kommata getrennt aufgeführt werden. Ein Import wird wie folgt angestoßen: # To import an entire module: import # If only one class from a module should be imported: from import
Über diesen Quellcode werden Module im eigenen Programmcode aufgerufen und eingebunden. Gehören mehrere Module zusammen und besteht zusätzlich eine Datei mit dem Namen „__init__.py“, können diese in einem Verzeichnis zusammengefasst werden (Klein o. J.-c; Python 2018c). Man spricht dann von Packages. In dem Verzeichnis können zusätzlich zur Datei „__init__.py“ mehrere weitere Python Module liegen. Die Datei „__init__.py“ kann leer sein oder beim Import auszuführenden Code enthalten. In einem Python Package wird auf das zugehörige Verzeichnis mit den Python Modulen referenziert. Normalerweise wird ein Python Package im folgenden Verzeichnis abgelegt: • Linux Nutzer: /usr/lib/python/site-packages • Windows Nutzer: C:/Python27/Lib/site-packages/ Um ein Paket im eigenen Quellcode nutzen zu können, muss dieses zunächst initialisiert werden: mypackage/__init__.pymypackage/mymodule.py
Ist dies erfolgt, kann das Package importiert werden. Dabei sind u. a. zwei Wege möglich. Entweder es wird ein Modul aus einem Package importiert oder nur eine Klasse aus einem der Module: # Import of a module from a package: import mypackage.mymodule # Import of a specific class from a module: from mypackage.mymodule import myclass
Weiterhin besteht die Möglichkeit, ein Modul beim Import umzubenennen, um es im eigenen Code bspw. mit einem kürzeren Namen aufzurufen: import as
34
2 Das generische Basismodell in OEMOF
2.1.1 Klassen, Objekte und Methoden Zunächst wird zwischen Klassen und Objekten unterschieden. Objekte dienen zur Kapselung von Daten und Funktionalität. Sie werden während der Laufzeit eines Computerprogramms erzeugt (Instanziiert). Sie besitzen konkrete Ausprägungen einer Klasse. Eine Klasse könnte auch als Objekttyp bezeichnet werden. Wir könnten bspw. die Klasse „Windkraftanlagen“ definieren. In dieser Klasse würde festgelegt werden, dass Windkraftanlagen aus den Teilen Rotorblatt, Rotornabe, Gondel, Getriebe, Generator und Turm bestehen. Eine bestimmte Windkraftanlage Typ A mit speziellen Ausprägungen (Eigenschaften), wie einem Durchmesser der Rotorblätter von 100 m und einer Höhe der Rotornabe von 210 m, wäre dann das Objekt. Die Klassen geben somit den Bauplan für eine Windkraftanlage vor und die Objekte sind die errichtete Windkraftanlage (Windkraftanlage Typ A). In der Klasse sind die gemeinsamen Strukturen und das gemeinsame Verhalten der realen Objekte abgelegt. Häufig wird davon gesprochen, dass Objekte Instanzen einer Klasse sind. Objekte besitzen einen Zustand, ein Verhalten und eine Identität. Der Zustand eines Objektes ist durch seine Eigenschaften (Attribute) sowie die Verbindungen zu anderen Objekten charakterisiert. Bei unserem Beispiel für das Objekt Windkraftanlage Typ A wäre die Rotordrehzahl ein Attribut. Diese kann bspw. maximale, minimale oder aktuelle Werte annehmen. Dem Attribut aktuelle Rotordrehzahl könnte dann ein Wert zugewiesen werden, bspw. aktuelle Drehzahl ist 12 U/min. Durch die Methoden wird das Verhalten eines Objektes beschrieben. Meist sind es mehrere Methoden, die das Verhalten eines Objektes festlegen. Eine Methode für das Objekt „Windkraftanlage Typ A“ könnte das Anfahren sein, eine andere Methode das Anhalten des Windrades oder das Messen der aktuellen Geschwindigkeit der Rotorblätter. Eine weitere Methode könnte auf die Eigenschaft Rotordrehzahl zugreifen und diese verändern. Dazu würde in der Methode der Rechenweg festgelegt werden: nimm die aktuelle Rotordrehzahl und erhöhe diese um 5. Anschließend wäre die neue Rotordrehzahl als aktuelle Drehzahl verfügbar. Diese Methode könnte „ändere_Rotordrehzahl“ genannt werden. Es handelt sich um eine Zugriffsmethode, da nur die in der entsprechenden Klasse definierte Methode auf die Eigenschaften und damit die Daten zu den Eigenschaften, hier die Daten der Eigenschaft Rotordrehzahl, zugreifen darf. Denn in dem objektorientierten Ansatz sind die Daten in den Objekten gekapselt. Nur die Methode einer Klasse hat die Informationen darüber, wie die Klasse und die Objekte implementiert sind (Klein o. J.-a). Es können auch Funktionen oder Prozeduren bestehen. Funktionen sind in einem Computerprogramm Verarbeitungsschritte, die durchgeführt werden sollen. Sie benötigen einen Input (Argument). Sind die Verarbeitungsschritte durchlaufen, wird ein Wert (Funktionswert – auch als Ergebniswert bezeichnet) zurückgeliefert. Ein anschauliches Beispiel für eine Funktion wäre, aus einer International Bank Account Number (IBAN-Nummer) die dazugehörige Bank zu ermitteln. Diese Funktion hätte als Argument die IBAN-Nummer und als Funktionswert (Wert) die Bank. Im Computerprogramm könnte dies ausgedrückt werden: Bank=IBAN. IBAN ist dabei der Name der Funktion. Im
2.1 Grundlagen in Python
35
Quellcode (Programmcode) dieser Funktion würde abgelegt werden, wie aus der IBAN die Bank ermittelt wird. Prozeduren sind Unterprogramme, die weder ein Argument haben noch einen Funktionswert zurückliefern. Es handelt sich bei Prozeduren um eine Abfolge von Programmbefehlen, die nacheinander vom Computerprogramm abgearbeitet werden. Wird in einem Computerprogramm immer wieder die gleiche Abfolge von Programmbefehlen benötigt, können diese in einer Prozedur zusammengefasst werden. Im Quellcode wird an den entsprechenden Stellen, wo die Abfolge an Programmbefehlen benötigt wird, einfach nur die Prozedur aufgerufen. Funktionen und Prozeduren sind Unterprogramme (Subroutinen). Sie erleichtern das Schreiben des Programmcodes und machen diesen übersichtlicher, wodurch Programmfehler leichter aufgefunden werden können. Jedes Objekt erhält eine Identität, um Objekte bei gleichem Zustand und gleichem Verhalten unterscheiden zu können. Die Identität wird durch die Zuordnung eines spezifischen Objekt-Schlüssels (identifier) im Programmcode abgelegt. Der Objektschlüssel für Windkraftanlagen könnte für die Anlage Typ A lauten: WK-A und für Windkraftanlage Typ B: WK-B. In der zugehörigen Klasse sind diese Informationen formal beschrieben. In der Klasse ist abgelegt, wie ein Objekt beschaffen ist, bspw. welche Attribute und Methoden diese besitzt (s. Abb. 2.1). Zwischen den Klassen bestehen häufig Beziehungen zueinander. Die Klasse der „Wind kraftanlagen“ wäre in unserem Beispiel eine Elternklasse, denn es gibt bspw. Kleinwindkraftanlagen und Großwindkraftanlagen. „Kleinwindkraftanlagen“ und „Großwindkraft anlagen“ stellen Kindklassen der Elternklasse „Windkraftanlage“ dar. Die Kindklassen leiten sich aus der Elternklasse ab. Es ist damit möglich, bestimmte Eigenschaften und Methoden der Elternklasse an die Kindklassen zu vererben. Die Kindklassen besitzen zudem weitere spezifische Attribute und Methoden (s. Abb. 2.2). Über die Methoden wird auf die Attribute zugegriffen, um diese bspw. auszulesen oder zu verändern. Eine Methode ist ebenfalls ein Unterprogramm. Sie wird bei einem objektorientierten Ansatz einer Klasse zugeordnet. In unserem Beispiel kann für die Klasse „Windkraftanlage“ die Rotordrehzahl über die Methoden „Anlage anfahren“ und „Anlage anhalten“ verändert werden. Über die Methode „Rotordrehzahl messen“ kann die aktuelle Drehzahl ausgelesen werden. Der Methode würde ein Eingabewert (Argument) von bspw. 5 U/min. übergeben werden, den sie ausgeben kann. Der Aufruf der Methode erfolgt Abb. 2.1 Modellierung der Klasse „Windkraftanlage“ mit ihren Eigenschaften (Attributen) und Methoden. (Nach Klein o. J.-a)
36
2 Das generische Basismodell in OEMOF
Abb. 2.2 Elternklasse und deren Kindklassen – das Prinzip Vererbung. (Nach Klein o. J.-a)
jeweils für ein Objekt. Die Syntax für den Aufruf einer Methode unterscheidet sich von der einer Funktion (s. Abschn. 2.1.3). Ein Objekt wird aus seiner Klasse heraus erzeugt. Zur Laufzeit besitzt dieses Objekt seinen eigenen Datentyp, wie bspw. Ganz- oder Kommazahlen, Datum oder Zeit, und seine eigenen Eigenschaften und Methoden.
2.1.2 Daten Im Grundkonzept gibt es in einem objektorientierten Ansatz Klassen, Objekte, Methoden und Daten. Daten sind die konkreten Ausprägungen eines Attributes eines Objektes. Greift eine Methode auf ein Attribut eines Objektes zu, verwendet dieses die Daten (z. B. für eine Datenausgabe oder Datenveränderung), wodurch auch das Attribut eines Objektes verändert werden kann. Durch die Kapselung der Daten in einem Objekt und die Notwendigkeit des Einsatzes einer Methode für den Zugriff auf die Daten sind diese vor dem Zugriff
2.1 Grundlagen in Python
37
anderer fremder Objekte geschützt. Denn ein fremdes Objekt müsste sowohl das entsprechende Objekt kennen als auch die zugehörige Zugriffsmethode besitzen. Die Methoden können Algorithmen für bspw. Plausibilitätstests, Datentypwandlungen oder notwendige Berechnungen enthalten (s. Abb. 2.3). Im Programmcode würde man das Attribut „Marke“ bspw. als Zeichenkette (String) ablegen. Da die Daten gekapselt sind, kann nicht direkt auf diese zugegriffen werden, sondern es braucht dazu eine entsprechende Methode, bspw. die Methode „HoleMarke“, die vom Anwender bei Bedarf mit dem Programm aufgerufen wird. Die Methode „Hole Marke“ kann dann den Namen der Marke ausgeben. Weiterhin könnte die Methode „Hole Marke“ prüfen, in welchem Land diese Marke verfügbar ist bzw. ob diese in dem vom Anwender gewünschten Land verfügbar ist. Noch besser ist es, die Attribute in den Klassen zu kapseln. Damit die Attribute nicht immer öffentlich zugänglich sind, bietet Python die in Tab. 2.1 dargestellten Mechanismus an. Abb. 2.3 Kapselung von Daten. (Nach Klein o. J.-a)
Tab. 2.1 Mechanismus für öffentliche und nicht öffentliche Attribute. (Nach Klein 2018) Name name
Bezeichnung Bedeutung Public Attribute ohne führende Unterstriche: les- und schreibbar sowohl innerhalb als auch außerhalb einer Klasse. _name Protected Von außen ist ein lesender und schreibender Zugriff möglich. Entwickler macht mit der Syntax deutlich, dass diese Attribute nicht benutzt werden sollten. __ Private Attribute mit doppelten führenden Unterstrichen: von außen weder name sichtbar noch benutzbar.
38
2 Das generische Basismodell in OEMOF
2.1.3 Erstellen von Klassen, Objekten und Methoden Wie wird eine Klasse in der Programmiersprache Python angelegt? Dazu verwenden wir unser Beispiel der Klasse „Windkraftanlage“ (windpowerplant). Diese wird wie folgt angelegt: class windpowerplant: pass
Die Klasse wird mit dem Schlüsselwort „class“ eingeführt. Noch hat die Klasse „wind powerplant“ weder Attribute noch Methoden. Das Wort „pass“ sagt dem Interpreter, dass die erforderlichen Anweisungen zu einem späteren Zeitpunkt nachgeliefert werden. Die Syntax zur Definition einer Klasse besteht damit aus den zwei Teilen: • Kopf: besteht meist aus dem Schlüsselwort „class“, einem Leerzeichen und einem Namen, hier „windpowerplant“. Handelt es sich um eine Kindklasse, wird in einer Klammer angefügt, von welcher Elternklasse diese erbt. Gibt es bspw. eine Kindklasse „small_wind_turbine“, die von der Elternklasse „windpowerplant“ erbt, so würde die Kindklasse wie folgt geschrieben: „class small_wind_turbine(windpowerplant)“. Wenn es keine Elternklasse gibt, entfällt in Python der Klammerausdruck. Als letztes Zeichen folgt ein Doppeltpunkt. • Körper: eingerückt werden ein oder mehrere Anweisungen angegeben. Hier steht als Anweisung eingerückt das Wort „pass“. Das Schlüsselwort „class“ ist eines von 33 Schlüsselwörtern in Python, wie sie in Tab. 2.2 aufgeführt sind. Ausgehend von der Klasse „windpowerplant“ sollen nun zwei Objekte (Instanzen) („a“, „b“) erzeugt und Eigenschaften zugeordnet werden. Tab. 2.2 Schlüsselwörter in Python (viel verwendet). (ZetCode o. J.) False None True and assert break class
continue Def Del Elif Else Except Finally
For From Global If Import In Is
Lambda Nonlocal Not Or Pass Raise Return
try while with yield
2.1 Grundlagen in Python
39
class windpowerplant: pass a = windpowerplant() b = windpowerplant() a.make = “Windk Super” a.construction_year = 2016 b.make = “Windk Plus” b. construction_year = 2017 print (a.make) Windk Super
Es werden zwei Objekte der Klasse „windpowerplant“ initialisiert, Objekt „a“ und Objekt „b“. Die leere Klammer in diesem Ausdruck („a = windpowerplant()“) deutet an, dass die Klasse „windpowerplant“ bereits zuvor instanziiert worden ist und es sich somit um ein Objekt und nicht um eine Klasse handelt. Jedem Objekt werden eine Marke und das Baujahr zugeordnet. Die Attribute werden getrennt durch einen Punkt an den Namen der Instanz angefügt. Die Eigenschaften werden Instanzvariablen genannt. Die Variablen werden nur innerhalb eines Objektes, z. B. Objekt „a“, definiert, wodurch das Objekt „a“ charakterisiert wird. Das Pendant bei den Klassen stellen Klassenvariablen dar. Dabei werden die Instanzvariablen direkt in der Klasse definiert. Weiterhin folgt der Aufruf „print (a.marke)“. Über diesen Aufruf wird dem Programm mitgeteilt, dass es die Instanzvariable „Marke“ vom Objekt „a“ ausgeben soll. Als Ausgabe erscheint „Windk Super“. Methoden werden in der Klasse definiert. Sie werden über das Schlüsselwort „def“ eingeführt. In Python unterscheidet sich eine Methode formal durch zwei Aspekte von einer Funktion (Klein o. J.-a): • Die Methode stellt eine Funktion dar, die innerhalb einer class-Definition definiert wird. • Mit dem ersten Parameter einer Methode wird immer auf das Objekt referenziert, für welches sie aufgerufen wird. Aus diesem Grund wird dort „self“ als erster Parameter als Referenz auf die Instanz einer Klasse angegeben. Im folgenden Beispiel wird die Methode „SageHallo“ nicht nur aufgerufen, sondern es wird ihr auch die Instanz „a“ mit einem Punkt getrennt vor dem Aufruf mitgegeben. Damit wird die Referenz auf die Instanz „a“ an „self“ übergeben. Das Objekt „a“ ist eine Instanz in dieser Klasse. Um ein einfaches Beispiel für eine Methode zu geben, lassen wir unsere Windkraftanlage über die Methode „SageHallo“ sprechen. Wird diese Methode aufgerufen, gibt sie den Wert „Hallo“ aus. Das ist zwar nicht sehr sinnvoll, verdeutlicht aber auf einfache Weise das Funktionsprinzip.
40
2 Das generische Basismodell in OEMOF
class windpowerplant: def SayHallo(self): print(„Hallo“) if __name__ == “__main__”: a = windpowerplant() a.SayHallo()
Die Programmzeile: if __name__ == “__main__” bedeutet folgendes: • __name__: Es handelt sich hierbei um ein so genanntes built-in-Attribut. In dem angegebenen built-in-Attribut ist der Name eines Moduls definiert. Hier stellt das Modul die Methode „print“ dar. Diese Methode ist unter dem Namen print.py gespeichert. • __main__: Wird das Modul print.py in den Programmcode importiert, wird dies mit dem Befehl „import print“ am Anfang des Programmcodes durchgeführt. Das bu iltin-Attribut „__name__“ besitzt dann den Wert „print“ (__name__ == „__print__“). Es besteht aber auch die Möglichkeit, die Methode „print“, also die Datei print.py, als eigenständiges Programm aufzurufen. Dann erfolgt dies in der angegebenen Art und Weise mit dem Wert „__main__“. Die Abfrage „if“ gibt dem Programm die Anweisung: Wenn das Programm als Hauptprogramm (__main__) aufgerufen und damit nicht importiert wurde, dann führe die folgende Anweisung durch. Mit der Syntax „a.SayHallo ()“ wird der Aufruf nur für die Instanz „a“ zum Ausgeben von „Hallo“ vorgegeben. Auf diese Weise erfolgt eine Zuweisung der Methode, für welche Instanz diese aufgerufen werden soll. Eine Funktion hat dahingegen einen anderen Aufbau. Um dies vorzustellen, wird mittels einer Funktion der Druck von bar in Pascal (Pa) umgerechnet: def pressure(bar_in_Pa): """ returns the pressure in pascal """ return bar_in_Pa * 10e5 for p in (22.6, 25.8, 27.3, 29.8): print(p, ": ", pressure(p))
Als Ergebins erhält man: 22.6 25.8 27.3 29.8
: : : :
2260000 2580000 2730000 2980000
2.1 Grundlagen in Python
41
Es ist zu erkennen, dass eine Funktion formal dem Aufbau folgt: def funktions-name(parameter_list): instruction(s)
Eine Funktion unterscheidet sich damit erkennbar von einer Methode.
2.1.4 Klassenvariablen Erzeugen wir nun die Attribute innerhalb einer Klasse. Innerhalb einer Klasse sind die In stanzen nicht bekannt. Darum wird erneut der Parameter „self“ mitgegeben. Attribute eines Objektes werden innerhalb einer Klasse mittels der Syntax „self.attributname = wert“ festgelegt. Dieses erfolgt bereits bei der Initialisierung des Objektes. Innerhalb einer Klasse kann anschließend mittels „self.attributname“ auf die zuvor definierten Attribute zugegriffen werden. Außerhalb einer Klasse erfolgt dies über die Syntax „instanzname.attributname“. In unserem Beispiel sind die Variablen ,„Make“ oder „construction year“ nicht im Objekt definiert und damit auch noch nicht bekannt. Daher muss die Zuweisung der Werte für die Attribute nun in den Klassen erfolgen. Dazu werden Methoden benötigt. Für den Aufruf von Methoden besteht ebenfalls eine spezifische Syntax. So werden Methoden innerhalb einer Klasse mittels self.methodenname() und außerhalb über instanzname.methodenname() aufgerufen. Im Folgenden erhalten die beiden Objekte der Klasse „windpowerplant“ über die Methode „SetMake“ eine Marke und über die Methode „SetConstructionYear“ ein construction year. class windpowerplant: def SayHallo(self) print(“Hallo, I’m of the make“ + self.make) def SetMake(self, make): self.make = make def SetConstructionYear(self, construction_year): self.construction_year = construction_year if __name__ == „__main__“: a = windpowerplant() a.SetMake(„Windk Super”) a. SetConstructionYear(2016) b = windpowerplant() b.SetMake(„Windk Plus”) b. SetConstructionYear(2017) a.SayHallo() b.SayHallo()
42
2 Das generische Basismodell in OEMOF
Über das „+“-Zeichen im Code wird angezeigt, dass bei der Ausführung der Funktion „SageHallo()“ zusätzlich zum String (Satz) „Hallo, ich bin von der Marke“ der Wert des entsprechenden Parameters „Marke“ des Objektes ausgegeben werden soll. Als Ausgabe würde für das Objekt „a“ erscheinen: „Hallo, I’m of the make Windk Super.
Eine wichtige Methode soll an dieser Stelle noch vorgestellt werden, die __init__-Methode. Sie dient dazu, die Attribute einer Instanz automatisch sofort nach deren Erzeugung zu definieren. Durch sie erfolgt die Initialisierung einer Instanz. Im Quellcode einer Klassendefinition sollte die __init__-Methode immer direkt unterhalb des Klassenheaders stehen. Folgendes Beispiel zu unserer Klasse „windpowerplant“ soll dies verdeutlichen: class windpowerplant: # Here a simple class „Windpowerplant“ is generated. def __init__(self, distance, noises): # Initialize a new windpowerplant. # Arguments are: # distance (string): distance to the next building. # noises (Wsch- noise when turbine is rotating self.distance = distance self.noises = noises # A method to return the distance and render the noises is provided. def show_distance(self): print self.distance def playnoise(self) print self.noises + “!!!”
Das obige Beispiel zeigt die Definition der Klasse „windpowerplant“. Im ersten Absatz wurde eine kurze Beschreibung der Klasse über einen Hashtag (#) eingeführt. In der folgenden Methode __init__ wird definiert, wie eine neue Instanz der Klasse „windpowerplant“ erzeugt wird. Es erfolgt ein automatischer Aufruf der Methode __ init__, sobald der Name der Klasse als Funktion aufgerufen wird. In Klammern sind die Argumente der Instanz angegeben, die als Eigenschaften gespeichert werden. Die Eigenschaften sind Abstand zu einem Gebäude und Geräusche, die durch das Drehen des Windrades entstehen. Zudem stehen dem neuen Objekt nach der Initialisierung die beiden definierten Funktionen „show_distance()“ und „playnoise()“ zur Verfügung. Sie dienen als Ausgabe der jeweiligen Objektvariablen.
2.1 Grundlagen in Python
43
Nun soll ausgehend vom obigen Beispiel eine neue Instanz der Klasse „windpower plant“ erzeugt werden: # Initializing of a new instance: my_windpower_plant = windpowerplant(“3 m”, “Wsch, Wsch, Wsch”) # Functions get tested: my_windpower_plant.show.distance() # The result is: “3 m” my_windpower_plant.playnoise() # The result is: „Wsch, Wsch, Wsch !!!
2.1.5 Vererbung Ein wichtiges Konzept in der objektorientierten Programmierung ist die „Vererbung“. Dazu wird eine Basisklasse definiert, die ihre Attribute und Methoden an eine abgeleitete Subklasse „vererbt“. Dabei können bei der Subklasse weitere Eigenschaften hinzukommen oder geerbte Eigenschaften angepasst werden. Im Quellcode erfolgt das Vererben durch die Angabe der Subklasse bei der Klassendefinition in runden Klammern. In diesem Fall wird von der Klasse „windpowerplant“ eine Subklasse gebildet. class Subclass(windpowerplant): pass
Die vorgestellten Inhalte sollen dazu dienen, ein Grundverständnis für die objektorientierte Programmiersprache Python zu erhalten. Für ein vertieftes Studium wird empfohlen, mithilfe konkreter Suchbegriffe im Internet nach aktuellen Ausführungen, wie bspw. zu Big Data oder Datenbanken, zu recherchieren.
2.1.6 Ein eigenes Beispiel in Python umsetzen Die Energiewende hat Auswirkungen auf die Mobilität. Dabei stehen unterschiedliche Kraftstoffe, wie bspw. Diesel, Ethanol oder Biomethan, zur Verfügung. Die Umstellung auf Elektromobile ist aktuell stark in der Diskussion. Welches Mobilitätskonzept sich durchsetzt bzw. welche Energiequelle zukünftig wirtschaftlich ist, kann mithilfe eines Optimierungsmodells analysiert werden. Dieser Ansatz soll genutzt werden, um die Programmiersprache Python anzuwenden. Dazu wird die Klasse Auto als Elternklasse und zwei Kindklassen, den Verbrenner (Kraftstoff) und das eMobil eingeführt. In der Elternklasse Auto werden zwei Methoden definiert. In den Kindklassen kommen weitere nur für die jeweilige Kindklasse geltende Me-
44
2 Das generische Basismodell in OEMOF
thoden hinzu. Weiterhin werden Parameter angelegt, wie bspw. Verbrauchskosten oder Gesamtkosten. Diesen wird ein Wert zugewiesen, der nach mit den Ergebnissen von Berechnungen überschrieben wird. Dieses Beispiel kann genutzt werden, um erste Versuche in Python zu unternehmen. Die Kindklasse „eMobil“ ist noch nicht weiter ausgeführt. Hier könnte bspw. eine Funktion zum Beladen angelegt werden. “““Modul car“““ # ---------------------------------------# Auxiliary functions # ---------------------------------------def value_return(label, value, unit=None): “““Formatted return of values with and without unit“““ if unit is None: print(ꞌ{b}: {w}ꞌ.format(b=label, w=value)) elif unit == ꞌEuroꞌ or unit == ꞌEURꞌ: print(ꞌ{b}: {w:.2f} {e}ꞌ.format(b=label, w=value, e=unit)) else: print(ꞌ{b}: {w} {e}ꞌ.format(b=label, w=value, e=unit)) def fuelcosts_per_km(price_in_euro_per_l, consumption_in_l_per_100km): “““Calculation of the fuel costs per kilometers :param price_in_euro_per_l: price per liter [EUR/l] :param consumption_in_l_per_100km: consumption of 100 km [l/100km] :return: fuel costs per kilometers [EUR/km] “““ return price_in_euro_per_l * consumption_in_l_per_100km / 100 # ---------------------------------------# car classes # ---------------------------------------class car: “““Each instance of the class car has a range.“““ def __init__(self, range):
self.range = range self.amount_fuelling = 0 self.consumption_costs = 0 self.total_costs = 0
# # # #
range in km per tank filling amount of fuelling accumulated consumption costs accumulated total costs
2.1 Grundlagen in Python
45
def fuelling(self, costs_per_km):
# Mileage (driven km) calculate and show mileage = self.amount_fuelling * self.range value_return(ꞌmileageꞌ, mileage, ꞌkmꞌ) # Show previous consumption costs value_return(ꞌ consumption costs before fuellingꞌ, self.consumption_costs, ꞌEuroꞌ) # Increase and show counter of fuilling self.amount_fuilling += 1 value_return(ꞌamount of fuillingꞌ, self. amount_fuilling) # Calculate and return costs of fuilling costs_fuilling = self.range * costs_per_km Show_value(ꞌ costs of fuilling ꞌ, costs_fuilling, ꞌEuroꞌ) # Calculate new and show accumulated consumption costs and total costs self.consumption_costs += costs_fuilling self.total_costs += costs_fuilling show_value(ꞌ consumption costs after fuillingꞌ, self.consumption_costs, ꞌEuroꞌ) show_value(ꞌtotal costs after fuillingꞌ, self.total_costs, ꞌEuroꞌ) class burners (car): “““Each instance oft he class burners can have a soot filter with a defined lifetime.“““ def __init__(self, range, filter_lietime_km=None): super(burner, self).__init__(range) self.drive_technology = ꞌcombustion engineꞌ if filter_lifetime_km: self.filter_lifetime_km = filter_lifetime_km # Lifetime of a soot filter in km self.filter_km = 0 # Driven km wuth soot filter def fuilling(self, costs_pro_km): super(burner, self).fuilling(costs_per_km)
46
2 Das generische Basismodell in OEMOF if self.filter_km is not None: # Show mileage of the soot filter show_value(' mileage of the soot filter', self.filter_km, 'km') # Check lifetime of the soot filter if self.filter_lifetime_km + self.range The soot filter has to be changed soon. < # bus.fuilling(0.25)
2.2 Strukturaufbau des generischen Models in OEMOF
49
# Mileage: 2400 km # Consumption costs before fuilling: 600.00 Euro # Amount of fuilling: 5 # Fuel costs: 150.00 Euro # Consumtion costs after fuilling: 750.00 Euro # Total costs after fuilling: 750.00 Euro # Mileage of the soot filter: 2400 km # > Change soot filter! < # bus.filter_change(500) # Consumption costs before changing soot filter: 750.00 Euro # Total costs after changing soot filter: 1250.00 Euro # bus.fuilling(0.24) # Mileage: 3000 km # Consumption costs before fuillling: 750.00 Euro # Amount of fuilling: 6 # Fuel costs: 144.00 Euro # Consumtion costs after fuilling: 894.00 Euro # Total costs after fuilling: 1394.00 Euro # Mileage oft he soot filter: 600 km
Dieses ist nur ein sehr einfaches Beispiel. Auf eine solche Art und Weise können eigene und sehr viel komplexere Programme angelegt werden, um Fragestellungen zu Energieversorgungssystemen zu beantworten.
2.2 Strukturaufbau des generischen Models in OEMOF Wie bereits aufgeführt wurde, ist das Programmsystem OEMOF mit einer objektorientierten Struktur aufgebaut. Es besitzt Klassen und Objekte. Zudem weist das Programmsystem einen modularen Aufbau bestehend aus Bibliotheken (Libraries), lokalen Modulen und Paketen auf. Zum jetzigen Zeitpunkt verfügt OEMOF über neun selbst programmierte Bibliotheken. Die Bibliotheken übernehmen bestimmte Aufgaben, wie bspw. die Erzeugung von Leistungskurven (Lastprofile) aus Winddaten. Manche Bibliotheken dienen der Erledigung bestimmter Arbeitsschritte. So wird nach der Berechnung bzw. Lösung eines Optimierungsproblems in der Bibliothek oemof.solph anschließend die Ausgabe der Daten erforderlich. Hierfür steht die Bibliothek oemof.outputlib zur Verfügung. In OEMOF sind die Bibliotheken zu organisatorischen und funktionalen Einheiten zusammengefasst, die sich durch gewisse Ebenen abbilden lassen. Über diese können sie anschließend auch charakterisiert werden (s Abb. 2.4).
50
2 Das generische Basismodell in OEMOF
Abb. 2.4 Anordnung der Bibliotheken in OEMOF in unterschiedlichen Schichten (layern). (Nach Hilpert et al. 2017)
Die vier Ebenen lassen sich wie folgt beschreiben: • Kernebene (Core Ebene) Die Kernebene beinhaltet in den zentralen core Bibliotheken die grundlegenden Klassen, über die die Basis-Schnittstellen (interface) definiert sind. In ihnen ist die generische Grafenstruktur abgelegt. Ein Energiesystem, wie es in OEMOF verstanden wird, wird dort beschrieben und die grundlegenden Programmierschnittstellen (basic application programming interface – API) sind darin definiert. Für die Input- und Outputdaten sind die Formate festgelegt. Damit ist auf dieser Ebene die grundlegende Repräsentation eines Energiesystems als Graph mit den zugehörigen Grundfunktionalitäten umgesetzt. • Ebene des Namensraums (namespace layer) In dieser Ebene erfolgt die Optimierung. Hier liegen Packages, die von den Basis- Modulen abstammen (Vererbung) und von diesen abhängig sind. Sie sind über die APIs an die Kernebene angebunden. Dies können Bibliotheken zur bspw. Optimierung von Kosten oder Energieflüssen sein. Diese Ebene wird auch Modellebene genannt, da darin die Modelle liegen, die auf der Graphenstruktur mit deren Klassen aufbauen bzw. von diesen erben. In der Version v0.2.0 war als einzige Bibliothek auf dieser Ebene die Bibliothek „solph“ zu nennen. Weitere Bibliotheken können jedoch zwischenzeitlich entwickelt worden sein.
2.2 Strukturaufbau des generischen Models in OEMOF
51
• „oemof“ Ebene (oemof cosmos) Darunter fallen Bibliotheken, die mit OEMOF verbunden sind. Sie enthalten Methoden, um diese in OEMOF anzuwenden. Ebenso können diese aber auch unabhängig davon genutzt werden. Die Bibliotheken sind sinnvolle Ergänzungen bei der Modellierung in OEMOF. Hier sind Bibliotheken rund um die Energiesystemoptimierung enthalten, die nicht System-Modelle sind und somit nicht auf der Graphenstruktur aufsetzen. So benötigen diese auch nicht die Core-Klassen und können unabhängig angewendet werden. Für eine leichtere Programmierung wird hierbei derselbe strukturelle Aufbau mit den gleichen Entwicklungsregeln angewendet. • „oemof-cosmos“-Ebene Hier, in der äußersten Ebene sind externe Pakete aus bereits erfolgreich durchgeführten Energieprojekten mit dem open source Quellcode abgelegt. Diese können Bibliotheken von Ebenen darunter nutzen oder eigene bereitstellen. Hierüber können Synergien zwischen den Objekten geschaffen werden, sodass auf bekanntes Wissen zurückgegriffen werden kann. Hierunter fällt bspw. die pv-Bibliothek. Diese ist qualitativ hochwertig, sodass in OEMOF keine eigene pv-Bibliothek umgesetzt wird. Wesentliches Kernstück der Bibliotheken ist die Beschreibung des Modells nach dem Netzwerkkonzept in den Bibliotheken „oemof.network“ und „oemof.solph“ (für oemof. network siehe aktuell https://github.com/oemof/oemof-network/tree/dev/src/oemof/network). Basierend auf dem objektorientierten Ansatz sind mehrere zentrale Kernklassen de finiert, die hierarchisch strukturiert sind. In Abb. 2.5 ist diese Struktur in der vereinheitlichten Modellierungssprache (Unified Modeling Language, kurz: UML) abgebildet.
Abb. 2.5 Hierarchische Struktur der Basisklassen (Core classes) in OEMOF (UML). (Nach Hilpert et al. 2017)
52
2 Das generische Basismodell in OEMOF
Die Basis bilden die Klassen „EnergySystem“, „Fluss“ (diese werden nach der Graphentheorie „Edge“ genannt) und „Knoten“ als Elternklassen. Darüber liegen die Kindklassen, die von den Elternklassen erben (s. Abb. 2.5). Die Klasse „EnergySystem“ fungiert wie ein Container. In ihr können weitere nutzerspezifische Informationen und Daten abgelegt werden Dazu gehören bspw. Informationen zum Zeitindex oder Daten über zusammengefasste Gruppen. Die Gruppierung erfolgt dabei nach bestimmten Kriterien, z. B. Gruppe aller Transformer des Typs Gasturbine. Vom Modellierer können auch selbst erstellte neue Elemente (features) über die Methode der Vererbung angelegt werden. Diese werden im Modul „custom.py“ abgelegt. Sind diese Klassen technisch gut umgesetzt, getestet und fehlerfrei, werden sie in das Modul „components.py“ übertragen. Aus den unterschiedlichen OEMOF-spezifischen und externen Bibliotheken entstehen anschließend sehr individuelle Modelle, die in einer Anwendung münden. Ordnet man die Bibliotheken einer Anwendung zu, entsteht das in Abb. 2.6 dargestellte Bild. Die in Abb. 2.6 abgebildeten Bibliotheken werden im folgenden Kapitel Abschn. 2.3 beschrieben.
Abb. 2.6 Verwendung der Bibliotheken zur Erstellung eines Modells im Rahmen einer Anwendung. (Nach Hilpert et al. 2017)
2.3 Libraries in OEMOF
53
2.3 Libraries in OEMOF Das Programmsystem OEMOF verfügt über fünf zentrale Bibliotheken: • • • • •
oemof-network oemof-solph oemof-outputlib feedinlib demandlib
Diese werden im Folgenden vertiefend vorgestellt. Der Nutzer kann über das Programmsystem GitHub auf den Quellcodes von OEMOF zugreifen. Bei GitHub handelt es sich um eine Entwickler Plattform die speziell für große open source Projekte entwickelt wurde. Der Zugang zu OEMOF erfolgt für den Nutzer über die in (s. Abb. 2.7) dargestellte Internetplattform. Direkt auf der obersten Seite erhält der Nutzer einen Einblick in die Struktur von OEMOF. Von der ersten Seite aus kommt man zu den unterschiedlichen Bibliotheken. Die Struktur hat sich zum Erscheinungsjahr 2019 etwas verändert. Module, wie bspw. oe mof-solph, sind auf die oberste Ebene gehoben und andere Module, wie bspw. oemof-ther mal, sind hinzugekommen. In einer individuellen Anwendung, diese wird in OEMOF Application genannt, werden bei der Modellierung eines bestimmten Energieversorgungssystems ein oder mehrere der aufgeführten Bibliotheken sowie möglicherweise weitere externe Bibliotheken eingebunden, um damit eine bestimmte Fragestellung, wie nach dem wirtschaftlichen Ausbau von Power-to-Gas-Anlagen, beantworten zu können.
Abb. 2.7 Eingangsmaske von OEMOF unter GitHub. (GitHub 2017r bzw. https://github. com/oemof)
54
2 Das generische Basismodell in OEMOF
2.3.1 Bibliothek „oemof-network“ Die Bibliothek oemof-network ist die Basis zur Modellierung eines Energieversorgungssystems in der Bibliothek oemof-solph. Im Rahmen der allgemeinen Modellierung eines Energieversorgungssystems wird die Bibliothek oemof-network nicht aktiv verwendet, sondern ihre Funktionen arbeiten im Hintergrund. In der Bibliothek oemof-solph wird anschließend die Grundstruktur des vom Modellierer betrachteten Energieversorgungssystems mittels der Graphentheorie umgesetzt. Die Bibliothek oemof-network steht dem Modellierer direkt nach der Installation von OEMOF zur Verfügung (urspr. (oemof 2014a)); aktuelle Informationen dazu finden sich unter: https://oemof-network.readthedocs.io/en/latest/reference/oemof.network.html#module-oemof.network.network. In dieser Bibliothek sind die Klassen zur Beschreibung der Elemente in einem Energieversorgungssystem inklusive der Inputs und Outputs, die sich in diesem Energieversorgungssystem befinden können, angelegt (GitHub 2017a, in 2023 https://github.com/ oemof/oemof-network/blob/dev/src/oemof/network/network.py). • • • • • • • • • •
class Inputs(MM) class Outputs(MM) class Edges(MM) class Node class Bus(Node) class Component(Node) class Sink(Component) class Source(Component) class Transformer(Component) class Entity
Klassen mit Klammern am Ende sind jeweils Kindklassen. Die jeweiligen Mutterklassen sind in den Klammern eingetragen. In den Mutterklassen werden die grundlegenden Methoden, Funktionen Parameter, Attribute sowie alle weiteren für die Modellierung wichtigen Daten abgelegt. Der Klammerausdruck „MM“ steht für MutableMapping. Dabei handelt es sich um eine abstrakte Python Basisklasse (ABC) für Container (Python 2018d). Diese Klassen können dazu genutzt werden, um zu prüfen, ob Klassen eine bestimmte Schnittstelle bereitstellen oder ob diese zerlegbar (hashable) sind oder ob es sich um eine Zuordnung handelt. Die ABC Klasse MutableMapping stellt die abstrakten Methoden „__getitem__“, „__setitem__“, „__delitem__“, „__iter__“ und „__len__“ zu Verfügung. Ein Import dieser Basisklasse in OEMOF erfolgt über den Aufruf: from collections import MutableMapping as MM
2.3 Libraries in OEMOF
55
Die Klasse „Edges(MM)“ stellt die Kanten des Modells nach der Graphentheorie dar. Die Klasse „Entitiy“ wurde bisher noch nicht aufgegriffen. Eine „Entity“ ist ein abstraktes Gebilde. Letztlich kann darunter jedes Element in einem Energieversorgungssystem, wie bspw. eine Gasturbine, verstanden werden. Wichtig ist, dass jede Entity eindeutig identifiziert werden kann und zudem über mindestens einen In- oder Output mit einer anderen Entity verbunden ist. Die Klasse „Entitiy“ dient dazu, die Eigenschaften (properties) der einzelnen Entities zu bündeln, damit sie folgenden Klassen zur Verfügung stehen. Diese Klasse wird durch die Objekte, die der Modellierer in seiner Applikation hinzufügt, gefüllt. Innerhalb der einzelnen Klassen werden bestimmte Methoden sowie bei manchen auch Parameter und Attribute definiert. Diese werden in den folgenden Kapiteln vorgestellt. Die Bibliothek ist in der Datei network.py abgelegt (GitHub 2017a).
2.3.2 Bibliothek „oemof-solph“ Die Bibliothek oemof-solph wurde entwickelt, um lineare und gemischt-ganzzahlig lineare Optimierungsprobleme aufzustellen und zu lösen (oemof 2014a). Aktuelle Informationen dazu finden sich unter: https://oemof-solph.readthedocs.io/en/latest/usage.html. Die Umsetzung erfolgte basierend auf der Programmiersprache Pyomo. Pyomo ist ein Programmpaket von Python, welches ebenfalls direkt im Installationspaket von OEMOF enthalten ist (oemof 2014a). Es handelt sich dabei um ein open source Modellierungswerkzeug, welches Klassen und Funktionen speziell zur Modellierung von Optimierungsfragestellungen, angebunden über eine API, bereitstellt. Das Paket verfügt über einen vielfältigen Satz von Optimierungsmöglichkeiten. Über die vier Basisklassen „Sink“, „Source“, „Transformer“, „Bus“ und weitere unter Umständen kundenspezifische Komponenten erfolgt die Gestaltung des betrachteten Energieversorgungssystems durch den Modellierer. Die Komponenten eines Energieversorgungssystems werden diesen Klassen entsprechend ihrer zuvor definierten Eigenschaften zugeordnet. Die vier Basisklassen stellen die Knoten (nodes) im Modell dar. Die Energieflüsse, die durch den Energiein- und -output entstehen, bilden die Kanten im Modell. Für deren Modellierung wird eine fünfte Basisklasse benötigt, die Klasse „Flow“. Diese Struktur kann anhand der Konvention in OEMOF mithilfe eines Netzplans dargestellt werden, wie dies in Kap. 1.2 in Abb. 1.2.7 erfolgt ist. In diesem Beispiel werden die Klassen „Source“ und „Sink“ als Rauten, die Klasse „Transformer“ als Rechteck, die Klasse „Bus“ als Kreis und die Klasse „Speicher“ als kundenspezifische Klasse als Stern dargestellt. Die Verbindungslinien stellen den „Flow“ dar, über den eine Bewertung des Energieflusses, z. B. über Kosten, erfolgt. Nachfolgend wollen wir ein etwas komplexeres Modell anhand eines Beispiels für die Stadt Rostock (nachfolgend einfach „Rostock“ genannt) betrachten. Das Modell beschreibt die Energieversorgung innerhalb und zwischen zwei Regionen (Region 1, Region 2) im Raum Rostock, Bundesland Mecklenburg-Vorpommern. Zur Bereitstellung von
56
2 Das generische Basismodell in OEMOF
Energie stehen in jeder Region mehrere unterschiedliche Komponenten zur Verfügung (s. Tab. 2.3). Des Weiteren verbindet ein Stromkabel die beiden Regionen. Über dieses Kabel fließt Strom sowohl von Region 1 in die Region 2 als auch umgekehrt von Region 2 in Region 1. Der Transport der Energie bzw. der Energiein- und -output wird über die Darstellung von Bussen umgesetzt. Ein Bus ist ein Bilanzknoten, der zu jeder Zeit ausgeglichen sein muss. Dies bedeutet auch, dass er keine Verluste aufweisen darf. Die betrachteten Komponenten und Flüsse sind in Tab. 2.3 zusammengetragen. Als Energienetzplan nach der Konvention von OEMOF ergibt sich für dieses Beispiel Abb. 2.8. Für die Umsetzung in oemof-solph ist eine Übertragung als bipatitierten Graph nach der Graphentheorie hilfreich (s. Abb. 2.9). Anhand des Graphenmodells erfolgt die programmtechnische Umsetzung in Quellcode. Diese Struktur ist die Eingangsinformation für die Bibliothek „oemof-solph“. Dazu Tab. 2.3 Knoten und Graphen für ein Energieversorgungssystem, Beispiel „Rostock“, Bundesland Mecklenburg- Vorpommern, Deutschland
Region 1 Komponente wind turbine biogas-cogeneration plant gas turbine electrical demand heat demand cable to region 2 Kundenspezifische Komponente storage Bus gas_bus electrical_bus local_heat_bus biogas_bus Region 2 Komponente coal plant chp plant (gas combined heat and power plant) p2g-facility Electrical demand Cable to region 1 heat demand Bus electrical_bus local_heat_bus coal_bus gas_bus
Verweis wt1 bg1 gt1 de1 dh1 cb1 Verweis st1 Verweis r1_gas r1_el r1_th r1_bio Verweis cp2 chp2 ptg2 de2 cb2 dh2 Verweis r2_el r2_th r2_coal r2_gas
2.3 Libraries in OEMOF
57
Abb. 2.8 Energienetzplan Beispiel „Rostock“
Abb. 2.9 Abbildung eines Energiemodells als bipatitierter Graph in OEMOF. (Nach oemof 2014c bzw. s. https://pythonhosted.org/oemof_base/meta_description.html)
58
2 Das generische Basismodell in OEMOF
müssen zunächst unsere Komponenten, wie Blockheizkraftwerke und die Verbindungen zwischen den Komponenten durch die Klassen „Bus“ und „Flow“ definiert werden. Weiterhin wird für die programmtechnische Umsetzung ein Container benötigt, in den das spezifische Energiemodell übertragen werden soll. Der Modellierer kann dazu auf das Modul oemof.energy_system (Datei energy_system.py) zurückgreifen. In diesem Modul ist die Klasse „EnergySystem“ definiert (s. https://oemof-network.readthedocs.io/en/latest/_modules/oemof/network/energy_system.html#EnergySystem und https://github. com/oemof/oemof-network/tree/dev/src/oemof/network). In der Klasse sind verschiedene Methoden abgelegt, die auf die Instanz der Klasse „EnergySystem“ in der eigenen Applikation angewendet werden können (GitHub 2017b). Eine eigene Instanz der Klasse „Ener gySystem“ ist bspw. die Modellierung des Beispiels „Rostock“. Bevor wir nun unser Energiemodell anlegen können, müssen die Klassen zur Beschreibung der energietechnischen Elemente in einem Energieversorgungssystem aus oemof.network eingelesen und Methoden für das interne Verarbeiten der Daten eines Energiesystem mithilfe des Moduls oemof.energy_system zur Verfügung gestellt werden. Dies kann wie folgt durchgeführt werden: # Certain classes from oemof.network are imported that are used in the own # application, for example Sink, Transformer. from oemof.network import Sink, Transformer, Bus, Flow # The class EnergySystem can be installed together with further classes # using the package oemof.solph: from oemof.solph import (EnergySystem, Sink, Transformer, Bus, Flow, Model, components) # Nachdem die Klasse EnergySystem der Applikation zur Verfügung steht, kann # dem Programm mitgeteilt werden, wie die Klasse im folgenden Code # aufgerufen werden soll. Hier soll es im weiteren Quellcode über die # Variable „es“ aufgerufen werden. Der Parameter „datetimeindex“ muss zuvor # entsprechend in Pandas in der Klasse „DatetimeIndex“ definiert werden # (s. ▶ Abschn. 2.5. es = EnergySystem(timeindex=datetimeindex)
Damit die Struktur des Energieversorgungssystems aus unserem Beispiel angelegt und als Eingabe für die Bibliothek oemof-solph dienen kann, wird die Struktur des Energieversorgungssystems im Quellcode umgesetzt: # Hier werden exemplarisch für einen Bus und eine Sink die Strukturen
2.3 Libraries in OEMOF # # # # #
59
des Energieversorgungssystem angelegt. Der Begriff „label“ weist dem Bus bei der späteren Ausgabe in einer Grafik den angegebenen Namen zu, hier „b_gas“. Der Ausdruck vor dem Gleichheitszeichen ist der Begriff unter dem das Element im Weiteren Quellcode aufgerufen wird.
# create bus 1 r1_gas = Bus(label="b_gas") # Nachfolgend wird eine Sink Strom implementiert. Diese braucht # Informationen über ihren Input, in diesem Fall vom Bus 1. Dies erfolgt # hier über den Aufruf des Busses „r1_gas“. # Dem Input wird noch mitgegeben, dass für den Energiefluss (flow) # Einnahmen durch variable Kosten in Höhe von 0,1 €/kWh anfallen. Diese # werden bei der späteren Optimierungsrechnung berücksichtigt. Bei der Modellierung ist darauf zu achten, dass Parameter immer in der gleichen Einheit angegeben werden, also Kosten dann überall in € und nicht in $. Es ist zu empfehlen, die Einheiten zur Dokumentation bspw. in den Quellcode zu fügen. Die Verwendung unterschiedlicher, voneinander abweichender Einheiten ist eines der Hauptgründe für unplausible Ergebnisse. Die große Gefahr ist jedoch, dass Unstimmigkeiten erst gar nicht auffallen. # create sink 1. de1 = Sink(label='strom', inputs={r1_gas: Flow(variable_costs=0,1)})
Wir sehen anhand des Codes, dass Kosten auftreten. Flüsse (Klasse „Flows“) bilden die Energieflüsse in einem Energieversorgungssystem ab. Für diese Flüsse sind in der Bibliothek oemof.solph im Modul network.py verschiedene Paramater definiert, auf die im weiteren Verlauf intensiver eingegangen wird. Unter den Parametern befinden sich einige zu Kosten. Aber auch weiterführende Parameter sind enthalten, wie bspw. Wirkungsgrade oder Grenzen, z. B. maximale Leistung. Bevor oemof-solph angewendet werden kann, muss mindestens ein linearer Solver (Löser) installiert werden. Intern wurde die Bibliothek oemof-solph mit den Solvern CBC und Gurobi getestet (oemof 2014a). Bei dem Solver CBS handelt es sich um einen open source Solver, wohingegen Gurobi ein proprietärer Solver ist, für den die Rechte und Möglichkeiten der Wieder- und Weiterverwendung sowie Änderung und Anpassung durch Nutzer und Dritte stark einschränkt sind. Hierzu zählt auch der Solver Cplex. Ein weiterer open source Solver, der in OEMOF verwendet werden kann, wäre bspw. der Solver GLPK. Mithilfe von oemof-solph kann unter Verwendung der Bibliothek oemof-network ein betrachtetes Energieversorgungssystem als Optimierungsmodell modelliert werden.
60
2 Das generische Basismodell in OEMOF
2.3.3 Bibliothek „oemof-outputlib“ Unter Verwendung dieser Bibliothek werden die Ergebnisse einer Optimierungsrechnung bearbeitet und dargestellt. Der Hauptzweck dieser Bibliothek ist das Zusammenfassen und Organisieren von Ergebnissen. Auch diese Bibliothek ist Teil der OEMOF Installation. Die Bibliothek basiert auf der internen Python Bibliothek Pandas. Pandas ist ein Tool zur Datenanalyse und -darstellung (Durmus 2017). Dazu verfügt Pandas über spezifische Datenstrukturen und Werkzeuge. Die Ergebnisse aus einer Optimierungsrechnung in OEMOF werden in einem pandas MultiIndex DataFrame gesammelt. Dazu werden die Daten in ein Dictionary umgewandelt. Dieses Dictionary beinhaltet für alle Knoten und Flüsse panda DataFrames und Serien. Das DataFrame enthält skalare Daten (z. B. Investitionen) und Sequenzen, die Knoten (mit Keys wie (Knoten, keine)) und Flüsse zwischen Knoten (mit Keys wie (node_1, node_2)) beschreiben. DataFrames können direkt in das Wörterbuch extrahieren, indem diese Keys verwendet werden, wobei „Knoten“ der Name des Objekts ist, das ansgeprochen werden soll. Wenn Objekte nach ihrem Label adressiert werden sollen, kann das Ergebnis-Wörterbuch umgewandelt werden, indem die Keys in Zeichenfolgen transferiert werden, die von den Etiketten angegeben sind (Github 2017a): views.convert_keys_to_strings(results) print(results[(wind, bus_electricity)]['sequences']
Eine andere Möglichkeit besteht darin, auf Daten zuzugreifen, die zu einer Gruppierung mit dem Namen der Gruppierungen gehören (s. Abschn. 2.4.2). Aufgrund des Labels eines Objekts, z. B. "Wind", kann über dieses Label auf die Gruppierung zugegriffen und diese verwenden werden, um Daten aus dem Ergebnis-Wörterbuch zu extrahieren. node_wind = energysystem.groups['wind'] print(results[(node_wind, bus_electricity)])
Weitere Informationen hierzu befinden sich unter: (pandas o. J.-a). Zusätzlich stellt oemof-outputlib weitere plot-Methoden zur Erstellung von Grafiken zur Verfügung. Siehe dazu (oemof 2014a). Informationen sind auch zu finden unter: https://github.com/oemof/oemof-solph/issues/699, https://pythonhosted.org/oemof_base/ api/oemof.outputlib.html oder https://oemof.org/libraries/. Beispielhaft wird hier ein Auszug aus dem Programmcode anhand unseres Beispiels „Rostock“ vorgestellt, mit dem Ergebnisdaten in einem pandas DataFrame gesammelt werden. Anschließend werden ausschließlich die Daten einer speziellen Komponente selektiert. Hier werden die Daten der Komponente Wärme herausgefiltert. Unter Verwendung des Moduls „processing“ erfolgt das Sammeln der Daten. Dies ist in der ersten Zeile des folgenden Codes umgesetzt.
2.3 Libraries in OEMOF
61
results = outputlib.processing.results(om) data_heat = outputlib.views.node(results, 'heat')
Oftmals ist es erforderlich, für einen speziellen Knoten Daten zu betrachten. Hierfür kann das Modul „view“ verwendet werden. Dazu erfolgt in der zweiten Zeile des Codes eine Abfrage zu allen relevanten Daten des betrachteten Knotens, indem entweder die Variable des Knoten oder dessen Label abgefragt wird. Eine andere Funktion ermöglicht das Sammeln und Drucken von Meta-Ergenissen, dies können Informationen über z. B. Daten zur Zielfunktion, das Optimierungsproblem oder den Solver sein: meta_results = outputlib.processing.meta_results(om) pp.pprint(meta_results)
Das Ergebnis kann anschließend bspw. über das Programmsystem Spyder visualisiert werden, sofern der Code darin abgespielt wird. Als Ergebnis der outputlib kann der Anwender die in Abb. 2.10 2.11 dargestellten Graphen erhalten (Abb. 2.10).
2.3.4 Bibliothek „feedinlib“ Diese Bibliothek ist nicht Teil der OEMOF Installation. Eine Installation erfolgt über PyPI. Der Name PyPI steht für Python Package Index (Python o. J.-a). Dies ist ein Speicherort für Python Pakete. Ein Zugriff auf PyPI kann über das Programm pip erfolgen. Das Programm pip ist ein Werkzeug zur Installation von Python Paketen (Python 2018b). Die Bibliothek feedinlib errechnet Outputdaten von Windkraft- und Photovoltaikanlagen (oemof 2014a). Dazu werden technische Parameter zur Beschreibung der Windund Photovoltaikanlagen und ein Wetterdatenset zu Sonne und Wind als Berechnungsgrundlage verwendet. So sind für beide (PV und Windkraftanlagen) Temperaturdaten erforderlich. Für die Berechnung der Winddaten sind die Windgeschwindigkeit, der Umgebungsdruck und die Rauhigkeitslänge erforderlich. Für den PV-Lastgang werden spezifische Daten zu Direkt- und Diffusionsstrahlung benötigt. Der Modellierer kann die benötigten Daten in einem gewissen Rahmen anpassen. Der Output ist ein Lastgang der Wind- und PV-Stromerzeugung. Aktuelle Informationen finden sich unter: https://feedinlib.readthedocs.io/en/stable/. Mit der Installation der Bibliothek feedinlib wird für PV Module und Windkraftanlagen jeweils eine CSV-Datei mit Daten mitgeliefert. Die CSV-Datei für Windkraftanlagen enthält eine Liste von über 90 Windkraftanlagen. In der Liste ist der Leistungsbeiwert cp abgelegt. Bei der Windkrafttechnologie wird anstatt von einem Wirkungsgrad von einem Leistungsbeiwert gesprochen. Dieses kommt daher, da es wie in der Luftfahrttechnik, die mit der die Windkrafttechnik verwandt ist, weitere Beiwerte, wie bspw. den Widerstandsbeiwert cW und den Aufriebsbeiwert cA, gibt.
Abb. 2.10 Beispielhafte Darstellung von Daten. (https://oemof-solph.readthedocs.io/en/latest/usage.html)
62 2 Das generische Basismodell in OEMOF
2.3 Libraries in OEMOF
63
Der Leistungsbeiwert cP kann vom Wirkungsgrad η abgeleitet werden. Der Leistungsbeiwert cP geht auf die Theorie von Albert Betz im Jahre 1920 zurück (Betz 1994). Der Wirkungsgrad η berechnet sich über das Verhältnis aus genutzter (von einer Maschine abgegebener) und zugeführter (in die Maschine eingebrachte) Energie oder Leistung. Dies wird wie folgt mathematisch formuliert (s. Gl. 2.1):
h=
Pout Pin
(2.1)
mit Pout = PRotor Pin = PWind
Entnehmbare Windleistung oder genutzte Leistung, welche der vom Rotor abgegebenen Leistung entspricht im Wind enthaltene Leistung oder maximal nutzbare Leistung
Damit berechnet sich der Leistungsbeiwert cP für Windkrafträder nach Gl. 2.2 (Ratka et al. 2015; Quaschning 2015):
h = cP =
PRotor PWind
(2.2)
Wird der Gesamtwirkungsgrad einer Windkraftanlage berechnet, so wird an der Stelle der vom Rotor abgegebenen Leistung die abgegebene elektrische Leistung eingesetzt. Die ans Netz abgegebene elektrische Leistung ist abhängig von der Windgeschwindigkeit. Darum wird angesetzt (s. Gl. 2.3) (Quaschning 2015):
PRotor = P = P ( vWind )
(2.3)
mit vWind Windgeschwindigkeit P ans Netz abgegebene elektrische Leistung Die Leistung des Windes berechnet sich nach Gl. 2.4 (Quaschning 2015):
PWind ( vWind ) = FWind · vWind =
r Air 2
3 · A · vWind
(2.4)
mit FWind
ρAir A
Kraft des Windes auf die senkrecht zur Windrichtung stehende vom Rotor überstrichene Fläche. Diese ist abhängig von der Dichte der Luft, der Windgeschwindigkeit und der Fläche, auf die die Kraft wirkt. Dichte der Luft Fläche, auf die die Kraft wirkt. Am Rotor überstrichene, senkrecht zur Windrichtung stehende Fläche
64
2 Das generische Basismodell in OEMOF
Die CSV-Datei für PV Module enthält Daten für über 500 Module. Das Parameterset für PV Module sowie die CP Werte für Windkraftanlagen können von der OEMOF Internetseite unter https://feedinlib.readthedocs.io/en/stable/getting_started. html#basic-usage heruntergeladen werden. Bei einer Installation der feedinlin befinden sich die Dateien in den Verzeichnissen: • C:\WinPython-64bit-3.6.3.0Qt5\python-3.6.3.amd64\Lib\pydoc_data • C:\WinPython-64bit-3.6.3.0Qt5\python-3.6.3.amd64\Lib\site-packages\windpowerlib\data Natürlich ist es möglich, auch eine eigene Liste zu erstellen. Mit folgendem Programmcode kann bspw. eine Berechnung des Lastgangs einer Windkraftanlage und einer Photovoltaikanlage angestoßen werden. # Es wird ein Objekt „my_weather“ der Klasse “weather.FeedinWeather“ # erzeugt. my_weather = weather.FeedinWeather() # Es wird eine Funktion zum Auslesen der Daten aus der CSV-Datei # „weather.CSV“ aufgerufen und der Pfadname (Filename) als Parameter # übergeben. my_weather.read_feedinlib_CSV(filename='weather.CSV') # Über die Wind-CSV-Datei werden die Daten der Windkraftanage ausgelesen, # hier der Windkraftanlage „enerconE126“. E126_power_plant = plants.WindPowerPlant(**enerconE126) # Damit wird das Objekt „E126_power_plant“ erzeugt und der # Oberklasse „plants.WindPowerPlant“ zugewiesen. # Der Klammerausdruck (**enerconE126) bedeutet: Über die beiden „**“ können # über ein dictionary Parameter definiert werden. Das dictionary wird dann # aufgelöst in: # „key1=value1, key2=value2,…“ # Jetzt werden die Wetterdaten eingelesen. Die installierte Leistung der # Windkraftanlage wird auf 15 MW begrenzt E126_feedin = E126_power_plant.feedin(weather=my_weather, installed_capacity=15000000) # Eine PV Anlage wird in gleicher Weise angelegt. Hier wird die Anzahl der # Module mit 5000 Stück festgelegt.
2.4 Zentrale Packages und Module in OEMOF
65
yingli_module = plants.Photovoltaic(**yingli205, YL205P-23b) pv_feedin = yingli_module.feedin(weather=my_weather, number=50000) # 50000 modules
2.3.5 Bibliothek „demandlib“ Diese Bibliothek ist ebenfalls nicht Teil der OEMOF Installation und muss separat über PyPI hinzugefügt werden (oemof 2014a). Die demandlib erzeugt Strom- und Wärmelastgänge aus bekannten Bedarfslastgängen. Die Lastgänge können für unterschiedliche Sektoren, wie bspw. Haushalte oder Industrie, erstellt werden (oemof 2016). Ebenso können die Lastgänge entsprechend der Modellerfordernisse skaliert werden. Aktuelle Informationen finden sich unter: https://demandlib.readthedocs.io/en/latest/.
2.4 Zentrale Packages und Module in OEMOF Das Programmsystem OEMOF hält bereits einige zentrale funktionsfähige Pakete für den Anwender bereit, um ein Optimierungsmodell für ein Energieversorgungssystem erstellen zu können. Die Pakete sind modular aufgebaut. Die Module sind in einzelnen Dateien gespeichert. Diese tragen die Endung „.py“. In den einzelnen Modulen sind Klassen definiert und konfiguriert, auf die im eigenen Programmcode durch Instanziierung zugegriffen werden kann. In der Version v0.5 wurden die Strukturen etwas verändert. Diese werden nachfolgend jedoch nicht beschrieben, da in diesem Buch das Verständnis für die Zusammenhänge vermittelt werden soll. Das Module energy_system wurde bereits vorgestellt. Dieses befindet sich in der Datenstruktur von OEMOF in der obersten oemof-Ebene. Dort befindet sich auch das Module groupings, welches dazu dient, Entities zu Gruppen zusammenzufassen. Eine Ebene darunter befinden sich die Module von drei zentralen Packages. Zu diesen gehören (oemof 2014e): • oemof.outputlib package • oemof.solph package • oemof.tools package Zunächst wird das Module energy_system und folgend das Module groupings vorgestellt, bevor auf die drei Packages eingegangen wird.
66
2 Das generische Basismodell in OEMOF
2.4.1 Module energy_system Der erste Schritt bei der Modellierung in OEMOF ist das Anlegen und Definieren eines Energiesystems. Dies ist wichtig, um die OEMOF solver Libraries verwenden zu können. Für diesen Schritt stellt OEMOF das Modul oemof.energy_system (oemof.energy_system) zur Verfügung. In diesem Modul ist die Klasse „EnergieSystem“ (oemof.energy_sys tem.EnergieSystem) angelegt: class EnergySystem(**kwargs)
Die Klasse erbt von der Elternklasse „object“. Der Parameter „kwargs“ steht für „keyword arguments“. Dieser Parameter ist ein „Platzhalter“, an dessen Stelle später beliebige keyword arguments eingetragen werden können. In der Version v0.5 befindet sich dieses Modul unter: https://github.com/oemof/oemof- network/blob/dev/src/oemof/network/energy_system.py. Die folgend vorgestellten Parameter können hier entnommen werden. In der Klasse „EnergySystem“ stehen einige Parameter zur Verfügung, die für folgende Modellierung relevant sind (s. Tab. 2.4). Die Parameter werden in die Attribute der Klasse „EnergySystem“ geschrieben. Die Attribute für diese Klasse sind in Tab. 2.5 aufgeführt.
Tab. 2.4 Liste der Parameter des Moduls oemof.energy_system. (GitHub 2017b) Parameter Zusatzinformation Entities - List of Entity - :class:`Entity ` - optional
Timeindex pandas.index, optional
Groupings list
Beschreibung A list containing the already existing Entities that should be part of the energy system. Stored in the entities attribute. Defaults to [] if not supplied. If this EnergySystem (:class:`EnergySystem`) is set as the registry attribute (:attr:`registry `), which is done automatically on EnergySystem construction, newly created Entities (:class:`Entities `) are automatically added to this list on construction. Define the time range and increment for the energy system. This is an optional parameter but might be important for other functions/methods that use the EnergySystem class as an input parameter. The elements of this list are used to construct Groupings or they are used directly if they are instances of Grouping. These groupings are then used to aggregate the entities added to this energy system into groups. By default, there’ll always be one group for each uid (unified Identifier) containing exactly the entity with the given uid.
2.4 Zentrale Packages und Module in OEMOF
67
Tab. 2.5 Attribute der Klasse „EnergySystem“. (GitHub 2017b) Attritbut entities
Zusatzinformation list of :class:`Entity `
groups results
Datentyp: dict Datentyp: dictionary
timeindex pandas.index, optional
Beschreibung A list containing the :class:`Entities ` that comprise the energy system. If this :class:`EnergySystem` is set as the :attr:`registry ` attribute, which is done automatically on :class:`EnergySystem` construction, newly created :class:`Entities ` are automatically added to this list on construction. A dictionary holding the results produced by the energy system. Is `None` while no results are produced. Currently only set after a call to :meth:`optimize` after which it holds the return value of :meth:`om.results() `. See the documentation of that method for a detailed description of the structure of the results dictionary. Define the time range and increment for the energy system. This is an optional atribute but might be import for other functions/methods that use the EnergySystem class as an input parameter
2.4.2 Module groupings Ein weiteres wichtiges Modul ist oemof.groupings (s. aktuell unter https://github.com/ oemof/oemof-network/blob/dev/src/oemof/network/groupings.py). Mit Hilfe dieses Moduls können Entitäten in einem Energiesystem gruppiert werden. Dazu wird die Klasse „Grouping“ in diesem Modul definiert. Diese Klasse wird immer aufgerufen, wenn eine Entität einem Energiesystem hinzugefügt wird. In OEMOF wird jeder Knoten, der dem Modell hinzugefügt wird, einer bestimmten Gruppe zugeordnet. Dadurch wird es möglich, für bestimmte Gruppen gezielte Aktionen durchzuführen, wie bspw. bestimmte Nebenbedingungen (constraints) zu erstellen. Die Entitäten werden mit einem unified Identifier (uid) in dem Modul Groupings abgelegt. Soll eine Entität in einem Modul Groupings abgelegt werden, wo bereits eine Entität mit der gleichen uid vorliegt, wird ein Error ausgegeben. Im Programmcode wird folgender Code abgelegt: oemof.groupings.DEFAULT =
Die linke Seite „oemof.groupings.DEFAULT“ in der Programmzeile definiert ein Objekt der Grouping Klasse. Es handelt sich um ein Standardobjekt (default object), welches dafür sorgt, dass jeder Knoten ersteinmal eine eigene Gruppe darstellt.
68
2 Das generische Basismodell in OEMOF
Entitäten in dieser Klasse erhalten das Attribut „groups“. Dieses wird im Code vereinfacht über den String „g“ ausgedrückt. Ein Aufruf der Klasse erfolgt über: # Der String „g“ steht für die class Groupings, „e“ steht für die Klasse # „Entity“, „groups“ ist das Attribut der Klasse „e“. Das Attribut „groups“ # ist ein # Katalog (dictionary), in welchem die Schlüssel der Gruppen # aufgeführt sind. g(e, groups)
In diesem Modul liegen drei Kindklassen oder auch Subklassen genannt. In Klammer steht die jeweilige Elternklasse: 1. 2. 3. 4.
class oemof.grouping – class Grouping class oemof.groupings.Nodes – class Nodes(Groupings) class oemof.groupings.Flows – class Flow(Nodes) class oemof.groupings.FlowsWithNodes – class FlowsWithNodes(Nodes)
Die Klasse „Grouping“ ist die Elternklasse der Klasse „Nodes“. Diese ist wiederum die Elternklasse der folgenden Klassen „Flow“ und „FlowsWithNodes“. Die Klasse „Grou ping“ wird immer aufgerufen, wenn eine Entität dem EnergySystem hinzugefügt wird. Für diese Klasse existieren entsprechende Parameter, wie sie in Tab. 2.6 dargestellt sind: Die weiteren Klassen in diesem Modul gruppieren bestimmte Entitäten. Kommen wir zu den einzelnen Klassen: Zu 1. class oemof.groupings.Nodes Mit dieser Klasse werden die Knoten gruppiert. Auch hier werden wieder Sets an Entitäten gebildet. Die Klasse basiert auf oemof.groupings.Grouping. Im Quellcode wird sie wie folgt aufgerufen: class oemof.groupings.Nodes(key=None, **kwargs)
constant_key=None,
filter=None,
In Klammern werden Werte für die Parameter key, constant_key und Filter gesetzt. Zu 2. class oemof.groupings.Flows Hierin werden alle Flüsse zu Sets zusammengefasst, die mit einem Knoten (Nodes) verbunden sind. Aus diesem Grund ist die Basis dieser Klasse die Klasse „oemof.grou pings.Node“s. Die Klasse „oemof.groupings.Grouping“ muss dazu die entsprechenden Informationen enhalten. Sind die Flüsse gruppiert, werden die Funktionen key und value auf das Set der Flüsse angewendet. Die Klasse „class oemof.groupings.Flows“ wird im Quellcode wie folgt definiert: class oemof.groupings.Flows(key=None, **kwargs)
constant_key=None,
filter=None,
2.4 Zentrale Packages und Module in OEMOF
69
Tab. 2.6 Liste der Parameter der Klasse „Grouping“. (GitHub 2017c) Parameter key(e)
Zusatzinformation callable (aufrufbar) or hashable (zerlegbar)
constant_key value
(hashable, optional) (callable, optional
filter
(callable, optional)
merge(new, old)
(callable, optional)
Beschreibung Unter diesem Key wird eine Gruppe abgespeichert. Der Parameter wird für jede Entität „e“ im Energiesystem aufgerufen. Ist der Parameter nicht callable (aufrufbar), wird ein Schlüssel für jede Entität des Energiesystems definiert. Ist der Parameter aufrufbar, wird der key für jede Entität im Energiesystem entfernt. Soll eine Entität „e“ nicht in einer Gruppe gespeichert werden, soll der Wert None zurückgegeben werden. Ein konstanter Schlüssel wird angelegt. Mit diesem Parameter wird ein standardmäßig gesetzter Wert überschrieben. Wird dieser Parameter mitgegeben, wird ein zurückgegebener value() entsprechend gefiltert. Dieser Parameter zeigt an, dass eine alte bestehende Gruppe mit einer neuen zusammengeführt werden soll. Diese Methode wird aufgerufen, wenn unter group[key(e)] bereits ein Wert abgespeichert ist. Wenn dies passiert, wird die Funktion merge(value(e), group[key(e)]) aufgerufen. Diese speichert nun die neue Gruppe unter dem Paramater key(e).
Das Ergebnis der Gruppierung ist ein Set an Flüssen. Zu 3. class oemof.groupings.FlowsWithNodes In dieser Klasse werden die mit Knoten verbundenen Flüsse in besonderer Weise gruppiert. Hier wird ein Set an Tupeln mit den Werten source, target, flow zusammengefügt. Ein Tupel besteht in der Mathematik aus einer Liste endlich vieler Objekte. Die Objekte müssen nicht notwendigerweise voneinander verschieden sein. Jedoch spielt bei Tupeln die Reihenfolge der Objekte eine wesentliche Rolle. Im mathematischen Sinne sind Tupel geordnete Mengen. In diesem Fall arbeiten die Funktionen key und value auf dem Set der Tupel. Im Programmcode wird geschrieben: class oemof.groupings.FlowsWithNodes(key=None, constant_key=None, filter=None, **kwargs)
In diesem Fall erhält man also ein Set an Tupeln.
2.4.3 oemof.outputlib package Zu diesem Packages gehören zwei Module: • processing.py • views.py
70
2 Das generische Basismodell in OEMOF
In den zwei Modulen sind Klassen, Parameter und Funktionen zur Verarbeitung von Grafiken angelegt. Damit hat der Modellierer die Möglichkeit neben den Standardfunktionen von Python OEMOF spezifische Funktionen zur Erstellung von Grafiken anzuwenden.
2.4.4 oemof.solph package Diese Bibliothek ist zentral, da in ihr das Optimierungsmodell erstellt wird. Sie beinhaltet die Module (GitHub 2017d): • • • • • • • • •
blocks.py components.py constraints.py custom.py facades.py groupings.py models.py network.py options.py
Inhalte des Moduls blocks.py Bei Blocks handelt es sich um einen Datenblock. Hier werden für spezielle Gruppen die Sets, Variablen, Nebenbedingungen und Teile der Zielfunktion (objective function) zusammengefasst. Generell gibt es eine Hauptklasse und eine Blockklasse für jede Komponente. In der Hauptklasse wird die Komponente initialisiert. Die Blockklasse dient dazu, die Constraints, also die eigentliche Mathematik, zur Verfügung zu stellen. Nur bei den Basis Modulen sind beide Klassen in unterschiedlichen Modulen (solph.network, solph. blocks) enthalten, was in der Vererbung begründet ist. Bei den anderen Komponenten stehen die Module untereinander. Für folgende Klassen werden Datenblöcke gebildet: • • • • •
class Flow(SimpleBlock) class InvestmentFlow(SimpleBlock) class Bus(SimpleBlock) class Transformer(SimpleBlock) class NonConvexFlow(SimpleBlock)
Alle aufgeführten Klassen erben von der Klasse „SimpleBlock“. Diese Klasse stammt aus dem Pyomo-Package. Um einen Block zu definieren, braucht es vier Blockklassen: • „BlockData“ • „Block“
2.4 Zentrale Packages und Module in OEMOF
71
• „SimpleBlock“ • „IndexedBlock“ Die Klasse „BlockData“ beinhaltet alles, was einen Block ausmacht. Sie ist der Container, der die Components und alles für die Implementierung der Schnittstellen enthält. „SimpleBlock“ und „IndexedBlock“ sind die Komponenten, die mit anderen Blöcken verbunden werden. Die Klasse „Block“ beinhaltet Methoden, wie die Methode „__ new__()“. Diese ermöglicht in Abhängigkeit des Konstruktorarguments eine Umleitung von der Klasse „Block“ auf entwerder die Klasse „SimpleBlock“ oder „Inde xedBlock“. Für die erste Klasse „Flow“ werden folgende Variablen zur Verfügung gestellt (s. Tab. 2.7). Für diese Klasse werden für alle Flüsse, die ein globales Limit über der Zeit besitzen, folgende Sets gebildet (s. Tab. 2.8). Die mathematische Beschreibung zur Durchführung der Berechnung ist in Kap. 3 aufgeführt. Über den folgenden Parameter „groups“ können die Objekte der Klasse gruppiert werden (s. Tab. 2.9). Die nächste Klasse ist „class InvestmentFlow(SimpleBlock)“. Hier werden Angaben zu Investitionen getätigt, was bedeutet, dass der Wert des Attributs „investment“ nicht „None“ ist. Es existieren in dieser Klasse einige Sets (s. Tab. 2.10). Des Weiteren ist die folgende Variable von Bedeutung (s. Tab. 2.11). Tab. 2.7 Variablen der Klasse „Flow“. (GitHub 2017k) Variable negative_ gradient positive_ gradient
Beschreibung Difference of a flow in consecutive timesteps if flow is reduced indexed by NEGATIVE_GRADIENT_FLOWS, TIMESTEPS. Difference of a flow in consecutive timesteps if flow is increased indexed by POSITIVE_GRADIENT_FLOWS, TIMESTEPS.
Tab. 2.8 Sets für die Klasse „Flow“. (GitHub 2017k) Set Beschreibung SUMMED_MAX_FLOWS A set of flows with the attribute :attr:`summed_max` being not None. SUMMED_MIN_FLOWS A set of flows with the attribute :attr:`summed_min` being not None. NEGATIVE_GRADIENT_ A set of flows with the attribute :attr:`negative_gradient` being not FLOWS None. POSITIVE_GRADIENT_ A set of flows with the attribute :attr:`positive_gradient` being not FLOWS None. INTEGER_FLOWS A set of flows where the attribute :attr:`integer` is True (forces flow to only take integer values).
72
2 Das generische Basismodell in OEMOF
Tab. 2.9 Parameter der Klasse „Flow“. (GitHub 2017k) Parameter Datentyp Erläuterung Group list List containing tuples containing flow (f) objects and the associated source (s) and target (t) of flow e.g. groups = [(s1, t1, f1), (s2, t2, f2),..]. Tab. 2.10 Sets der Klasse „InvestmentFlow“. (GitHub 2017k) Set FLOWS FIXED_FLOWS SUMMED_MAX_ FLOWS SUMMED_MIN_ FLOWS MIN_FLOWS
Beschreibung A set of flows with the attribute :attr:`invest` of type :class:`.options. Investment`. A set of flow with the attribute :attr:`fixed` set to `True`. A subset of set FLOWS with flows with the attribute :attr:`summed_ max` being not None. A subset of set FLOWS with flows with the attribute :attr:`summed_ min` being not None. A subset of FLOWS with flows having set a value of not None in the first timestep.
Tab. 2.11 Variable der Klasse „InvestmentFlow“. (GitHub 2017k) Variable invest :attr:`om. InvestmentFlow.invest[i, o]`
Beschreibung Value of the investment variable (i.e. equivalent to the nominal value of the flows after optimization (indexed by FLOWS)).
Tab. 2.12 Parameter der Klasse „InvestmentFlow“. (GitHub 2017k) Parameter Datentyp Erläuterung group list List containing tuples containing flow (f) objects that have an attribute investment and the associated source (s) and target (t) of flow e.g. groups = [(s1, t1, f1), (s2, t2, f2),..].
Ein Groupierungs-Parameter ist auch hier verfügbar (s. Tab. 2.12). Für die Klasse „Bus(SimpleBlock)“ wird ein Parameter aufgeführt (s. Tab. 2.13). Diese Klasse fasst alle Daten für Busse zusammen, deren In- und Output ausbalanciert ist. Als vorletzte Klasse wird die Klasse „Transformer(SimpleBlock)“ aufgeführt. Diese Klasse fasst die linearen Beziehungen der Knoten (nodes) des Typs „:class:`~oemof.solph. network.Transformer`“ zusammen mit dem Set (s.Tab. 2.14). Auch hier gibt es einen Parameter für Groupings (s. Tab. 2.14 und 2.15). In der letzten Klasse „NonConvexFlow(SimpleBlock)“ sind erneut Sets definiert (s. Tab. 2.16). Bei dieser Klasse ist zu beachten, dass alle diese Flüsse das Attribut „Noncon vex“ besitzen müssen. Ebenso gibt es auch hier einen Grouping-Parameter (s. Tab. 2.17). Wichtige Variablen für diese Klasse sind in Tab. 2.18 aufgeführt.
2.4 Zentrale Packages und Module in OEMOF
73
Tab. 2.13 Parameter der Klasse „Bus“. (GitHub 2017k) Parameter Datentyp Erläuterung group list List of oemof bus (b) object for which the bus balance is created e.g. group = [b1, b2, b3, .....]. Tab. 2.14 Sets der Klasse „Transformer“. (GitHub 2017k) Set TRANSFORMERS
Beschreibung A set with all :class:`~oemof.solph.network. Transformer` objects.
Tab. 2.15 Parameter der Klasse „Transformer“. (GitHub 2017k) Parameter Datentyp Erläuterung Group List List of oemof.solph.Transformers objects for which the linear relation of inputs and outputs is created e.g. group = [trsf1, trsf2, trsf3, ...]. Note that the relation is created for all existing relations of all inputs and all outputs of the transformer. The components inside the list need to hold an attribute `conversion_factors` of type dict containing the conversion factors for all inputs to outputs. Tab. 2.16 Sets der Klasse „NonConvexFlow“. (GitHub 2017k) Set MIN_FLOWS
Beschreibung A subset of set NONCONVEX_FLOWS with the attribute :attr:`min` beeing not None in the first timestep. STARTUP_FLOWS A subset of set NONCONVEX_FLOWS with the attribute :attr:`startup_ costs` being not None. SHUTDOWN_ A subset of set NONCONVEX_FLOWS with the attribute FLOWS :attr:`shutdown_costs` being not None. Tab. 2.17 Parameter der Klasse „NonConvexFlow“. (GitHub 2017k) Parameter Datentyp Erläuterung Group list List of oemof.solph.NonConvexFlow objects for which the constraints are build. Tab. 2.18 Variablen der Klasse „NonConvexFlow“. (GitHub 2017k) Variable Status
Datentyp Attribut Binary attr:`om.NonConvexFlow. status`: Startup Binary :attr:`om. NonConvexFlow.startup`: Shutdown Binary :attr:`om. NonConvexFlow. shutdown`:
Beschreibung Variable indicating if flow is >= 0 indexed by FLOWS. Variable indicating startup of flow (component) indexed by STARTUP_FLOWS. Variable indicating shutdown of flow (component) indexed by SHUTDOWN_ FLOWS.
74
2 Das generische Basismodell in OEMOF
Inhalte des Moduls components.py In diesem Modul werden benutzerspezifische Komponenten mit ihren Klassen, den zugehörigen individuellen Nebenbedingungen sowie den Informationen zu Groupings abgelegt. Zu jeder Klasse besteht eine „Blockklasse“ blocks (Datenblocks). Diese enthält die für die zugehörige Klasse erforderlichen Nebenbedingungen. Damit sind alle Informationen für die Klassen und deren Nebenbedingungen direkt beieinander angegeben. Zum jetzigen Zeitpunkt sind in diesem Modul folgende Klassen definiert: • • • • • • •
GenericStorage(network.Transformer) GenericStorageBlock(SimpleBlock) GenericInvestmentStorageBlock(SimpleBlock) GenericCHP(network.Transformer) GenericCHPBlock(SimpleBlock) ExtractionTurbineCHP(solph_Transformer) ExtractionTurbineCHPBlock(SimpleBlock)
Die erste Klasse bildet allgemein Speicher ab. Sie erbt von der Klasse „network. Tranformer“: class GenericStorage(network.Transformer) In der Klasse „GenericStorage“ können grundlegende Eigenschaften eines Speichers modelliert werden. Die Klasse verfügt über einige Parameter (s. Tab. 2.19). Die nächste Klasse ist „GenericStorageBlock“. Einige mathematische Nebenbedingungen werden für diese Klasse formuliert. Diese sind in Kap. 3 aufgeführt. Eine Erweiterung der Klasse „GenericStorageBlock“ stellt die Klasse „GenericInvest mentStorageBlock“ dar. Diese wird von der Klasse „SimpleBlock“ vererbt: class GenericInvestmentStorageBlock(SimpleBlock) Zu dieser Klasse gehören folgende Sets (s. Tab. 2.20). Die Bildung von Sets dient dazu, für alle in diesem Set enthaltenen Komponenten bestimmte constraints zur Verfügung zu stellen. Weiterhin können folgende Variablen gesetzt werden (s.Tab. 2.21). Der Parameter „n“ steht für eine Komponente und t für die Zeit. Die nächste Klasse „GenericCHP“ beschreibt generische Combined Heat and Power (CHP) Komponenten. Diese Klasse erbt von der „network.transformer“ Klasse: class GenericCHP(network.Transformer) Mit dieser Klasse können Gegendruckturbinen, Entnahmeturbinen sowie Entnahmegegendruckturbinen modellliert werden. Dabei können die Anlagen mit einem mathematischen mixed-integer linearen Ansatz beschrieben werden. Für die Modellierung müssen einige Parameter angegeben werden (s. Tab. 2.22). Die lineare Beziehung der Knoten der Klasse „GenericCHP“ wird mittels der Klasse „GenericCHPBlock“ modelliert. Es handelt sich dabei um einen Datenblock. Diese Klasse erbt von: class GenericCHPBlock(SimpleBlock) Sie verfügt über einen Parameter zur Gruppierung der CHP-Objekte (s. Tab. 2.23).
2.4 Zentrale Packages und Module in OEMOF
75
Tab. 2.19 Parameter der Klasse „GenericStorage“. (GitHub 2017e) Parameter nominal_ capacity nominal_ output_ capacity_ratio
Datentyp numeric
Erläuterung Absolute nominal capacity of the storage.
numeric
Ratio between the nominal outflow of the storage and its capacity. For batteries this is also known as c-rate. Note: This ratio is used to create the Flow object for the outflow and set its nominal value of the storage in the constructor. If no investment object is defined it is also possible to set the nominal value of the flow directly in its constructor. Ratio between the nominal inflow of the storage and its capacity. see: nominal_output_capacity_ratio. The capacity of the storage in the first (and last) time step of optimization. The relative loss of the storage capacity from between two consecutive timesteps.
nominal_input_ numeric capacity_ratio initial_capacity numeric capacity_loss
inflow_ conversion_ factor outflow_ conversion_ factor capacity_min
capacity_max
numeric (sequence or scalar) numeric (sequence or scalar) numeric (sequence or scalar) numeric (sequence or scalar) numeric (sequence or scalar)
investment
The relative conversion factor, i.e. efficiency associated with the inflow of the storage. See: inflow_conversion_factor.
The nominal minimum capacity of the storage as fraction of the nominal capacity (between 0 and 1, default: 0). To set different values in every time step use a sequence. See: capacity_min.
:class:`oemof.solph.options.Investment` object. Object indicating if a nominal_value of the flow is determined by the optimization problem. Note: This will refer all attributes to an investment variable instead of to the nominal_capacity. The nominal_capacity should not be set (or set to None) if an investment object is used.
Tab. 2.20 Sets für die Klasse „GenericInvestmentStorageBlock“. (GitHub 2017e) Set INVESTSTORAGES INITIAL_CAPACITY MIN_ INVESTSTORAGES
Beschreibung A set with all storages containing an Investment object. A subset of the set INVESTSTORAGES where elements of the set have an initial_capacity attribute. A subset of INVESTSTORAGES where elements of the set have a capacity_min attribute greater than zero for at least one time step.
76
2 Das generische Basismodell in OEMOF
Tab. 2.21 Variablen für die Klasse „GenericInvestmentStorageBlock“. (GitHub 2017e) Variable Attribut Beschreibung capacity om.InvestmentStorage.capacity[n, t] Level of the storage (indexed by STORAGES and TIMESTEPS). invest om.InvestmentStorage.invest[n, t] Nominal capacity of the storage (indexed by STORAGES). Tab. 2.22 Parameter der Klasse „GenericCHP“. (GitHub 2017e) Parameter fuel_input
Datentyp Dict
electrical_ output
Dict
heat_output Dict
Beta
back_ pressure
List of numerical values Boolean
Erläuterung Dictionary with key-value-pair of `oemof.Bus` and `oemof.Flow` object for the fuel input. Dictionary with key-value-pair of `oemof.Bus` and `oemof.Flow` object for the electrical output. Related parameters like `P_max_ woDH` are passed as attributes of the `oemof.Flow` object. Dictionary with key-value-pair of `oemof.Bus` and `oemof.Flow` object for the heat output. Related parameters like `Q_CW_min` are passed as attributes of the `oemof.Flow` object. Beta values in same dimension as all other parameters (length of optimization period). Flag to use back-pressure characteristics. Works of set to `True` and `Q_CW_min` set to zero.
Tab. 2.23 Parameter der Klasse „GenericCHPBlock“. (GitHub 2017e) Parameter group
Datentyp List
Erläuterung List containing `GenericCHP` objects. e.g. groups=[ghcp1, gchp2,..].
Mehrere Variablen werden zur Bestimmung des mathematischen Gleichungssystems für diese Klasse definiert, wie in Tab. 2.24 dargestellt. Auch sind Nebenbedingungen aufgeführt, die in Kap. 3 enthalten sind. Die Klasse „ExtractionTurbineCHP“ steht zur Verfügung, um eine Entnahmeturbine mit einem linearen mathematischen Ansatz zu modellieren. Die Basisklasse, von der diese abstammt, ist „solph_Transformer“: class ExtractionTurbineCHP(solph_Transformer) Es handelt sich hierbei um eine Entnahmeturbine, bei der Dampf während der Stromund Wärmeerzeugung aus dem Prozess entnommen wird. Dies erhöht die Flexibilität der Fahrweise und damit des Einsatzes der Turbine durch Anpassung an den Lastbedarf. Eine geregelte Entnahme macht eine perfekte Prozessintegration der Turbine möglich. Im Gegensatz zur Anzapfturbine, bei der Hilfsdampfmengen bzw. ausschließlich kleine Prozessdampfmengen besonders zur Vorwärmung des Speisewassers zur Verfügung gestellt werden, stellt die Entnahmeturbine große Mengen Dampf zur Wärmeerzeugung bereit. Der Dampf wird mit höheren Drücken an einer oder meheren Stellen an der Turbine
2.4 Zentrale Packages und Module in OEMOF
77
Tab. 2.24 Variablen der Klasse „GenericCHPBlock“. (GitHub 2017e) Variable self.H_F
Beschreibung Var(self.GENERICCHPS, m. TIMESTEPS, within=NonNegativeReals)
self.H_L_ FG_max
Var(self.GENERICCHPS, m. TIMESTEPS, within=NonNegativeReals)
self.H_L_ FG_min
Var(self.GENERICCHPS, m. TIMESTEPS, within=NonNegativeReals)
self.P_ woDH
Var(self.GENERICCHPS, m. TIMESTEPS, within=NonNegativeReals) Var(self.GENERICCHPS, m. TIMESTEPS, within=NonNegativeReals) Var(self.GENERICCHPS, m. TIMESTEPS, within=NonNegativeReals) Var(self.GENERICCHPS, m. TIMESTEPS, within=Binary)
self.P
self.Q
self.Y
Bezeichnung (auf (Mollenhauer et al. 2016)) fuel input H F : is usually calculated based on the lower heating value of the fuel and the corresponding mass flow. H L , FG are the losses to the environment with the flue gas flow, here maximum value. H L , FG are the losses to the environment with the flue gas flow, here minimum value. PwoDH is the equivalent electrical power generation without district heat extraction for a constant fuel rate H F . P is the electrical power.
Q the heat rate extracted for district heating. Y is the binary status variable. It equals 1, if the unit is committed or 0, when the unit is not in operation.
aus dem Prozess entnommen. Dies macht eine Anpasung der Betriebsart bei saisonalen Schwankungen des Wärmebedarfs, wie bei Haushalten, möglich. Fehlt der Wärmebedarf ist aber auch eine vollständige Ausnutzung der Dampfenthalpie bis zum Kondensationsdruck zur reinen Stromerzeugung im Niederdruckteil der Turbine möglich. Die Entnahme von Dampf bei höheren Drücken zur Wärmebereitstellung erhöht den Gesamtwirkungsgrad der Anlage, verringert aber gleichzeitig die Turbinenleistung zur Stromerzeugung. Die unterschiedlichen Wrkungsgrade sind mathematisch entsprechend abzubilden. Thermodynamische Grundlagen finden sich bspw. in (Nagel 2015). Zur der Modellierug dieser Anlage in OEMOF sind die beiden Dampfströme im Prozess abzubilden. Dazu wird ein Hauptstrom (main output flow) angelegt und zusätzlich ein Entnahmestrom (tapped out flow), der die Entnahme von Dampf bei entsprechender Druckstufe symbolisiert. Der Hauptstrom geht durch die Turbine und dient zur Stromerzeugung. In OEMOF wird für beide Ströme ein Umwandlungsfaktor (conversion factor) definiert. Zwei Grenzfälle sind zu modelliernen. Bei der Annahme „full CHP mode“ wird die maximale Menge an Dampf abgezogen und nur Wärme produziert. Für den Fall „full condensing mode“ wird von einer reinen Stromerzeugung ausgegangen und kein Dampf entnommen. Für die beiden Fälle „full CHP mode“ und „full condensing mode“ muss je-
78
2 Das generische Basismodell in OEMOF
weils ein Konversion Faktor definiert werden. Es ist auch möglich, die Variabilität der Entnahme so zu begrenzen, dass der zweite Fall der reinen Stromerzeugung („full condensing mode“) nicht auftritt. Zwei Parameter sind für diese Klasse relevant (s. Tab. 2.25). Kommen wir zur Klasse „ExtractionTurbineCHPBlock“, als weiteren Datenblock, in dem die Knoten des Typs „ExtractionTurbineCHP“ zusammengefasst werden. Hier wird ein Set angegeben (s. Tab. 2.26). In dieser Klasse wird nur ein Parameter aufgeführt (s. Tab. 2.27). Das Package „oemof.solph.components“ verfügt über Werkzeuge zur Modellierung unterschiedlicher spezifischer Komponenten in einem Energieversorgungssystem. Inhalte des Moduls constraints.py Dieses Modul steht zur Verfügung, um weitere Nebenbedingungen anzulegen. Klassen werden in diesem Modul nicht definiert. Eine erste Nebenbedingung bezieht sich auf die Investitionen (s. Abschn. 3.3): def investment_limit(m, limit=None) Mit dieser Bedingung wird dem Optimierungsproblem ein Limit für die Total Investment Kosten einer Investition vorgegeben. Hierfür sind mehrere Parameter erforderlich (s. Tab. 2.28). Tab. 2.25 Parameter der Klasse „ExtractionTurbineCHP“. (GitHub 2017e) Parameter conversion_factors
Datentyp Erläuterung Dict Dictionary containing conversion factors for conversion of inflow to specified outflow. Keys are output bus objects. The dictionary values can either be a scalar or a sequence with length of time horizon for simulation. conversion_factor_ Dict The efficiency of the main flow if there is no tapped flow. Only full_condensation one key is allowed. Use one of the keys of the conversion factors. The key indicates the main flow. The other output flow is the tapped flow. Tab. 2.26 Sets für die Klasse „ExtractionTurbineCHPBlock“. (GitHub 2017e) Set Beschreibung VARIABLE_FRACTION_TRANSFORMERS A set with all objects of ExtractionTurbineCHP. Tab. 2.27 Parameter der Klasse „ExtractionTurbineCHPBlock“. (GitHub 2017e) Parameter Datentyp Erläuterung Group List List of oemof.solph.Transformers (trsf) objects for which the linear relation of inputs and outputs is created e.g. group = [trsf1, trsf2, trsf3, ...]. Note that the relation is created for all existing relations of the inputs and all outputs of the transformer. The omponents inside the list need to hold a attribute `conversion_factors` of type dict containing the conversion factors from inputs to outputs.
2.4 Zentrale Packages und Module in OEMOF
79
Tab. 2.28 Parameter der constraint „investment_limit“. (GitHub 2017l) Parameter Model Limit
Datentyp oemof.solph.Model Float
Erläuterung Model to which the constraint is added. Absolute limit of the investment (i.e. RHS of constraint).
Tab. 2.29 Parameter der constraint „emission_limit“. (GitHub 2017l) Parameter Datentyp Erläuterung om oemof. Model to which constraints are added. solph.Model flows Dict Dictionary holding the flows that should be considered in constraint. Keys are (source, target) objects of the Flow. If no dictionary is given all flows containing the 'emission' attribute will be used. limit Numeric Absolute emission limit.
Tab. 2.30 Parameter der constraint „equate_variables“. (GitHub 2017l) Parameter Datentyp var1 pyomo. environ.Var var2 pyomo. environ.Var factor1 Float name Str model
oemof.solph. Model
Erläuterung First variable, to be set to equal with Var2 and multiplied with factor1. Second variable, to be set equal to (Var1 * factor1). Factor to define the proportion between the variables. Optional name for the equation e.g. in the LP file. By default the name is: equate + string representation of var1 and var2. Model to which the constraint is added.
Eine weitere Nebenbedingung wird zu den Emissionen eingeführt (s. Abschn. 3.3): def emission_limit(om, flows=None, limit=None) Diese gibt eine absolute globale Obergrenze der Emissionen aus. Zur Berechnung braucht es die Parameter aus Tab. 2.29. In einer anderen Nebenbedingung „def equate_variables(model, var1, var2, factor1=1, name=None)“ kann durch die Multiplikation einer Variable 1 mit einem Faktor eine neue Variable 2 erzeugt werden. Hierfür werden folgende Parameter aus Tab. 2.30 verwendet. Ein Beispiel zeigt, wie eine Übertragungslinie (transmission line) im Investment Modus durch Verbinden der beiden Investment Variablen definiert wird (GitHub 2017l): >>> import pandas as pd >>> from oemof import solph >>> date_time_index = pd.date_range('1/1/2012', periods=5, freq='H') >>> energysystem = solph.EnergySystem(timeindex=date_time_index) >>> bel1 = solph.Bus(label='electricity1')
80
2 Das generische Basismodell in OEMOF >>> bel2 = solph.Bus(label='electricity2') >>> energysystem.add(bel1, bel2) >>> energysystem.add(solph.Transformer( ... label='powerline_1_2', ... inputs={bel1: solph.Flow()}, ... outputs={bel2: solph.Flow( ... investment=solph.Investment(ep_costs=20))})) >>> energysystem.add(solph.Transformer( ... label='powerline_2_1', ... inputs={bel2: solph.Flow()}, ... outputs={bel1: s o l p h . F l o w ( i n v e s t m e n t = s o l p h . I n v e s t m e n t ( e p _ costs=20))})) >>> om = solph.Model(energysystem) >>> line12 = energysystem.groups['powerline_1_2'] >>> line21 = energysystem.groups['powerline_2_1'] >>> solph.constraints.equate_variables( ... om, ... om.InvestmentFlow.invest[line12, bel2], ... om.InvestmentFlow.invest[line21, bel1]) """
Inhalte des Moduls custom.py In dem Modul können spezifische anwenderbezogene Komponenten mit ihren Constraints (blocks) und Groupings abgelegt werden. Die Definitionen der Klassen und deren Blöcke stehen direkt beieinander. Es sind mehrere Klassen definiert. • • • • • • • • •
class ElectricalBus(Bus) class ElectricalLine(Transformer) class ElectricalLineBlock(SimpleBlock) class Link(Transformer) class LinkBlock(SimpleBlock) class GenericCAES(Transformer) class GenericCAESBlock(SimpleBlock) class OffsetTransformer(Transformer) class OffsetTransformerBlock(SimpleBlock)
Die erste Klasse heißt „ElectricalBus(Bus)“. Diese erbt von der Klasse „Bus“: class ElectricalBus(Bus) Es handelt sich um ein spezielles electrical bus Object. Dieses Bus Objekt wird in Kombination mit den ElectricalLine Objekten verwendet. Beide Objekte werden für lineare optimale Stromflusssimulationen (linear optimal power flow (lopf) simulations) eingesetzt.
2.4 Zentrale Packages und Module in OEMOF
81
Eine andere Klasse erbt von der Klasse „Transformer“ und heißt „ElectricalLine“: class ElectricalLine(Transformer) Diese Klasse wird vewendet, um lineare optimale Stromflussberechnungen durchzuführen, die auf der Formulierung der Phasenwinkel basieren. Ein mit dieser Klasse verbundener Bus muss vom Typ „ElectricalBus“ sein. Der zur Berechnung erforderliche Parameter kann der Tab. 2.31 entnommen werden. Nun folgt die Klasse „ElectricalLineBlock“, die von der Klasse „SimpleBlock“ erbt: class ElectricalLineBlock(SimpleBlock). Dies ist ein Datenblock für die lineare Beziehung von Knoten mit Typklasse „Electri calLine“. In dieser Klasse existiert ein Parameter (s. Tab. 2.32). In diesem Modul werden vier Variablen kreiert, die für die Netzspannungwinkel wichtig sind: self.ELECTRICAL_BUSES = Set(initialize=[n for n in m.es.nodes if isinstance(n, ElectricalBus)]) self.electrical_flow = Constraint(group, noruleinit=True)
self._equate_electrical_flows = Constraint(group, noruleinit=True)
self.electrical_flow_build = BuildAction(rule=_voltage_angle_relation)
Weiterhin ist eine Klasse „Link(Transformer)“ angelegt, die von der Klasse „Trans former“ erbt. class Link(Transformer) Dies ist ein Link mit 1 bis 2 Inputs und 1 bis 2 Outputs. Es existiert ein Parameter (s. Tab. 2.33). Tab. 2.31 Parameter der Klasse „ElectricalLine“. (GitHub 2017m) Parameter Reactance
Datentyp float or array of floats
Erläuterung Reactance of the line to be modelled.
Tab. 2.32 Parameter der Klasse „ElectricalLineBlock“. (GitHub 2017m) Parameter Datentyp Erläuterung group List List of oemof.solph.ElectricalLine (eline) objects for which the linear relation of inputs and outputs is created e.g. group = [eline1, eline2, ...]. The components inside the list need to hold an attribute `reactance` of type Sequence containing the reactance of the line.
82
2 Das generische Basismodell in OEMOF
Tab. 2.33 Parameter der Klasse „Link“. (GitHub 2017m) Parameter conversion_ factors
Datentyp Erläuterung dict Dictionary containing conversion factors for conversion of each flow. Keys are the connected tuples (input, output) bus objects. The dictionary values can either be a scalar or a sequence with length of time horizon for simulation.
Tab. 2.34 Parameter der Klasse „LinkBlock“. (GitHub 2017m) Parameter Datentyp Erläuterung Group list List of oemof.solph.custom.Link objects for which the relation of inputs and outputs is created e.g. group = [link1, link2, link3, ...]. The components inside the list need to hold an attribute `conversion_factors` of type dict containing the conversion factors for all inputs to outputs.
Ein Beispiel für einen Einblick in die Anwendung der Klasse sieht wie folgt aus: >>> from oemof import solph >>> bel0 = solph.Bus(label="el0") >>> bel1 = solph.Bus(label="el1") >>> link = solph.custom.Link( ... label="transshipment_link", ... inputs={bel0: solph.Flow(), bel1: solph.Flow()}, ... outputs={bel0: solph.Flow(), bel1: solph.Flow()}, ... conversion_factors={(bel0, bel1): 0.92, (bel1, bel0): 0.99}) >>> print(sorted([x[1][5] for x in link.conversion_factors. items()])) [0.92, 0.99] >>> type(link)
>>> sorted([str(i) for i in link.inputs]) ['el0', 'el1'] >>> link.conversion_factors[(bel0, bel1)][3] 0.92
Ebenso gibt es auch hier einen Datenblock, mit der Klasse „LinkBlock“: class LinkBlock(SimpleBlock) Diese fasst die Relation der Knoten von der Typklasse „custom.Link“ zusammen. Ein Parameter wird für diese Klasse definiert (s. Tab. 2.34). Für weitere Informationen zu diesem Modul und den aktuellen Stand wird auf OEMOF verwiesen: https://github.com/oemof. Um Druckluftspeicher zu modellieren, gibt es in OEMOF die Klasse „GenericCAES“. Sie leitet sich aus der Klasse „Transformer“ ab: class GenericCAES(network.Transformer)
2.4 Zentrale Packages und Module in OEMOF
83
Für die In- und Outputs sind in dieser Klasse Parameter definiert (s. Tab. 2.35). Eine Darstellung der modellhaften Beschreibung eines Druckluftspeicherkraftwerkes (Compressed Air Energy Storage – CAES) befindet sich in (Kaldemeyer et al. 2017). Als nächste Klasse im Package „oemof.solph.components“ wird die Klasse „Generic CAESBlock“ aufgeführt. Dies ist ein Datenblock für Knoten der Klasse „GenericCAES“. Diese Klasse erbt von der Klasse „SimpleBlock“. class GenericCAESBlock(SimpleBlock) In dieser Klasse werden die Knoten (Nodes) des Typs GenericCAES in einem Block zusammengefasst. Auch diese Klasse verfügt über einen Parameter „group“ (s. Tab. 2.36). Für diese Klasse müssen mehrere Variablen eingeführt werden (s. Tab. 2.37). Eine neue Klasse in diesem Release ist „OffsetTransformer(Transformer)“. Dies ist eine spezielle Komponente mit einem Input und einem Output. Für die Klasse wird der in Tab. 2.38 gelistete Parameter eingeführt. Zu der soeben aufgeführten Klasse existiert auch die Block-Klasse „OffsetTransfor merBlock(SimpleBlock)“. Hierbei handelt es sich um die Beziehung von Knoten des Typs „OffsetTransformer“, die als Block zusammengefasst werden. Auch hier wird der Parameter „group“ eingeführt (s. Tab. 2.39). Inhalte des Moduls facades.py In dieses Modul können Klassen abgelegt werden, die vereinfachte/reduzierte energie spezifische Oberflächen (facades) für Lösungskomponenten darstellen, um die Anwendung zu vereinfachen (GitHub 2017n). Inhalte des Moduls groupings.py Das Gruppieren von Objekten läuft in OEMOF automatisch im Hintergrund ab. Ein Modellierer benötigt für seine Arbeit mit OEMOF kein Wissen darüber. Somit ist die Funktionalität der Gruppierung von Objekten für die meisten Modellierer von OEMOF nicht von Interesse. Tab. 2.35 Parameter der Klasse „GenericCAES“. (GitHub 2017m) Parameter electrical_ input fuel_input electrical_ output
Datentyp Erläuterung dict Dictionary with key-value-pair of `oemof.Bus` and `oemof.Flow` object for the electrical input. dict Dictionary with key-value-pair of `oemof.Bus` and `oemof.Flow` object for the fuel input. dict Dictionary with key-value-pair of `oemof.Bus` and `oemof.Flow` object for the electrical output.
Tab. 2.36 Parameter der Klasse „GenericCAESBlock“. (GitHub 2017m) Parameter Group
Datentyp list
Erläuterung List containing `.GenericCAES` objects.
84 Tab. 2.37 Variablen der Klasse „GenericCAESBlock“. (GitHub 2017m)
2 Das generische Basismodell in OEMOF Variable self.cmp_st
Beschreibung Compression: Binary variable for operation status self.cmp_p Compression: Realized capacity self.cmp_p_max Compression: Max. Capacity self.cmp_q_out_s Compression: Heat flow self.cmp_q_waste Compression: Waste heat self.exp_st Expansion: Binary variable for operation status self.exp_p Expansion: Realized capacity self.exp_p_max Expansion: Max. Capacity self.exp_q_in_sum Expansion: Heat flow of natural gas co-firing self.exp_q_fuel_in Expansion: Heat flow of natural gas co-firing self.exp_q_add_in Expansion: Heat flow of additional firing self.cav_level Cavern: Filling levelh self.cav_e_in Cavern: Energy inflow self.cav_e_out Cavern: Energy outflow self.tes_level TES: Filling levelh self.tes_e_in TES: Energy inflow self.tes_e_out TES: Energy outflow self.exp_p_spot Spot market: Positive capacity self.cmp_p_spot Spot market: Negative capacity TES: thermal energy storage
Tab. 2.38 Parameter der Klasse „OffsetTransformer“. (GitHub 2017m) Parameter Datentyp Erläuterung Coefficients Dict Dictionary containing the first two polynomial coefficients i.e. the y-intersect and slope of a linear equation. Keys are the connected tuples (input, output) bus objects. The dictionary values can either be a scalar or a sequence with length of time horizon for simulation.
Tab. 2.39 Parameter der Klasse „OffsetTransformerBlock“. (GitHub 2017m) Parameter Datentyp Erläuterung group List List of oemof.solph.custom.OffsetTransformer objects for which the relation of inputs and outputs is created e.g. group = [ostf1, ostf2, ostf3, ...]. The components inside the list need to hold an attribute `coefficients` of type dict containing the conversion factors for all inputs to outputs.
2.4 Zentrale Packages und Module in OEMOF
85
Für besonders Interessierte sei erwähnt, dass in diesem Modul eine Funktion zur Gruppierung von Nebenbedingungen abgelegt ist: def constraint_grouping(node) Hierbei erfolgt eine Zurodnung unterschiedlicher Flüsse zu der Klasse „FlowWithNo des“. Um die genaue Funktionalität zu verstehen, wird auf den Quellcode verwiesen (s. (GitHub 2017c)). investment_flow_grouping = groupings.FlowsWithNodes( constant_key=blocks.InvestmentFlow, # stf: a tuple consisting of (source, target, flow), so stf[2] is the # flow. filter=lambda stf: stf[2].investment is not None) standard_flow_grouping = groupings.FlowsWithNodes( constant_key=blocks.Flow) nonconvex_flow_grouping = groupings.FlowsWithNodes( constant_key=blocks.NonConvexFlow, filter=lambda stf: stf[2].nonconvex is not None)
Inhalte des Moduls models.py Dieses Modul bildet den Rahmen für ein Optimierungsmodell. Dazu sind zwei Klassen angelegt. Die erste Klasse dient zur Modellierung eines Basis Modells, „BaseModel“. Die Klasse erbt von der Klasse „po.ConcreteModel“: Diese Klasse stammt aus Pyomo. Der Import erfolgt beispielhaft durch: import pyomo.environ as po po.ConcreteModel
Die Klasse wird im Quellcode wie folgt aufgeführt: class BaseModel(po.ConcreteModel) Die erforderlichen Parameter sind in Tab. 2.40 zusammengestellt: Die zweite Klasse heißt “Model” und erbt von der Klasse “BaseModel”: Diese Klasse dient dazu, ein Optimierungsmodell für eine Betriebs- und Investitionsoptimierung zu erstellen. class Model(BaseModel) Folgende Parameter sind hinterlegt (s. Tab. 2.41). Die zugehörigen Sets sind (s. Tab. 2.42). Eine Variable steht dieser Klasse zur Verfügung (s. Tab. 2.43). Des Weiteren sind unter Umständen Nebenbedingungen erforderlich. Solph prüft die Gruppen des gegebenen Energiesystems auf solche Nebenbedingungen und nutzt diese, um die Nebenbedingungen des Optimierungsmodells zu erstellen. Hier lautet der Verweis:
86
2 Das generische Basismodell in OEMOF
Tab. 2.40 Parameter der Klasse „BaseModel“. (GitHub 2017f) Parameter Datentyp energysystem EnergySystem object constraint_ list (optional) groups auto_ construct
Boolean
Erläuterung Object that holds the nodes of an oemof energy system graph. Solph looks for these groups in the given energy system and uses them to create the constraints of the optimization problem. Defaults to :const:`Model.CONSTRAINTS` If this value is true, the set, variables, constraints, etc. are added, automatically when instantiating the model. For sequential model building process set this value to False and use methods `_add_parent_block_sets`, `_add_parent_block_variables`, `_ add_blocks`, `_add_objective`.
Tab. 2.41 Parameter der Klasse „Model“. (GitHub 2017f) Parameter Datentyp energysystem EnergySystem object constraint_ list groups
Erläuterung Object that holds the nodes of an oemof energy system graph. Solph looks for these groups in the given energy system and uses them to create the constraints of the optimization problem. Defaults to :const:`Model.CONSTRAINTS`.
Tab. 2.42 Sets für die Klasse „Model“. (GitHub 2017f) Set NODES TIMESTEPS FLOWS
Beschreibung A set with all nodes of the given energy system. A set with all timesteps of the given time horizon. A 2 dimensional set with all flows. Index: `(source, target)`.
Tab. 2.43 Variable für die Klasse „Model“. (GitHub 2017f) Variable Flow
Beschreibung Flow from source to target indexed by FLOWS, TIMESTEPS. Note: Bounds of this variable are set depending on attributes of the corresponding flow object.
CONSTRAINT_GROUPS = [blocks.Bus, blocks.Transformer, blocks.InvestmentFlow, blocks.Flow, blocks.NonConvexFlow]
Inhalte des Moduls network.py Die in diesem Modul definierten Klassen sind erforderlich, um innerhalb von Solph ein Energiesystem zu modellieren. Wie bereits bekannt ist, wird ein Energiesystem innerhalb von OEMOF als graph bzw. Netzwerk von Knoten abgebildet. Die Knoten besitzen dabei sehr spezifische Nebenbedingungen, die festlegen, welche Knoten miteinander verbunden
2.4 Zentrale Packages und Module in OEMOF
87
werden dürfen. Diese Informationen sind in der Bibliothek solph hinterlegt. Die Klassen sind von den OEMOF core network Klassen abgeleitet und für spezielle Optimierungsaufgaben angepasst. Eine Auflistung der in diesem Modul vorhanden Klassen gibt einen ersten Überblick über dieses Modul: • • • • • •
class EnergySystem(es.EnergySystem) class Flow class Bus(on.Bus) class Sink(on.Sink) class Source(on.Source) class Transformer(on.Transformer)
Es zeigt sich, dass die Energie-spezifischen Klassen, die hier aufgeführt werden, in verschiedenen Modulen eingeführt werden, um dort spezielle weiterführende Dateninformationen zu erhalten. Die Klasse „EnergySystem“ erbt im Package Solph von der Klasse „es.EnergySystem“. Die Klasse „es.EnergySystem“ ist Teil von oemof core. Auf dieser Ebene ist die Klasse speziell für die Bearbeitung durch die Bibliothek solph zugeschnitten. Dazu ist es u. a. erforderlich, dass bei der Modellierung Groupings eingerichtet werden. Die Klasse „Flow“ besitzt in dieser Ebene verschiedene Paramater, die besondere Eigenschaften aufweisen. Diese sind nachfolgend in Tab. 2.44 aufgeführt. Das nachfolgende Beispiel soll die Modellierung mit diesem Package etwas verdeutlichen (nach GitHub 2017g). Dabei dient dieses Beispiel nur zur Verdeutlichung der Formulierungsmechanismen mit Python. Eine fachliche Sinnhaftigkeit ist an dieser Stelle nicht gegeben. # Creating a fixed flow object. Dabei wird zunächst der Buchstabe “f” der # Klasse “Flow” zugewiesen: f = Flow(actual_value=[10, 4, 3], fixed=True, variable_costs=5) # Der Parameter wird aufgerufen und dessen Wert abgefragt: f.variable_costs[1] # Das Ergebnis ist die Zahl “5”. 5 # Nun wird der nächste Paramater “f.actuel_value” aufgerufen und nach der # zweiten Stelle in der Klammer gefragt: f.actual_value[2] # Als Ergebnis erhält man die Zahl 4. 4 # Creating a flow object with time-depended lower and upper bounds: f1 = Flow(min=[0.2, 0.3], max=0.99, nominal_value=100) f1.max[1] 0.99
Datentyp numeric
:class:`oemof. solph.options. Investment` object
investment
nonconvex
numeric (sequence or scalar) Boolean
numeric
numeric (sequence or scalar) numeric (sequence or scalar) numeric
variable_ costs fixed
positive_ gradient negative_ gradient summed_ max summed_ min
numeric (sequence or scalar) max numeric (sequence or scalar) actual_value numeric (sequence or scalar)
Parameter nominal_ value min
Specific maximum value summed over all timesteps. Will be multiplied with the nominal_value to get the absolute limit. Specific minimum value summed over all timesteps. Will be multiplied with the nominal_value to get the absolute limit. Wenn es im Rahmen der Modellbildung möglich ist, sind alle Attribute spezifische Größen, die von der absoluten Leistung abhängen. Das erweist sich als prakatisch für den Investment Modus. Denn in diesem Modus kann die Variable für die Nominale Kapazität mit der Investment-Variablen ausgetauscht werden. The costs associated with one unit of the flow. If this is set the costs will be added to the objective expression of the optimization problem. Boolean value indicating if a flow is fixed during the optimization problem to its ex-ante set value. Used in combination with the:attr:`actual_value`. Object indicating if a nominal_value of the flow is determined by the optimization problem. Note: This will refer all attributes to an investment variable instead of to the nominal_value. The nominal_value should not be set (or set to None) if an investment object is used. If a nonconvex flow object is added here, the flow constraints will be altered significantly as the mathematical model for the flow will be different, i.e. constraint etc from :class:`oemof.solph.blocks.NonConvexFlow` will be used instead of :class:`oemof.solph.blocks.Flow`.
Specific value for the flow variable. Will be multiplied with the nominal\_value to get the absolute value. If fixed attr is set to True the flow variable will be fixed to actual_value * :attr:`nominal_value`, I.e. this value is set exogenous. The normed maximal positive difference (flow[t-1] < flow[t]) of two consecutive flow values. Dies bedeutet, wird Leistung in einem Zeitschritt größer als im vorherigen, dann ist es ein positiver Gradient. The normed maximum negative difference (from[t-1] > flow[t]) of two consecutive timesteps.
Erläuterung The nominal value of the flow. If this value is set the corresponding optimization variable of the flow object will be bounded by this value multiplied with min(lower bound)/max(upper bound). Normed minimum value of the flow. The flow absolute minimum will be calculated by multiplying :attr:`nominal_value` with :attr:`min`. Nominal maximum value of the flow (see. :attr:`min`).
Tab. 2.44 Parameter der Klasse „Flow“. (GitHub 2017g)
88 2 Das generische Basismodell in OEMOF
2.4 Zentrale Packages und Module in OEMOF
89
Tab. 2.45 Parameter der Klasse „Transformer(on.Transformer)“. (GitHub 2017g) Parameter conversion_ factors
Datentyp Erläuterung dict Dictionary containing conversion factors for conversion of each flow. Keys are the connected bus objects. The dictionary values can either be a scalar or a sequence with length of time horizon for simulation.
Für die nachfolgenden Klassen „class Bus(on.Bus)“, „class Sink(on.Sink)“, „class Source(on.Source)“ sind keine speziellen Parameter erforderlich. Einzig für die Klasse „Transformer(on.Transformer)“ ist ein Parameter definiert (s. Tab. 2.45). Ein Beispiel verdeutlicht die Zusammenhänge bezüglich dieser Klasse (GitHub 2017g).: Es folgt ein weiteres Beispiel mit der Klasse „Transfomer“, welches die Zusammenhänge bezüglich dieser Klasse verdeutlichen soll (GitHub 2017g). Dabei sind zwei Besonderheiten zu beachten. Im oberen Objekt erfolgt zunächst eine Anweisung zum Sortieren der Zahlenwerte des „conversion_factors“. Die Anweisung „sorted“ besagt, dass die Items der Größe nach aufsteigend sortiert werden sollen. Anschließend werden die Inputs alphabetisch von A bis Z sortiert. Es wird dann eine neue Instanz der Klasse „Trans former“ definiert. Inputs, Outputs und Umwandlungsfaktor werden festgelegt. # Defining a transformer. Dazu wird zunächst die Bibliothek solph # importiert. >>> from oemof import solph # Es folgt die Zuweisung der Klassen Bus und Transformer. Für den # Tranformer werden die In- und Outputs mit deren conversion factors # angegeben. Am Ende wird der print-Befehl mit der Bedingung ausgegeben, # dass die Liste der Größe nach sortiert sein soll. >>> bgas = solph.Bus(label="natural_gas") >>> bcoal = solph.Bus(label="hard_coal") >>> bel = solph.Bus(label="electricity") >>> bheat = solph.Bus(label="heat") >>> trsf = solph.Transformer( ... label="pp_gas_1", ... inputs={bgas: solph.Flow(), bcoal: solph.Flow()}, ... outputs={bel: solph.Flow(), bheat: solph.Flow()}, ... conversion_factors={bel: 0.3, bheat: 0.5, ... bgas: 0.8, bcoal: 0.2}) >>> print(sorted([x[1][5] for x in trsf.conversion_factors.items()])) # Nach Durchlaufen des Programms erhält man: [0.2, 0.3, 0.5, 0.8] # Im foglenden Schritt werden die Klasse Transformer und conversion_factors # für “bel” und “bheat” mit Werten angelegt. Im letzten Schritt wird der
90
2 Das generische Basismodell in OEMOF
# conversion_factor für bgas im Zeitschritt [3] abgefragt. Da dieser nicht # angelegt ist, vergibt das System automatisch den Faktor “1”. >>> type(trsf)
>>> sorted([str(i) for i in trsf.inputs]) ['hard_coal', 'natural_gas'] >>> trsf_new = solph.Transformer( ... label="pp_gas_2", ... inputs={bgas: solph.Flow()}, ... outputs={bel: solph.Flow(), bheat: solph.Flow()}, ... conversion_factors={bel: 0.3, bheat: 0.5}) >>> trsf_new.conversion_factors[bgas][3] 1
Inhalte des Moduls options.py Unter diesem Modul können optional Klassen zur Klasse „network“ hinzugefügt werden. Hierin bereits angelegt sind die beiden Klassen „Investment“ und „NonConvex“. Für die Klasse „Investment“ sind in diesem Modul folgende Parameter angelegt (s. Tab. 2.46). Für die Klasse „NonConvex“ existieren ebenfalls Parameter (s. Tab. 2.47). Berechnungen werden in diesem Modul nicht durchgeführt.
2.4.5 oemof.tools package Zu diesem Package gehören drei Module: • economic.py • helpers.py • logger.py Das erste Modul „economic.py“ dient dazu, Funktionen für ökonomische Berechnungen zusammenzufassen (GitHub 2017h). Im zweiten Modul, „helpers.py“, sind Hilfsfunktionen enthalten (GitHub 2017i). Diese können von unterschiedlichen Klassen genutzt werden. Im letzten Modul „logger.py“ befinden sich Hilfsfunktionen, um den eigenen Modellierungsprozess zu loggen (GitHub 2017j). Tab. 2.46 Parameter der Klasse „Investment“. (GitHub 2017g) Parameter maximum minimum ep_costs
Datentyp float float float
Erläuterung Maximum of the additional invested capacity. Minimum of the addtional invested capacity. Equivalent periodical costs for the investment, if period is one year these costs are equal to the equivalent annual costs.
2.5 Vorgehen bei der Entwicklung eines eigenen Modells
91
Tab. 2.47 Parameter der Klasse „NonConvex“. (GitHub 2017g) startup_costs shutdown_costs
numeric numeric
numeric
Costs associated with a start of the flow (representing a unit). Costs associated with the shutdown of the flow (representing a until). Minimum time that a flow must be greater then its minimum flow after startup. Minimum time a flow is forced to zero after shutting down.
numeric (0 or 1)
Integer value indicating the status of the flow in the first time step (0 = off, 1 = on).
minimum_uptime numeric minimum_ downtime initial_status
2.5 Vorgehen bei der Entwicklung eines eigenen Modells Bei der Entwicklung einer eigenen Anwendung zur Optimierung eines Energieversorgungssystems können OEMOF Bibliotheken und externe Python Bibliotheken flexibel miteinander kombiniert werden. Unabhängig von der Komplexität und der damit verbundenen Einbindung von internen und externen Bibliotheken, besitzt jeder Arbeitsprozess der Modellierung vier zentrale Schritte (s. Abb. 2.11) Im ersten Schritt wird zunächst ein leeres Objekt (Instanz) eines Energiesystems aufgesetzt, indem aus dem in OEMOF angelegten bestehenden Energiesystem eine neue In stanz gebildet wird. Dieses Objekt dient als Container. In diesem Container sind die Knoten angelegt und die Information zum globalen Zeitindex angegeben. Über den globalen Zeitindex werden Periode und Zeitschritte für die Optimierung definiert. Zudem befinden sich in diesem Container Methoden, die sich auf die Knoten beziehen. Im Schritt „population of energy system“ werden aus dem ursprünglichen, in OEMOF bestehenden Energiesystem die Knoten und Flüsse für die neue Instanz des Energiesystems initialisiert. Dabei können die Knoten und Flüsse nach und nach in der Programmiersprache Python angelegt werden. Um zwischen dem Input von Daten und Parametern und der Erstellung von Knoten zu unterscheiden, ist es sinnvoll, der neuen Instanz des Energiesystems eigene Funktionen zur Verfügung zu stellen. Eine Besonderheit zur Definition eines neuen Energiesystems mit seinen Parametern in OEMOF ist die Verwendung eines speziell formatierten CSV-Files (Hilpert et al. 2017). Dies erleichtert die Arbeit und beugt Fehlern vor. Ist in OEMOF das zu optimierende Energiesystem angelegt, kann der Rechenlauf im Schritt „Compute results“ erfolgen. Jetzt kann die Suche nach der optimalen Lösung beginnen. Im abschließenden Schritt „Process results“ werden mit der Output-Bibliothek (outputlib) die Ergebnisse ausgegeben. Mit der outputlib können auf eine einfache Art und Weise unterschiedliche Diagramme erstellt und so mehrere Perspektiven auf das Ergebnis gezeigt werden. Dazu verwendet die Output-Bibliothek ein einheitliches Datenausgabe-Format.
92
2 Das generische Basismodell in OEMOF
Abb. 2.11 Zentrale Arbeitsschritte im Modellierungsprozess. (Nach Hilpert et al. 2017)
Diese vier Schritte sind bei jeder Modellierung eines Energieversorgungsystems erforderlich. Anhand des folgenden Programmcodes können die vier Schritte exemplarisch nachvollzogen werden. Nachfolgend ist der erste Schritt mit dem Aufsetzen eines leeren Objektes (Instanz) eines Energiesystems und der Definition des „timeindex“ beschrieben. Im Vorfeld können Importe durchgeführt werden, um die für den Optimierungsprozess erforderlichen Packages verfügbar zu haben: import oemof.solph # create the energy system es = solph.EnergySystem(timeindex=date_time_index)
Die weiteren Schritte können in einem Programmcode wie folgt zugewiesen werden. Hierbei lässt sich in den ersten beiden Schritten der spezifische Programmcode sehr gut direkt dem entsprechenden Schritt zuweisen. Aus diesem Grund ist dieser in „fett“ dargestellt. Auch wenn es nur um die vier zentralen Schritte geht, sind in einem Quellcode viel mehr Angaben vorhanden, die im Programmablauf an den entsprechenden Stellen abgearbeitet werden. Diese werden jedoch nicht hervorgehoben. In den letzten beiden Schritten lässt sich kein spezifischer Programmcode hervorheben. Hier sind jeweils je nach Modellierer unterschiedliche Codeangaben möglich. # # # # # #
Um die vier Schritte des Vorgehens im Programmcode vorzustellen, wird auszugsweise auf das Beispiel „Rostock“ zurückgegriffen. Dieses wird jedoch um weitere Komponenten erweitert. Es wird mit oemof-Objekten ein Energiesystem modelliert und mit dem Solph Modul gelöst. Die Ergebnisse werden mit der Bibliothek outputlib geplottet.
2.5 Vorgehen bei der Entwicklung eines eigenen Modells
93
# ##################### Instantiate energy system ##################### # -------------------------------Imports ---------------------------# Default logger of oemof from oemof.tools import logger from oemof.tools import economics import oemof.solph as solph from oemof.outputlib import processing, views import logging import os import pandas as pd import pprint as pp number_timesteps = 8760 # „import pandas as pd“: Pandas ist ein Programm zum Bearbeiten von Daten. # Über den aufgeführten Befehl wird Pandas in die eigene Applikation # importiert. # „import logging“: Logging ist eine Python Bibliothek und ermöglicht das # Protokollieren sämtlicher Ereignisse beim Programmablauf. # # # # #
„import os“: Bei os (os=Operating System) handelt es sich um eine Standardbibliothek von Python [Python Tutorial 2013]. Sie stellt eine Schnittstelle zum Betriebssystem zur Verfügung. Diese bietet eine Möglichkeit der Verwendung von Betriebssystem unabhängigen Funktionen [PythonForBeginners 2013].
„from oemof.tools import logger“: Nun wird aus dem Pa# ckage „oemof.tools“ # das Modul „logger“ importiert. # „from oemof.tools import economics“: Hier wird aus dem Package
# „oemof.tools“ das Modul „economics“ importiert. # -- Initialize the energy system and read/calculate necessary parameters logger.define_logging() logging.info('Initialize the energy system') date_time_index = pd.date_range('1/1/2017', periods=number_timesteps, freq='H')
94
2 Das generische Basismodell in OEMOF
energysystem = solph.EnergySystem(timeindex=date_time_index) # Der Parameter datetimeindex ist Pandas spezifisch. Der erste Parameter # gibt das Startdatum (1.1.2017). Der zweite Parameter gibt vor, in # wieviele Zeitschritte (Perioden) der Zeitstrahl aufgeteilt werden soll. # Der dritte Parameter gibt die Fequenz vor, hier Stundenwerte. # Informationen dazu befinden sich unter [pandas o.J.-b]. # ------------------------------- Read data file ------------------------full_filename = os.path.join(os.path.dirname(__file__), 'storage_investment.CSV') data = pd.read_CSV(full_filename, sep=",") # Diese Anweisung gibt vor, dass die CSV-Datei kommasepariert gelesen # werden soll. fossil_share = 0.2 consumption_total = data['demand_el'].sum() # If the period is one year the equivalent periodical costs (epc) of an # investment are equal to the annuity. Use oemof's economic tools. epc_wind = economics.annuity(capex=1000, n=20, wacc=0.05) epc_pv = economics.annuity(capex=1000, n=20, wacc=0.05) epc_storage = economics.annuity(capex=1000, n=20, wacc=0.05) # ##################### Populate energy system ########################## # Jetzt wird die Funktion logging.info aufgerufen, die den Wert “Creating # oemof objekts” ausgeben soll. Dies dient zur Überprüfung, dass das # Optimierungsmodell erstellt wird. logging.info('Create oemof objects') # create gas bus r1_gas = solph.Bus(label="gas") # create electricity bus r1_el = solph.Bus(label="electricity") # Mit dem folgenden Befehl werden die soeben eingeführten Elemente dem # Energiesystem hinzugefügt. energysystem.add(r1_gas, r1_el) # create excess component for the electricity bus to allow overproduction excess = solph.Sink(label='excess_r1_el', inputs={r1_el: solph.Flow()}) # create source object representing the natural gas commodity (annual
2.5 Vorgehen bei der Entwicklung eines eigenen Modells
95
# limit) gas_resource = solph.Source(label='rgas', outputs={r1_gas: solph.Flow (nominal_value=fossil_share * consumption_total / 0.58 * number_timesteps / 8760, summed_max=1)}) # create fixed source object representing wind power plants wt1 = solph.Source(label='wind', outputs={r1_el: solph.Flow( actual_value=data['wind'], fixed=True, investment=solph.Investment(ep_costs=epc_wind))}) # create fixed source object representing pv power plants pv = solph.Source(label='pv', outputs={r1_el: solph.Flow( actual_value=data['pv'], fixed=True, investment=solph.Investment(ep_costs=epc_pv))}) # create simple sink object representing the electrical demand de1 = solph.Sink(label='demand', inputs={r1_el: solph.Flow( actual_value=data['demand_el'], fixed=True, nominal_value=1)}) # create simple transformer object representing a gas power plant gt1 = solph.Transformer( label="pp_gas", inputs={r1_gas: solph.Flow()}, outputs={r1_el: solph.Flow(nominal_value=10e10, variable_ costs=0)}, conversion_factors={bel: 0.58}) # create storage object representing a battery st1 = solph.components.GenericStorage( label='storage', inputs={r1_el: solph.Flow(variable_costs=0.0001)}, outputs={r1_el: solph.Flow()}, capacity_loss=0.00, initial_capacity=0, nominal_input_capacity_ratio=1/6, nominal_output_capacity_ratio=1/6, inflow_conversion_factor=1, outflow_conversion_factor=0.8, investment=solph.Investment(ep_costs=epc_storage), ) # Wieder werden die Elemente dem Energiesystem hinzugegeben. energysystem.add(excess, gas_resource, wt1, pv, de1, gt1, st1) # ############################ Compute results ######################## # ---------------------- Optimise the energy system -------------------logging.info('Optimise the energy system')
96
2 Das generische Basismodell in OEMOF
# initialise the operational model # Die Klasse Model, die vom EnergySystem vererbt wird, wird im # weiterführenden Code über das Kürzel “om” aufgerufen. om = solph.Model(energysystem) # if tee_switch is true solver messages will be displayed logging.info('Solve the optimization problem') om.solve(solver='cbc', solve_kwargs={'tee': True}) # # # # #
Jetzt wird der Solver „CBC“ zur Optimierung des Energiesystems aufgerufen. Die Parameter „tee“ und „keep“ stammen aus Pyomo und werden hier nicht weiter aufgeführt. Die Zeile „solve_kwargs” stammt aus Pyomo. Mit “tee=True” gibt man dem Solver den Befehl, den Output auszugeben.
# ############################## Process results ####################### # ------------------------ Check and plot the results -------------# check if the new result object is working for custom components results = processing.results(om) custom_storage = views.node(results, 'storage') electricity_bus = views.node(results, 'electricity') meta_results = processing.meta_results(om) pp.pprint(meta_results) my_results = electricity_bus['scalars']
# installed capacity of storage in GWh my_results['storage_invest_GWh'] = (results[(storage, None)] ['scalars']['invest']/1e6) # installed capacity of wind power plant in MW my_results['wind_invest_MW'] = (results[(wind, r1_el)] ['scalars']['invest']/1e3) # installed capacity of pv power plant in MW my_results['pv_invest_MW'] = (results[(pv, r1bel)] ['scalars']['invest']/1e3) # resulting renewable energy share my_results['res_share'] = (1 - results[(pp_gas, r1_el)] ['sequences'].sum()/results[(r1_el, de1)] ['sequences'].sum()) pp.pprint(my_results)
Literatur
97
Literatur Betz, A.: Windenergie und ihre Ausnutzung durch Windmühlen. Nachdruck der Originalausgabe aus 1926. Ökobuch Verlag, Freiburg (1994) Durmus, M.: Die 15wichtigsten Python Bibliotheken fürDatenanalyse und maschinelles Lernen. (2017). http://www.aisoma.de/2017/11/16/die-15-wichtigsten-python-bibliotheken-fuer- datenanalyse-undmaschinelles-lernen/. Accessed 21 Nov 2017 GitHub Inc.: oemof/oemof/network.py. (2017a). https://github.com/oemof/oemof/blob/dev/oemof/ network.py. Accessed 14 Dec 2017 GitHub Inc.: oemof/oemof/network.py. (2017b). https://github.com/oemof/oemof/blob/dev/oemof/ energy_system.py. Accessed 14 Dec 2017 GitHub Inc.: oemof/oemof/groupings.py. (2017c). https://github.com/oemof/oemof/blob/dev/ oemof/groupings.py. Accessed 18 Dec 2017 GitHub Inc.: oemof/oemof/solph/. (2017d). https://github.com/oemof/oemof/tree/dev/oemof/solph. Accessed 18 Dec 2017 GitHub Inc.: oemof/oemof/solph/components.py. (2017e). https://github.com/oemof/oemof/blob/ dev/oemof/solph/components.py. Accessed 18 Dec 2017 GitHub Inc.: oemof/oemof/solph/models.py. (2017f). https://github.com/oemof/oemof/blob/dev/ oemof/solph/models.py. Accessed 18 Dec 2017 GitHub Inc.: oemof/oemof/solph/network.py. (2017g). https://github.com/oemof/oemof/blob/dev/ oemof/solph/network.py. Accessed 19 Dec 2017 GitHub Inc.: oemof/oemof/tools/economics.py. (2017h). https://github.com/oemof/oemof/blob/dev/ oemof/tools/economics.py. Accessed 19 Dec 2017 GitHub Inc.: oemof/oemof/tools/helpers.py. (2017i). https://github.com/oemof/oemof/blob/dev/ oemof/tools/helpers.py. Accessed 19 Dec 2017 GitHub Inc.: oemof/oemof/tools/logger.py. (2017j). https://github.com/oemof/oemof/blob/dev/ oemof/tools/logger.py. Accessed 19 Dec 2017 GitHub Inc.: oemof/oemof/solph/blocks.py. (2017k). https://github.com/oemof/oemof/blob/dev/ oemof/solph/blocks.py. Accessed 20 Dec 2017 GitHub Inc.: oemof/oemof/solph/constraints.py. (2017l). https://github.com/oemof/oemof/blob/dev/ oemof/solph/constraints.py. Accessed 21 Dec 2017 GitHub Inc.: oemof/oemof/solph/custom.py. (2017m). https://github.com/oemof/oemof/blob/dev/ oemof/solph/custom.py. Accessed 21 Dec 2017 GitHub Inc.: oemof/oemof/solph/facades.py. (2017n). https://github.com/oemof/oemof/blob/dev/ oemof/solph/facades.py. Accessed 21 Dec 2017 GitHub Inc.: oemof organisation. (2017r). https://github.com/oemof. Accessed 21 Dec 2017 Hilpert, S. et al.: Addressing energy system modelling challenges: the contribution of the Open Energy Modelling Framework (oemof). Preprints, Basel (2017). https://pdfs.semanticscholar. org/aa20/4002418763fafe77221bc6e40778653217f2.pdf. Accessed 16 Nov 2017 Kaldemeyer, C., et al.: A generic formulation of compressed air energy storage as mixed integer linear program – unit commitment of specific technical concepts in arbitrary market environments materials today. In: Selection and/or Peer-Review Under Responsibility of 5th International Conference on Nanomaterials and Advanced Energy Storage Systems (INESS 2017). Science Direct, Elsevier, Amsterdam (2017) Klein, B.: Python 3 Tutorial: Klassen. o.J.-a. https://www.python-kurs.eu/python3_klassen.php. Accessed 10 Nov 2017 Klein, B.: Modularisierung. o.J.-b. https://www.python-kurs.eu/modularisierung.php. Accessed 14 Nov 2017
98
2 Das generische Basismodell in OEMOF
Klein, B.: Modularisierung. o.J.-c. https://www.python-kurs.eu/python3_modularisierung.php. Accessed 15 Dec 2017 Klein, B.: Einführung in Python 3: Für Ein- und Umsteiger. 3. Auflage, Hanser Verlag, München (2018) Maier, W.: reStructuredText (reST). The Python Wiki. (2015). https://wiki.python.org/moin/reStructuredText. Accessed 21 Feb 2018 Mollenhauer, E., et al.: Evaluation of an energy- and exergy-based generic modelling approach of combined heat and power plants. Int. J. Energy Environ. Eng. 7(2), 167–176 (2016). https://link. springer.com/content/pdf/10.1007%2Fs40095-016-0204-6.pdf. Accessed 03 Jan 2018 Nagel, J.: Nachhaltige Verfahrenstechnik. Hanser Verlag, München (2015) oemof-developer-group: Using oemof. (2014a). http://oemof.readthedocs.io/en/stable/using_oemof. html. Accessed on 21 Nov 2017 oemof-Developer-Group: About oemof. (2014b). http://oemof.readthedocs.io/en/stable/about_ oemof.html. Accessed 29 Nov 2017 oemof-developer-group: oemof-network. (2014c). http://oemof.readthedocs.io/en/stable/oemof_ network.html. Accessed on 29 Nov 2017 oemof-developer-group: oemof package. (2014e). http://oemof.readthedocs.io/en/stable/api/oemof. html. Accessed 05 Dec 2017 oemof-developer-group: Getting started. (2016). http://demandlib.readthedocs.io/en/latest/getting_ started.html#introduction. Accessed on 21 Nov 2017 Python Software Foundation: PyPI – the Python package index. o.J.-a. https://pypi.python.org/pypi. Accessed 21 Nov 2017 Python Software Foundation: Project Summaries. (2018b). https://packaging.python.org/key_projects/#pip. Accessed 21 Nov 2017 Python Software Foundation: 6. Modules. (2018c). https://docs.python.org/3/tutorial/modules.html. Accessed on 06 Jan 2018 Python Software Foundation: 8.4. collections.abc – Abstract Base Classes for Containers. (2018d). https://docs.python.org/3/library/collections.abc.html. Accessed 19 Jan 2018 pandas: MultiIndex/Advanced Indexing. o.J.-a. http://pandas.pydata.org/pandas-docs/stable/advanced.html. Accessed 05 Dec 2017 pandas: pandas.date_range. (2017b). http://pandas.pydata.org/pandas-docs/stable/generated/pandas. date_range.html. Accessed on 07 Dec 2017 Quaschning, V.: Regenerative Energiesysteme: Technologie – Berechnung – Simulation, 9th edn. Hanser Verlag, München (2015) Ratka, A. et al, Ehrmeier, B. (Ed.): Technik Erneuerbarer Energien. UTB Band-Nr. 4343, Verlag Eugen Ulmer KG, Stuttgart, 2015 ZetCode: Python keywords. o.J. http://zetcode.com/lang/python/keywords/. Accessed 13 Nov 2017
3
Mathematische Beschreibung der Objekte
Das Programmsystem OEMOF mit seinem objektorientierten Ansatz bietet mehrere Klassen zur Modellierung der Energieversorgung in einer betrachteten Region, an einem speziellen Standort oder unter weiteren vergleichbaren Rahmenbedingungen an. Das Wissen über die mathematische Umsetzung der einzelnen technischen Komponenten und der ökonomischen Zusammenhänge hilft, ein besseres Verständnis für das eigene Energiemodell zu erlangen. In der mathematischen Umsetzung sind zwei Zusammenhänge zu betrachten. Das ist zum einen die Zielfunktion. Diese stellt zumeist einen sehr komplexen mathematischen Term dar. Je nach Fragestellung und der sich daraus ergebenden Zielstellung kann sich dieser mathematische Ausdruck aus technischen, ökologischen und ökonomischen Termen zusammensetzen. Im anderen handelt es sich um die Nebenbedingungen. Nebenbedingungen können genutzt werden, um das Einhalten von Emissionsgrenzen, bestimmten Betriebszuständen o. ä. zu erreichen. Diese Bedingungen werden mathematisch oft durch Ungleichungen modelliert, die obere oder untere Grenzen vorgeben. Die Umsetzung der mathematischen Zusammenhänge in Programmcode durch die Entwickler kann auf unterschiedliche Art und Weise erfolgen. Die hier vorgestellte Umsetzung unterliegt damit ständigen Veränderungen. Ein Grundverständnis für die Umsetzung hilft, Neuerungen besser zu verstehen und damit eigene Optimierungsprobleme für Energieversorgungssysteme mit dem Tool OEMOF umsetzen und modellieren zu können. Im Folgenden werden wir uns mathematische Terme anschauen, die entweder einen Beitrag zur Zielfunktion leisten oder Nebenbedingungen darstellen. Es werden die mathematischen Terme der bereits in Kap. 2 vorgestellten Klassen: • Bus • Sink
© Springer Nature Switzerland AG 2023 J. Nagel, Optimierung von Energieversorgungssystemen, https://doi.org/10.1007/978-3-031-36355-9_3
99
100
3 Mathematische Beschreibung der Objekte
• Source • Transformer • Flow beschrieben. Die mathematische Beschreibung erfolgt in den einzelnen Modulen. In OEMOF wird, wie bereits beschrieben, ein Energiemodell als bipartiter Graph mit Kanten und Knoten dargestellt, wobei Components immer nur durch einen Bus und andersherum Busse nur über eine Component miteinander verbunden werden. Die Knoten in dem bipartiten Graphen werden durch Entitäten abgebildet, die durch die Basisklasse „Entity“ beschrieben werden (oemof 2014b). Diese Basisklasse „Entity“ ist vorrangig für Programmierer (developper) interessant. Der reine Modellierer wird mit dieser Klasse nicht in Kontakt kommen. Eine Entität repräsentiert einen Bus oder eine Component. Die Entitäten lassen sich in die folgenden Klassen und Subklassen einteilen: • Entity –– Bus –– Component –– Sink • Simple –– Source • Commodity • DispatchSource • FixedSource –– Transformer • Simple • CHP • SimpleExtractionCHP • Storage • ElectricalLine* • Link* • GenericCAES* • OffsetTransformer* –– Transport • Simple Die Subklassen, die mit einem „*“ markiert sind, stammen aus dem Modul custom.py und stellen damit spezfische Klassen dar. Diese wurden durch kundenspezifische Weiterentwicklungen und nach erfolgter Überprüfung durch die OEMOF-developper in OEMOF aufgenommen.
3 Mathematische Beschreibung der Objekte
101
Der hier beschriebene Ansatz kann aus dem Ansatz des Entity Relationship Modelling (ERM) abgeleitet werden. Beim ERM werden zur Modellierung von Systemen die Modellelemente • Objekte (Entities) • deren Eigenschaften (Attribute), sowie • ihre Beziehungen (Relationships) eingesetzt (Chen 1976). In OEMOF werden die Beziehungen über die bereits aufgeführten Flüsse beschrieben. Die Eigenschaften werden den jeweiligen Objekten im Programmcode mitgegeben. Für ein besseres Verständnis hilft der Blick auf die Graphentheorie. Diese findet u. a. Anwendung in der Netzplanung oder der Informatik. Allgemein besteht ein schlichter Graph G aus einer Menge von Knoten V (in der Graphentheorie zumeist als vertices bezeichnet; in OEMOF wird der Ausdruck nodes verwendet) und aus einer Menge von Kanten (edges), hier nachfolgend mit K bezeichnet. Die Kanten stellen die zuvor erwähnten Verbindungen oder auch Relationen zwischen den Knoten dar. Ein schlichter Graph G ist ein einfacher, ungerichteter Graph ohne Mehrfachkanten oder Schleifen. Dabei ist die Menge K eine Teilmenge der 2-elementigen Teilmenge von V. Dies bedeutet, jede Kante ist eine Menge von zwei Knoten. Die Knotenpaare sind dabei ungeordnet und besitzen somit keine Richtung. Dies führt zu der Darstellung nach Gl. 3.1 (nach Werners 2013):
G V, K
(3.1)
Grafisch lässt sich dies, wie in Abb. 3.1 dargestellt, abbilden. Es kann geschrieben werden, dass jede Kante k ∈ K zwei unterschiedliche Knoten als Kantenendpunkte miteinander verbindet. Jede Kante ist damit als zweielementige Menge mit diesen beiden Knoten u ∈ V und v ∈ V − {u} als Elementen definiert. Jede Kante besteht somit aus einem Knotenpaar {u, v}. Es gelten Gl. 3.2 und 3.3:
Abb. 3.1 Beispielhafte Darstellung eines schlichten Graph G = (V,K) mit V = {a,b,c,d} und K = {{a,b}, {a,c},{a,d},{b,c},{c,d}}. (Nach Domschke et al. 2015)
k u , v
(3.2)
K u , v | uεV, vεV u
(3.3)
102
3 Mathematische Beschreibung der Objekte
Das Zeichen „⊆“ bedeutet in der Mengenlehre „ist Teilmenge von“. Das Zeichen „∈“ steht in der Mathematik für „ist Element von“. Im Folgenden stellen Kleinbuchstaben einzelne Elemente, wie u, dar. Großbuchstaben hingegen kennzeichnen eine Menge an Elementen, wie V = (u1, u2, u3, …) die Menge mehrerer Elemente von ui. Ist der Graph ein gerichteter Graph, werden statt der geschweiften Klammern „{}“ runde Klammern „()“ gesetzt. Es ergibt sich damit, dass die Menge K der Kanten eine Teilmenge der Potenzmenge von V (Menge von Knoten ist (s. Gl. 3.4) (Domschke et al. 2015):
K p V bzw. K VxV
(3.4)
Hierin steht der Ausdruck „VxV“ für das Kreuzprodukt der Menge V. Der zuvor allgemein in Abb. 3.1 dargestellte schlichte Graph G = (V, K) besteht aus der Menge von Knoten V mit den Elementen a, b, c, d (s. Gl. 3.5):
V a, b, c, d
(3.5)
Sowie der Menge von Kanten K bestehend aus den Tupeln (Paaren) von Knoten (s. Gl. 3.6):
K a, b , a, c , a, d , b, c , c, d
(3.6)
Da wir es in OEMOF mit einem gerichteten Graphen zu tun haben, wollen wir diesen hier ebenfalls betrachten. Die Verbindungen zwischen den Knoten bei einem gerichteten Graphen sind Pfeile mit einem Start- und einem Endknoten. Dabei ist V ebenfalls die Menge der Knoten mit mehreren Elementen wie a, b, c und d. So kann auch hier die Menge von Kanten K bestehend aus den Tupeln (Paaren) von Knoten in folgender Form dargestellt werden (s. Gl. 3.7): K a, b , a, c , a, d , b, c (3.7) Zur Verdeutlichung wollen wir hier die Menge an gerichteten Paaren mit einem K darstellen. Weiterhin gilt hier ebenfalls die Gl. 3.4. Ein bipartiter Graph, wie er in OEMOF zugrunde gelgt wird, ist ein Spezialfall eines Graphen. Die Knotenmenge bipartiter Graphen besteht aus zwei disjunkten Teilmengen. Dies bedeutet, dass die Menge K in zwei Teilmengen A und B zerfällt, die kein gemeinsames Element besitzen. In unserem Fall von OEMOF wären dies die Menge der Busse und die Menge der Components. Dies sind damit zwei unterschiedliche Typen an Knoten. Weiterhin verbinden deren Kanten ausschließlich jeweils Knoten aus den beiden unterschiedlichen Teilmengen bzw. verschiedenen Typen miteinander. In Abb. 3.2 ist dies beispielhaft dargestellt. Wenn in OEMOF untersucht werden soll, welche Komponente, bspw. ein Blockheizkraftwerk, wieviel Energie über einen Bus an eine andere Komponente, bspw. ein Haus mit einem Wärmebedarf, zur Verfügung stellt, kann dies über einen bipartiten Graphen
3 Mathematische Beschreibung der Objekte
103
Abb. 3.2 Beispielhafte Darstellung eines bipartiten Graphen
modelliert werden. Dabei sind ausschließlich Kanten zwischen Komponenten und Bussen zur Darstellung der energetischen Beziehung vorhanden. In der Grahphentheorie lässt sich dies allgemein wie folgt darstellen (s. Gl. 3.8):
G V1 , V2 , K mit V1 V2 {} f ur i j und K u , v|, u V1 |, v V2 (3.8)
Der senkrechte Strich „|“ in Gl. 3.8 wird gesprochen als: „für die gilt“. Es ergibt sich damit: Die Menge K ist Teilmenge der Paare {u, v} für die gilt, dass u Element der Menge V1 ist und v Element der Menge V2. Kommen wir zurück zu OEMOF und wenden das Beschriebene der Graphentheorie auf die hier beschriebene Modellbildung an. Die Nebenbedingungen sind Teil des mathematischen Modells, das nachfolgend beschrieben wird. Über die Graphentheorie lassen sich diese Nebenbedingungen auf eine ganz allgemeine Schreibweise zurückführen (s. oemof 2014b). Zunächst werden in OEMOF die Knoten (vertices) als „nodes“ bzw. „Entitäten“ bezeichnet. Um in dieser Konvention zu bleiben, werden nachfolgend die Knoten (nodes) als „Entitäten“ „E“ aufgeführt. Die Kanten (Verbindungen bzw. Relationen) in einem Energiesystem sind die bereits bekannten Flüsse (flows) zwischen den Entitäten. Sie sind die Inputs und Outputs der Entitäten. Hierbei handelt es sich um gerichtete Graphen, da jeder Fluss einen Anfangspunkt und einen Endpunkt besitzt, der diesem eine Richtung vorgibt. Nachfolgend wird die Menge von Inputs mit „I“ und ein Element davon mit „i“ ausgedrückt. Für die Outputs gilt entsprechendes. Die Mengen werden „O“ und ein Element davon mit „o“ dargestellt. Die Inputs und Outputs werden als Kanten angezeigt, die die Knoten verbinden. Jede Kante im entwickelten Graphen hat einen Wert (value) [Kap. 3–19] (Domschke et al. 2015). Dieser Wert wird als Gewicht (weight) der Kante bezeichnet. Werte können Kosten, bspw. in €/kWh oder Emissionen, bspw. in g/kWh, sein. In OEMOF können die Menge von Knoten als eine Menge (englisch Set) von Entitäten „E“ und deren Elemente mit „e“ dargestellt werden. Das Set E von den Elementen der Entitäten e ist eine Vereinigung der Mengen (Sets) von Bussen (B), Transformern (T), Sources (S), Sinks (K) und Transports (P) wie in Gl. 3.9 dargestellt:
104
3 Mathematische Beschreibung der Objekte
E EB ET ES EK EP (3.9)
Ein Set an Komponenten EC lässt sich wie folgt darstellen (s. Gl. 3.10):
EC := E \ EB (3.10)
Die Gl. 3.10 entspricht einem Ausdruck aus der Mengenlehre und bedeutet: Die Menge der Komponenten EC ist gleich der Menge der Entitäten E ohne die Menge der Busse EB. Jede Kante sitzt zwischen zwei Entitäten (em, en). Sie stellt in OEMOF einen Input in eine Entität (en) und gleichzeitig einen Output aus einer anderen benachbarten Entität (em) dar. Die Kante besitzt hierdurch eine Richtung. Ein Set an gerichteten Kanten ( E ) kann dann nach Gl. 3.11 als Paarmenge von Entitäten, zwischen denen sich eine gerichtete Kante befindet, dargestellt werden. E : em , en , (3.11) mit m, n ∈ ℕ∗ Nummer der Entität, Element der Menge der positiven ganzen Zahlen. Innerhalb der Paare besteht eine festgelegte Reihenfolge, die über die Anordnung der Elemente in der Klammer (em, en) ausgedrückt wird, mit em als Startpunkt und en als Endpunkt. Im Graphenmodell von OEMOF kommen grundsätzlich keine ungerichteten Kanten vor. Dazu an dieser Stelle zwei Anmerkungen. Betrachten wir einen Stromspeicher. Dieser wird in einem Zeitschritt be- und in einem anderen Zeitschritt entladen. Dies bedeutet, die Richtung des Flusses dreht sich um. In OEMOF wird dazu die Komponente Speicher mit einem Bus über zwei Kanten unterschiedlicher Richtung verbunden, wobei die Kanten im Release v0.1.0 nicht explizit modelliert werden mussten. Dies führte im Release v0.1.0 jedoch dazu, dass ein Speicher im gleichen Zeitschritt be- wie auch entladen werden kann, was physikalisch nicht möglich ist. Dadurch werden bei der folgenden Optimierungsrechnung falsche Ergebnisse berechnet und sollte aus diesem Grund unterbunden werden. Wie dies im Release v0.2 abgebildet wird, ist zum jetzigen Zeitpunkt nicht bekannt und muss noch überprüft werden. Ein weiterer Aspekt sind Transformatoren. Dies können auch Leitungen sein. Diese Komponenten können physikalisch Leistungsflüsse in zwei verschiedene Richtungen haben. Dazu mussten in OEMOF im Release v0.1.0 zwei Objekte modelliert werden, ein „Teiltransformator“ bzw. eine „Teilleitung“ für die Übertragung in eine Richtung und ein „Teiltransformator“ bzw. eine „Teilleitung“ für die Übertragung in die andere Richtung. Auch hier ist zu prüfen, in wie weit dies im neuen Release v0.5 geändert wurde. Die in den Gleichungen aufgeführten Zusammenhänge sind in Abb. 3.3 dargestellt. An jedem Knoten beginnt oder endet eine gerichtete Kante. Dies bedeutet, es gibt mindestens einen Inflow (i) oder Outflow (o). Diese Inflows und Outflows lassen sich als Teil mengen Ie und Oe der gerichteten Kanten E darstellen, die einer Entität e zugeordnet sind
3 Mathematische Beschreibung der Objekte
105
Abb. 3.3 Allgemeine Darstellung der Knoten (Node) und gerichteten Kanten (Edge) nach den Gl. 3.9 bis 3.11
(s. Gl. 3.12 und 3.13). Dabei werden Inflows den Entitäten en zugeordnet, in die sie münden. Outflows werden den Entitäten em zugeordnet, aus denen sie entspringen. I e : i E | i, e E (3.12) Oe : o E | e, o E (3.13)
Nun müssen Bilanzgrenzen und Bilanzgleichungen um jede Entität gezogen werden. Für jede Bilanzgrenze sind entsprechende Nebenbedingungen erforderlich. Eine Nebenbedingung erfolgt für die Entitäten der Komponenten (EC). Hierfür wird die Funktion f als „transfer function“ (Übertragungsfunktion) für jede Komponente eingesetzt (s. Gl. 3.14): f I e , Oe 0, e EC (3.14) Die Gl. 3.14 abstrahiert, dass an jeder Entität e der Menge der Entitäten der Komponenten (EC) zwischen den Inputs und den Outputs ein Umwandlungsverlust auftritt. Bildet man also eine Bilanz um eine Komponente, ist aufgrund von Verlusten bei der Umwandlung die Differenz zwischen Input und Output kleiner oder gleich Null. In der Betrachtung eines realen Beispiels, wie bei einem Verbrennungsprozess in einem Blockheizkraftwerk, bei dem Umwandlungsverluste auftreten, ist die Energiemenge des Inputs nicht identisch mit der Energiemenge des Outputs. Dieser Zusammenhang wird durch Gl. 3.14 betrachtet. Die Umwandlungsverluste werden im Modell immer den Komponenten zugeordnet. Das Zeichen „∀“ steht in der Mathematik für „alle“. Es gilt immer, dass jeder Output einer Entität (oe) auch gleichzeitig ein Input der darauffolgenden Entität (ie) sein muss. Aus diesem Grund gilt weiterhin, dass die Bilanz um eine Kante zwischen zwei Entitäten (em, en) immer ausgeglichen sein muss (s. Gl. 3.15):
oe ie 0 (3.15)
106
3 Mathematische Beschreibung der Objekte
Mit der Gl. 3.15 wird beschrieben, dass jeder Output einer Entität (oe) gleichzeitig der Input der folgenden Entität (ie) ist. Dies hängt von der jeweiligen Bilanzgrenze ab, wie in Abb. 3.3 dargestellt. Dies macht deutlich, würde die Bilanzgrenze um die Entität 1 gezogen werden, wäre Kante (edge) 1 kein Input sondern ein Output. Der Bilanzwert einer Kante muss nartürlich unabhängig von der Bilanzgrenze immer gleich groß sein. Mit diesem Grundverständnis werden im nächsten Kapitel die Nebenbedingungen in den einzelnen Modulen vorgestellt. Zur Nachverfolgung der einzelnen Module erfolgt der Einstieg über die Maske „OEMOF organisation“ (s. Abb. 2.7). Auf die zweite und dritte Ebene gelangt man jeweils über den Botton „OEMOF“ (s. Abb. 3.4 und 3.5). Über den Botton „solph“ geht es zur vierten und hier relevanten Ebene. Nun können, wie in Abb. 3.6 dargestellt, die in diesem Kapitel aufgeführten Module nachverfolgt werden.
Abb. 3.4 Zweite Ebene der Internetplattform OEMOF. (GitHub 2018b)
3 Mathematische Beschreibung der Objekte
Abb. 3.5 Dritte Ebene der Internetplattform OEMOF. (GitHub 2018a)
Abb. 3.6 Vierte Ebene der Internetplattform OEMOF. (GitHub 2017d)
107
108
3 Mathematische Beschreibung der Objekte
Im Release OEMOF v0.5 ist die Ebenenstruktur etwas anders. Unter dem Button „oemof“ befinden sich organsistionsspezifische Informationen. Damit entfällt diese Ebene für die Module. Insbesondere die beiden Module oemof-solph und oemof-network befinden sich nun in der obersten Ebene. Diese beiden Module haben jeweils eine Zwischenebene bevor man zu den eigentichen Informationen gelangt. Dazu ist unter oemof-solph der Button „src/oemof/solph“ und unter oemof-network der Button „src/oemof/network“ relevant. Exemplarisch sei dies hier für das Modul oemof-solph in Abb. 3.7 dagestellt. Man gelangt dann in die nächst tiefere Ebene (3. Ebene), in der auf die einzelnen Programme, wie bspw. _energy_system.py zugegriffen werden kann (s. Abb. 3.8).
Abb. 3.7 Ebene des Moduls oemof-solph (2. Ebene). (https://github.com/oemof/oemof-solph)
Abb. 3.8. Ebene von OEMOF im Modul oemof-solph. (https://github.com/oemof/oemof-solph/ tree/dev/src/oemof/solph)
3.1 Mathematisches Gleichungssystem im Modul „blocks.py“
109
Der nun letzten Ebene (4. Ebene) kann der Quellcode, wie bspw. für das Modul _ energy_system.py, entnommen werden. Die Modellierung der mathematischen Terme erfolgt im Package oemof-solph. Die in diesen Modulen aufgeführten mathematischen Terme sind Ungleichungen zur Formulierung von Nebenbedingungen und einer aus mehreren Termen bestehenden Zielfunktion. Weiterführende Methoden und Funktionen geben unter anderem Regeln für die Berechnung von Parametern oder Variablen vor. Bei der Umsetzung des Programmcodes in OEMOF sind die Funktionen als inner oder nested functions angelegt. Diese sind nur im Kontext der umgebenden Methode oder Funktion bekannt. Nachfolgend werden inner oder nested functions allgemein als Funktionen bezeichnet. Die umgebende Methode in OEMOF lautet zumeist: def _create(self, group=None):
Im Rahmen dieser umgebenden Methode werden die Parameter, Sets, Variablen und Constraints angelegt. Diese Methode wird nachfolgend zumeist nicht aufgeführt. Ihre Inhalte sind bereits in Kap. 2 vorgestellt. In den nachfolgend aufgeführten Programmcodes sind regelmäßig kommentierende Texte zur Erläuterung spezieller Programmteile eingefügt. Im weiter entwickelten Release OEMOF v0.5 ist die nachfolgend beschriebene Struktur anders aufgebaut. Die einzelnen nachfolgend aufgeführten Klassen (class) existieren zwar noch, sind jedoch strukturell an anderen Orten zu finden. Auch die mathematischen Gleichungen haben noch ihre Gültigkeit. Die programmtechnische Umsetzung im Quellcode ist jedoch neu aufgebaut. Die folgenden Ausführungen können trotzdem dabei helfen, auch den neuen Code besser zu verstehen, da in den aufgeführten Quellcodes Erläuterungen zum besseren Verständnis des Codes eingefügt wurden. Die Quellcodes (Python-Programme) für die nachfolgend aufgeführten mathematischen Gleichungen befinden sich unter den Buttons „buses“, „components“, „constraints“ und „flows“
3.1 Mathematisches Gleichungssystem im Modul „blocks.py“ In diesem Modul wird für die folgenden Klassen (oemof 2014a): • • • • •
class Flow(SimpleBlock) class InvestmentFlow(SimpleBlock) class Bus(SimpleBlock) class Transformer(SimpleBlock) class NonConvexFlow(SimpleBlock)
110
3 Mathematische Beschreibung der Objekte
das Gleichungssystem aufgestellt. Alle in den folgenden Gleichungen bzw. Ungleichungen auftretenden Parameter, Variablen und gebildeteten Mengen sind in Kap. 2 beschrieben. Nachfolgend werden diese zum besseren Verständnis und besseren Übersicht noch einmal kurz eingeführt. Alle aufgeführten Klassen besitzen als Elternklasse die Pyomo Klasse „SimpleBlock“. In der Klasse „SimpleBlock“ sind Methoden enthalten wie „Print block information“ und „Display values in the block“. Die Klasse „SimpleBlock“ erbt ihrerseite von der Pyomo Klasse „Block“. Es handelt sich dabei um indizierte Komponenten, die andere Komponenten enthalten (Pyomo 2014b). Dies können ebenfalls blocks sein.
3.1.1 class Flow(SimpleBlock) Der Energieinput oder -output einer component wird über die Klasse „flow“ beschrieben. Ein flow entspricht in der Graphentheorie einer edge. Ein Energiefluss kann ökologische Aspekte aufweisen, wie Emissionen, ebenso wie ökonomische, wie Investitionskosten. Über entsprechende Nebenbedingungen gehen diese Aspekte in das Gleichungssystem ein (siehe dazu oemof 2014c). Die erste Nebenbedingung fordert, dass ein flow in OEMOF grundsätzlich positiv oder null ist. Betrachten wir den Fall, dass ein Speicher nicht nur Strom aus dem Netz aufnimmt, sondern auch Strom ins Netz einspeist. Dies ist über zwei entgegengesetzte Kanten (Fluss/Flows) zu modellieren. Dabei stellt der Flow der einen Kante den Input und der Flow der anderen Kante den Output dar. Für jeden Flow in jede Richtung gilt, dass dieser wieder positiv oder null sein muss. Um einen Überschuss im System zu vermeinden bzw. diesen aufzufangen, empfiehlt es sich, eine component „Sink“ als „excess“ einzuführen. Zur Vermeidung einer Unterversorgung ist es möglich, eine component „Source“ als „shortage“ zu definieren. Eine zweite Nebenbedinung gibt für einen Flow innerhalb eines Zeitintervalls einen oberen Maximalwert vor. Diese Obergrenze ist eine dimensionslose Größe. Nach Multiplikation dieser Größe mit dem Nennwert (nominal value) kann eine Leistung berechnet werden. Darüber erhält ein Flow eine maximale Leistung, die nicht überschritten werden darf. Die folgende Nebenbedingung drei beschreibt eine obere maximale Grenze für die Summe eines Flows über alle Zeitintervalle t. Dabei wird der Wert der Variablen flow(i, o, t) über alle Zeitschritte (timesteps) t aufsummiert (s. Gl. 3.16). Das Produkt aus den Zeitschritten mit der Variablen flow(i, o, t) ergibt eine dimensionslose numerische Zahl. Diese numerische Zahl darf nicht größer als ein vorgegebener spezifischer Maximalwert (summed_max) sein. Der Parameter summed_max ist ebenfalls eine numerische Zahl und damit dimensionslos. Der Maximalwert wird mit dem Nominalwert, bspw. der Nennleistung, nominal value (nominal_value, Datentyp: numeric) multipliziert, worüber das absolute Limit, wie die maximale Energie, für diesen Fluss errechnet wird:
3.1 Mathematisches Gleichungssystem im Modul „blocks.py“
∑
t
flow ( i, o, t ) ⋅ t ≤ summed _ max ( i, o )
111
(3.16)
Darin ist: τ Zeitschritt, bei 1-Stundenwerten τ = 1, bei ½-Stundenwerten τ = 0,5 Diese Gleichung gilt für:
i, o SUMMED _ MAX _ FLOW
Die Variable „flow(i, o, t)“ stammt von der Klasse „Model“ (s. Tab. 2.43). Der Ausdruck „SUMMED _ MAX _ FLOW“ steht für ein Subset aller Flows, bei denen das Attribut „summed_max“ auf „not None“ gesetzt wurde. Als Beispiel für diesen Wert sei folgender Code aus dem Beispiel basic_simple.py aufgeführt: # Create source object representing the natural gas commodity 'rgas' # (annual limit). This object has the output flow 'bgas'. 'bgas' is the bus # for natural gas. energysystem.add(solph.Source(label='rgas', outputs={bgas: solph.Flow( nominal_value=29825293, summed_max=1)}))
Die Formel wird im Quellcode als reStructuredText (reST) im Rahmen dieses Moduls beispielhaft wie folgt formuliert: Flow max sum :attr:`om.Flow.summed_max[i, o]` .. math:: \sum_t flow(i, o, t) \cdot \tau \leq summed\_max(i, o), \\ \forall (i, o) \in \textrm{SUMMED\_MAX\_FLOWS}
Der Ausdruck „om“ vor der Klasse „Flow“ („om.Flow.summed_max …“) steht für OperationsModel. In neueren Releases ab v0.2.0 wird hierfür „model“ geschrieben. Es handet sich dabei um ein typisches Least-cost-Gleichgewichtsmodell. Dies ist ein bereits fertig umgesetztes Kostenmodell, welches in OEMOF bereitgestellt wird. Für den Modellierer besteht aber auch die Möglichkeit, eigene Modelle zu entwickeln oder das bestehende Least-cost-Gleichgewichtsmodell abzuändern. So wie es eine Begrenzung eines Flusses nach oben gibt, existiert auch eine Grenze nach unten. Dies beschreibt die nächste Nebenbedingung. Hierin wird eine untere minimale Grenze für die Summe der Variablen flow(i, o, t) für alle Zeitschritte (timesteps) vorgegeben (s. Gl. 3.17):
t flow i, o, t summed _ min i, o
(3.17)
112
3 Mathematische Beschreibung der Objekte
Dies gilt für:
i, o SUMMED _ MIN _ FLOWS
Die Begrenzung der Flüsse innerhalb eines Zeitintervalls oder über eine gesamte Zeitdauer ist nur ein Aspekt zur Modellierung der Realität. Weiterhin kann es erforderlich sein, die Änderung von Flüssen von einem Zeitschritt zum nächsten zu betrachten. Auch hierfür braucht es je nach eingesetzten technischen Anlagen Grenzwerte. Dazu schauen wir uns zwei technische Betriebsweisen an. Es werden hierbei immer zwei aufeinanderfolgende Zeitschritte eines Flows betrachtet. Eine Betriebsweise ist das Zu- oder Abschalten einzelner oder mehrerer Anlagen. Ein Beispiel hierfür ist der Betrieb einer Biogasanlage mit mehreren BHKW. Je nach Bedarfsfall können im Betrieb ein oder mehrere BHKW zu- oder abgeschaltet werden. BHKW sind große Motoren, die schnelll an- und abgefahren werden können, sodass der Schaltvorgang deutlich kürzer als ein Zeitschritt sein kann. Wird nun ein BHKW hinzugeschaltet, fährt dieses innerhalb kürzester Zeit auf Betriebsleistung hoch, bspw. 75 kW. Dies bedeutet, dass eine Änderung von einem Zeitschritt zum nächsten gerade dieser Betriebsleistung entspricht. Wird ein BHKW abgeschaltet, entspricht dies dem negativen Wert der Betriebsleistung. Der Betrag der Änderung ist somit in beiden Fällen gerade die Betriebsleistung dieses einen BHKW. Dies macht deutlich, dass bestimmte Änderungen nur in Stufen erfolgen können. Zur Abbildung dieses Sachverhaltes im Modell, ist für einen Fluss eine Begrenzung des Absolutwertes der Änderung nach unten erforderlich. Die zweite Betriebsweise ist die Laständerung einer Anlage. Dies ist u. a. bei Braunkohlekraftwerken relevant, da deren Laständerung nicht zu schnell erfolgen darf. Laständerungen treten beim Anfahr- bzw. Abschaltprozess auf. Um die Geschwindigkeit der Laständergung, wie beim Anfahren eines Braunkohlekraftwerkes, einen bestimmten Wert nicht überschreiten zu lassen, muss dieser nach oben begrenzt werden. Für diesen typischen Fall muss für den Absolutwert der Änderung eines Flusses eine obere Grenze eingeführt werden. Um in OEMOF zu beschreiben, wie Änderungen der Betriebsweise von Anlagen erfolgen dürfen, müssen Grenzen für die Flüsse eingeführt werden. Dazu werden zwei Paramter, der positive Gradient und der negative Gradient, eingeführt. Der psotive Gradient steht dabei für den Anstieg der Leistungskurve, also bspw. das Anfahren, hingegen der negagtive Gradient für eine sinkende Kurve eingesetzt wird. Beide Parameter benötigen jeweils eine Grenze nach oben bzw nach unten. Insgesamt gibt es somit vier Begrenzungen. Betrachten wir zunächst den positiven Gradienten. Hier sind die zwei genannten Abgrenzungen zu unterscheiden: 1. In einem Zeitintervall darf nur eine stufenweise Änderung eines Flusses erfolgen. Die Anlagen dürfen bspw. nur stufenweise hinzugeschaltet bzw. es darf nur stufenweise in den Zeitintervallen zwischen den Anlagen hin- und hergeschalatet werden. Dies führt
3.1 Mathematisches Gleichungssystem im Modul „blocks.py“
113
zu einer Mindestschrittgröße für die Leistungsänderung zwischen zwei Zeitintervallen. Die Änderung eines Flows muss damit größer als die vorgegebene Mindestschrittgröße sein. 2. Die Geschwindigkeit einer Laständerung darf einen bestimmten maximalen Wert nicht überschreiten. Dieser vorgegebene Maximalwert (maximale Geschwindigkeit der Laständerung) regelt, dass das Anfahren nur in kleinen Stufen möglich ist. Die Änderung eines Flusses ist damit nach oben begrenzt. Noch einmal kurz zusamengefasst. Hingegen der positive Gradient das Zuschalten (Fall 1) bzw. das Hochfahren (Fall 2) betrachtet, gilt der negative Gradient für die Wegnahme einer Anlage (Umkehrung Fall 1) bzw. Herunterfahren einer Anlage (Umkehrung Fall 2). In OEMOF ist im Release v0.2.0 mit den folgenden beiden Gleichungen (Gl. 3.18 und 3.19) nur für den Fall 1 sowohl für den positiven wie für den negativen Gradienten eine Nebenbedingung formuliert. Für den Fall 1 kann die Nebenbedingung für den positiven Gradienten nach Gl. 3.18 formuliert werden: flow i, o, t flow i, o, t 1 positive _ gradient i, o, t
(3.18)
Dies gilt für:
i, o POSITIVE _ GRADIENT _ FLOWS und t TIMESTEPS
Für den negativen Gradieten ergibt sich Gl. 3.19: flow i, o, t 1 flow i, o, t negative _ gradient i, o, t
(3.19)
Dies gilt für:
i, o NEGATIVE _ GRADIENT _ FLOWS und t TIMESTEPS
Die in Gl. 3.18 und 3.19 betrachteten Werte sind numerisch und damit dimensionslos. Die Ausdrücke „POSITIVE _ GRADIENT _ FLOWS“ und „NEGATIVE _ GRADIENT _ FLOWS“ geben an, dass es sich hier um ein Subset an Flüssen handelt, bei denen das Attribut „ positive _ gradient“ bzw. „negative _ gradient“ auf „not None“ gesetzt ist. Im Modul werden die Nebenbedingungen wieder als reST, beispielhaft dargestellt für den negativen Gradienten, wie folgt formuliert: Negative gradient constraint :attr:`om.Flow.negative_gradient_constr[i, o]`: .. math:: flow(i, o, t-1) - flow(i, o, t) \geq \ negative\_gradient(i, o, t), \\ \forall (i, o) \in \textrm{NEGATIVE\_GRADIENT\_FLOWS}, \\ \forall t \in \textrm{TIMESTEPS}
114
3 Mathematische Beschreibung der Objekte
Die Formulierung als reST dient dem Aufbau einer Dokumentation. Die Texte werden in dieser Form ohne weitere Schritte direkt in eine normal lesbare Form umgewandelt. Im folgenen werden diese reST-Texte nicht weiter dargestellt. In dieser Klasse wird ein Teil der Zielfunktion (objective function) formuliert (s. Gl. 3.20). Dies ist jedoch nur der Fall, wenn im Modell der Parameter „variable_costs“ gesetzt wird. (3.20) (i , o ) t flow i, o, t variable _ costs i, o, t Es sei an dieser Stelle angemerkt, dass bei nachfolgenden Darstellungen mit einer Summenbildung über i, o, wie bei Gl. 3.20, eine Summe immer entweder über i oder über o erfolgt. Weiterhin werden die Werte der positiven wie der negativen Gradienten jeweils mit dem nominal _ value mulitpliziert: # Die Inputs (i), Outputs (o) und Flüsse (f) sind in Gruppen # zusammengefasst, so dass die Gleichungen nicht einzeln für jedes Element # formuliert werden müssen: „for i, o, f in group:“. # Im folgenden Satz bedeutet: „m.flow[i, o]“: „m“ referenziert auf die # Elternklasse. Dies ist im Code weiter vorne durch: # „m = self.parent_block()“ # definiert [oemof 2014d]. Die Funktion „parent_block()“ stammt # aus Pyomo. Diese Funktion gibt die Elternklasse dieses Objektes zurück. # Der Ausdruck „.positive_gradient“ ist das Attribut des Flusses. Es # handelt sich dabei um ein Dictionary. Das Attribut hat zwei Parameter. # Der erste Parameter „ub“ ist die obere Grenze (upper bound) des Flusses # und der zweite Parameter ist der Zeitschritt, für den der Wert „0“ # gesetzt wird. Der Zeitschritt t = 0 stellt den ersten Zeitschritt dar. # Ist der Gradient des ersten Zeitschritts „None“, folgt, dass alle anderen # ebenso den Wert „None“ haben und die Iteration über die weiteren # Zeitschritte wird abgebrochen. Deshalb wird nur der erste Zeitschritt # abgefragt. Ist der Wert hingegen „not None“, geht die Iteration über die # folgenden Zeitschritte. # Der Ausdruck „setub“ steht für:“set upper bound“ for i, o, f in group: if m.flows[i, o].positive_gradient['ub'][0] is not None: for t in m.TIMESTEPS: self.positive_gradient[i, o, t].setub( f.positive_gradient['ub'][t] * f.nominal_value)
3.1 Mathematisches Gleichungssystem im Modul „blocks.py“
115
if m.flows[i, o].negative_gradient['ub'][0] is not None: for t in m.TIMESTEPS: self.negative_gradient[i, o, t].setub( f.negative_gradient['ub'][t] * f.nominal_value)
Eine Gruppe besteht aus einer Liste mit Tupeln für die einzelenen Elemente der Gruppe. Die Liste enthält das Flussobjekt (flow object) „f“, die zugehörige Source (associated source) „s“ und das Ziel (target) „t“, z. B. einen Speicher. Für eine Gruppe würde sich bspw. folgender Ausdruck für diese Klasse ergeben (s. Gl. 3.21) (oemof 2014d):
groups s1, t1, f 1 , s 2, t 2, f 2 ,
(3.21)
Die Gleichungen Gl. 3.16, 3.17, 3.18, 3.19 und 3.20 in dieser Klasse sind in mehreren Funktionen umgesetzt. Die ersten beiden Funktionen geben die Nebenbedingungen für die Berechnung der maximalen und minimalen Grenze für die Summe der im Block bestehenden Flüsse an. Dies sind die Werte des Sets „self.SUMMED_MAX_FLOWS“. Für die obere Grenze (maximale Summe) ergibt sich (GitHub 2017d): def _flow_summed_max_rule(model): for inp, out in self.SUMMED_MAX_FLOWS: lhs = sum(m.flow[inp, out, ts] * m.timeincrement[ts] for ts in m.TIMESTEPS) rhs = (m.flows[inp, out].summed_max * m.flows[inp, out].nominal_value) self.summed_max.add((inp, out), lhs = rhs) self.summed_min = Constraint(self.SUMMED_MIN_FLOWS, noruleinit=True) self.summed_min_build = BuildAction(rule=_flow_summed_min_rule)
Für die Berechnung des positiven und des negativen Gradienten ergeben sich zwei weitere Funktionen. Für den positiven Gradienten die Funktion „_positive_gradient_flow_ rule(model)“: def _positive_gradient_flow_rule(model): for inp, out in self.POSITIVE_GRADIENT_FLOWS: for ts in m.TIMESTEPS: if ts > 0: lhs = m.flow[inp, out, ts] - m.flow[inp, out, ts-1] rhs = self.positive_gradient[inp, out, ts] self.positive_gradient_constr.add((inp, out, ts), lhs 0: lhs = m.flow[inp, out, ts-1] - m.flow[inp, out, ts] rhs = self.negative_gradient[inp, out, ts] self.negative_gradient_constr.add((inp, out, ts), lhs m.TIMESTEPS[1]: expr = (self.startup[i, o, t] >= self.status[i, o, t] self.status[i, o, t-1]) else: expr = (self.startup[i, o, t] >= self.status[i, o, t] m.flows[i, o].nonconvex.initial_status) return expr self.startup_constr = Constraint(self.STARTUPFLOWS, m.TIMESTEPS, rule=_startup_rule)
126
3 Mathematische Beschreibung der Objekte
Die Nebenbedingung für den Shutdown findet sich in der Funktion „_shutdown_rule(block, i, o, t)“ (s. Gl. 3.33): def _shutdown_rule(block, i, o, t): if t > m.TIMESTEPS[1]: expr = (self.shutdown[i, o, t] >= self.status[i, o, t-1] self.status[i, o, t]) else: expr = (self.shutdown[i, o, t] >= m.flows[i, o].nonconvex.initial_status self.status[i, o, t]) return expr self.shutdown_constr = Constraint(self.SHUTDOWNFLOWS, m.TIMESTEPS, rule=_shutdown_rule)
Zwei neue Nebenbedinungen für die minimale Betriebszeit (minimum uptime) (_min_ uptime_rule(block, i, o, t)) und die minimale Stillstandszeit (minimum downtime) (_min_ downtime_rule(block, i, o, t)) sind hinzugekommen: def _min_uptime_rule(block, i, o, t): if (t >= m.flows[i, o].nonconvex.max_up_down and t from oemof import solph >>> bel = solph.Bus(label='electricityBus') >>> bth = solph.Bus(label='heatBus') >>> bgas = solph.Bus(label='commodityBus') >>> ccet = solph.components.GenericCHP( ... label='combined_cycle_extraction_turbine', ... fuel_input={bgas: solph.Flow( ... H_L_FG_share_max=[0.183])}, ... electrical_output={bel: solph.Flow( ... P_max_woDH=[155.946], ... P_min_woDH=[68.787], ... Eta_el_max_woDH=[0.525], ... Eta_el_min_woDH=[0.444])}, ... heat_output={bth: solph.Flow( ... Q_CW_min=[10.552])}, ... Beta=[0.122], back_pressure=False) >>> type(ccet)
3.2.5 class GenericCHPBlock(SimpleBlock) In diesem Abschnitt werden die weiteren Gleichungen bzw. Ungleichungen für die Modellierung einer CHP Anlage vorgestellt. Diese sind in OEMOF nur in der zugehörigen Datei zu finden (s. (GitHub 2017b) bzw. https://github.com/oemof/oemof-solph/blob/dev/src/ oemof/solph/components/_generic_chp.py). Die Klasse “GenericCHPBlock(SimpleBlock)” dient dazu, alle CHP Anlagen zusammenzufassen, sodass die Berechnungsansätze nur einmal zentral eingeführt werden müssen.
3.2 Mathematisches Gleichungssystem im Modul „components.py“
139
Zur Modellierung des Kraftwerkseinsatzes einer CHP Anlage sind einige Nebenbedingungen zur Berechnung des Kraftstoffs, der Wärme und des Stroms erforderlich. Eine Beschreibung der mathematischen und thermodynamischen Zusammenhänge findet sich in (Mollenhauer et al. 2016). In die Anlage hinein geht der Kraftstoff. Zur Berechnung des Energiegehalts des Kraftstoff-Inputs ( H F ) wird der untere Heizwert (Hu) und der zugehörige Massenstrom (m F) angesetzt (s. Gl. 3.47):
H F H u m F (3.47)
Im Quellcode findet sich die Nebenbedingung in der folgende Funktion “_H_flow_rule(block, n, t)”. Hierbei werden die Werte für den Energiegehalt des Kraftstoffs aus einer zuvor angelegten Liste entnommen: def _H_flow_rule(block, n, t): expr = 0 expr += self.H_F[n, t] expr += - m.flow[list(n.fuel_input.keys())[0], n, t] return expr == 0 self.H_flow = Constraint(self.GENERICCHPS, m.TIMESTEPS, rule=_H_flow_rule)
Nach der Umwandlung kommt als ein Energiestrom Wärme aus der Anlage heraus. Mithilfe des folgenden Codes für die Funktion “_Q_flow_rule(block, n, t)” wird die erzeugte Wärme mit dem Output der Anlage über den Flow verbunden: def _Q_flow_rule(block, n, t): expr = 0 expr += self.Q[n, t] expr += - m.flow[n, list(n.heat_output.keys())[0], t] return expr == 0 self.Q_flow = Constraint(self.GENERICCHPS, m.TIMESTEPS, rule=_Q_flow_rule)
Ein weiterer Energiestrom aus der Anlage ist der produzierte Strom, der über den Flow ebenfalls mit der Anlage verknüpft werden muss. Dies erfolgt in der Funktion “_P_flow_ rule(block, n, t)”: def _P_flow_rule(block, n, t): expr = 0 expr += self.P[n, t] expr += - m.flow[n, list(n.electrical_output.keys())[0], t] return expr == 0 self.P_flow = Constraint(self.GENERICCHPS, m.TIMESTEPS, rule=_P_flow_rule)
140
3 Mathematische Beschreibung der Objekte
Um einen lastabhängigen Wirkungsgrad in MILP zu modellieren, kann Gl. 3.48 verwendet werden: H F t 1 Y t 2 PwoDH
(3.48)
Dies gilt für ∀t
mit Y(t) binäre Statusvariable
Die grundlegenden Formeln zur Berechnung der Koeffizienten α1 und α2 wurden mit den Gleichungen Gl. 3.44, 3.45 und 3.46 bereits vorgestellt. Im Programmcode ist Gleichung Gl. 3.48 wie folgt umgesetzt: def _H_F_1_rule(block, n, t): expr = 0 expr += - self.H_F[n, t] expr += n.alphas[0][t] * self.Y[n, t] expr += n.alphas[1][t] * self.P_woDH[n, t] return expr == 0 self.H_F_1 = Constraint(self.GENERICCHPS, m.TIMESTEPS, rule=_H_F_1_rule)
Zwischen dem Kraftstoff Input, dem Strom Output und dem Wärme Output gibt es Abhängigkeiten. Um diese zu berechnen, wird Gl. 3.48 um die Wärmeauskopplung mittels des Energieverlustkoeffizienten β erweitert (s Gl. 3.49):
H F t 1 Y t 2
P t T
FF t , TRF t , T0 t Q t
(3.49)
Dies gilt für
∀t
mit Q Wärmestrom Hierbei wird der Koeffizient β für jeden Zeitschritt t mit einer feed (Vorlauf-) (TFF) und return (Rücklauf-) flow (TRF) Temperatur für eine vordefinierte Raumtemperatur angesetzt. Die Temperatur T0 ist eine Referenztemperatur. Der Energieverlustkoeffizient β errechnet sich nach der thermodynamischen Gleichung Gl. 3.50.
3.2 Mathematisches Gleichungssystem im Modul „components.py“
T 1 0 TM
141
(3.50)
Die Termperatur T0 ist die Referenz-Temperatur für die Exergie Berechnung. Diese sollte mit der Kühlwassereintrittstemperatur TM gleichgesetzt werden. Die Temperatur TM stellt die mittlere logarithmische Termperatur dar, deren Berechnung (Windisch 2014; Mollenhauer et al. 2016) entnommen werden kann. Im Quellcode ist die Gleichung in Form der Funktion „_H_F_2_rule(block, n, t)“ entsprechend umgesetzt: def _H_F_2_rule(block, n, t): expr = 0 expr += - self.H_F[n, t] expr += n.alphas[0][t] * self.Y[n, t] expr += n.alphas[1][t] * (self.P[n, t] + n.Beta[t] * self.Q[n, t]) return expr == 0 self.H_F_2 = Constraint(self.GENERICCHPS, m.TIMESTEPS, rule=_H_F_2_rule)
Zwischen der minimalen Kessellast und dem Abschalten der Anlage gibt es eine betriebliche Lücke. Ebenso besteht im oberen Arbeitsbereich einer Anlage eine Grenze. Aus diesem Grund wird für den Betrieb ein oberer maximaler sowie ein unterer minimaler Arbeitsbereich festgelegt: Für den oberen Arbeitsbereich gilt (s. Gl. 3.51): H F ( t ) ≤ Y ( t ) ⋅
Pmax , woDH
ηel , max , woDH
(3.51)
Dies gilt für
∀t
Für den Programmcode gilt Funktion “_H_F_3_rule(block, n, t)”: def _H_F_3_rule(block, n, t): expr = 0 expr += self.H_F[n, t] expr += - self.Y[n, t] * \ (list(n.electrical_output.values())[0].P_max_woDH[t] / list(n.electrical_output.values())[0].Eta_el_max_woDH[t]) return expr = 0 self.H_F_4 = Constraint(self.GENERICCHPS, m.TIMESTEPS, rule=_H_F_4_rule)
Bei einer Kraft-Wärme-Kopplungs-Anlage entsteht durch das Abgas ein Verlust, der bestimmt werden muss. Hierfür wird eine obere maximale Grenze beschrieben. Diese ist abhängig vom eingesetzten Kraftstoff. Dies wird mittels der Funktion “_H_L_FG_max_ rule(block, n, t)” in OEMOF berechnet. def _H_L_FG_max_rule(block, n, t): expr = 0 expr += - self.H_L_FG_max[n, t] expr += self.H_F[n, t] * \ list(n.fuel_input.values())[0].H_L_FG_share_max[t] return expr == 0 self.H_L_FG_max_def = Constraint(self.GENERICCHPS, m.TIMESTEPS, rule=_H_L_FG_max_rule)
Die in einer Wärme-Kraft-Kopplungs-Anlage produzierte Wärme hängt vom eingesetzten Kraftstoff und vom produzierten Strom ab. Für die Modellierung ist es wichtig, eine maximale obere Grenze für die Wärmeauskopplung vorzugeben (s. Gl. 3.53):
P t H F t Q t H L , FG t Q CW ,min Y t
(3.53)
3.2 Mathematisches Gleichungssystem im Modul „components.py“
143
Dies gilt für: ∀t
mit H L , FG t Abgasverlust Q CW ,min minimaler Wärmetransport im Kondensator Die programmtechnische Umsetzung ist entsprechend in der Funktion „_Q_max_res_rule(block, n, t)“ definiert: def _Q_max_res_rule(block, n, t): expr = 0 expr += self.P[n, t] + self.Q[n, t] + self.H_L_FG_max[n, t] expr += list(n.heat_output.values())[0].Q_CW_min[t] * self.Y[n, t] expr += - self.H_F[n, t] # back-pressure characteristics or one-segment model if n.back_pressure is True: return expr == 0 else: return expr = 0 else: return Constraint.Skip self.Q_min_res = Constraint(self.GENERICCHPS, m.TIMESTEPS, rule=_Q_min_res_rule)
3.2.6 class ExtractionTurbineCHP(solph_Transformer) Die thermodynamischen Berechnungswege und mathematischen Nebenbedingungen für die Klasse “ExtractionTurbineCHP(solph_Transformer)” entsprechen den in Abschn. 3.2.4 und 3.2.5 vorgestellten Gleichungs- bzw. Ungleichungssystemen. Weitere Gleichungen finden sich unter Abschn. 3.2.7. Der folgende Quellcode gibt einen beispielhaften Überblick, wie in dieser Klasse die Objekte angelegt werden (GitHub 2017b bzw, https://github.com/oemof/oemof-solph/ blob/dev/src/oemof/solph/components/_extraction_turbine_chp.py): >>> from oemof import solph >>> bel = solph.Bus(label='electricityBus') >>> bth = solph.Bus(label='heatBus') >>> bgas = solph.Bus(label='commodityBus') >>> et_chp = solph.components.ExtractionTurbineCHP( ... label='variable_chp_gas', ... inputs={bgas: solph.Flow(nominal_value=10e10)}, ... outputs={bel: solph.Flow(), bth: solph.Flow()}, ... conversion_factors={bel: 0.3, bth: 0.5}, ... conversion_factor_full_condensation={bel: 0.5})
3.2 Mathematisches Gleichungssystem im Modul „components.py“
145
3.2.7 class ExtractionTurbineCHPBlock(SimpleBlock) In dieser Klasse werden zwei Nebenbedingungen definiert, um das variable Verhältnis zwischen den unterschiedlichen Dampfströmen (Dampf-Input-, Dampf-Output- und Entnahmestrom (tapped output)) einer Kraft-Wärme-Kopplungsanlage abzubilden. Über den Dampf-Output-Hauptstrom erfolgt die Stromerzeugung in der Turbine und über den Dampf-Entnahmestrom die Wärmeerzeugung. Zunächst wird das Verhältnis des Input Flows zu den beiden output strömen (main output flow and tapped output flow) über den Kondensationswirkungsgrad (efficiency _ condensing(n, t)) nach Gl. 3.55 betrachtet (oemof o.J.-c): flow input , n, t
( flow n, main _ output , t flow n,tapped _ output , t main _ flow _ loss _ index n, t efficiency _ condensing n, t
(3.55)
Dies gilt für:
t TIMESTEPS undn VARIABLE _ FRACTION _ TRANSFORMERS
Der Kondensationswirkungsgrad “efficiency _ condensing(n, t)” ist ein Umwandlungsfaktor bei vollständiger Kondensation, der im folgenden Quellcode als “conversion_factor_full_condensation“ bezeichnet wird. Im Weiteren wird eine Nebenbedingung für das Verhältnis der ausgehenden Ströme über den Umwandlungsfaktor bei Entnahme eines Teils des Dampfstroms (conversion _ factor(n, tapped _ output, t)) nach Gl. 3.56 definiert: flow n, main _ output , t
flow n, tapped _ output , t conversion _ factor n, main _ output , t
conversion _ factor n, tapped _ output , t
(3.56)
Dies gilt für:
t TIMESTEPS undn VARIABLE _ FRAKTION _ TRANSFORMERS
Dabei sind Umwandlungsfaktoren sowohl für die reine Wärmeproduktion (full CHP mode) als auch für die reine Stromerzeugung ohne Dampfentnahme (full condensing mode) zu definieren. Die beiden Nebenbedingungnen Gl. 3.55 und 3.56 sind den nachfolgenden Funktionen verankert. Diese werden in der Methode “_create(self,group = None)” angelegt. Es werden folgend zunächst die Methoden und dann die beiden Funktionen einzeln aufgeführt (s. https://github.com/oemof/oemof-solph/blob/dev/src/oemof/solph/components/_extraction_turbine_chp.py):
146
3 Mathematische Beschreibung der Objekte
def _create(self, group=None): Parameters ---------group : list List of :class:`oemof.solph.ExtractionTurbineCHP` (trsf) objects for which the linear relation of inputs and outputs is created e.g. group = [trsf1, trsf2, trsf3, ...]. Note that the relation is created for all existing relations of the inputs and all outputs of the transformer. The components inside the list need to hold all needed attributes. """ if group is None: return None m = self.parent_block() for n in group: n.inflow = list(n.inputs)[0] n.label_main_flow = str( [k for k, v in n.conversion_factor_full_condensation. items()] [0]) n.main_output = [o for o in n.outputs if n.label_main_flow == o.label][0] n.tapped_output = [o for o in n.outputs if n.label_main_flow != o.label][0] n.conversion_factor_full_condensation_sq = ( n.conversion_factor_full_condensation[ m.es.groups[n.main_output.label]]) n.flow_relation_index = [ n.conversion_factors[m.es.groups[n.main_output.label]][t] / n.conversion_factors[m.es.groups[n.tapped_output.label]][t] for t in m.TIMESTEPS] n.main_flow_loss_index = [ (n.conversion_factor_full_condensation_sq[t] n.conversion_factors[m.es.groups[n.main_output.label]][t]) / n.conversion_factors[m.es.groups[n.tapped_output.label]][t] for t in m.TIMESTEPS]
3.2 Mathematisches Gleichungssystem im Modul „components.py“
147
Die erste Funktion “_input_output_relation_rule(block)” definiert die Nebenbedingungen für die Relation zwischen Input und Output (Hauptstrom und Entnahmestrom) nach Gl. 3.55: def _input_output_relation_rule(block): for t in m.TIMESTEPS: for g in group: lhs = m.flow[g.inflow, g, t] rhs = ( (m.flow[g, g.main_output, t] + m.flow[g, g.tapped_output, t] * g.main_flow_loss_index[t]) / g.conversion_factor_full_condensation_sq[t] ) block.input_output_relation.add((n, t), (lhs == rhs)) self.input_output_relation = Constraint(group, noruleinit=True) self.input_output_relation_build = BuildAction( rule=_input_output_relation_rule)
Die zweite Funktion “_out_flow_relation_rule(block)” betrachtet das Verhältnis der Output Flüsse, also des Hauptstroms und des Entnahmestroms, im Kraft-Wärme-Kopplungs-Modus nach Gl. 3.56: def _out_flow_relation_rule(block): for t in m.TIMESTEPS: for g in group: lhs = m.flow[g, g.main_output, t] rhs = (m.flow[g, g.tapped_output, t] * g.flow_relation_index[t]) block.out_flow_relation.add((g, t), (lhs >= rhs)) self.out_flow_relation = Constraint(group, noruleinit=True) self.out_flow_relation_build = BuildAction( rule=_out_flow_relation_rule)
Eine weitere Methode “component_grouping(node)” regelt das Grouping in dieser Klasse: def component_grouping(node): if isinstance(node, GenericStorage) and isinstance(node.investment, Investment): return GenericInvestmentStorageBlock if isinstance(node, GenericStorage) and not isinstance(node.investment, Investment): return GenericStorageBlock
148
3 Mathematische Beschreibung der Objekte if isinstance(node, GenericCHP): return GenericCHPBlock if isinstance(node, ExtractionTurbineCHP): return ExtractionTurbineCHPBlock
3.3 Mathematisches Gleichungssystem im Modul „constraints.py“ In diesem Modul sind bisher Nebenbedingungen zu Emissionen und Investitionen angelegt (s. https://github.com/oemof/oemof-solph/tree/dev/src/oemof/solph/constraints). Beispielsweise besteht die Möglichkeit, Investitionen ein oberes Limit zuzuweisen (s. Gl. 3.57): (3.57) investment _ costs limit Der Parameter “limit” gibt als Floatzahl das absolute Limit einer Investition an. Im Quellcode ist dies in der Methode “investment_limit(model, limit = None)” wie folgt umgesetzt: def investment_limit(model, limit=None): def investment_rule(m): expr = 0 if hasattr(m, "InvestmentFlow"): expr += m.InvestmentFlow.investment_costs if hasattr(m, "GenericInvestmentStorageBlock"): expr += m.GenericInvestmentStorageBlock.investment_costs return expr > a = S(5) >>> a.getX() 5 >>> a.setX(10) >>> b = a.getX() * 0.21 >>> b 2.100000000000000 >>>
Wir wollen vermeinden, den folgenden Code schreiben zu müssen: b = a.getX() * 0.21
Dies macht den Code schlecht lesbar, zudem ist es komplexer beim Schreiben. Um es einfacher schreibbar und besser lesbar zu machen, erweitern wir den Quellcode um den Dekorator „property“. Zur Veranschaulichung führen wir die Klasse „S“ mit dem Attribut „x“ ein: class S: def __init__(self): self._x = x def getx(self): return self._x def setx(self, x): self._x = x
4.1 Die Arbeit mit dem Framework von OEMOF
165
def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.")
Nun initialisieren wir eine Instanz „s“ der Klasse „S“. Dann würde „return S._x“ den Getter aufrufen. Ausgehend von dem nächsten Absatz „S._x=x“ würde der Setter aufgerufen werden. Durch den letzten Absatz mit dem Aufruf „del „S._x“ würde der Inhalt von „x“ gelöscht und stattdessen "I'm the 'x' property."ausgegeben werden. Durch dieses Konstrukt ist es nun möglich, wie folgt vorzugehen (Klein o. J.-d): >>> >>> >>> >>> >>> 30 >>>
from s import S a = S(10) b = P(20) c = a.x + b.x c
Noch einmal ein konkretes Beispiel. Führen wir eine Klasse „Niederspannungsnetz“ ein. Nun wird für das Attribut „x“ das Attribut „voltage“ mit einem Wert übergeben. Dann wird der Dekorator „property“ aufgerufen und es wird der Wert für das Attribut „voltage“ (100000) mit dem docstring „I'm the 'x' property.“ überschrieben und stattdessen „Get the current voltage.“ ausgegeben. Dies zeigt der folgende Quellcode (Python 2018-d): class Niederspannungsnetz: def __init__(self): self._voltage = 100000 @property def voltage(self): """Get the current voltage.""" return self._voltage
Dekorator „@classmethod“ Dieser Dekorator transformiert eine Methode in eine Klassenmethode (Python 2018d). Eine Klassenmethode erhält die Klasse als erstes implizites Argument. Dies kann im Programmcode wie folgt umgesetzt werden. Wir führen dazu die Klasse „Stromnetz“ ein: class Stromnetz: @classmethod def f(cls, arg1, arg2, ...): ...
Weitere Informationen dazu finden sich unter (Python 2018d).
166
4 Modellierung in OEMOF
Dekorator „@staticmethod“ Mit Hilfe dieses Dekorators wird eine Methode in eine statische Methode transformiert. Das Besondere einer statischen Methode ist, dass dieser Methode nicht als erstes ein implizites Argument übergeben wird. Statische Methoden können ohne eine Objektinstanz über die Klasse aufgerufen werden. Aus diesem Grund wird der statischen Methode nicht als erstes Argument die Objektinstanz (self) übergeben, denn diese existiert beim Aufruf über die Klasse nicht. Dies führt dazu, dass statische Methoden auch nicht auf die Eigenschaften eines Objektes zugreifen können. Weitere Informationen dazu sind ebenfalls unter (Python 2018d) zu finden. Dekorator „@nodes.setter“ Dies ist eine weitere Möglichkeit eines Decorators, der in OEMOF verwendet wird. Die Methode „setter“ (bekannt als mutator methods) sorgt dafür, dass Daten geändert werden (Klein o. J.-d). Die folgende Methode, die als „setter“ funktionieren soll, wird mit „@nodes.setter“ dekoriert. „Nodes“ ist dabei der Name der Funktion. Würde die Funktion „p“ heißen, würde die Methode mit „@p.setter“.aufgerufen werden. Built-in Funktionen Dekoratoren gehören zu den so genannten built-in Funktionen. Der Python Interpretor verfügt über eine Vielzahl an Funktionen und Typen, die darin eingebaut sind. Diese sind immer verfügbar. Die eingebauten Funktionen (built-in functions) sind in Tab. 4.1 aufgeführt. Eine Funktion, die bei OEMOF des öfteren auftaucht, ist „hasattr(object, name)“. Die Argumente dieser Funktion sind ein Objekt und ein String. Das Ergebnis dieses Ausdrucks ist wahr („True“), wenn der String der Name eines Attributs des Objektes ist. Man erhält als Ergebnis „False“, wenn dem nicht so ist. Tab. 4.1 Python „built-in functions“. (Python 2018d) Built-in Functions abs() all() any() ascii() bin() bool() bytearray() bytes() callable() chr() classmethod() compile() complex() delattr()
dict() dir() divmod() enumerate() eval() exec() filter() float() format() frozenset() getattr() globals() hasattr() hash()
help() hex() id() input() int() isinstance() issubclass() iter() len() list() locals() map() max() memoryview()
min() next() object() oct() open() ord() pow() print() property() range() repr() reversed() round() set()
setattr() slice() sorted() staticmethod() str() sum() super() tuple() type() vars() zip() __import__()
4.1 Die Arbeit mit dem Framework von OEMOF
167
„Magic Methods“ In Python gibt es so genannte Magic Methods. Diese zeichnen sich durch doppelte „__“ am Anfang und Ende des Namens der Methode aus. Diese werden durch Python automatisch, quasi „magisch“ im Hintergrund ausgeführt. Ein Beispiel hierfür ist die Mehtode „__call__()“. Mit Hilfe dieser Methode kann eine beliebige Instanz einer Klasse aufrufbar gemacht werden (Python 2018e). Dazu wird die Methode in der entsprechenden Klasse eingeführt. Dies ist nützlich, um bspw. die Argumente eines Objektes anzufragen. Eine Umsetzung kann wie folgt aussehen: # Eine Instanz „Hochspannung“ der Klasse „object“ wird erzeugt und die # Argumente der Instanz abgefragt. class Hochspannung(object): def __call__(self, voltage, frequence): print voltage print frequence t=Test t(1000, 50)
Als Ergebnis wird folgendes erwartet: (1000, 50)
Um diese Funktion nun aufrufen zu können, wird das erzeugte Objekt mit der Methode „__call__()“ im Quellcode angelegt (Python 2018e): HochspannungBerlin.__call__(self[, voltage, frequence])
Jetzt kann ein Aufruf erfolgen und die Werte ausgegeben werden. Mit diesem Wissen können wir den weiteren Quellcode betrachten.
4.1.2 Oemof-energy_system Das Modul „energy_system.py“ ist zentral im Framework von OEMOF. Es ist wichtig für die Struktur in OEMOF. In diesem Modul wird die Basisklasse „EnergySystem“ definiert (GitHub 2017b). Für die Klasse „EnergySystem“ sind mehrere Parameter und Attribute relevant, die in Abschn. 2.4.1 aufgeführt sind. Mit der ersten Methode „__init__„ werden für eine Instanz (Objekt) der Klasse „EnergySystem“ die Attribute eingeführt. def __init__(self, **kwargs): for attribute in ['entities']: setattr(self, attribute, kwargs.get(attribute, []))
168
4 Modellierung in OEMOF
self._groups = {} self._groupings = ([BY_UID] + [g if isinstance(g, Grouping) else Nodes(g) for g in kwargs.get('groupings', [])]) for e in self.entities: for g in self._groupings: g(e, self.groups) self.results = kwargs.get('results') self.timeindex = kwargs.get('timeindex', pd.date_range(start=pd.to_datetime('today'), periods=1, freq='H'))
Nun folgt der Dekorator „@staticmethod“ mit der Methode: • „def _regroup(entity, groups, groupings)“ Mit dieser Methode kann eine neue Gruppierung der enthaltenen Entities der Gruppe durchgeführt werden. Es folgen weitere Methoden: • „def _add(self, entity)“ Diese Methode kann genutzt werden, um weitere Entities hinzuzufügen. • „def add(self, *nodes)“ Hier werden weitere „nodes“ hinzugefügt. Im Programmcode ist dies wie folgt umgesetzt: @staticmethod def _regroup(entity, groups, groupings): for g in groupings: g(entity, groups) return groups def _add(self, entity): self.entities.append(entity) self._groups = partial(self._regroup, entity, self.groups, self._groupings) def add(self, *nodes): """ Add :class:`nodes ` to this energy system. """ for n in nodes: self._add(n)
4.1 Die Arbeit mit dem Framework von OEMOF
169
Der Dekorator „@property“ wird zweimal im Programmcode aufgeführt, um jeweils Attribute zu verändern. @property def groups(self): # Die nächsten drei Zeilen führen zu: Solange das Objekt self._groups # aufrufbar (callable) ist, wird dieses Objekt aufgerufen self._groups() # und das Ergebnis self._groups neu zugewiesen und wieder auf Aufrufbarkeit # getestet. Sobald self._groups nicht mehr aufrufbar ist, wird dessen Wert # als Eigenschaftswert von groups zurückgeliefert. while callable(self._groups): self._groups = self._groups() return self._groups
Die zweite Methode lautet: @property def nodes(self): return self.entities
Unter der Zeile Dekorator „@nodes.setter“ wird die Methode „nodes“ durchgeführt: • „def nodes(self, value)“ Hier werden den Entities Werte (values) übergeben. Es folgen weitere Methoden: • „def flows(self)“ Diese Methode dient dazu, flows in Inputs und Outputs aufzuteilen. • „def dump(self, dpath=None, filename=None)“ Über die Methode „dump()“ wird die Hierarchie eines Objektes serialisiert. • „def restore(self, dpath=None, filename=None)“ Eine Instanz eines Energiesystems wird zurückgegeben. Dazu ebenfalls der Quellcode: @nodes.setter def nodes(self, value): self.entities = value def flows(self): return {(source, target): source.outputs[target] for source in self.nodes for target in source.outputs}
170
4 Modellierung in OEMOF def dump(self, dpath=None, filename=None): r""" Dump an EnergySystem instance. """ if dpath is None: bpath = os.path.join(os.path.expanduser("~"), '.oemof') if not os.path.isdir(bpath): os.mkdir(bpath) dpath = os.path.join(bpath, 'dumps') if not os.path.isdir(dpath): os.mkdir(dpath)
if filename is None: filename = 'es_dump.oemof' # Das Modul „pickle“ implementiert ein binäres Protokoll, um # Objektstrukturen in Python zu serialisieren [Python 2018h]. # Unter Serialisierung (Serializing) wird in der Informatik die Umwandlung von # strukturierten Daten auf eine sequenzielle Darstellungsform verstanden. # Dies wird verwendet, wenn Objekte mit deren Daten über einen längeren # Zeitraum bereitgehalten werden sollen (Persistierung). Auch ist dies bei # verteilten Softwaresystemen nützlich, wenn Objekte über das Netzwerk # übertragen werden sollen. Über diesen Prozess wird eine Python Objekt # Hierarchie in eine byte stream Struktur umgewandelt. # Die dump() Funktion des Moduls pickle serialisiert ein dictionary-Objekt # in eine geöffnete Datei. Mit self._dict_ wird das Attribute-Dictionary # des Objekts übergeben. Der folgende Befehl bedeutet: Pickle das „data“ # dictionary unter der Verwendung des verfügbaren Protokolls # [Python 2018h]. Hierbei erzeugt open() ein file-Objekt im binären # Schreibmodus. Das Kürzel 'wb' steht für: „write binary“. # Anmerkung: „pickle“ wird auch als Begriff für das Serialisieren verwendet. pickle.dump(self.__dict__, open(os.path.join(dpath, filename), 'wb')) # Bei „msg“ handelt es sich um eine messaging library. Diese library kann # dazu genutzt werden, um Live Chats oder Instant Messenger und ähnliches # aufzubauen [Python o.J.]. msg = ('Attributes dumped to: {0}'.format(os.path.join( dpath, filename))) logging.debug(msg) return msg
4.1 Die Arbeit mit dem Framework von OEMOF
171
def restore(self, dpath=None, filename=None): r""" Restore an EnergySystem instance. """ logging.info( "Restoring attributes will overwrite existing attributes.") if dpath is None: dpath = os.path.join(os.path.expanduser("~"), '.oemof', 'dumps') if filename is None: filename = 'es_dump.oemof' self.__dict__ = pickle.load(open(os.path.join(dpath, filename), "rb")) msg = ('Attributes restored from: {0}'.format(os.path.join( dpath, filename))) logging.debug(msg) return msg
Das Programmsystem OEMOF arbeitet intern so, dass alle Elemente des Systems anhand ihres unified Identifier (uid) gruppiert werden. Ein Beispiel einer Umsetzung im Quellcode kann wie folgt aussehen (GitHub 2017b): from oemof.network import Entity from oemof.network import Bus, Sink es = EnergySystem() bus = Bus(label='electricity') es.add(bus) bus is es.groups['electricity'] True
Mit Hilfe der ersten Zeile wird die Basisklasse „Entity“ importiert. Die zweite Zeile führt dazu, dass zusätzlich weitere gewünschte Nodes, nämlich „Bus“ und „Sink“ importiert werden. Um im Quellcode nicht immer die Klasse „EnergySystem“ ausschreiben zu müssen, wird ihr das Kürzel „es“ übergeben. Anschließend wird der Klasse „Bus“ die Kennzeichnung (label) „electricity“ zugewiesen und gleichzeitig mitgeteilt, dass die Klasse „Bus“ im Quellcode als „bus“ aufgerufen wird. In der nächsten Zeile wird die Klasse „bus“ der Klasse „EnergySystem“ zugeordnet. Nun erfolgt die Gruppierung anhand der Kennzeichnung „electricity“. Ist dies alles „wahr“, soll der Wert „True“ ausgegeben werden. In dieser Form lässt sich eine Gruppierung sehr einfach durchführen.
172
4 Modellierung in OEMOF
4.1.3 Oemof-groupings Das Modul „groupings“ ist ein Modul in OEMOF, welches im Hintergrund arbeitet. Ein Modellierer benötigt es nicht zur Erstellung des eigenen Modells. Für Entwickler ist es jedoch interessant. Der Ausdurck „groupings“ oder „group“ wurde bereits mehrfach aufgeführt. Bevor die Methoden und Funktionen aus OEMOF beschrieben werden, soll dieses Modul unter einem praktischen Aspekt betrachtet werden. Wie kann mit diesem Modul gearbeitet werden? In einer Gruppe können bspw. alle gasgefeuerten Transformer zusammengefasst werden. Durch die Gruppenbildung können Nebenbedingungen, Variablen und Objekt-Ausprägungen für eine gesamte Gruppe definiert werden. Jedem Element der Gruppe würden diese Nebenbedingungen, Variablen und Objekt-Ausprägungen zur Verfügung stehen. Eine weitere Möglichkeit ist die Ausgabe bestimmter Knoten (nodes), die ein vordefiniertes Attribut besitzen. Ein solches Attribut könnte sein: „alle Busse, deren Bilanz ausgeglichen ist“. Dies bedeutet, es muss nach jedem Knoten eines Energiesystems geschaut und anschließend in Abhängigkeit des zuvor gesetzten Attributes ein Key zurückgegeben werden. Im Quellcode kann dies wie folgt umgesetzt werden: def constraint_grouping(node): if isinstance(node, Bus) and node.balanced: return blocks.Bus if isinstance(node, Transformer): return blocks.Transformer GROUPINGS = [constraint_grouping]
Diese Funktion kann anschließend in ein Listenformat überführt werden. Es werden hier zwei Listen gebildet. Eine Liste enthält alle Transformer, die andere Liste alle Busse, die ausgeglichen sind. Diese Gruppen werden abschließend in einem Dictionary gespeichert. Wenn es um die Ausgabe der Ergebnisse nach dem Optimierungsprozess geht, können die Funktion groupings und die Listenausgabe hilfreich sein, z. B. um bestimmte Entities des Modells zu betrachten. Dazu muss auf die Listen bzw. Dictionarys zugegriffen und die Inhalte ausgegeben werden. Kommen wir nun zur Umsetzung des Moduls „groupings“ in OEMOF. In diesem Modul werden die Methoden für die Klassen „Grouping“, „Nodes(Grouping)“, „Flows(Nodes)“ und „FlowsWithNodes(Nodes)“ definiert. Für die Klasse „Grouping“ wird zunächst die Methode „__init__“ eingeführt: def __init__(self, key=None, constant_key=None, filter=None, **kwargs): if key and constant_key: raise TypeError( "Grouping arguments `key` and `constant_ key` are " +
4.1 Die Arbeit mit dem Framework von OEMOF
173
" mutually exclusive.") if constant_key: self.key = lambda _: constant_key elif key: self.key = key else: raise TypeError( "Grouping constructor missing required argument: " + "one of `key` or `constant_key`.") self.filter = filter for kw in ["value", "merge", "filter"]: if kw in kwargs: setattr(self, kw, kwargs[kw])
Die nächste Methode stellt einen Schlüssel bereit, unter dem die Gruppe gespeichert wird: def key(self, e):
Nun wird die Gruppe mit ihren Entitäten erzeugt: def value(self, e): """ Generate the group obtained from :obj:`e`. This methd returns the actual group obtained from :obj:`e`. Like :meth:`key `, it is called for every :obj:`e` in the energy system. If there is no group stored under :meth:`key(e) `, :obj:`groups[key(e)]` is set to :meth:`value(e) `. Otherwise :meth:`merge(value(e), groups[key(e)]) ` is called. The default returns the :class:`entity ` itself. """ return e
Wird eine neue Gruppe zu einer bestehenden alten Gruppe erzeugt, können diese mit der Funktion „def merge(self, new, old)“ zusammengeführt werden: def merge(self, new, old): """ Merge a known :obj:`old` group with a :obj:`new` one. This method is called if there is already a value stored under :obj:`group[key(e)]`. In that case, :meth:`merge(value(e), group[key(e)]) ` is called and should return the new group to store under :meth:`key(e) `.
174
4 Modellierung in OEMOF The default behaviour is to raise an error if :obj:`new` and :obj:`old` are not identical. """ if old is new: return old raise ValueError("\nGrouping \n " + "{}:{}\nand\n {}:{}\ncollides.\n".format( id(old), old, id(new), new) + "Possibly duplicate uids/labels?")
Die Methode „def filter(self, group)“ macht es dem Anwender möglich, vor der Speicherung der Entitäten in einer Gruppe einen Filter zu setzen. Dadurch werden nur die Entitäten in die Gruppe übertragen, für die der Filter gesetzt ist: def filter(self, group): """ :func:`Filter ` the group returned by :meth:`value` before storing it. Should return a boolean value. If the :obj:`group` returned by :meth:`value` is :class:`iterable `, this function is used (via Python's :func:`builtin filter `) to select the values which should be retained in :obj:`group`. If :obj:`group` is not :class:`iterable `, it is simply called on :obj:`group` Itself and the return value decides whether :obj:`group` is stored (:obj:`True`) or not (:obj:`False`).
Hier wird die Methode „def __call__(self, e, d)“ eingeführt. def __call__(self, e, d): k = self.key(e) if callable(self.key) else self.key if k is None: return v = self.value(e) if isinstance(v, MuMa): for k in list(filterfalse(self.filter, v)): v.pop(k) elif isinstance(v, Mapping): v = type(v)((k, v[k]) for k in v if self.filter(k)) elif isinstance(v, Iterable): v = type(v)(filter(self.filter, v)) elif self.filter and not self.filter(v): return if not v: return
4.1 Die Arbeit mit dem Framework von OEMOF
175
for group in (k if (isinstance(k, Iterable) and not isinstance(k, Hashable)) else [k]): d[group] = (self.merge(v, d[group]) if group in d else v)
Ist die Klasse „Grouping“ initialisiert, kann die Klasse „Nodes(Grouping)“ im Programmcode beschrieben. werden. Diese Klasse erbt alle Eigenschaften von der Klasse „Grouping“. Auch für diese Subklasse muss erneut eine Methode „def value(self, e)“ formuliert werden, dieses Mal für alle Nodes. def value(self, e): """ Returns a :class:`set` containing only :obj:`e`, so groups are :class:`sets ` of :class:`node `. """ return {e}
Diese Klasse erhält ebenfalls eine Methode zum Zusammenführen einer alten mit einer neuen Gruppe: def merge(self, new, old): """ :meth:`Updates ` :obj:`old` to be the union of :obj:`old` and :obj:`new`. """ return old.union(new)
Die Klasse „Flow“ in diesem Modul erbt von der vorherigen Klasse „Nodes“. Die Methode „def value(self, flows)“ hat hier das Argument „flows“. Darüber können die Werte der Flows ausgegeben werden: def value(self, flows): """ Returns a :class:`set` containing only :obj:`flows`, so groups are :class:`sets ` of flows. """ return set(flows)
Wir sehen, dass die Subklassen die gleichen Methoden erhalten, jedoch mit dem jeweiligen Filter der Subklasse. Es folgt die Methode „def __call__(self, n, d)“. def __call__(self, n, d): flows = set(chain(n.outputs.values(), n.inputs.values())) super().__call__(flows, d)
176
4 Modellierung in OEMOF
Die Klasse „FlowsWithNodes(Nodes)“ erbt alle Attribute und Methoden von der Subklasse „Nodes“. Jedoch wird hierbei der Filter so gesetzt, dass alle Flows herausgegeben werden, die mit einem Knoten (node) verbunden sind. Das Ergebnis sind Tupel (tuples). Für diese Tupels wird erneut die Funktion „def value(self, tuples)“, dieses mal jedoch für die Tupels, formuliert: def value(self, tuples): """ Returns a :class:`set` containing only :obj:`tuples`, so groups are :class:`sets ` of :obj:`tuples`. """ return set(tuples)
Auch eine „def __call__(self, n, d)“ für die Tupels ist angelegt: def __call__(self, n, d): tuples = set(chain( ((n, t, f) for (t, f) in n.outputs.items()), ((s, n, f) for (s, f) in n.inputs.items()))) super().__call__(tuples, d)
Die Methode „def _uid_or_str(node_or_entity)“ sorgt dafür, dass Entitäten in Knoten überführt werden: def _uid_or_str(node_or_entity): """ Helper function to support the transition from `Entitie`s to Node`s. """ return (node_or_entity.uid if hasattr(node_or_entity, "uid") else str(node_or_entity))
Der Ausdruck, der am Ende des Codes angegeben ist, ist bei der Klasse „EnergySystem“ immer präsent: DEFAULT = Grouping(_uid_or_str) """ The default :class:`Grouping`. This one is always present in a :class:`energy system `. It stores every :class:`entity ` under its :attr:`uid ` and raises an error if another :class:`entity ` with the same :attr:`uid ` get's added to the :class:`energy system `. """
4.1 Die Arbeit mit dem Framework von OEMOF
177
4.1.4 Oemof-network Zur Aufstellung eines Energiemodells für ein zu optimierendes Energieversorgungssystem werden in oemof-network alle Entities (components – sink, source, transformer – und bus) und deren Verbindungen angelegt. Wie dies genau erfolgt, wird anhand des in Abschn. 2.3, Abb. 2.8 dargestellten Energienetzwerkplans des Beispiels „Rostock“ gezeigt. Folgend kann der Programmcode für dieses Beispiel studiert werden (oemof-Team 2017a): from oemof.network import Entity from oemof.energy_system import bus, sink, source, transformer, storage # create the energy system # Die folgende Code-Zeile erzeugt eine Objektinstanz ‚es‘ der Klasse # EnergySystem. es = EnergySystem() # Zunächst werden die Busse angelegt. Diese verbinden die components # miteinander. # create bus 1 bus_1 = Bus(label="r1_gas") # create bus 2 bus_2 = Bus(label="r1_el") # create bus 3 bus_3 = Bus(label="r1_th") # create bus 4 bus_4 = Bus(label="r1_bio") # create bus 5 bus_5 = Bus(label="r2_el") # create bus 6 bus_6 = Bus(label="r2_gas") # create bus 7 bus_7 = Bus(label="r2_coal") # create bus 8 bus_8 = Bus(label="r2_th") # Jetzt werden die Senken definiert. # Dabei werden die Senken mit den Bussen verknüpft. Damit sind In- und # Output festgelegt.
178
4 Modellierung in OEMOF
# create sink 1 # Leere eckige Klammern “[]” erzeugen eine leere Liste. Sink(label='de1', inputs={bus_1: []}) # create sink 2 Sink(label='dh1', inputs={bus_3: []}) # create sink 3 Sink(label='de2', inputs={bus_5: []}) # create sink 4 Sink(label='dh2', inputs={bus_8: []}) # Nun folgen die Quellen. Diese haben Output wird # mit dem entsprechenden Bus verknüpft.
jeweils
einen
Output.
Der
# create source Source(label='wt1', outputs={bus_2: []}) # Es erfolgt die Verbindung der Transformer mit den Bussen. Darüber sind # In- und Output definiert. # create transformer 1 Transformer(label='gt1', inputs={bus_1: []}, outputs={bus_2: []}) # create transformer 2 Transformer(label='cb1', inputs={bus_2: []}, outputs={bus_5: []}) # create transformer 3 Transformer(label='bg1', inputs={bus_4: []}, outputs={bus_2: []}, outputs={bus_3: []}) # create transformer 4 Transformer(label='cb2', inputs={bus_5: []}, outputs={bus_2: []}) # create transformer 5 Transformer(label='ptg2', inputs={bus_5: []}, outputs={bus_6: []}) # create transformer 6 Transformer(label='cp2', inputs={bus_7: []}, outputs={bus_5: []}) # create transformer 7 Transformer(label='chp2', inputs={bus_6: []}, outputs={bus_5: []}, outputs={bus_8 []})
4.1 Die Arbeit mit dem Framework von OEMOF
179
# Der Speicher muss ebenfalls hier definiert werden. # create storage Storage(label='sp1', inputs={bus_2: []}, outputs={bus_2: []})
Es wurde bereits darauf eingegangen, dass in OEMOF Gruppen gebildet werden. Oftmals erfolgt eine Gruppierung entsprechend einer Klasse bzw. des Typs einer Klasse, wie bspw. alle Flüsse mit dem Attribut „investment“. Dazu muss eine Funktion bereitgestellt werden, die aus der Klasse Entity („class:`entity `“) für eine Gruppe und jedes Element der Gruppe einen Schlüssel (key) errechnet. Anschließend wird die Gruppe mit ihrem Schlüssel in der Klasse „class:`entity `“ entsprechend des Gruppierungskriterium „Typ“ abgespeichert. Folgendes Beispiel soll dies verdeutlichen: # Hier erfolgt zunächst eine Gruppierung nach dem type, bspw. „Sink“. Der # Ausdruck „range()“ steht für eine Funktion in Python, mit deren Hilfe # Listen geliefert werden, die arithmisch aufgeführt werden und somit # arithmetischen Aufzählungen entsprechen [Klein o.J.-c]. Dies # kann gut für for-Schleifen genutzt werden. In diesem Beispiel „for i in range(9)“ werden für i neun Zahlen beginnend bei der Zahl „0“ hochgezählt. # Der Begriff „format“ steht für die String-Methode "format". Damit wird # ein String erzeugt, der in diesem Fall die zuvor definierten Busse # aufführt. Zur Erläuterung wird das Beispiel aus [Klein o.J.-b] # vorgestellt: >>> "Erstes Argument: {0}, zweites: {1}".format(47,11) 'Erstes Argument: 47, zweites: 11'
True True
es = EnergySystem(groupings=[type]) buses = set(Bus(label="Bus {}".format(i)) for i in range(9)) es.add(*buses) components = set(Sink(label="Component {}".format(i)) for i in range(9)) es.add(*components) buses == es.groups[Bus] components == es.groups[Sink]
Die Bildung von Gruppen erfolgt in OEMOF automatisch. In dem bereits aufgeführten Quellcode wurde diese Funktionalität immer wieder verwendet. Weitere Methoden in diesem Modul sind für den Prozess des Programmablaufs, nicht aber für die Modellierung notwendig, sodass auf diese nicht weiter eingangen wird. Nachdem das Energiesystem mit seinen Verbindungen angelegt ist, folgt die weitere Beschreibung in der Bibliothek oemof.solph.
180
4 Modellierung in OEMOF
4.1.5 Oemof-solph Die Bibliothek oemof.solph steht zur Modellierung und Optimierung linearer Problemstellungen zur Verfügung. Dazu zählen neben den rein linearen Problemen auch die gemischt-ganzzahlig linearen Optimierungen. Die Bibliothek ermöglicht es dem Modellierer, zwischen Modellen zur Optimierung der Einsatzplanung von Kraftwerken (dispatch) und der Investitionskosten zu wechseln. Auch eine Mischung aus beidem ist möglich. Mit Hilfe der Investitionskosten kann ermittelt werden, ob bspw. eine neue Anlage im Betrieb günstiger als eine bestehende Anlage ist. Für diesen Ansatz wird die Annuitätenmethode verwendet. Nicht nur die Minimierung von Kosten, sondern auch anderer Parameter, wie bspw. Emissionen, könnten hier interessant sein. Solph verwendet zur Formulierung eines Optimierungsmodells das Python Paket pyomo. Zunächst muss in oemof-solph über die Klasse „pandasDatetimeIndex“ ein Datetime Index vergeben werden, um ein Energiesystem anzulegen (pandas 2017e). Über die Klasse „pandasDatetimeIndex“ werden der betrachtete Zeitbereich sowie die Zeitschritte dazwischen festgelegt. Pandas stellt dazu die Funktion time_range zur Verfügung. Weitere Informationen zu dieser Funktion finden sich unter (pandas 2017b). Darin sind für den Datetime Index die Parameter aufgeführt. Es gibt drei wesentliche Parameter: „start“, „end“ und „peroide“, wobei zwei davon definiert sein müssen. Welche Ausprägungen die Parameter annehmen können, ist in Tab. 4.2 dargestellt. Zu dem Parameter „freq“ existieren für den Datentyp string mehrere Aliases, so genannte Offset Aliases, die unter (pandas 2017c) aufgeführt sind. Beispielsweise wird mit „H“ eine stündliche Frequenz und mit „S“ eine Sekundenfrequenz beschrieben.
Tab. 4.2 Angaben aus Pandas zu den Parametern für den Datetime Index (pandas.date_range). (pandas 2017b) Parameter Datentyp/Wert start string or datetime-like, default None end string or datetime-like, default None periods integer, default None freq string or DateOffset, default ‘D’ (calendar daily) tz string, default None normalize bool, default False name closed
string, default None string, default None
Erläuterung Left bound for generating dates Right bound for generating dates Number of periods to generate Frequency strings can have multiples, e.g. ‘5H’ Time zone name for returning localized DatetimeIndex, for example Asia/Hong_Kong Normalize start/end dates to midnight before generating date range Name of the resulting DatetimeIndex Make the interval closed with respect to the given frequency to the ‘left’, ‘right’, or both sides (None)
4.1 Die Arbeit mit dem Framework von OEMOF
181
Zur Festlegung des Datetime Index werden im Programmcode beispielhaft für das Jahr 2017 mit stündlichen Zeitschritten folgende Angaben gemacht: # Zunächst wird Pandas importiert: import pandas as pd # Nun wird der Datetime Index festgelegt. Dazu ist der Startwert mit # 1.1.2017 und die Periode, hier die Aufteilung des Jahres in 8760 h, # vorgegeben. # Zusätzlich wird die Frequenz mit Stundenwerten (H) angegeben. my_index = pd.date_range('1/1/2017', periods=8760, freq='H')
Die Datenrückgabe lehnt sich an folgenden Code an: return DatetimeIndex(start=start, end=end, periods=periods, freq=freq, tz=tz, normalize=normalize, name=name, closed=closed, **kwargs)
Hierüber kann nun das Energiesystem definiert werden. import oemof.solph as solph my_energysystem = solph.EnergySystem(timeindex=my_index)
Ist dieser Schritt abgeschlossen, können die Komponenten angelegt werden. Einen Bus in Solph anlegen In OEMOF haben Busse In- und Outputs, die über die Flows modelliert werden. Dabei sind die Energieflüsse zwischen In- und Output eines Busses ausgeglichen. Um eine Instanz eines Busses zu definieren, muss über ein „label“ ein eindeutiger Name, hier bspw. „r1_ bio“ aus dem Beispiel „Rostock“, vergeben werden. Eine Instanz eines Busses wird dann beispielhaft wie folgt angelegt (oemof-Team 2017a): solph.Bus(label='r1_bio')
Es wäre ebenso möglich, mit einer Variablen (hier bspw. „electricity“) zu arbeiten, die dem Objekt zugewiesen wird. Eine spätere Verbindung mit einer Komponente kann auf diese Weise leichter erfolgen (oemof-Team 2017a): electricity_bus = solph.Bus(label='electricity')
Als Modellierer kann man sich den Output des Objektes „r1_bio“ ausgeben lassen. Nun zeigt sich der Unterschied im Programmieren mit einer Variablen und ohne (oemof- Team 2017a): print(my_energsystem.groups['r1_bio'] print(electricity_bus)
182
4 Modellierung in OEMOF
Mit einer Variablen kann der Quellcode an manchen Stellen eingekürzt werden. Die Klasse Flow Die Klasse „Flow“ dient zur Verbindung zwischen den Entities und stellt, wie bereits erwähnt, die Kanten im Graphenmodell dar. Mit Hilfe der Flow-Klasse werden bspw. Investitionen und andere Kostenflüsse oder auch Emissionen dargestellt. Diese Flüsse sind für gewöhnlich mit den Komponenten verbunden und werden mit diesen gemeinsam definiert. Flüsse weisen obere und untere Grenzen auf, die entweder zeitunabhängig (konstant) oder zeitabhängig (variabel) sind. Ebenso sind zusammengefasste Begrenzungen (limits) möglich. Im Programmcode wird ein Fluss sehr einfach angelegt (oemof-Team 2017b): solph.Flow()
Eine Senke in OEMOF Der Energiebedarf in einem Energiesystem wird in OEMOF über eine Senke modelliert. Eine Senke kann aber auch dazu genutzt werden, Energieüberschüsse im System aufzunehmen. Anhand des zuvor definierten Busses mit der Variablen „electricity_bus“ wird nachfolgend die Definition einer Senke in Solph modelliert. Eine Senke ist bspw. ein Stromabnehmer, wie ein Mehrfamilienhaus oder eine Produktionsstätte. Im Beispiel „Rostock“ ist dies ausgedrückt über den Strombedarf „de1“. Die Bilanz des Busses aus diesem Beispiel muss über den In- und Output ausgeglichen sein. Solph wird über den folgenden Code mitgeteilt, dass das Objekt „de1“ den Input des Busses aus der zuvor eingeführten Variablen „electricity_bus“ besitzt (oemof-Team 2017b): solph.Sink(label='de1', inputs={electricity_bus: solph.Flow( actual_value=my_demand_series, fixed=True, nominal_value=nominal_demand)})
Der Parameter “my_demand_series” beinhaltet eine Folge von normalisierten Werten für den Bedarf (sequence of normalised values). Dahingegen berechnet sich der Parameter „nominal_value“ aus dem Produkt maximaler Bedarf multipliziert mit der entsprechenden normalisierten Sequenz. Über den Parameter „fixed=True“ wird festgelegt, dass der Parameter „actual_value“ durch den Solver nicht verändert werden kann. Eine Überschuss-Senke hat im Regelfall weniger Restriktionen. Sie ist dazu da, den gesamten Überschuss im System aufzunehmen (oemof-Team 2017b): solph.Sink(label='electricity_excess', inputs={electricity_bus: solph.Flow()})
Über die Sink-Klasse werden die im System erzeugten und bereitgestellten Energien aufgenommen. Man könnte sie wie einen Stecker betrachten. Sie gibt keine weiteren Beschränkungen vor und stellt auch keine weiteren Variablen zur Verfügung.
4.1 Die Arbeit mit dem Framework von OEMOF
183
Eine Ressource in oemph-solph In OEMOF ist eine Ressource bspw. eine PV- oder Windkraftanlage. Ebenso kann es ein Energieimport, wie bspw. ein Erdgasimport, sein. Es ist auch möglich, darüber eine so genannte Schlupfvariable (slack variable) zu definieren, damit die Energieflüsse im System ausgeglichen sind und das System lösbar bleibt. In unserem Beispiel „Rostock“ existieren eine Windkraftanlage und eine Gasturbine. Für die Windkraftanlage können stundenweise Wetterdaten als feed-in angegeben werden. Für die Gasturbine kann der Energieträger Gas importiert werden, für den bestimmte Restriktionen bestehen. So kann für den Gasimport bspw. ein maximaler Wert (maximum value) durch den Parameter „nominal_value“ vorgegeben werden. Dieser beschreibt, wieviel Gas pro Stunde maximal importiert werden darf. Der Paramater „nominal_value“ kann aber auch, wie im Falle einer Windkraftanlage, eine installierte Leistung bedeuten. Eine weitere Restriktion könnte ein jährliches Limit sein. Dies könnte durch einen Parameter „summed_max“ ausgedrückt werden. Ein Gasimport in ein System hinein verursacht Kosten. Diese werden durch variable Kosten im System berücksichtigt. Ein entsprechender Parameter „variable_cost“ wird dazu in Solph angelegt (oemof-Team 2017b). Diese geben die Kosten je Zeitschritt an: solph.Source( label='import_natural_gas', outputs={my_energsystem.groups['r1_gas']: solph.Flow( nominal_value=1000, summed_max=1000000, variable_ costs=50)}) solph.Source(label='wt1', outputs={r1_el: solph.Flow( actual_value=wind_power_feedin_series, nominal_value=1000000, fixed=True)})
Eine Ressource funktioniert ähnlich wie eine Senke. Im Unterschied gibt eine Ressource Energie in ein System hinein und eine Senke nimmt Energie aus einem System heraus. Auch hier gibt es keine weiteren Beschränkungen oder Variablen. Die Klasse Transformer in oemof-solph Da es sich bei OEMOF prinzipiell um einen linearen Optimierungsansatz handelt, werden Umwandlungsprozesse, die mit der Klasse „Transformer“ beschrieben werden, als zeitlich konstant betrachtet. Dies bedeutet, dass bspw. der Wirkungsgrad über einen definierten Zeitraum als konstant angesetzt wird. Es ist jedoch möglich, je Zeitabschnitt unterschiedliche Wirkungsgrade anzugeben. Dies kann bspw. bei Blockheizkraftwerken der Fall sein, die in unterschiedlichen Betriebspunkten (Volllast, Teillast) unterschiedliche Wirkungsgrade aufweisen können. In OEMOF wird von einem Transformer gesprochen. Die Klasse „Transformer“ wurde im Release v0.2.0 überarbeitet und ist mit den vorhergehenden Releases v0.1.x nicht mehr kompatibel. Die in Abschn. 3.1.4 vorgestellten Zusammenhänge beziehen sich ausschließ-
184
4 Modellierung in OEMOF
lich auf das Release v0.2.0. Da bereits mehrere Studien auf den alten Releases v0.1.x beruhen, wird an dieser Stelle auf die vorherige Version der Klasse „Transformer“ eingegangen. Dadurch wird es möglich, sich in die Strukturen und Konzepte der veröffentlichten Studien, wie bspw. (Gaudschau et al. 2017) hineinzudenken. Nach den vorherigen Releases 0.1.x kann bei einem Transformer zwischen zwei unterschiedlichen Arten von Komponenten unterschieden werden. Die eine Art besitzt einen Input und ein bis mehrere Outputs. Hierunter würden bspw. Kraft-Wärme-Kopplungsanlagen (KWK CHP) fallen. Bei der anderen Art existieren mehrere Inputs und ein Output. Dies ist bspw. bei Wärmepumpen der Fall. In OEMOF 0.1.x wird aus diesem Grund zwischen dem Transformer Typ (1xM) und dem Transformer Typ (Nx1) unterschieden. Der Klammerausdruck macht deutlich, dass bei der Variante (1xM) nur ein Input und M Outputs existieren, hingegen bei der zweiten Variante (Nx1) N Inputs und ein Output vorhanden sind. Im Release v0.2.0 existiert nur noch die Klasse „Transformer“. Diese ist so angelegt, dass sie die beiden beschriebenen Unterschiede direkt abbilden kann. Mit ihr können Komponenten beschrieben werden, die über N Inputs und M Outputs verfügen. Prinzipiell kann ein Transformer jede Art von Transformationsprozess innerhalb eines Energiesystems sein, wie bspw. ein Kraftwerk, eine Stromleitung, ein Elektrolyseur oder eine Kühleinheit. Schauen wir zunächst ausgehend vom Release v0.1.x auf einen Transformer Typ (1xM). Am Beispiel „Rostock“ würde sich für die eingeführte Gasturbine nach Release v0.1.x bspw. ergeben: solph.LinearTransformer( label="gt1", inputs={my_energsystem.groups['r1_gas']: solph.Flow()}, outputs={r1_el: solph.Flow(nominal_value=10e10)}, conversion_factors={r1_el: 0.58})
Ein Blockheizkraftwerk (combined heat and power (CHP)), wie jenes zur Biogasanlage, würde in gleicher Art und Weise abgebildet werden (Release v0.1.x): # Zunächst werden die zwei Busse für den Output in Solph eingeführt. r1_el = solph.Bus(label='electricity') r1_th = solph.Bus(label='heat') # Jetzt wird der LinearTransformer(1xM) definiert. solph.LinearTransformer( label='bg1', inputs={r1_bio: Flow()}, outputs={r1_el: Flow(nominal_value=30), r1_th: Flow(nominal_value=40)}, conversion_factors={r1_el: 0.3, r1_th: 0.4})
4.1 Die Arbeit mit dem Framework von OEMOF
185
Was ergibt sich für die zweite Möglichkeit eines Transformers Typ (Nx1)? Auch hier muss der Wirkungsgrad, im Falle einer Wärmepumpe die Leistungszahl (Coefficient Of Performance, COP), innerhalb eines Zeitschrittes konstant sein. Jedoch kann auch hier für jeden Zeitschritt ein anderer Wirkungsgrad definiert werden. Dies ist vorab festzulegen und kann während der Optimierung nicht geändert werden. Betrachtet wird beispielhaft eine Wärmepumpe. Da diese bisher nicht in unserem Beispiel „Rostock“ enthalten ist, werden zur Veranschaulichung neue Busse eingeführt. Bei der Wärmepumpe gehen Strom und Wärme niederer Temperatur hinein (Input) und Wärme höherer Temperatur heraus (Output). Es wird ein neues Objekt eines Transfomers Typ (Nx1) angelegt. Das Objekt wird im Modell als „heat_pump“ bezeichnet (Release v0.1.x): # Zunächst werden die drei Busse (Input: Strom, Wärme niederer Temperatur; # Output: Wärme höherer Temperatur angelegt. b_el = solph.Bus(label='electricity') b_th_low = solph.Bus(label='low_temp_heat') b_th_high = solph.Bus(label='high_temp_heat') # Die Leistungszahl (coefficient of performance) der Wärmepumpe wird definiert. cop = 3 # Jetzt wird dem Programm mitgeteilt, dass es sich um einen # LinearN1Transformer handelt. # Das Objekt Wärmepumpe wird initialisiert. solph.LinearN1Transformer( label='heat_pump', inputs={bus_elec: Flow(), bus_low_temp_heat: Flow()}, outputs={bus_th_high: Flow()}, conversion_factors={bus_elec: cop, b_th_low: cop/(cop-1)})
Eine weitere Möglichkeit für Transformer sind solche, die einen Input und zwei Outputs besitzen. Die Besonderheit liegt in dem variablen Verhältnis zwischen den beiden Outputströmen. Betrachtet man bspw. eine Kraft-Wärme-Kopplungsanlage (KWK- Anlage), so erzeugt diese Anlage Strom und Wärme. Eine KWK-Anlage kann auf zwei Arten betrieben werden. Bei quasi konstantem Bedarf an Strom und Wärme wird das Verhältnis der Produktion dieser beiden Energieformen vor Inbetriebnahme der Anlage festgelegt und ist damit fix. Dies bedeutet, es wird immer der gleiche Anteil an Strom und Wärme erzeugt. Hierfür eignen sich bspw. Gegendruckturbinen. Ist der Bedarf jedoch schwankend, bspw. wird unter der Woche Strom und Wärme, an den Wochenenden jedoch nur Strom benötigt, wird die Anlage flexibel betrieben, das Verhältnis zwischen Stromund Wärmeproduktion ist dabei variabel. Für diesen Zweck werden bspw. Entnahme- Kondensationsturbinen eingesetzt. Die jeweilige Betriebsweise hat Auswirkungen auf den Wirkungsgrad, was bei der Berechnung entsprechend berücksichtigt werden muss.
186
4 Modellierung in OEMOF
Für die Abbildung im Modell eignet sich die Klasse „VariableFractionTransformer“. Durch die Modellierung eines Hauptflusses und eines Entnahmestroms wird diese variable Zusammensetzung zwischen den beiden Outputströmen programmtechnisch umgesetzt. In unserem Fall ist der Hauptfluss die Produktion von Strom, da dieser durchgängig produziert wird und der angeschlossene Entnahmestrom, die Wärme, da diese am Wochenende nicht benötigt wird. Jetzt kann für Wochenenden für den Hauptfluss Strom ein spezifischer Wirkungsgrad als eigener Parameter festgelegt werden, z. B. als „conversion_factor_single_flow“. Für unser Beispiel „Rostock“ kann für das BHKW der Biogasanlage der Bus „r1_el“ als Hauptfluss und der Bus „r1_th“ als Nebenfluss modelliert werden. Um auszudrücken, dass die Produktion von Strom und Wärme flexibel erfolgt, wird dem Namen des Objektes Biogas-BHKW der Zusatz „variable“ mitgegeben (variable_bg1) (oemof-Team 2017b). solph.VariableFractionTransformer( label='variable_bg1', inputs={r1_bio: solph.Flow(nominal_value=10e10)}, outputs={r1_l: solph.Flow(), r1_th: solph.Flow()}, conversion_factors={r1_el: 0.3, r1_th: 0.5}, conversion_factor_single_flow={r1_el: 0.5} )
Im Release v0.2.0 ist ein Transformer flexibel mit N Inputs und M Outputs angelegt. Eine Unterscheidung zwischen einem „Transformer(Nx1)“ und einem „Transformer(1xM)“ ist nicht mehr erforderlich. Spezifische Klasse Speicher Speicher gehören nicht zu den im oemof-network Modul definierten Klassen. Diese Klasse existiert nur in oemof-solph. Ein Speicher weist einige Besonderheiten auf. Ein Speicher besitzt eine Leistung, die Nennleistung, die über den Parameter „nominal_value“ gekennzeichnet wird. Da ein Speicher nur begrenzte Input- und Output-Flüsse in Abhängigkeit seiner Leistung besitzt, ist es sinnvoll, eine Verhältniszahl für die beiden Größen Fluss und Leistung anzugeben. Dazu kann ein Parameter für den Inputfluss, wie bspw. „nominal_input_capacity_ratio“, und einer für den Outputfluss, wie bspw. „nominal_output_capacity_ratio“ eingeführt werden. Weitere wichtige Parameter für einen Speicher sind dessen Wirkungsgrad für den Vorgang des Ladens und Entladens sowie ein zeitlich steigender Leistungsverlust. Für unser Beispiel „Rostock“ wird der Speicher im Programmcode eingeführt. # Der Speicher wird in Solph angelegt. Zudem werden als flow variable # Kosten für die In- und Output-Flüsse angegeben. solph.Storage( label='storage', inputs={r1_el: solph.Flow(variable_costs=10)},
4.1 Die Arbeit mit dem Framework von OEMOF
187
outputs={r1_el: solph.Flow(variable_costs=10)}, capacity_loss=0.001, nominal_value=50, nominal_input_capacity_ratio=1/6, nominal_output_capacity_ratio=1/6, inflow_conversion_factor=0.98, outflow_conversion_ factor=0.8)
Ein Optimierungsmodell in Solph aufsetzen Wie bereits beschrieben, kann in OEMOF ein Modell zur Optimierung der Kosten oder der Einsatzplanung aufgesetzt werden. Kosten können in OEMOF jedoch nicht nur durch Währungen, sondern auch durch z. B. Emissionswerte ausgedrückt werden. Einen tieferen Einblick in die Investitionsberechnungen gibt Abschn. 6.1. Um ein Optimierungsmodell aufzusetzen, muss Solph mitgeteilt werden, welche Art der Optimierung durchgeführt wird. Wird ein Dispatch Modell aufgesetzt, muss zunächst die Bibliothek „os“ importiert werden. Dann kann Solph übergeben werden, dass es sich um ein Dispatch Modell handelt. import os # Es existiert ein einfaches simple least cost optimisation Modell # (OperationalModel). om = solph.OperationalModel(my_energysystem) # Mithilfe des Solvers CBC sollen die Kosten im Modell minimiert werden. om.solve(solver='cbc', solve_kwargs={'tee': True})
Im eigenen Modell wird immer eine Instanz (Objekt) der Klasse „investment” erzeugt. Diese Instanz kann an die im Modell betrachteten Komponenten angehängt werden. Auch Speicher oder Flüsse können mit Investitionskosten versehen werden. Sind im Modell Parameter gesetzt worden, die auf den Parameter „nominal_value“ oder „nominal_capacity“ referenzieren, zeigen diese nun auf die Variable „investment“. Aus diesem Grund sollte bei Anhängen eines Objektes „investment“ an eine Komponente der Parameter „nominal_value“ oder „nominal_capacity“ nicht gesetzt werden. In unserem Beispiel-Modell „Rostock“ könnte mit dem Objekt investment analysiert werden, welches die optimale Kapazität der Windturbine wt1 bei minimalen Kosten des Gesamtenergiesystems wäre. solph.Source(label='wt1', outputs={r1_el: solph.Flow( actual_value=wind_power_time_series, fixed=True, investment=solph.Investment(ep_costs=epc, maximum=100000))})
Für periodische Kosten werden mehrere Parameter definiert und in Ansatz gebracht. Der Parameter „capex“ (capital expenture) gibt die für ein längerfristiges Anlagegut, wie bspw. eine Anlage, Maschine oder ein Gebäude, die Investitionsausgaben (umgangssprachlich: Investitionskosten) an, die im Regelfall aus Eigenkapital und Fremdkapital fi-
188
4 Modellierung in OEMOF
nanziert werden. Der Parameter „wacc“ (Weighted Average Cost of Capital) setzt sich zusammen als gewichtetes arithmetisches Mittel der Eigen- und Fremdkapitalkostensätze eines Unternehmens oder in unserem Fall einer Investition, wobei die Gewichte in den jeweiligen Anteilen des Eigen- bzw. Fremdkapitals am Gesamtkapital bestehen (Gabler 2018). Für unsere Windkraftanlage könnte dies wie folgt aussehen: # The investment cost capex = 2000 # The life expectancy lifetime = 25 # The weighted average capital cost (wacc) wacc = 0.05 # Berechnung des epc. epc = capex * (wacc * (1 + wacc) ** lifetime) / ((1 + wacc) ** lifetime - 1)
Geben wir beispielhaft für den Speicher in unserem Beispiel „Rostock“ Investitionskosten an: solph.Storage( label='st1', capacity_loss=0.01, inputs={r1_el: solph.Flow()}, outputs={r1_el: solph.Flow()}, nominal_input_capacity_ratio=1/6, nominal_output_capacity_ratio=1/6, inflow_conversion_factor=0.99, outflow_conversion_factor=0.8, investment=solph.Investment(ep_costs=epc))
Wahl eines Mixed-Integer linear Problems Um in Solph ein MIP umzusetzen, bietet OEMOF ein oemof.solph.options Module an. In diesem Modul gibt es die beiden MIP Klassen „BinaryFlow“ und „DiscreteFlow“. Im Release v0.2.0 sind diese noch nicht mit der investment class kompatibel. Eine gemeinsame Verwendung ist damit noch nicht möglich. Um die beiden Klassen im eigenen Modell zu verwenden, werden im Programmcode die Klassen innerhalb der Flow()-declaration aufgerufen. r1_el = solph.Bus(label='electricity') r1_th = solph.Bus(label='heat') solph.LinearTransformer( label='bg1', inputs={r1_bio: Flow(discrete=DiscreteFlow())}, outputs={r1_el: Flow(nominal_value=30, binary=BinaryFlow()), r1_th: Flow(nominal_value=40)}, conversion_factors={r1_el: 0.3, r1_th: 0.4})
4.1 Die Arbeit mit dem Framework von OEMOF
189
Damit ist die Input-Variable „Flow“ des LinearTransformers „bg1“ als diskrete Variable festgelegt. Diese könnte nun Werte von bspw. (min, …, 5, 6, 7, …, max) annehmen. Für den Output wird die Variable des Busses „r1_el“ als binäre Variable deklariert. Diese besagt, dass bspw. beim Wert 1 Elektrizität fließt und beim Wert 0 keine Elektrizität erzeugt wird. Das Modul Groupings Groupings wurden bereits mehrfach vorgestellt. Es wird an dieser Stelle auf den Abschn. 4.1.3 hingewiesen. Weitere Modellierungsbeispiele Die folgenden Beispiele beziehen sich auf das Release v0.2.0. Damit exitieren die zuvor beschrieben Transfomer Typ (1xM) und (Nx1) nicht mehr, sondern es gibt nur noch die Klasse „Transformer“. Für eine Transmissionline wird beispielhaft die Modellierung im Investment Mode für die Verbindung beider Investmentvariablen vorgestellt. Hierbei sind die äquivalent periodischen Kosten (equivalent periodical costs (epc)) der Linie 20 „Geldeinheiten“. Die Geldeinheiten müssen vorab festgelegt werden, wie Euro. Versuchsweise könnte man diesen Wert nur an eine Line übergeben und die andere auf Null setzen und schauen, welche Auswirkung dieses auf das Ergebnis hat: import pandas as pd from oemof import solph date_time_index = pd.date_range('1/1/2017', periods=5, freq='H') energysystem = solph.EnergySystem(timeindex=date_time_index) bel1 = solph.Bus(label='electricity1') bel2 = solph.Bus(label='electricity2') energysystem.add(bel1, bel2) energysystem.add(solph.Transformer( label='powerline_1_2', inputs={bel1: solph.Flow()}, outputs={bel2: solph.Flow( investment=solph.Investment(ep_costs=20))})) energysystem.add(solph.Transformer( label='powerline_2_1', inputs={bel2: solph.Flow()}, outputs={bel1: solph.Flow( investment=solph.Investment(ep_costs=20))})) om = solph.Model(energysystem) line12 = energysystem.groups['powerline_1_2'] line21 = energysystem.groups['powerline_2_1'] solph.constraints.equate_variables(
190
4 Modellierung in OEMOF om, om.InvestmentFlow.invest[line12, bel2], om.InvestmentFlow.invest[line21, bel1])
Für die Klasse „Link(Transformer)“ wird nachfolgend gezeigt, wie die Klasse mit ihren Attributen angelegt, aber auch, wie ein Print-Auftrag erstellt wird. from oemof bel0 bel1 link
import solph = solph.Bus(label="el0") = solph.Bus(label="el1") = solph.custom.Link( label="transshipment_link", inputs={bel0: solph.Flow(), bel1: solph.Flow()}, outputs={bel0: solph.Flow(), bel1: solph.Flow()}, conversion_factors={(bel0, bel1): 0.92, (bel1, bel0): 0.99}) print(sorted([x[1][5] for x in link.conversion_factors.items()])) [0.92, 0.99] type(link)
sorted([str(i) for i in link.inputs]) ['el0', 'el1'] link.conversion_factors[(bel0, bel1)][3] 0.92
Nachfolgend zwei Beispiele zur Flow Klasse, wie diese Klasse zum einen als fixed Flow und zum anderen als Fluss mit einer oberen und unteren Grenze modelliert werden können. Zunächst als fixed Flow: f = Flow(actual_value=[10, 4, 8], fixed=True, variable_costs=8)
Soll ein Fluss eine obere und untere Grenze haben, lässt sich dies wie folgt umsetzen. Hier wird zu Demonstrattionszwecken der maximal Wert der Funktion „f1“ abgefragt: f1 = Flow(min=[0.2, 0.3], max=0.99, nominal_value=100)
4.1.6 Oemof-outputlib Die Ausgabe von Berechnungsergebnissen wird durch das mitgelieferte Pandas Package unterstützt. (oemof 2018b) informiert darüber, wie das Toolkit von Pandas zur Analyse von Daten eingesetzt werden kann. Mit dem Release v0.2.0 wurde die outputlib überarbeitet. Die neue Bibliothek beinhaltet keine Tools mehr zum Plotten von Optimierungsergebnissen. Dazu wurde das
4.1 Die Arbeit mit dem Framework von OEMOF
191
neue Visualisierungspackage „oemof_visio“ entwickelt. Dieses befindet sich aktuell im neuen Release v0.5 unter: https://github.com/tbors/oemof_visio. Nachfolgend wird es weiter vorgestellt. Die Bibliothek „oemof-outputlib“ Die Bibliothek „oemof-outputlib“ dient in der neuen Version ausschließlich zum Sammeln und Organisieren von Ergebnisdaten. Die Organisation der Daten erfolgt als Python Dictionary. Darin sind die scalaren Werte als pandas Serien enthalten. Alle Knoten und dazwischenliegenden Flüsse werden als pandas DataFrames zurückgegeben. Informationen zur aktuellen Bibliothek findet sich unter: https://pythonhosted.org/ oemof_base/api/oemof.outputlib.html Die Sequenzen/Folgen und Scalare bilden Knoten ab. Diese erhalten intern einen Key wie „(node, None)“. Für die dazwischenliegenden Flüsse werden ebenfalls Keys vergeben, die jeweils ein Tupel beschreiben, wie „(node_1, node_2)“. Über diese Keys können die Daten direkt aus dem Dictionary ausgelesen werden. „Node“ ist der Name des Objektes, welches addresiert werden soll. Wie erfolgt die Sammlung der Daten? Dazu steht das Processing Modul zur Verfügung. Ein Aufruf des Processing Moduls erfolgt mit diesem Programmcode (oemof-Team 2014): results = outputlib.processing.results(om)
Als Ergebnis erhält man die Daten. Um sich bspw. einen Überblick zu verschaffen, welche Flüsse angelegt wurden, ist es möglich, sich die Keys ausgeben zu lassen. Beispielhaft ist dies im folgenden Programmcode modelliert: energysystem.results['main'] = outputlib.processing.results(optimization_model) energysystem.results['meta'] = outputlib.processing.meta_results(optimization_model) print(energysystem.results['main'].keys()) dict_keys([(, ), (, ), (, ), (, ), (, ), (, ), (, ), (, ), (, # ... es folgen weitere Daten, die Liste ist an dieser Stelle abgebrochen.
Da die Keys in dieser Darstellung schwer lesbar sind, können sie in einen String umgewandelt werden. Die Umwandlung der Keys in lesbare strings wird mit dem folgenden Programmcode umgesetzt: string_results = outputlib.views.convert_keys_to_strings(energysystem. results['main']) print(string_results.keys()) dict_keys([('pp_chp', 'bth'), ('pp_lig', 'bel'), ('bel', 'heat_pump'), ('bel', 'demand_el'), ('bth', 'demand_th'), ('oil', 'pp_oil'), ('lignite', 'pp_lig'), ('pp_coal', 'bel'), ('bel', 'excess_el'), ('pv', 'bel'), ('gas', 'pp_chp'), ('pp_gas', 'bel'), ('gas', 'pp_gas'), ('pp_ oil', 'bel'), ('heat_pump', 'bth'), ('wind', 'bel'), ('b_heat_source', 'heat_pump'), ('heat_source', 'b_heat_source'), ('pp_chp', 'bel'), ('coal', 'pp_coal')])
Nun ist es möglich, sich die Daten als Tabelle mithilfe des folgenden Codes ausgeben zu lassen: df = node_results_bel['sequences'] df.head(2)
Der Ausdruck „df“ steht für „pandas DataFrame“. An dieser Stelle wird die „pandas DataFrame“-Ergebistabelle „node_results_bel['sequences']“ an „df“ übergeben: Mit dem Aufruf „df.head(2)“ werden die ersten beiden Zeilen des DataFrame ausgegeben, wie dies in Tab. 4.3 dargestellt ist. Es ist auch möglich, selbst eine Tabelle aufzubauen. Dazu dient der folgende Quellcode: d = {'high' : pd.Series([3., 2., 1.], index=['a', 'b', 'c']), 'low' : pd.Series([4., 3., 2., 1.], index=['a', 'b', 'c', 'd'])} df = pd.DataFrame(d)
Der Ausdruck „d“ ist der Name des Objektes, welches die Ergebnisdaten beinhaltet. Als Ausgabe erhält man:
a b c d
high 3.0 2.0 1.0 NaN
low 4.0 3.0 2.0 1.0
2017-01-01 00:00:00 2017-01-01 01:00:00 0.911379
52.169653
0.0
((bel, heat_ pump), flow) 0.821571
((bel, ((bel, demand_ excess_el), el), flow) flow) 52.169653 0.0 2.450731
((pp_chp, bel), flow) 2.559823
Tab. 4.3 Tabellarische Darstellung der Ergebnisse mit Hilfe der Outputlib
20.2
0.0
11.8
0.0
0.0
((pv, ((pp_coal, ((pp_gas, ((pp_lig, ((pp_oil, bel), bel), flow) bel), flow) bel), flow) bel), flow) flow) 20.2 0.0 11.8 0.0 0.0
18.6303
((wind, bel), flow) 18.4314
4.1 Die Arbeit mit dem Framework von OEMOF 193
194
4 Modellierung in OEMOF
In OEMOF sind die Daten bekanntlich als groupings angelegt. Dies kann man sich bei der Ausgabe der Ergebnisse nützlich machen und den Aufruf über die Groupings starten. Dazu wird der Name der Gruppe aufgerufen, wie die Gruppe „wind“: node_wind = energysystem.groups['wind'] print(results[(node_wind, bus_electricity)])
Toolkit “oemof_visio“ Zusätzlich zur outputlib ist nun das Toolkit “oemof_visio“ zur Visualisierung der Ergebnisse verfügbar. Dazu wird das Toolkit “oemof_visio“ zunächst installiert. Dies wird unter Verwendung von „pypi“ mit dem Kommando „pip install“ durchgeführt: pip install git+https://github.com/oemof/oemof_visio.git
Weitere Infomationen dazu befinden sich aktuell in der Revision v5.0 unter: https://github.com/tbors/oemof_visio. Erhält man dabei eine Fehlermeldung wie nachfolgend, wurde das Program „git“ noch nicht installiert: Error [WinError 2] … while executing command git clone -q https://github.com/oemof/oemof_visio.git C:\Users\...\AppData\Local\Temp\pip- rkdz0ake- build Cannot find command ‘git’
Dies kann nachgeholt werden. Eine Möglichkeit, dies durchzuführen, kann unter https://git-scm.com/ erfolgen. Unter dem Verzeichnis oemof_visio liegt das Paket „plot.py“, in welchem Funktionalitäten zur Ausgabe von Daten angelegt sind, z. B.: • Slicing the DataFrame • Plotting parts of the DataFrame • Creating a color dictionary Siehe dazu auf https://github.com/tbors/oemof_visio/blob/master/oemof_visio/plot.py. Unter den Beispielen von OEMOF in dem neuen Release v0.5. (siehe dazu: https://github.com/oemof/oemof-examples) befindet sich ein Beispiel, mit dem in ein spezielles Verzeichnis für die Vorstellung von Plotting Möglichkeiten gewechselt werden kann, siehe dazu: https://github.com/oemof/oemof-examples/tree/master/oemof_examples/oemof. solph/v0.2.x/plotting_examples. Zur Darstellung von Plotting-Ergebnissen wurde das Beispiel „storage_investment_plot.py“ ausgewählt (siehe dazu: https://github.com/oemof/ oemof-examples/blob/master/oemof_examples/oemof.solph/v0.2.x/plotting_examples/ storage_investment_plot.py). Bei eigenen Programmsystemen wird von Applikationen gesprochen.
4.1 Die Arbeit mit dem Framework von OEMOF
195
Um sich allgemein Ergebnisse unter oemof_visio plotten zu lassen, erfolgt der Aufruf „view“: # Getting results and views results = processing.results(om) custom_storage = views.node(results, 'storage') electricity_bus = views.node(results, 'electricity')
In der Applikation „storage_investment_plot.py“ werden die Daten für den „custom_ storage“ und den „electricity_bus“ ausgegeben. Nachfolgend werden mehrere Varianten für das Plotten vorgestellt. In der ersten Variante wird die Bilanz um die Komponente „custom_storage“ gezogen. Die Ergebnisse in der Grafik a) werden als farbige Linien gezeichnet, wie dies in Abb. 4.1 dargestellt ist. In der Grafik b) wird der datetime ticks geändert. Um diese Ergebnisse zu erhalten, muss folgender Programmcode umgesetzt werden (oemof 2018b). Zunächst für den linken Graphen a): # ***** 1. Example a) *************************************************** # Plot directly using pandas custom_storage['sequences'].plot(kind='line', drawstyle='steps-post') plt.show()
Abb. 4.1 Variante 1: Darstellung der Ergebnisse für die Komponente „custom_storage“ als Linien, (a) Plot direkt mit Pandas, (b) Änderung des datetime ticks. (Nach oemof 2018b)
196
4 Modellierung in OEMOF
Nun für den rechten Graphen b): # ***** 1. Example b) *************************************************** # Change the datetime ticks ax = custom_storage['sequences'].reset_index(drop=True).plot( kind='line', drawstyle='steps-post') ax.set_xlabel('2017') ax.set_title('Change the xticks.') oev.plot.set_datetime_ticks(ax, custom_storage['sequences'].index, date_format='%d-%m', number_autoticks=6) plt.show()
Im nächsten Bild in Abb. 4.2 für die zweite Variante erfolgt die Bilanz um den „electricity_bus“. Zusätzlich werden die Farben geändert. Auch zu dieser Grafik in Abb. 4.2 folgt der Programmcode, beginnend mit der Ausgabe sämtlicher Flüsse der Komponenten im Modell: # ***** 2. Example a) ************************************************** * cdict = { (('electricity', 'demand'), 'flow'): '#ce4aff', (('electricity', 'excess_bel'), 'flow'): '#555555', (('electricity', 'storage'), 'flow'): '#42c77a', (('pp_gas', 'electricity'), 'flow'): '#636f6b', (('pv', 'electricity'), 'flow'): '#ffde32', (('storage', 'electricity'), 'flow'): '#42c77a', (('wind', 'electricity'), 'flow'): '#5b5bae'}
Abb. 4.2 Darstellung der Ergebnisse für den „electricity_bus“ als Linien, (a) Plot direkt mit Pandas, (b) mit geänderten Farben entsprechend des Dictionaries. (Nach oemof 2018b)
4.1 Die Arbeit mit dem Framework von OEMOF
197
# Plot directly using pandas electricity_bus['sequences'].plot(kind='line', drawstyle='steps-post') plt.show()
Jetzt werden die Farben geändert: # ***** 2. Example b) ************************************************** * cdict = { (('electricity', 'demand'), 'flow'): '#ce4aff', (('electricity', 'excess_bel'), 'flow'): '#555555', (('electricity', 'storage'), 'flow'): '#42c77a', (('pp_gas', 'electricity'), 'flow'): '#636f6b', (('pv', 'electricity'), 'flow'): '#ffde32', (('storage', 'electricity'), 'flow'): '#42c77a', (('wind', 'electricity'), 'flow'): '#5b5bae'} # Change the colors using the dictionary above to define the colors colors = oev.plot.color_from_dict(cdict, electricity_bus['sequences']) ax = electricity_bus['sequences'].plot(kind='line', drawstyle='steps-post', color=colors) ax.set_title('Change the colors.') plt.show()
Bei der dritten Variante wird die Bilanz wieder um den „electricity_bus“ gezogen. Nun werden die Inputflows ausgegeben (s. Abb. 4.3). Zur Erstellung dieses Graphen kann wie folgt vorgegangen werden: # ***** 3. example *************************************************** # Plot only input flows in_cols = oev.plot.divide_bus_columns( 'electricity', electricity_bus['sequences'].columns)['in_cols'] ax = electricity_bus['sequences'][in_cols].plot(kind='line', drawstyle='steps-post') ax.set_title('Show only input flows.') plt.show()
Der Term „oev“ steht für das Package oemof_visio. Es wird damit die Plotfunktion aus oemof_visio aufgerufen. Der nächste Term in dem Ausdruck „divide_bus_columns“ erstellt ein Dictionary mit zwei Listen. Die eine Liste enthält die Inputs, während die andere Liste die Outputs enthält. Diese werden an das Dataframe übergeben. Von dort kann
198
4 Modellierung in OEMOF
Abb. 4.3 Darstellung der Ergebnisse für den „electricity_bus“ als Linien, jedoch werden nur die Inputflows selektiert. (Nach oemof 2018b)
Abb. 4.4 Darstellung der Ergebnisse für den „electricity_bus“ als gestapelte Flächen. (Nach (oemof 2018b)
man sich die gewünschte Liste, in diesem Fall die Input Flüsse, ausgeben lassen. Dazu ist am Anfang der Zeile der Ausdruck „in_cols“ für den Namen der Liste der Input Flüsse anzugeben. In der vierten Variante werden die Daten als gestapelte Flächen ausgegeben (s. Abb. 4.4). Es liegt erneut die Bilanz um den „electricity_bus“ zugrunde. Im Programmcode ändert sich folgendes: # ***** 4. example *************************************************** # Create a plot to show the balance around a bus. # Order and colors are customisable.
4.1 Die Arbeit mit dem Framework von OEMOF
199
inorder = [(('pv', 'electricity'), 'flow'), (('wind', 'electricity'), 'flow'), (('storage', 'electricity'), 'flow'), (('pp_gas', 'electricity'), 'flow')] fig = plt.figure(figsize=(10, 5)) electricity_seq = views.node(results, 'electricity')['sequences'] plot_slice = oev.plot.slice_df(electricity_seq, date_from=pd.datetime(2017, 2, 15)) my_plot = oev.plot.io_plot('electricity', plot_slice, cdict=cdict, inorder=inorder, ax=fig.add_subplot(1, 1, 1), smooth=False) ax = shape_legend('electricity', **my_plot) oev.plot.set_datetime_ticks(ax, plot_slice.index, tick_distance=48, date_format='%d-%m-%H', offset=12) ax.set_ylabel('Power in MW') ax.set_xlabel('2017') ax.set_title("Electricity bus")
Mit dem Befehl „inorder“ wird die Reihenfolge der Stapel vorgegeben. Der Fluss der Komponente „pv“ liegt unten und es folgen die Flüsse der Komponente „wind“, „storage“ und „pp_gas“. Zusätzlich wird bei dieser Grafik die y-Achse mit 'Power in MW' beschriftet. Für eine vereinfachte Darstellung kann die gestapelte Fläche geglättet (smooth) dargestellt werden. Das Ergebnis ist in Abb. 4.5 dargestellt.
Abb. 4.5 Darstellung der Ergebnisse für den „electricity_bus“ als gestapelte Flächen, Ränder sind smooth. (Nach oemof 2018b)
200
4 Modellierung in OEMOF
Nachfolgend wird der erforderliche Quellcode zu Abb. 4.5 des letzten Beispiels dargestellt: # ***** 5. example *************************************************** # Create a plot to show the balance around a bus. # Make a smooth plot even though it is not scientifically correct. inorder = [(('pv', 'electricity'), 'flow'), (('wind', 'electricity'), 'flow'), (('storage', 'electricity'), 'flow'), (('pp_gas', 'electricity'), 'flow')] fig = plt.figure(figsize=(10, 5)) electricity_seq = views.node(results, 'electricity')['sequences'] plot_slice = oev.plot.slice_df(electricity_seq, date_from=pd.datetime(2017, 2, 15)) my_plot = oev.plot.io_plot('electricity', plot_slice, cdict=cdict, inorder=inorder, ax=fig.add_subplot(1, 1, 1), smooth=True) ax = shape_legend('electricity', **my_plot) ax = oev.plot.set_datetime_ticks(ax, plot_slice.index, tick_distance=48, date_format='%d-%m-%H', offset=12) ax.set_ylabel('Power in MW') ax.set_xlabel('2017') ax.set_title("Electricity bus") plt.show()
Jetzt ist im Quellcode das Attribut „smooth“ auf „True“ gesetzt. Abbildung der Graphenstruktur Auf eine Besonderheit soll an dieser Stelle noch hingewiesen werden. Es gibt die Möglichkeit, die Grahpenstruktur (Netzplan) des Modells des Optimierungsproblems als Plot ausgeben zu lassen. Dazu sind zwei Packages erforderlich, die installiert werden müssen: • NetworkX • PyGraphviz Es handelt sich bei NetworkX um ein Python Package. Es ist ebenfalls ein Open Source Programm. Dieses dient dazu, komplexe Netzstrukuren anzulegen. Es kann aber auch dazu genutzt werden, diese anschließend zu analysieren und zu verändern.
4.1 Die Arbeit mit dem Framework von OEMOF
201
Das Package NetworkX wird über den Befehl „pip install“ installiert. Weitere Informationen zu dem Programmsystems NetworkX befinden sich unter dem Link: https://networkx.org/. Das Programm PyGraphviz stammt von dem Programmsystem Graphviz ab, welches ebenfalls ein Open Source Programmpaket ist (Graphviz o. J.). Dieses wurde entwickelt, um die aus der Graphentheorie stammenden gerichteten und ungerichteten Graphen zu visualisieren. Das Programm verwendet die Auszeichnungssprache „DOT“. PyGraphviz ist speziell für Python Programme entwickelt. Es ist ein Interface für das Programm Graphviz (PyGraphviz developer team 2016). Mit Hilfe dieses Programms kann innerhallb von Python ein Graph erstellt, bearbeitet und angezeigt werden. Weitere Informationen befinden sich bspw. unter: https://pypi.org/project/pygraphviz/. Das Programm PyGraphviz wird sowhl für Windows als auch andere Betriebssysteme angeboten. Die Installation von PyGraphviz ist allerdings komplizierter, da keine direkte Installation unter Windows möglich ist. Eine Empfehlung für den besten Weg einer Installation unter Windows kann hier nicht gegeben werden. Eine Möglichkeit ist, zunächst das Programm Graphviz zu installieren. Für Windows-Systems kann der Link: https://graphviz.gitlab.io/download/ verwendet werden. Unter der Rubrik Windows gelangt man über die angebotenen Links, wie Stable 2.38 Windows install packages, zu den Installationsmöglichkeiten. Die Version „graphviz-2.38.msi“ ist zum heutigen Zeitpunkt, April 2018, für die Installation sehr gut geeignet. Das Installationspgrogramm schlägt ein Verzeichnis vor, welches gewählt werden kann. Ist das Programm Graphviz installiert, muss noch der Pfad der Systemvariablen angepasst werden. Bei der Installation von OEMOF wird in Abschn. 5.1 die Veränderung der Systemvariablen beschrieben. Der Pfad muss auf die Datei „dot.exe“, welche sich im „bin“-Ordner des Programms Graphviz befindet, angelegt werden. Abschließend muss Graphviz noch über die WinPython Command Prompt Console installiert werden. Dazu wird auf der WinPython Command Prompt Console der Befehl „pip install graphviz“ eingegeben. Bei erfolgreicher Installation kann das Programm unabhängig von Python zur Visualisierung und Analyse der Netzstruktur beitragen. Nun kann PyGraphviz installiert werden. Der Download kann bspw. über den folgenden Link durchgeführt werden: https://pypi.org/project/pygraphviz/#files. Sollte es zu Fehlermeldungen bei der Anwendung kommen, sind über die zusätzliche Installation des Programmsystems „Anaconda“ die fehlenden Bibliotheken nachzuinstallieren. Das Programmsystem „Anaconda“ kann über die folgende Internetseite runtergeladen und installiert werden: https://www.anaconda.com/download/ In Abb. 4.6 ist die Ausgabe eines Optimierungsproblems als Grafenstruktur (Netzplan) mithilfe von PyGraphviz dargestellt.
4.1.7 Oemof-feedinlib Ist das betrachtete Energiesystem als Optimierungsmodell in OEMOF umgesetzt, geht es darum, dem Modell Daten zur Verfügung zu stellen. Während die Bibliothek „demandlib“
202
4 Modellierung in OEMOF
Abb. 4.6 Darstellung des Modells eines Energieversorgungssystems als Netzplan bzw. Graphenstruktur
Daten für den Energiebedarf zur Verfügug stellt, dient die Bibliothek „feedinlib“ dazu, Daten der Komponenten, hier im Speziellen der Photovoltaik- und Windkraftanlage, zur Verfügung zu stellen. Dem Modellierer können die Daten der Photovoltaik- und Windkraftanlage bereits als Zeitreihen vorliegen. Verfügt der Modellierer jedoch nur über Windund Wetterdaten, so müssen die Leistungen in den definierten Zeitabschnitten erst berechnet werden. Diese Berechnung erfolgt in der „feedinlib“. Die Bibliothek „feedinlib“ ist in den cosmos von OEMOF eingebunden, kann aber auch unabhängig davon verwendet werden. Wo Daten erhältlich sind, ist in Abschn. 4.2 aufgeführt.
4.1 Die Arbeit mit dem Framework von OEMOF
203
Informationen zur Modellierung Zur Berechnung der Leistungsdaten werden einige Parameter angesetzt. Dabei ist darauf zu achten, dass die richtigen Einheiten konsequent und einheitlich verwendet werden. Nur so kann ein korrektes Ergebnis errechnet werden. Folgende Einheiten können für die einzelnen Parameter angesetzt werden: • • • • • • •
pressure [Pa] wind speed [m/s] irradiation [W/m2] peak power [W] installed capacity [W] nominal power [W] area [m2]
Die Festlegung der Einheiten, obliegt dem Modellierer. Eine PV-Anlage oder Windkraftanlage jedes einzelnen Herstellers besitzt spezifische technische Daten. Zur Betrachtung eines Energieversorgungssystems werden die Anlagen aufgrund günstiger Rahmenbedingungen, wie bspw. der geografischen Lage, an ausgewählten Standorten aufgestellt. Dies kann auch rein modellhaft betrachtet und vorgegeben werden und nicht die Realität abbilden. Dann erhält man ein anderes Ergebnis, mit welchem nur modellhafte Aussagen möglich sind. Basierend auf dem Aufstellungsort ergeben sich die technischen Daten der Anlagen, mit denen im Weiteren Prozess gerechnet werden kann. Für PV-Anlage und Windkraftanlagen werden folgend beispielhaft mehrere Parameter aufgeführt, die zur Berechnung der Leistung erforderlich sind. In Tab. 4.4 werden Parameter für Windkraftanlagen aufgeführt. Für ein PV-Modul können bspw. die in Tab. 4.5 aufgeführten Parameter verwendet werden. In OEMOF liegen bereits Daten zu PV-Modulen und Windkraftanlagen vor, die direkt verwendet werden können. Diese enthalten die aufgeführten Parameter. Jeder Anwender kann aber ebensogut Daten selbst ausgewählter Anlagen seinem Modell zur Verfügung stellen. Um nun die eigene Windkraftanlage bzw. das eigene PV-Modul zu initialisieren, muss bspw. eine Excel Datei mit einem Datenblatt (Factsheet) angelegt werden, welches die zur Berechnung notwendigen Daten beinhaltet. Wesentliche Information ist der Name der Anlage. Der Name muss als erster Parameter auf dem Datenblatt angelegt werden.
Tab. 4.4 Beispielhafte Darstellung der Parameter für Windkraftanlagen. (oemof developing group 2015) Parameter h_hub d_rotor wind_conv_type
Beschreibung height of the hub in meters diameter of the rotor in meters Name of the wind converter according to the list in the csv file
204 Tab. 4.5 Beispielhafte Darstellung der Parameter für ein PV-Modul. (oemof developing group 2015)
4 Modellierung in OEMOF Azimuth angle of the pv module in azimuth degree tilt Tilt angle of the pv module in degree module_name According to the sandia module library (s. Abschn. 4.2) albedo Albedo value
Um eine Windkraftanlage oder ein PV-Modul in OEMOF anzulegen, kann folgender Quellcode angegeben werden: # Es wird nachfolgend ein eigenes Windturbinen und PV-Modul Objekt # erzeugt. Jedem Objekt werden von der Klasse „WindPowerPlant“ bzw. # „Photovoltaic“ ein Modell (SimpleWindModel bzw. PvlibBased) übergeben. # Auf die Modelle wird zur Berechnung der feedin Daten zurückgegriffen. # Desweiteren wird ein eigenes Parameterset übergeben. Dies wird über den # Ausdruck „**my_parameter_set“ ausgedrückt. Hierin wird durch die # vorangestellten „**“ ein Dictionary als Keyed-Parameter übergeben. my_wind_turbine = plants.WindPowerPlant(model=SimpleWindModel, **my_parameter_set) my_pv_module = plants.Photovoltaic(model=PvlibBased, **my_parameter_set)
Der im o. g. Programmcode genannte Keyed-Parameter ist ein Parameter einer Funktion, der nicht durch die Position, sondern durch den Namen beim Aufruf zugeordnet wird, z. B. model=SimpleWindModel, mit model als Namen. Sind Argumente für den Aufruf in einem Dictionary gespeichert (als Name-Wert-Paar) können die Werte den entsprechenden Keyed-Parametern zugeordnet werden, wenn dem Dictionary „**“ vorangestellt wird. Ohne diese „Sterne“ müsste jeder einzelne Wert beim Aufruf angegeben werden. Die feedinlib berechnet die Eingabedaten (Zeitreihen/Lastgänge) für Photovoltaik- und Windkraftanlagen aus dem Wetterdaten Set (weather data sets). Man erhält den elektrischen Output aus den vorgegebenen Anlagen. Die feedinlib verfügt über zwei Bibliotheken, die pvlib und die windpowerlib. Hierfür stehen ein Wind- und ein PV-Modell zur Verfügung. Es ist ebenso möglich, ein eigenes Wind- bzw. Wettermodell mit eigenen Berechnungen zu hinterlegen. Wird ein eigenes Wind- und PV-Modell genutzt, empfiehlt es sich, eine Liste der erforderlichen Daten an das Model zu übergeben: own_wind_model = models.YourWindModelClass(required=[parameter1, parameter2]) own_pv_model = models.YourPVModelClass() your_wind_turbine = plants.WindPowerPlant(model=own_wind_model, **your_parameter_set)
4.1 Die Arbeit mit dem Framework von OEMOF
205
your_pv_module = plants.Photovoltaic(model=own_pv_model, **your_parameter_set) feedin_series_wp1 = your_wind_turbine.feedin(data=my_weather_df, number=5) feedin_series_pv1 = your_pv_module.feedin(data=my_weather_df) # One Module
Unter Verwendung der feedinlib wird ein von OEMOF abgekapseltes Wetterobjekt initialisiert. Ein Wetterobjekt benötigt ein Set an Wetterdaten sowie weitere mögliche erforderliche Metadaten, z. B. zum Standort. Zur Initialisierung wird eine Klasse „FeedinWeather“ eingeführt. Dieser Klasse wird das eigene Wetterdatenset übergeben: my_weather_a = weather.FeedinWeather( data=my_weather_pandas_DataFrame, timezone='Continent/City', latitude=x, longitude=y, data_heigth=coastDat2 )
Als „timezone“ kann bspw. Europe/Berlin oder Australien/Sydney gewählt werden. Für die Attribute „latitude“ und „longitude“ werden Float-Werte vergeben. Für das Wettermodell in OEMOF müssen nicht alle Parameter angegeben werden, dies gilt bspw. für die Parameter „latitude“ und „longitude“. Ebenso ist der Parameter „timezone“ nicht erforderlich, sofern ein kompletter time index (full time index) mit einer Zeitzone vergeben wurde. Bei dem „coastDat2“ handelt es sich um ein spezielles Dictionary für die Bewertung von Wetterstatistiken und Klimaveränderungen (https://essd.copernicus.org/articles/6/147/2014/). Dieses wurde in den letzten Jahren weiterentwickelt (s. https://www.coastdat.de/about_us/index.php.en). Im Folgenden wird noch auf „coastDat2“ zugegriffen. Sollen im Optimierungsmodell Wind- und PV-Berechnungen durchgeführt werden, sind für das PV Modell Informationen zur Strahlung, Temperatur und Windgeschwindigkeit erforderlich. Für Berechnungen im Wind-Modell sind Daten zu Druck, Windgeschwindigkeit, Temperatur und die Rauhheitslänge notwendig. Für das „data_height“ dicionary müssen diese Daten beispielhaft wie folgt angegeben werden: coastDat2 = { 'dhi': 0, 'dirhi': 0, 'pressure': 0, 'temp_air': 2, 'v_wind': 10, 'Z0': 0}
206
4 Modellierung in OEMOF
Wird vom Modellierer ein eigenes Dictionary mit eigenen Bezeichnungen für die Spalten verwendet, müssen diese Bezeichnungen mit denjenigen in der feedinlib verwendeten Bezeichnungen überschrieben werden. Dies ist auf folgendem Weg möglich: name_dc 'my 'my 'my 'my 'my 'my
= { diffuse horizontal radiation': 'dhi', direct horizontal radiation': 'dirhi', pressure data set': 'pressure', ambient temperature': 'temp_air', wind speed': 'v_wind', roughness length': 'z0'}
my_weather_DataFrame.rename(columns=name_dc)
Wird die feedinlib zusammen mit OEMOF verwendet, müssen Daten für die „time series“ im eigenen Optimierungsmodell vorgegeben werden. Dazu wird das Wetter-Objekt dem eigenen Modell übergeben. Wird nur das Wetter-Objekt übergeben, kann die elektrische Leistung einer Turbine berechnet werden. Für die Berechnung für nur ein PV-Modul ist folgender Quellcode interessant: feedin_series_pv1 = my_pv_module.feedin(weather=my_weather_df)
Es ist aber auch möglich, Daten für mehr als eine Wind- oder PV-Anlage zu berechnen. Dazu können bspw. die Parameter „number“ und „installed capacity“ für die Berechnung von Windkraftanlagen angegeben werden. Für PV-Module eignen sich die Parameter „number“, „peak_power“ und „area“. Für die Berechnung von mehreren Wind-Modulen kann der Quellcode wie folgt erweitert werden: feedin_series_wp1 number=5)
=
your_wind_turbine.feedin(data=my_weather_df,
Den Berechnungsmodellen für PV- und Wind-Modulen liegen die nachfolgenden mathematischen Beschreibungen zugrunde. Berechnungsansatz im PV-Modul Dem Berechnungsmodell für das PV-Modell liegt die pvlib library unter: http://pvlib- python.readthedocs.io/en/latest/ zugrunde (Krien et al. 2017). Für eine Photovoltaik Anlage sind der Sonnenstand und der Einfallswinkel der Sonne von hoher Wichtigkeit. Der Einfallswinkel lässt sich aus dem Sonnenstand und der Ausrichtung des PV-Modules berechnen (https://pvlib-python.readthedocs.io/en/stable/_modules/pvlib/irradiance.html). Zur Bestimmung des Sonnenstandes bietet die feedinlib das ephemeris Modell der pvlib an (pvlib.solarposition.get_solarposition). Viele Wetter Datenbanken stellen stündliche Mittelwerte eines jeden Parameters zur Verfügung, die zur Be-
4.1 Die Arbeit mit dem Framework von OEMOF
207
rechnung verwendet werden können. Aus diesem Grund berechnet auch das Modell der feedinlib stündliche Mittelwerte des Sonnenstandes basierend auf 5-Minuten Werten (Krien et al. 2017). Damit keine nicht darstellbaren Werte (nan-values) ausgegeben werden, werden Werte kleiner 0°und größer als 90°abgeschnitten. In das Modell der pvlib geht die Direktnormalstrahlung (direct normal irradiation: dni) ein. Dieser Wert wird in der feedinlib mittels eines geometrischen Ansatzes berechnet (s. Gl. 4.1): dni =
dirhi æp ö sin ç - a zenith ÷ è2 ø
(4.1)
Darin sind: dni direct horizontal irradiation, dieser Wert wird vom Wetterdatenset bereitgestellt. αzenith Sonnenzenitwinkel Es können große Abweichungen für den Wert von dni bei kleinen Abweichungen der Ergebnisse aus dem Modell zur Bestimmung der Sonnenposition und dem Wettermodell bzw. eigenen zur Verfügung gestellten Messwerten entstehen. Dies tritt jedoch nur auf, wenn der Sonnenzenitwinkel (αzenith) einen Winkel größer 88°aufweist, da dann der Nenner (denominator) der Gl. 4.1 sehr klein wird. Unter der Annahme, dass Abstrahlung nahe dem höchsten Punkt klein ist, sowie der Effekt der Abstrahlung bei in-plane Irradiance sehr klein ist, kann für αzenith größer 88° die direct horizontal irradiation dirhi mit der Direktstrahlung dni gleichgestezt werden (s. Gl. 4.2). dni = dirhi
(4.2)
Die Bestrahlungsstärke auf der Ebene (In-plane Irradiance) ist definiert als das Sonnenlicht, welches schräg auf den Boden scheint (Tamura et al. 2003). Weiterhin wird in der feedinlib zur Bestimmung der diffusen Strahlung aus dem Himmel das Perez Modell eingesetzt. Dazu werden von der pvlib die folgenden Funktionen verwendet (Krien et al. 2017): • • • • •
pvlib.irradiance.extraradiation pvlib.atmosphere.relativeairmass pvlib.irradiance.perez pvlib.irradiance.grounddiffuse pvlib.irradiance.globalinplane
Die Größe “pvlib.irradiance.grounddiffuse” dient dazu, die Erdrefllexion zu berechnen. Des weiteren werden über die Funktion “global in plane” (pvlib.irradiance.globalinplane) die verschiedenen Licht-Fraktionen aufsummiert.
208
4 Modellierung in OEMOF
Der elektrische Output eines PV Modules wird über die Größe “pvlib.pvsystem.sapm” ausgedrückt. Zur Berechnung des elektrischen Outputs wird in der feedinlib das Sandia PV Array Performance Model (SAPM) verwendet. Der elektrische Output kann erst berechnet werden, wenn die Temperatur der Solarzelle (pvlib.pvsystem.sapm_celltemp) bestimmt wurde. Die Temperatur der Solarzelle ist eine wichitge Größe im SAPM. Berechnungsansatz im Wind-Modul Dem Wind-Modul liegt ein einfacher Ansatz zugrunde (Krien et al. 2017). Wesentlich ist der cp-Wert (Leistungsbeiwert) einer spezifischen Windkraftanlage. Die Daten zum cp-Wert für eine Anlage können beim Hersteller abgefragt werden. Die Werte liegen in einer Liste mit diskreten Windgeschwindigkeiten in Stufen von 0,5 bis 1 m/s vor. Für die feedinlib ist eine kontinuierliche Kurve der cp-Werte über der Windgeschwindigkeit relevant. Um dies zu erreichen, wird in der feedinlib eine lineare Interpolation der cp-Werte durchgeführt (s. Gl. 4.3).
mit drotor ρair, hup vwind, hub cp
1 2 3 Pwpp = × r air ,hup × drotor × p × vwind , hub × cp ( vwind , hub ) 8
(4.3)
Durchmesser des Rotors in [m] Luftdichte auf Höhe der Nabe, angenommene Temperatur 6,5 K und ein Druckgradient von -1/8 hPa/m Windgeschwinigkeit auf Nabenhöhe cp-Werte gegen die Windgeschwingikeit
Die Windgeschwinigkeit auf Nabenhöhe berechnet sich aus Gl. 4.4:
vwind ,hub mit vwind, hub hhub hwind, data
æh ö ln ç hub ÷ è z0 ø = vwind , data × æh ö ln ç wind , data ÷ è z0 ø
(4.4)
Windgeschwindigkeit auf der Höhe entsprechend des Wettermodells oder der Messung Höhe der Nabe Höhe der Windgeschwinigkeitsmessung oder Höhe der Windgheschwindigkeit innerhallb des Wettermodells
Über Gl. 4.4: wird ein logarithmisches Windprofil errechnet.
4.1 Die Arbeit mit dem Framework von OEMOF
209
Die Temperatur auf Höhe der Nabe berechnet sich nach Gl. 4.5: THub = Tair , data - 0, 0065 × ( hhub - hT , data )
mit Tair, data hhub hT, data
(4.5)
Temperatur in der Höhe entsprehend des Wettermodells oder der Messung Höhe der Nabe Höhe der Temperatumessung oder Höhe der Temperatur innerhalb des Wettermodells
Ist die Temperatur auf Höhe der Nabe berechnet, kann im nächsten Schritt die Luftdichte bestimmt werden (s. Gl. 4.6):
r air ,hub mit pdata Thub hhub hp, data
1ö æ pdata ç 100 - ( hhub - hp, data ) × 8 ÷ è ø = ( 2,8706 × Thub )
(4.6)
Druck in der Höhe entsprechenddes Wettermodells oder der Messung Temperatur oder Luft auf Höhe der Nabe Höhe der Nabe Höhe der Luftmessung oder Höhe des Drucks aus dem Wettermodell
Informationen zum Programmcode Zur Bibliothek feedinlib gehören unter anderem das Verzeichnis „models“ als auch das Modul „powerplant“: • models Zu finden unter: https://github.com/oemof/feedinlib/tree/dev/src/feedinlib/models • powerplants.py Zu finden unter: https://github.com/oemof/feedinlib/blob/dev/src/feedinlib/powerplants.py Beginnen wir mit dem Modul „models“. In dem Modul sind mehrere Klassen und deren Methoden definiert. Die erste Klasse ist die Basisklasse „Base(ABC)“ für die feedinlib Modelle: class Base(ABC):
Die Klasse „PvlibBased(Base)“ ist für den Output aus dem Photovoltaik Modell erforderlich: class PvlibBased(Base)
210
4 Modellierung in OEMOF
Hier werden Berechnungen angestellt, die auf der pvlib-Bibliothek basieren. Um diese Bibliothek zu importieren, kann wie folgt vorgegangen werden: from feedinlib import models pv_model = models.PvlibBased()
Diese Klasse beinhaltet die Methode „def feedin(self, **Kwargs)“, mit der die Zeitreihen für das vorgegebene PV Module eingelesen werden. Ausgegeben werden Zeitreihen für den Strom Output als Pandas-Serie für ein gegebenes PV-Modell. Die nächste Methode „def solarposition_hourly_mean(self, location, data, **kwargs)“ bestimmt die mittlere stündliche Sonnenposition. Die Berechnung erfolgt über alle Winkel über dem Horizont. Für diese Methode sind mehrere Parameter relevant, die Tab. 4.6 entnommen werden können. Vom „pandas.DataFrame“ kommen die Parameter azimuth, zenith und elevation hinzu. Mit Hilfe des folgenden Programmcodes kann die Sonnenposition für eine vorgegebene Zeit berechnet und ausgegeben werden: data_5min = pd.DataFrame( index=pd.date_range(data.index[0], periods=data.shape[0]*12, freq='5Min', tz=kwargs['weather'].timezone)) data_5min = pvlib.solarposition.get_solarposition( time=data_5min.index, latitude=location.latitude, longitude=location.longitude, method='ephemeris') return pd.concat( [data,data_5min.clip_lower(0).resample('H').mean()], axis=1, join='inner')
Tab. 4.6 Parmeter für die Methode „def solarposition_hourly_mean(self, location, data, **kwargs)“. (GitHub 2018b) Parameter Bezeichnung Location pvlib.location. Location Data pandas.DataFrame Method string, optional
Freq
string, optional
Beschreibung A pvlib location object containing the longitude, latitude and the timezone of the location. Containing the time index of the location. Method to calulate the position of the sun according to the methods provided by the pvlib function (default: 'ephemeris') 'pvlib.solarposition.get_solarposition'. The time interval to create the hourly mean (default: '5Min').
4.1 Die Arbeit mit dem Framework von OEMOF
211
Über die Methode „def solarposition(self, location, data, **kwargs)“ wird mit Hilfe des Zeitindexes die Sonnenposition ermittelt. Die Umsetzung erfolgt über: import pvlib import pandas as pd from feedinlib import models loc = pvlib.location.Location(52, 13, 'Europe/Berlin') pvmodel = models.PvlibBased() data = pd.DataFrame(index=pd.date_range(pd.datetime(2010, 1, 1, 0), periods=8760, freq='H', tz=loc.tz)) elevation = pvmodel.solarposition(loc, data).elevation print(round(elevation[12], 3)) 14.968 return pd.concat(
[data, pvlib.solarposition.get_solarposition( time=data.index, latitude=location.latitude, longitude=location.longitude, method=kwargs.get('method', 'ephemeris'))], axis=1, join='inner')
Der Winkel der Inzidenz wird über die „pvlib aoi funktion“ bestimmt. Dazu steht die Methode „def angle_of_incidence(self, data, **kwargs)“ zur Verfügung. Für diese Methode sind die Parameter aus Tab. 4.7 erforderlich. Zurückgegeben wird der Einfallswinkel in Grad als pandas.serie über: return pvlib.irradiance.aoi( solar_azimuth=data['azimuth'], solar_zenith=data['zenith'], surface_tilt=self.powerplant.tilt, surface_azimuth=self.powerplant.azimuth)
Die Methode „def global_in_plane_irradiation(self, data, **kwargs)“ ermittelt die global Einstrahlung auf die geneigte Fläche. Bei dieser Methode werden die direkte und diffuse Strahlung, der Auftreffwinkel und die Ausrichtung der Fläche berücksichtigt, um die globale Strahlung in der Ebene zu bestimmen. Es werden dazu die Methode „pvlib.irradiance.globalinplane function“ sowie weitere Funktionen der Module pvlib.atmosphere Tab. 4.7 Parmeter für die Methode „def angle_of_incidence(self, data, **kwargs)“. (GitHub 2017p) Parameter Data Tilt Azimuth
Bezeichnung pandas.DataFrame float float
Beschreibung Containing the timeseries of the azimuth and zenith angle. Tilt angle of the pv module (horizontal = 0°). Azimuth angle of the pv module (south = 180°).
212
4 Modellierung in OEMOF
Tab. 4.8 Parmeter für die Methode „def global_in_plane_irradiation(self, data, **kwargs)“. (GitHub 2017p) Parameter Bezeichnung Data pandas. DataFrame Tilt Float azimuth Float albedo Float
Beschreibung Containing the time index of the location and columns with the following timeseries: (dirhi, dhi, zenith, azimuth, aoi). Tilt angle of the pv module (horizontal = 0°). Azimuth angle of the pv module (south = 180°). Albedo factor around the module.
und die pvlib.solarposition verwendet. Aus diesen werden die Input Parameter für die „globalinplane function“ generiert. Die Methode verwendet die Parameter aus Tab. 4.8 Als Pandas.DataFrame werden zusätzliche Spalten vorgegeben; poa_global, poa_diffuse und poa_direct. Nun folgen einige Funktionen für die Bestimmung wichtiger Größen (GitHub 2017p): • Bestimme die extraterrestrische Strahlung: data['dni_extra'] = pvlib.irradiance.extraradiation(datetime_or_doy=data. index.dayofyear)
• Bestimme die relative Luftmasse: data['airmass'] = pvlib.atmosphere.relativeairmass(data['zenith'])
• Bestimme die direkte normale Bestrahlung: data['dni'] = (data['dirhi']) / np.sin(np.radians(90 - data['zenith']))
• Hier kommt die Vorgabe, wenn αzenith größer 88° ist, wird die direkte horizontale Einstrahlung dirhi mit der Direktstrahlung dni gleichgesetzt: data['dni'][data['zenith'] > 88] = data['dirhi']
• Bestimme die diffuse Himmelseinstrahlung in der Ebene mit dem Modell von Perez: data['poa_sky_diffuse'] = pvlib.irradiance.perez( surface_tilt=self.powerplant.tilt, surface_azimuth=self.powerplant.azimuth, dhi=data['dhi'], dni=data['dni'], dni_extra=data['dni_extra'], solar_zenith=data['zenith'], solar_azimuth=data['azimuth'], airmass=data['airmass'])
4.1 Die Arbeit mit dem Framework von OEMOF
213
• Setze NaN Werte auf Null: data['poa_sky_diffuse'][ pd.isnull(data['poa_sky_diffuse'])] = 0
• Bestimme die diffuse Strahlung aus der Bodenreflexion in der Ebene: data['poa_ground_diffuse'] = pvlib.irradiance.grounddiffuse( ghi=data['dirhi'] + data['dhi'], albedo=self.powerplant.albedo, surface_tilt=self.powerplant.tilt)
• Bestimme die gesamte Bestrahlungsstärke in der Ebene: data = pd.concat( [data, pvlib.irradiance.globalinplane( aoi=data['aoi'], dni=data['dni'], poa_sky_diffuse=data['poa_sky_diffuse'], poa_ground_diffuse=data['poa_ground_diffuse'])], axis=1, join='inner') return data
Über die folgende Funktion werden die Moduldaten von der Sandia Module Library geholt: def fetch_module_data(self, lib='sandia-modules', **kwargs)
Für die Ausgabe der Daten aus dem PV-System steht die folgende Funktion zur Verfügung: def pv_module_output(self, data, **kwargs)
Dazu wird die Funktion „pvlib.pvsystem.sapm“ der pvlib mit den Paramtern aus Tab. 4.9 genutzt. Im DataFrame von Pandas (pandas.DataFrame) werden weitere Spalten mit zwei neuen Parametern angegeben: p_pv_norm und p_pv_norm_area. Abschließend bekommt man über die folgende Funktion den Output eines gegebenen PV-Modules (GitHub 2017p): def get_pv_power_output(self, **kwargs)
214
4 Modellierung in OEMOF
Tab. 4.9 Parmeter für die Methode „pv_module_output(self, data, **kwargs)“. (GitHub 2017p) Parameter module_ name Data
Method
Bezeichnung string
Beschreibung Name of a pv module from the sam.nrel database.
pandas. DataFrame
Containing the time index of the location and columns with the following timeseries: (temp_air [K], v_wind, poa_global, poa_ diffuse, poa_direct, airmass, aoi). string, optional Method to calulate the position of the sun according to the methods provided by the pvlib function (default: 'ephemeris') 'pvlib. solarposition.get_solarposition'.
Tab. 4.10 Parmeter für das Modell „SimpleWindTurbine“. (GitHub 2017p) Parameter h_hub d_rotor wind_conv_ type
Datentyp Float Float String
Beschreibung Height of the hub of the wind turbine. Diameter of the rotor [m]. Name of the wind converter type. Use self.get_wind_pp_types() to see a list of all possible wind converters.
Es folgen die erforderlichen Methoden und Funktionen für die Klasse „ SimpleWind Turbine(Base)“: class SimpleWindTurbine(Base)
Um diese Klasse in der eigenen Applikation aufzurufen, kann wie folgt geschrieben werden: from feedinlib import models required_ls = ['h_hub', 'd_rotor', 'wind_conv_type', 'data_height'] wind_model = models.SimpleWindTurbine(required=required_ls)
Soll das Windmodell genutzt werden, sind folgende Parameter wesentlich (s. Tab. 4.10): Im nächsten Modul „powerplants.py“ befindet sich Quellecode zur Initialisierung der Klassen: • • • •
class Base(ABC) Dies ist die Basisklasse für die feedinlib der Powerplants class Photovoltaic(Base) class WindPowerPlant(Base)
Zu diesen Klassen bestehen für die Ausführungen in diesem Buch keine wesentlichen Methoden, sodass an dieser Stelle nicht weiter darauf eingegangen wird.
4.1 Die Arbeit mit dem Framework von OEMOF
215
4.1.8 Oemof-demandlib Die Bibliothek „demandlib“ stellt Strom- und Wärmelastgänge von Verbrauchern zur Verfügung. Hierbei nutzt OEMOF die Daten des Bundesverbandes der deutschen Energieund Wasserwirtschaft (BDEW) und stellt diese den Nutzern zur Verfügung. Als Nutzer können die Lastprofile des BDEW auf die eigenen Bedürfnisse einer jährlichen Nachfrage angepasst werden. Als zentrales Modul in OEMOF dient die Datei „bdew.py“. Diese stellt zwei Klassen zur Verfügung, eine für den Stromlastgang und eine für den Wärmelastgang von Gebäuden (oemof developing group 2016): • class demandlib.bdew.ElecSlp(year, seasons=None, holidays=None) Basierend auf der BDEW Methode werden in dieser Klasse standardisierte Stromprofile generiert. • class demandlib.bdew.HeatBuilding(df_index, **kwargs) In diese Klasse werden die Wärmelastgänge für Gebäude erzeugt. Die erste Klasse „ElecSlp“ verfügt über die folgenden Attribute (s. Tab. 4.11): In der folgenden Tab. 4.12 werden die Parameter zur Berechnung der Lastprofile aufgeführt. Weiterhin stehen drei Methoden zur Verfügung, über die die Berechnung der Profile erfolgt (s. Tab. 4.13): Das Attribut „ann_el_demand_per_sector“ in der Methode „get_profile“ ist ein Dictionary mit jährlichen Werten. Als Ergebnis erhält der Nutzer von dieser Klasse eine Tabelle mit allen Lastgangprofilen in Form eines pandas.DataFrame. Für die Klasse „HeatBuilding“ existiert ein Parameter (s. Tab. 4.14).
Tab. 4.11 Attribute der Klasse „demandlib.bdew.ElecSlp(year, seasons=None, holidays=None)“. (oemof developing group 2016) Attribute datapath date_time_index
Datentyp string pandas.DateTimeIndex
Beschreibung Path to the csv files containing the load profile data. Time range for and frequency for the profile.
Tab. 4.12 Parameter der Klasse „demandlib.bdew.ElecSlp(year, seasons=None, holidays=None)“. (oemof developing group 2016) Parameter Datentyp year integer Optionale Parameter seasons dictionary holidays dictionary or list
Beschreibung Year of the demand series. Describing the time ranges for summer, winter and transition periods. The keys of the dictionary or the items of the list should be datetime objects of the days that are holidays.
216
4 Modellierung in OEMOF
Tab. 4.13 Methoden der Klasse „demandlib.bdew.ElecSlp(year, seasons=None, holidays=None)“. (oemof developing group 2016) Methode all_load_profiles(time_df, holidays=None) create_bdew_load_profiles(dt_index, slp_ types, holidays=None) get_profile(ann_el_demand_per_sector)
Beschreibung Hierüber werden die Profile geladen. Calculates the hourly electricity load profile in MWh/h of a region. Get the profiles for the given annual demand.
Tab. 4.14 Parameter der Klasse „demandlib.bdew.HeatBuilding(df_index, **kwargs)“. (oemof developing group 2016) Parameter year
Datentyp integer
Beschreibung Year or which the profile is created.
Tab. 4.15 Attribute der Klasse „demandlib.bdew.HeatBuilding(df_index, **kwargs)“. (oemof developing group 2016) Attribute datapath temperature
Datentyp string pandas. Series float
annual_heat_ demand building_class integer shlp_type
string
wind_class
integer
ww_incl
boolean
Beschreibung Path to the bdew basic data files (csv). Series containing hourly temperature data. Annual heat demand of building in kWh. Class of building according to bdew classification possible numbers are: 1–11. Type of standardized heat load profile according to bdew possible types are: GMF, GPD, GHD, GWA, GGB, EFH, GKO, MFH, GBD, GBA, GMK, GBH, GGA, GHA. Wind classification for building location (0 = not windy or 1 = windy). Decider whether warm water load is included in the heat load profile.
Auch hier gibt es Attribute, die in Tab. 4.15 aufgeführt sind. Weiterhin verfügt die Klasse „HeatBuilding“ über Methoden (s. Tab. 4.16). Zur Berechnung einer Serie an mittleren Temperaturen, kann Gl. 4.7 herangezogen werden. T= mit TD TD − i
TD + 0, 5 × TD -1 + 0, 25 × TD - 2 + 0,125 × TD -3 1 + 0, 5 + 0, 25 + 0,125
Mittelwert der Temperatur an dem entsprechenden Tag Mittelwert der Temperatur am Tag – i –
(4.7)
4.2 Beschaffung von Daten
217
Tab. 4.16 Methoden der Klasse „demandlib.bdew.HeatBuilding(df_index, **kwargs)“. (oemof developing group 2016) Methode get_bdew_profile()
Beschreibung Calculation of the hourly heat demand using the bdew- equations. get_sf_values(filename='shlp_hour_ Determine the h-values.Hierfür ist ein Parameter, factors.csv') „filename“ (string) – name of file where sigmoid factors are stored, wichtig. get_sigmoid_ Retrieve the sigmoid parameters from csv-files. Auch hier parameters(filename='shlp_sigmoid_ braucht es den Parameter „filename“ (string) – name of factors.csv') file where sigmoid factors are stored. get_temperature_interval() Appoints the corresponding temperature interval to each temperature in the temperature vector. get_weekday_ Retrieve the weekday parameter from csv-file, mit dem parameters(filename='shlp_ Parameter „filename“. weekday_factors.csv') weighted_ A new temperature vector is generated containing a multitemperature(how='geometric_series') day average temperature as needed in the load profile function. Hier gibt es den Parameter „how“ (string) – string which type to return (“geometric_series” or “mean”).
Der BDEW hat zwischenzeitlich ein eigenes Python Programmsystem aufgebaut, welches unter https://pypi.org/project/bdew-datetimes/ zu finden ist. In OEMOF wurde für die BDEW-Daten ein eigenes Verzeichnis unter https://github.com/oemof/demandlib/tree/ dev/src/demandlib/bdew angelegt.
4.2 Beschaffung von Daten Die Beschaffung von Daten ist für die Optimierung eines Energieversorgungssystems elementar. Es nützt kein Modell, wenn anschließend keine Daten verfügbar sind. Bei der Suche nach Daten sind zwei Aspekte wichtig: • Aktualität der Daten • Akkumulation der Daten Eine Akkumulation erfolgt einmal auf der zeitlichen, aber auch auf der räumlichen Skala. So können Stunden-, Viertelstunden- oder Minutenwerte benötigt werden. Die räumliche Skala ergibt sich aus der eigenen Fragenstellung. Anhand der Fragenstellung wird eine Bilanzgrenze gezogen. Aus der Bilanzgrenze ergibt sich, ob Daten für eine gesamte Region, ein ganzes Land oder ein Wohnviertel gesucht werden. Die Frage ist, wie kleinteilig das Modell werden soll. Je kleinteiliger, desto detailliertere Daten werden benötigt.
218
4 Modellierung in OEMOF
Desweiteren werden u. a. Daten benötigt zu: • Stromlastgänge für –– Gebäuden –– Industrie • Wärmelastgänge für –– Gebäude, Raumwärme, Warmwasser –– Industrie, Raumwärme, Warmwasser –– Industrie, Prozesswärme • Mobilität mit den Parametern: –– Kraftstoffe/Energie –– Fahrstrecken pro Tankfüllung/Energiegehalt • Anlagentechnik • Kraftstoffen • Netze (Gas, Strom, Fernwärme) • Potenziale, wie Reststoffe für Biogas, Ausbaumöglichkeiten für Power-to-Heat, Dachoder Grünflächen für Photovoltaikanlagen Jeder Modellierer stellt sich zu Beginn seines Projektes die Frage, woher diese Daten bezogen werden können. Daten sind ein sehr wertvolles Gut und darum zumeist nur sehr kostenintensiv zu beziehen. Jedoch gibt es auch neue Entwicklungen, Daten kostenfrei anzubieten. Dazu läuft seit 01.01.2017 beim Projektträger Forschungszentrum Jülich GmbH das Projekt „Verbundvorhaben: Harmonisierung und Entwicklung von Verfahren zur regional und zeitlich aufgelösten Modellierung von Energienachfragen (DemandRegio), Teilvorhaben: Indikatoren“. Dieses wurde in 2020 abgeschlossen (s. https://www.fz- juelich.de/de/iek/iek-ste/forschung/projekte/abgeschlossene-projekte/demandregio). Im Rahmen dieses Projektes erfolgte die Entwicklung eines einheitlichen und transparenten Verfahrens zur zeitlichen Auflösung und zur Regionalisierung von Strom- und Gasnachfragen. Die erhobenen Daten sollen anschließend interessierten Nutzern im Rahmen von OpenData Initiativen zur Verfügung gestellt werden. Durch die Harmonisierung von Modelleingangsdaten und die Verringerung von Datenunsicherheiten können zukünftig unterschiedliche Modell- und Szenarioergebnisse miteinander verglichen werden. Aussagen zu zukünftigen Entwicklungen können dann belastbarer veröffentlicht werden. Ein anderes Projekt nennt sich „OPSD project“ (https://open-power-system-data.org/). Das Projekt stellt dem Anwender eine Open Power System Data Plattform frei zur Verfügung. Im Rahmen des Projektes werden veröffentlichte Daten auf ein einheitliches Format aufbereitet, sodass deren Verwendung vereinfacht wird. Weitere Datenquellen sind z. B.: • Studien Hierbei ist auf die Aktualität der Studie zu achten. Ein weiterer wichtiger Aspekt ist die verwendete Methode, mit der die Daten erhoben wurde. Die Methode ist wichtig, vor
4.2 Beschaffung von Daten
219
allem vor dem Hintergrund, wenn Daten aus unterschiedlichen Studien verwendet werden sollen. Studien werden zum Beispiel von Verbänden, wie dem Bundesverband der Energie- und Wasserwirtschaft (BDEW), Bundesverband Erneuerbare Energie (BEE) oder Bundesverband WindEnergie (BWE), erstellt. Ebenso werden Studien von Unternehmen, wie der Agora Energiewende, erstellt. Dazu gehören auch Unter nehmensberatungen, die im Auftrag von Verbänden oder der Politik Studien erstellen. • Energieversorger Es gibt Energieversorger für Gas, Strom und Wärme. Manche Versorger stellen Daten im Internet zur Verfügung. Meist ist es jedoch erforderlich, die Versorger zu kontaktieren und Daten in der gewünschten Auflösung abzufragen. In Abb. 4.7 und 4.8 sind beispielhaft für Deutschland die Stromnetzbetreiber grafisch aufgeführt. Es zeigt sich, dass je nach Spannungsebene (Hoch-, Mittel- und auch Niederspannungsnetz) unterschiedliche Stromnetzbetreiber am Markt aggieren. Die Stromnetzbetreiber der Hochspannungsebene sind flächendeckender verteilt, als in der Mittelspannungsebene. Ansprechpartner für die Abfrage von Lastgängen sind damit je Spannungsebene auszuwählen. • Datenbanken Dazu zählt bspw. die Energiedatenbank (http://www.energie-datenbank.eu/), die technische Daten zu Anlagenkompenten aus dem Bereich Wind, Sonne und Wärme bereitstellt. Das Fraunhofer ISE stellt auf ihrer Internetseite sehr gute grafische Auswertungen unter https://www.energy-charts.de/index_de.htm zur Verfügung. Über die Datenbank eurostat (http://ec.europa.eu/eurostat/de/web/energy/data/database) sind umfangreiche statistische Daten abrufbar. Über die Interneseite der Firma Agora Energiewende können über das Tool Agorameter (https://www.agora-energiewende.de/de/themen/- agothem-/Produkt/produkt/76/Agorameter/) umfangreiche Grafiken zu Lastgängen erstellt werden. • GIS Karten Kartenmaterial, das über GIS erstellt wird, ist zumeist kostenpflichtig. Die Nutzung dieser Technologie im Energiebereich ist jedoch von hohem Interesse. • Gebäudebestände, Industrie Daten über die regional bestehenden Gebäude oder Einwohner können bspw. vom Statistischen Bundesamt bezogen werden. Auch die Datenbank eustat liefert hier eine gute statistische Datenbasis. Jedoch sind die Daten oftmals auf Gesamtdeutschland oder die EU akkumuliert. • Wetterdaten Für Deutschland können über den Deutschen Wetterdienst (DWD) Wetterdaten bezogen werden (https://www.dwd.de/DE/Home/home_node.html). Über die Internetseite https://www.wetteronline.de/rueckblick?gid=euro können Wetterdaten für Europa abgefragt werden.
220
4 Modellierung in OEMOF
Abb. 4.7 Karte der Stromnetzbetreiber Mittelspannungsnetz. (ene’t GmbH 2017b)
4.2 Beschaffung von Daten
Abb. 4.8 Karte der Stromnetzbetreiber Hochspannungsnetz 2017. (ene’t GmbH 2017a)
221
222
4 Modellierung in OEMOF
Abb. 4.9 Beispielhafte Darstellung einer csv-Datei als feedin in OEMOF, hier Darstelllung der Ressourcen
Weltwetterdaten, die dem Programmsystem OEMOF in der Bibliothek feedinlib zugrundeliegen, entstammen der coastDat2 Datenbank (https://www.coastdat.de/) des Institute of Coastal Research, Helmholtz-Zentrum Geesthacht, Geesthacht, Germany. Die Daten sind jedoch lizenrechtlich geschützt und werden aus diesem Grund nicht veröffentlicht. Anfragen über einen möglichen kostenfreien Bezug von Daten sind direkt an das Helmholtz-Zentrum Geesthacht zu richten. Im Programmsystem OEMOF werden Daten für PV-Module vom Sandia Laboratories unter https://sam.nrel.gov/ zur Verfügung gestellt. Über cp-Werte von Windkraftanlagen verfügt das Reiner Lemoine Institut. Sind die Daten erhoben, werden sie in OEMOF als „.csv“-Datei eingelesen (s. Abb. 4.9). Die Namen der Spalten müssen im Quellcode zuvor entsprechend definiert werden. Die Zusammenstellung der Daten erfolgt entsprechend des Optimierungsmodells mit der damit verbundenen Fragestellung.
4.3 Das Programsystem Spyder Ein Programmcode hat eine bestimmte Semantik, die in einem Browser in einer bestimmten Art und Weise dargestellt wird. So erfolgen in einem Browser Einrückungen von Quellcode und farbliche Darstellung von Wörtern, um den Quellcode besser lesbar zu machen. Weiterhin ist man sowohl als Modellierer in OEMOF wie auch als Entwickler darauf angewiesen, seinen eigenen Code zu überprüfen (debugging) und Fehler herauszufinden. Eine Darstellung der Ergebnisse ist ebenfalls wünschenswert.
4.3 Das Programsystem Spyder
223
Dies alles ermöglicht beispielsweise das Programmsystem Spyder. Es handelt sich dabei um ein Open Souce Programmsystem, welches bei der Installation von OEMOF direkt mitgeliefert wird. Spyder ist speziell für die Programmiersprache Python entwickelt (The Spyder Project o. J.). Es handelt sich um eine Scientific Python Development Environment. Durch den Einsatz von IPython (enhanced interactive Python interpreter) und popular Python libraries wie NumPy (linear algebra), SciPy (signal and image processing) oder matplotlib (interactive 2D/3D plotting) verfügt es über eine numerische Computerumgebung.
4.3.1 Spyder Editor Über den Editor wird der Programmcode in Spyder formuliert. Die Oberfläche von Spyder ist dazu in mehrere Fenster mit unterschiedlichen Funktionen aufgeteilt. Auf der linken Seite befindet sich der Text-Editor zur Umsetzung von Programmcode (s. Abb. 4.10). Die Farben im Code haben eine bestimmte Bedeutung (s. Abb. 4.11). Hier wird die Farbe „blau“ für Keywords verwendet und „grün“ für einen String. Die Farben können den eigenen Bedürfnissen auch angepasst werden. Über den Editor können mehrere Sprachen eingegeben werden (multi-language editor). Neben der farblichen Kennzeichnung wird in Echtzeit der Code analysiert (s. Abb. 4.12). Weiterhin erfolgt eine Wörter-Vervollständigung mit Methodenhinweisen und „go-to-
Abb. 4.10 Oberfläche des Programmsystems Spyder, linke Seite Text-Editor. (The Spyder Project o. J.)
224
4 Modellierung in OEMOF
Abb. 4.11 Spyder Syntax Einfärbung
Abb. 4.12 Echtzeitanalysis des Quellcodes mit Fehlerkennzeichnung
4.3 Das Programsystem Spyder
225
definition“ Features (powered by rope) (The Spyder Project o. J.). Viele weitere F unktionalitäten werden von Spyder zur Verfügung gestellt. Hier zu nennen wäre noch ein Funktions/Klassen-Browser und die Möglichkeit der horizontalen/vertikalen Teilung der Fenster.
4.3.2 Variable Explorer Das Fenster in Spyder kann vertikal oder horizontal aufgeteilt werden (s. Abb. 4.13). Im oberen Fenster auf der linken Seite werden die Variablen im „Variablen Editor“ mit ihrem Namen aufgelistet und beschrieben. Im unteren Fenster auf der linken Seite befindet sich die IPython Konsole. Hier werden die Schritte des Programmdurchlaufs aufgezeigt, Ergebnisse ausgegeben und Fehler gemeldet. Im unterern linken Feld der Abb. 4.13 ist zu erkennen, dass die Optimierungsergebnisse als Graphen dargestellt werden können. Dies erfordert die Angabe von Befehlen zur Visualisierung im Quellcode, wie dies in Abschn. 4.1.6 vorgestelt wurde. An dieser Stelle soll noch abschließend auf die in Abschn. 4.1.6 hingewiesene Besonderheit der Nutzung des Programms PyGraphviz eingegangen werden. Bei der Arbeit mit OEMOF kann die Graphenstruktur eines Optimierungsmodells als Netzstruktur in Spyder ebenfalls mittels der IPython Konsole dargestellt werden. Für eine intensivere Auseinandersetzung mit dem Programmsystem Spyder wird auf die Internetseite verwiesen (s. https://www.spyder-ide.org/).
Abb. 4.13 Aufteilung der Fester im Programm Spyder, vertikal
226
4 Modellierung in OEMOF
Literatur ene’t GmbH: Karte der Stromnetzbetreiber Hochspannung (2017a). https://www.enet.eu/assets/ media/images/karten/2017/Karte-der-Stromnetzbetreiber-Hochspannung-2017.jpg. Accessed on 17 Jan 2018 ene’t GmbH: Karte der Stromnetzbetreiber Mittelspannung (2017b). https://www.enet.eu/assets/ media/images/karten/2017/Karte-der-Stromnetzbetreiber-Mittelspannung-2017.jpg. Accessed on 17 Jan 2018 Gabler Wirtschaftslexikon: WACC. Springer Gabler (2018). https://wirtschaftslexikon.gabler.de/definition/wacc-50279/version-273501. Accessed on 28 Apr 2018 Gaudschau, E. et al. (Authors), Fraktion BÜNDNIS 90/DIE GRÜNEN im Brandenburger Landtag (Ed.): Kohleaustieg à la Brandenburg: Funktioniert die Energiestrategie 2013 der Brandenburger Landesregierung? Studie des Reiner Lemoine Instituts gGmbH (RLI) und der Hochschule für Technik und Wirtschaft (HTW Berlin) im Auftrag der Fraktion BÜNDNIS 90/DIE GRÜNEN im Brandenburger Landtag Potsdam, (2017). https://www.gruene-fraktionbrandenburg.de/fileadmin/ltf_brandenburg/Dokumente/Publikationen/Untersuchungen_zur_Energiestrategie_2030. pdf, Accessed on 20 Jan 2018 GitHub Inc.: oemof/oemof/solph/constraints.py (2017b). https://github.com/oemof/oemof/blob/dev/ oemof/tools/helpers.py. Accessed on 19 Dec 2017 GitHub Inc.: feedinlib/feedinlib/powerplants.py. (2017p). https://github.com/oemof/feedinlib/blob/ dev/feedinlib/powerplants.py. Accessed on 15.01.2018 GitHub Inc.: feedinlib/feedinlib/models.py (2018b). https://github.com/oemof/feedinlib/blob/dev/ feedinlib/models.py. Accessed on 02 Feb 2018 Graphviz: Graph Visualization. o.J. http://www.graphviz.org/. Accessed on 03 Feb 2018 Klein, B.: Python-Kurs: Formatierte Ausgabe. o.J.-b. https://www.python-kurs.eu/python3_formatierte_ausgabe.php. Accessed on 01 Mar 2018 Klein, B.: Python-Kurs: for-Schleifen. o.J.-c. https://www.python-kurs.eu/for-schleife.php. Accessed on 01 Mar 2018 Klein, B.: Python-Kurs: Properties als Ersatz für Getter und Setter Properties. o.J.-d. https://www. python-kurs.eu/python3_properties.php. Accessed on 01 Mar 2018 Krien, U. et al.: feedinlib Documentation. Release beta (2017). https://media.readthedocs.org/pdf/ feedinlib/v0.0.10/feedinlib.pdf. Accessed on 14 Jan 2018 oemof-developer-group: oemof-outputlib (2014). http://oemof.readthedocs.io/en/stable/oemof_outputlib.html#creating-an-input-output-plot-for-buses. Accessed on 07 Mar 2018 oemof developing group: Getting started (2015). http://pythonhosted.org/feedinlib/getting_started. html#initialise-your-turbine-or-module. Accessed on 13 Jan 2018 oemof-developer-group: Getting started (2016). http://demandlib.readthedocs.io/en/latest/getting_ started.html#introduction. Accessed on 21 Nov 2017 oemof-Team: oemof Documentation (2017a). https://media.readthedocs.org/pdf/oemof/latest/ oemof.pdf. Accessed on 20 Jan 2018 oemof-Team: oemof Documentation (2017b). https://media.readthedocs.org/pdf/oemof/stable/ oemof.pdf. Accessed on 12 Jan 2017 oemof-developer-group: Using oemof. (2018b). https://oemofsolph.readthedocs.io/en/v0.2.3/using_ oemof.html. Accessed on 08.09.2023 oemof-developer-group: oemof_examples/examples/oemof_0.2/plotting_examples/storage_investment_plot.py (2018b). https://github.com/oemof/oemof_examples/blob/master/examples/ oemof_0.2/plotting_examples/storage_investment_plot.py. Accessed on 07 Mar 2018 pandas: pandas.date_range (2017b). http://pandas.pydata.org/pandas-docs/stable/generated/pandas. date_range.html. Accessed on 07 Dec 2017
Literatur
227
pandas: Time Series/Date functionality (2017c). http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases. Accessed on 07 Dec 2017 pandas: pandas.DatetimeIndex (2017e). https://pandas.pydata.org/pandasdocs/stable/reference/api/ pandas.DatetimeIndex.html. Accessed on 08.09.2023 PyGraphviz developer team: PyGraphviz (2016). https://pygraphviz.github.io/. Accessed on 03 Feb 2018 Python For Beginners: Python Docstrings. o.J. http://www.pythonforbeginners.com/basics/python- docstrings. Accessed on 11 Jan 2018 Python Software Foundation: PEP 318 – Decorators for Functions and Methods (2003). https:// www.python.org/dev/peps/pep-0318/. Accessed on 11 Jan 2018 Python Software Foundation: PythonDecorators (2016). https://wiki.python.org/moin/PythonDecorators. Accessed on 11 Jan 2018 Python Software Foundation: 2. Built-in Functions. (2018d). https://docs.python.org/3/library/functions.html. Accessed on 11.01.2018 Python Software Foundation: 3. Data model. (2018e). https://docs.python.org/3/reference/datamodel.html. Accessed on 12.01.2018 Stack Overflow: What does the “at” (@) symbol do in Python? o.J. https://stackoverflow.com/questions/6392739/what-does-the-at-symbol-do-in-python. Accessed on 11 Jan 2018 Tamura, J., et al.: A new method of calculating in-plane irradiation by one-minute local solar irradiance. In: Proceedings of 3rd World Conference on Photovoltaic Energy Conversion, vol. 3, pp. 2265–2268. Japan (2003). http://ieeexplore.ieee.org/abstract/document/1305038/. Accessed on 14 Jan 2018 The Spyder Project Contributors: Spyder – Documentation. o.J. http://pythonhosted.org/spyder/. Accessed on 22 Jan 2018
5
Getting startet
Die Grundlagen in der Programmiersprache Python sind gelegt, der Aufbau von und die Modellierung im Tool OEMOF ist eingeführt – nun kann die Arbeit mit OEMOF beginnen. Dazu muss OEMOF auf dem eigenen Rechner zunächst installiert werden. Ist dies erfolgt, kann das erste eigene Modell erstellt werden.
5.1 Installation von OEMOF Der Weg der Installation von OEMOF ist vom eigenen Betriebssystem abhängig: • Linux • Windows • Mac OSX Eine aktuelle und ausführliche Beschreibung der Installation für jedes der Betriebssysteme befindet sich unter anderem unter: https://pythonhosted.org/oemof_base/installation_and_ setup.html. Für die Version v0.2.0 ist ein Tutorial für die Instillation unter Windows auf youtube unter https://www.youtube.com/watch?v=eFvoM36_szM verfügbar: Im Folgenden wird die Installation unter Windows 10 vorgestellt. Es wird an dieser Stelle empfohlen, auf der OEMOF Internetseite den Anweisungen unter „Using WinPython (community driven)“ zu folgen. Die Verwendung von Anaconda unter Windows führte in der Vergangenheit vermehrt zu Problemen. Zur Installation von OEMOF unter Windows sind vier Schritte erforderlich: 1 . Installation Python 3 (WinPython) 2. Installation OEMOF
© Springer Nature Switzerland AG 2023 J. Nagel, Optimierung von Energieversorgungssystemen, https://doi.org/10.1007/978-3-031-36355-9_5
229
230
5 Getting startet
3 . Auswahl und Installation eines Solvers 4. Testlauf Gehen wir die Schritte nacheinander durch: Installation Python (WinPython) Das Tool OEMOF ist in der Programmiersprache Python umgesetzt. Dies macht eine Python 3 Umgebung auf dem eigenen Rechner erforderlich. Ein Download des Programmsystems wird auf der Internetseite der OEMOF Developer Group angeboten (https:// pythonhosted.org/oemof_base/installation_and_setup.html). Zur Auswahl der richtigen Version, wie WinPython, ist der Systemtyp des eigenen Computers relevant. Es wird zwischen den Systemtypen eines 32-bit und 64-bit Betriebssystem unterschieden. Wie finde ich heraus, welcher Systemtyp mein Computer ist? Dazu gehen wir zunächst auf den Windows Symbol unten links am Bildschirmrand (s. Abb. 5.1). Wird dieser Button mit der Maustaste gedrückt, erhält man das in Abb. 5.2 dargestellte Fenster. Die gesuchten Informationen zum Systemtyp befinden sich unter dem Button „System“ (s. Abb. 5.3). Ist der Systemtyp bekannt, kann das richtige WinPython Programm über die Internetseite von WinPython (s. http://winpython.github.io/) ausgewählt werden (s. Abb. 5.4). Über die Internetseite der OEMOF Entwickler Gruppe wird der Anwender ebenfalls direkt auf den Link zu WinPython geleitet (s. https://pythonhosted.org/oemof_base/installation_and_setup.html).
Abb. 5.1 Auswahl des Windows Symbols
5.1 Installation von OEMOF
231
Abb. 5.2 Aufklappen des Buttons „Windows“
Abb. 5.3 Informationen zum System des eigenen Computers
Auf der Internetseite von WinPython werden unter dem Systemtyp 32-/64-bit mehrere Versionen aufgeführt. Es lohnt sich, im Netz zu überprüfen, welche WinPython Version aktuell empfohlen wird. Schließlich ist das Verzeichnis, unter dem WinPython gespeichert werden soll, auszuwählen. Prinzipiell kann das Programm überall auf dem Rechner gespeichert werden. Eine
232
5 Getting startet
Abb. 5.4 WinPython Recent Releases
Abb. 5.5 Beispielhafte Darstellung des Speicherortes von WinPython
empfehlenswerte Variante ist direkt unter dem Hauptverzeichnis, z. B. „C:“. Sind die richtige Version und das gewünschte Verzeichnis ausgewählt, kann der Download beginnen. In Abb. 5.5 ist beispielhaft die Struktur von WinPython nach der Installation dargestellt. Nach erfolgreicher Installation von WinPython kann mit Schritt 2 das Programmsystem OEMOF installiert werden. Dazu wird die „WinPython Command Prompt“ Konsole gestartet (s. Abb. 5.6).
5.1 Installation von OEMOF
233
Abb. 5.6 Aufruf der WinPython Command Prompt Konsole
Abb. 5.7 Der Befehl: „pip install oemof“ auf der „WinPython Command Prompt“ Konsole
Dort wird der Befehl: pip install oemof eingegeben (s. Abb. 5.7). Unter https://pythonhosted.org/oemof_base/installation_and_setup.html wird angegeben, dass der Befehl sudo pip3 install oemof − base zu verwenden ist. Während sich OEMOF installiert, kann der nächste Schritt mit der Auswahl eines Gleichungslösers eingeleitet werden. Auf der Internetseite der OEMOF Developer Group wird die Verwendung des Open Source Solver CBC (Coin-or branch and cut) empfohlen. Weitere Informationen sind unter https://pythonhosted.org/oemof_base/installation_and_setup.html zu finden. Auch hier ist es wichtig, zu wissen, ob man ein 32- oder 64-bit System hat. Die Installation des CBC Solver wird nachfolgend vorgestellt. Nach dem Klick auf den Link des CBC Solver wird für ein 64-bit System die Datei „cbc-win64.zip“ heruntergeladen und im Downloadbereich des Computers zur Verfügung gestellt. Das Entpacken erfolgt in einem
234
5 Getting startet
Abb. 5.8 Verzeichnis mit Dateien des „CBC“-Ordners
Abb. 5.9 Suche der Systemumgebungsvariablen auf den Systemeinstellungen
neu einzurichtenden Ordner, z. B. mit dem Namen CBC direkt unter dem Ordner „WinPython-64bit-3.6.3.0Qt5“ (s. Abb. 5.8). Nach dem Entpacken stehen dem Anwender zwei spezifische CBC-Dateien zur Verfügung. Zunächst sollte die Umgebungsvariable des Systems angepasst werden. Dazu wird über das Windows Symbol und den Button „System“ auf die Seite Einstellungen gewechselt. Über das Feld „Einstellungen suchen“ kann man nach dem Begriff „Umgebungsvariable“ suchen (s. Abb. 5.9). Der Anwender erhält die Optionen • „Umgebungsvariable für dieses Konto bearbeiten“ • „Systemvariablen bearbeiten“
5.1 Installation von OEMOF
235
Abb. 5.10 Maske zur Anpassung der Systemumgebungsvariablen
zur Auswahl angezeigt und wählt die zweite Option aus (s. Abb. 5.10): Über diesen Button gelangt der Anwender zum nächsten Screen, wo die Systemumgebungsvariablen geändert werden können. Dazu muss im unteren Feld die Zeile „Path“ und dann der Button „Bearbeiten …“ ausgewählt werden (s. Abb. 5.11). In der folgenden Maske kann der neue Path für den CBC Solver angelegt werden (s. Abb. 5.12). Über den Button „Neu“ wird in der Liste unten eine neue Zeile eingefügt, in die der Path eingetragen werden kann. Der Vorgang wird durch „OK“ beendet. Nachdem der Solver angelegt wurde, sollte er über die „WinPython Command Prompt“ Konsole mit dem Befehl „coemof_installation_test“ überprüft werden. Sollte dies nicht funktionieren, kann die Überprüfung auch über Spyder erfolgen. Nach erfolgreicher Installation erhält man über Spyder im linken unteren Feld das Ergebnis, dass der CBC Solver installiert ist und arbeitet (s. Abb. 5.13). Ebenso erhält man die Information, welcher Solver nicht installiert ist und damit auch nicht arbeitet.
236
5 Getting startet
Abb. 5.11 Maske zum Ändern der Umgebungsvariablen
Es sind alle wichtigen Installationen erfolgt und das System ist funktionsfähig. Erste eigene Modelle können nun umgesetzt werden. Es empfiehlt sich, noch zwei weitere Installationen durchzuführen. Dazu gehört die Installation der feedinlib und der demandlib, da beide Programme nicht zum OEMOF-Packet gehören und direkt installiert werden. Über die „WinPython Command Prompt“ Konsole kann über den Befehl „pip3“ die Installation einfach und schnell durchgeführt werden. Die Befehle lauten: pip3 install feedinlib und pip3 install demandlib. Sind diese beiden Tools abschließend installiert, ist die Installation abgeschlossen.
5.1 Installation von OEMOF Abb. 5.12 Neuen Path für den CBC Solver anlegen
Abb. 5.13 Statusangabe über Installation und Funktion der Solver in OEMOF
237
238
5 Getting startet
5.2 Mein erstes Modell Erste Grundlagen zur Entwicklung eines eigenen Modells wurden bereits vorgestellt. Ein gängiges Vorgehen ist die Verwendung bestehender Modelle, die auf die eigene Fragestellung angepasst werden. Viele Modellierer gehen diesen Weg, da eine Auseinandersetzung mit den Importen von Daten und Funktionen sowie der Ausgabe der Ergebnisse nach der durchgeführten Optimierung über Plots nicht erfolgen muss. Dieses Vorgehen ist legitim und je nach Anwendungsfall auch sinnvoll. Das Programmsystem ermöglicht diesen Weg, wodurch gezeigt wird, wie flexibel und einfach das Tool angewendet werden kann. Weiterhin ist es empfehlenswert, sich die Beispiele unter https://github.com/oemof/ oemof-examples/tree/master/oemof_examples anzuschauen, welche von der OEMOF Developer Group zur Verfügung gestellt werden. Jegliche Fragen zu den Beispielen oder eigenen entwickelten Modellen können unter https://oemof.org/contact/ an die Developer Group gerichtet werden. Für die Modellierung ist vorab eine wichtige Entscheidung darüber zu treffen, ob ein Investment oder ein Dispatch Modell entwickelt werden soll. Das Investment Modell optimiert die Kosten, wodurch geklärt wird, ob Anlagen gebaut werden sollen. Das Dispatch Modell hingegen optimiert die Einsatzweise von Anlagen. Welche Unterschiede bei der Modellierung u. a. zu berücksichtigen sind, macht Tab. 5.1 deutlich. Der prinzipielle Aufbau einer Zielfunktion wurde bereits in Kap. 1 vorgestellt. Auf das Programmsystem OEMOF angewendet, kann ebenfalls eine ganz allgemeine einfache Zielfunktion formuliert werden (s. Gl. 5.1): y flow1 c1 flow2 c2 min. (5.1)
mit
flow Kanten des Modells, die bspw. einen Energie- oder Kostenfluss symbolisieren c Gewichtungsfaktoren, bspw. Kosten oder Emissionen Tab. 5.1 Unterschied in den Parametern zwischen einem Dispatch und einem Investment Modell (invest) dispatch storage = components.GenericStorage( label='storage', inputs={bel: Flow(variable_costs=0.1)}, outputs={bel: Flow()}, nominal_capacity=30, capacity_loss=0.01, initial_capacity=0, nominal_input_capacity_ratio=1/6, nominal_output_capacity_ratio=1/6, inflow_conversion_factor=1, outflow_conversion_factor=0.8) -
invest storage = components.GenericStorage( label='storage', inputs={bel: Flow(variable_costs=0.1)}, outputs={bel: Flow()}, capacity_loss=0.01, initial_capacity=0, nominal_input_capacity_ratio=1/6, nominal_output_capacity_ratio=1/6, inflow_conversion_factor=1, outflow_conversion_factor=0.8, investment=Investment(ep_costs=epc))
5.2 Mein erstes Modell
239
Die Parameter c (c1, c2, ... ) sorgen für das Gewicht eines jeden Flusses (flow) und damit für dessen Einfluss auf das Ergebnis, welches minimiert werden soll. Für ein Minimierungsproblem, „c1 ist doppelt so groß wie c2“, wird der Solver also versuchen, den flow2 statt den flow1 zu nutzen. Die Parameter c1 und c2 können in Abhängigkeit von der Optimierungsfrage bspw. monetäre variable Kosten oder spezifische Emissionen oder andere Faktoren darstellen. Welchen Wert die Parameter annehmen, ist keine Frage des Modells, sondern wird durch die Parametrisierung bestimmt. Dies soll anhand der folgenden zwei Gleichungen verdeutlicht werden. Dazu werden für dieselben Parameter c1 und c2 unterschiedliche Werte eingesetzt (s. Gl. 5.2 und 5.3).
ct ct flowErdgas 9 kWh kWh
(5.2)
gCO2 gCO2 flowErdgas 33 kWh kWh
(5.3)
flowKohle 6
oder
flowKohle 99
Die mathematische Modellbildung erfolgt für beide Fälle auf dem gleichen Weg. Erst durch die Zuweisung der Werte für die Parameter erhält man einen Kosten- oder Emissionsfluss. Dies bedeutet auch, dass ein und dasselbe Modell genutzt werden kann, um bspw. eine Klimaschutzstudie zu erstellen oder um eine Grundlage für Investitionsentscheidungen zu erhalten. Die Berechnung der Emissionen, wie CO2 Emissionen, kann auch in die Nebenbedingungen einfließen. Durch die Vorgabe einer Restriktion können die CO2-Emissionen begrenzt werden (s. Gl. 5.4).
flowKohle cCO2 max. (5.4)
mit cCO2
Emissionen an CO2, bspw. mg pro kWh
Der Solver muss in diesem Fall eine kostengünstige Lösung mit begrenzten CO2- Emissionen finden. Die Zielfunktion kann auch etwas komplexer sein und bspw. Annuitäten zur Entscheidungsfindung von Investitionen enthalten. Eine Zielfunktion könnte dann allgemein, wie in Gl. 5.5 dargestellt, aussehen.
y flow1 c1 flow2 c2 cap1 a1 cap2 a 2 (5.5)
mit cap Annuität der Investition a Installierte Leistung, z. B. einer Windkraftanlage
240
5 Getting startet
In der Modellbildung sind zwei weitere Herangehensweisen zu unterscheiden. Ein Modell kann zukunftsgerichtet sein, wenn es sich um den Neubau oder Ausbau eines Energieversorgungssystems handelt. Hierbei können sowohl Investitionsentscheidungen als auch Entscheidungen zur optimalen Fahrweise im Rahmen der Planung relevant sein (Investment und/ oder Dispatch Modelle). Ein Modell kann sich aber auch auf einen bestehenden aktuellen Anlagenpark beziehen, der in seiner Fahrweise optimiert werden soll (reines Dispatch Modell). Sind diese grundlegenden Entscheidungen gefällt, kann mit der Modellbildung in OEMOF begonnen werden. Es ist sinnvoll, zunächst ein bestehendes Dispatch, Investment oder CHP Modell auszuwählen, welches der eigenen Problemstellung am nächsten kommt. Dieses kann dann Schritt für Schritt an die eigenen Modellzusammenhänge angepasst werden.
5.3 Definition von Szenarien Die Zukunft ist komplex und wird von vielen Faktoren – gesellschaftlichen, politischen, technischen und wirtschaftlichen – beeinflusst. Wie die Zukunft aussieht, vermag niemand vorherzusagen. Um dennoch eine Idee der zukünftigen Entwicklung zu bekommen, werden Szenarien entwickelt. Diese ermöglichen die Darstellung von „Wunschbildern“ der Zukunft und erlauben darüber das Aufzeigen von Handlungsstrategien, um diese zu erreichen. Szenarien werden beschrieben als (Gausemeier et al. 1996; von Reibnitz 1991; Götze 1993): • Ausbildung eines Zukunftsbildes, durch die Darstellung einer möglichen zukünftigen Situation, • dabei wird der Entwicklungspfad zur Erreichung der zukünftigen Situation ebenfalls aufgezeigt. Durch den zweiten Teil der Beschreibung von Entwicklungen, mit den darin enthaltenen treibenden und hemmenden Kräften sowie den möglichen Entwicklungsdynamiken, unterscheidet sich ein Szenario von einem reinen Zukunftsbild (Gausemeier et al. 1996; Götze 1993). Dabei stellt ein Szenario bewusst kein umfassendes Bild der Zukunft dar. Es sollen vielmehr bestimmte interessierende Aspekte, wie die Entwicklung von Ölpreisen, hervorgehoben werden. Man spricht in diesem Zusammenhang auch von Konstruktionsarbeit (Kosow et al. 2008). Dies bedeutet, dass unter Beachtung eines definierten Zeithorizontes bestimmte Schlüsselfaktoren ausgewählt und miteinander kombiniert werden. Die Zukunft wird damit unter bestimmten zuvor definierten Annahmen, z. B. einem 20 % igen Bevölkerungswachstum in den nächsten 20 Jahren, konstruiert. Dazu braucht es fundiertes Wissen, was ein hohes Maß an Erfahrungswissen beinhaltet. Szenarien sind rein hypothetisch und damit mit Wahrscheinlichkeiten ausdrückbar. Szenarien stellen keine Prognosen der Zukunft dar. Im Gegensatz zu Prognosen, welche Aussagen zu erwartbaren zukünftigen Entwicklungen darstellen, stützen sich Szenarien nicht auf die Auswertung einer Wissensbasis mit aktuellen und vergangenen Daten. Prognosen können
5.3 Definition von Szenarien
241
durch die statistische Extrapolation gegenwärtiger und vergangener Trends aufgestellt werden. Wetter- oder Ölpreisprognosen sind ein typisches Beispiel für Prognosen. Szenarien dienen zur Entscheidungsfindung für die aktuelle Situation, um zukünftige Ziele erreichen zu können. Im Rahmen der Energiewende muss entschieden werden, in welche bestehenden Technologien investiert werden soll bzw. welche Technologien weiter erforscht werden sollten. Ein anderer Aspekt wäre, welche politischen Rahmenbedingungen gesetzt werden müssen, um das Klimaziel zu erreichen. Um dies herauszuarbeiten, wird zumeist mit mehreren alternativen Szenarien gearbeitet. Die Entwicklung eines jeden Zukunftsbildes wird beeinflusst durch die individuelle Sichtweise auf die eigene Zukunft. Diese Sichtweise geht einher mit dem persönlichen Verständnis, wie sich die Zukunft unter den Aspekten Gegenwart und Vergangenheit verhalten wird (Kosow et al. 2008). Es lassen sich drei Sichtweisen unterscheiden, die jemand bei der Entwicklung eines Zukunftsbildes einnehmen kann (Grunwald 2002): • Zukunft ist berechenbar Mit dem Wissen aus Gegenwart und Vergangenheit lässt sich ableiten, was in Zukunft passiert. Bei diesem Ansatz liegt es nahe, Daten der Vergangenheit zur Ableitung eines Zukunftsbildes heranzuziehen. • Zukunft ist evolutiv Diesem Ansatz liegt das Paradigma zugrunde, dass der Verlauf der Zukunft chaotisch, unkontrolliert und zufällig ist. Eine bewusste Steuerung ist nicht möglich. Hier können Bilder und Pfade ausgearbeitet werden, die völlig unabhängig von der Vergangenheit und dem Ist-Zustand sind. Die Wahl von Einflussfaktoren erfolgt völlig willkürlich. Ein Zukunftsbild lässt sich dabei nur mit Ansätzen aus der Theorie des Chaos beschreiben. • Zukunft ist gestaltbar Zukunft kann zumindest teilweise durch unser Handeln beeinflusst werden. Dabei ist sie weder vorhersagbar noch chaotisch in ihrer Entwicklung. Wenn die Zukunft gestaltbar ist, liegen gewisse Wahrscheinlichkeiten zugrunde, das gewählte Ziele über einen bestimmten Weg erreichen zu können. Wir sind bei der Konstruktion eines Szenarios nicht frei von diesen Sichtweisen. Dies beginnt schon bei der persönlichen Vorstellung der Zukunft und führt sich fort bei der Wahl einer Technik zur Entwicklung von Szenarien. Die Sichtweisen haben damit grundlegende Auswirkungen auf die Konstruktion eines Szenarios. So werden auch je nach Sichtweise unterschiedliche Einflussfaktoren ausgewählt und in den Mittelpunkt gerückt. Dabei hat jeder Einflussfaktor Auswirkungen auf die Entwicklung der Zukunft. Es lohnt sich, dies im Blick zu haben, um auch andere Wege zur Konstruktion eines Szenarios einschlagen zu können. Die ausgewählten Einflussfaktoren bestimmen die Entwicklung der Zukunft und sind somit entscheidend für die Konstruktion eines Szenarios. Dabei sind die Einflussfaktoren nicht starr, sondern verändern sich auf dem Weg in die Zukunft. Je weiter der Blick in die Zukunft gerichtet wird, desto größer ist die Zahl der möglichen und unterschiedlichen Ent-
242
5 Getting startet
wicklungen von Einflussfaktoren in der Zukunft. Es entsteht ein Möglichkeitsraum. Dieser kann durch mehrere sich voneinander abgrenzende Szenarien beschrieben werden. Damit entsteht nicht nur eine Zukunft, sondern vielmehr mehrere Zukünfte. Die unterschiedlichen Szenarien werden im Rahmen des Prozesses der Entscheidungsfindung oder Strategiebildung miteinander verglichen (Kosow et al. 2008). Ein Grund hierfür liegt darin, unterschiedliche zukünftige Entwicklungen sichtbar zu machen. Ein zweiter zielt darauf ab, dass die Auswirkungen unterschiedlicher Entwicklungen bzw. Entscheidungen, die getroffen werden, durchgespielt werden können. Die Entwicklung von Szenarien ist für die Bildung zukunftsgerichteter Modelle eine wichtige Voraussetzung. Unter anderem im Energiebereich werden im Rahmen von Szenarioentwicklungen Pfade zukünftiger Entwicklungen komplexer Technologiefelder aufgezeigt. So wird bspw. von (Henning et al. 2015) oder auch (Nagel 2017) der zeitliche Einsatz technologischer Möglichkeiten zur Flexibilisierung des Energiesystems anhand eines Entwicklungspfades aufgezeigt. Der zeitliche Weg geht dabei über die Schritte der Forschung, Entwicklung, Aufbau von Demoanlagen und abschließend dem Monitoring der Energiewende. Dabei sieht (Henning et al. 2015), dass zunächst eine Flexibilisierung konventioneller Kraftwerke erfolgt, der mit dem Ausbau des Stromnetztes einhergeht. Es kommen Power-toHeat Anlagen mit Wärmenetzen zum Einsatz bis hin zur Bereitstellung synthetischer Brennstoffe für die Strom- und Wärmeerzeugung zum Ende des Entwicklungspfades. Die Aussagen zu zukünftigen Entwicklungen speziell im Energiebereich sind mit hohen Prognoseunsicherheiten belegt. Oftmals wird in einem Zeitraum von 10, 20 oder gar 30 Jahren in der Zukunft gedacht. Da in diesen Zeiträumen technische Entwicklungen erfolgen, gesellschaftliche Rahmenbedingungen sich verändern und politische Entscheidungen gefällt werden, die schwer bis gar nicht vorhersehbar sind, ist es hilfreich, einen Möglichkeitsraum zukünftiger Entwicklungen im Energiebereich zu eröffnen. Zur Ausarbeitung eines eigenen Modells kann entweder auf bereits veröffentlichte Szenarien zurückgegriffen werden, wie bspw. anhand der Studie des BDI zu den Klimapfaden Deutschlands (s. Gerbert et al. 2018), oder es können selbst Szenarien entwickelt werden. Zur Entwicklung von Szenarien bietet sich die Durchführung eines Szenario-Workshops an. Ein Vorgehen zur Durchführung eines solchen Workshops wird nachfolgend in seinen Grundsätzen vorgestellt. Ziele eines Szenarioworkshops sind (Meyer et al. 2009): • Vermittlung von Informationen und Diskussion über zukünftige Entwicklungen des Energiesektors. Aufzeigen ihrer Probleme und Chancen. • Persönliche Wahrnehmungen und Beurteilungen von Problemen bei der Entwicklung der Energieversorgung der einzelnen Teilnehmer sollen identifiziert und formulierbar werden. • Basierend auf den herausgearbeiteten Einschätzungen zu den möglichen zukünftigen Entwicklungen im Energiesektor werden diese in Szenarien gebündelt. Prinzipiell existieren unterschiedliche Ansätze bei der Entwicklung von Szenarien. Es kann zwischen folgenden Ansätzen unterschieden werden:
5.3 Definition von Szenarien
243
• Explorative und normative Szenarien: Explorative Szenarien: Im Vordergrund stehen Entwicklungen von Schlüsselfaktoren und Rahmenbedingungen. Normative Szenarien: Berücksichtigung des Einflusses unterschiedlicher gesellschaftlicher Zielvorstellungen, worunter wünschenswerte Zukünfte beschrieben werden. • Mengen- bzw. Qualitäts-orientierte Szenarien: Quantitative und qualitative Szenarien. • Zeitorientierte Szenarien: Beispielsweise mittelfristige oder langfristige Szenarien. Der zugrunde liegende Ansatz für die zu entwickelnden Szenarien muss vor Beginn eines Szenario-Workshops festgelegt werden. Weitere grundsätzliche Vorgaben sind die Problemstellung (z. B. „Wie sieht eine sichere Energieversorgung im Jahr 2050 aus?“), als auch die Ziele und die Festlegung des Betrachtungsrahmens, der so genannten Systemgrenze. Sind diese Punkte festgelegt, ist das Szenariofeld definiert. Die Kunst in der erfolgreichen Eröffnung des Workshops durch den Moderator liegt darin, den Teilnehmern passende Botschaften und Bilder zu vermitteln, um diese gut in den Szenarioprozess zu integrieren. Gemeinsam werden Rahmenbedingungen für die Entwicklung von Szenarien festgelegt. Dies sind Annahmen, die allen Szenarien gemein und damit unveränderbar sind. Dazu kann bspw. eine Annahme zur Bevölkerungsentwicklung in Deutschland zählen. Es ist sinnvoll, sich zu Beginn eines Workshops auf bestimmte Rahmendaten zu einigen. Weiterhin müssen die Schlüsselfaktoren identifiziert werden. Schlüsselfaktoren sind die Einflussgrößen im System. Diese bestimmen die Möglichkeiten der zukünftigen Entwicklung und sind damit ausschlaggebend für die Szenarioentwicklung. Dazu zählen gesetzliche Regulierungen oder das Akzeptanzverhalten der Bevölkerung gegenüber Erneuerbaren Energien und andere Einflussgrößen. Im Rahmen des Workshops werden zunächst in einem Brainstroming-Prozess alle möglichen Einflussfaktoren identifiziert und gesammelt. Diese werden dann unter dem Aspekt Bedeutung für die Zielstellung und Unsicherheit in der Ausprägung bewertet. Am Ende dieses Prozesses ergeben sich die Schlüsselfaktoren, wie bspw. „soziale Akzeptanz“, „Technologie“, „CO2-Preis“ oder „Klimaveränderung“. Jeder dieser Schlüsselfaktoren muss hinsichtlich seiner Ausprägung weiter diskutiert werden. Es sind nach Möglichkeit mehr als zwei Ausprägungen pro Schlüsselfaktor zu definieren. Betrachtet man den Faktor „Technologie“, können dies unterschiedliche Technologien, wie Power-to-Gas oder Speicher, sein. Für den Schlüsselfaktor „soziale Akzeptanz“ können die alternde Bevölkerung oder das Bedürfnis nach Unabhängigkeit gewählte Ausprägungen sein. Zu jedem der herausgearbeiteten Schlüsselfaktoren sind nun die zukünftigen Entwicklungen zu identifizieren. Dies kann einerseits unter dem Aspekt der hohen Bedeutung und andererseits der hohen Unsicherheit erfolgen. Durch die Betrachtung der Entwicklung von Schlüsselfaktoren werden unterschiedliche Projektionen auf die Zukunft dieser Schlüsselfaktoren erzeugt. Für den Schlüsselfaktor „Entwicklung der Treibstoffkosten“ könnten die Projektionen „Treibstoffkosten bleiben konstant“ und „Treibstoffkosten verdoppeln sich“ lauten.
244
5 Getting startet
Aus den Szenarien ergeben sich die erforderlichen Parameter für die folgende mathematische Optimierungsmodellbildung. Ein Parameter im Optimierungsmodell kann der CO2Preis sein. Ein weiterer Parameter kann die zu verwendende Technologie sein. Darüber würde festgelegt, dass nur bestimmte Technologien ausgewählt und andere nicht in das Modell einbezogen werden. Im Folgenden wird dieser Prozess etwas ausführlicher vorgestellt. Die Entwicklung eigener Szenarien ist ein komplexer Prozess, da er sich mit diesen meist unbekannten und unsicheren Faktoren der Zukunft beschäftigt. Im Rahmen der Szenarioentwicklung ist es möglich, isolierte Annahmen über die positive oder negative Einwirkung einzelner Einflussfaktoren, wie bspw. Bevölkerungswachstum, auf die weitere zukünftige Entwicklung und die Wechselwirkungen zwischen verschiedenen Triebkräften, wie Energieverbrauch und Akzeptanz der Energieerzeugung aus Windkraft, zu treffen und diese dann zu umfassenden Zukunftsbildern zusammenzufassen (Meyer et al. 2009). Darüber lässt sich der Möglichkeitsraum der zukünftigen Entwicklungen, wie im Energiebereich im Rahmen der Energiewende, plausibel und detailliert beschreiben. Durch eine einfache und verständliche Beschreibung der Zukunftsbilder lassen sich anschließend die Faktoren für die folgende Modellbildung zielgerichtet herausarbeiten. Ein Szenarioprozess lässt sich in fünf Phasen einteilen (s. Abb. 5.14). Zunächst muss in der ersten Phase die Problemstellung herausgearbeitet werden. Für welche Fragestellung sollen Szenarien entwickelt werden? Was wird betrachtet und wo liegen die Grenzen. In der nächsten Phase werden die Schlüsselfaktoren identifiziert. Diese sollen das Szenariofeld beschreiben. Schlüsselfaktoren wirken auf das Szenariofeld bzw. wirken über das Ereignisfeld nach außen. Sie stellen in unserem Fall Variablen, wie Einschalten oder Ausschalten einer Windkraftanlage oder Parameter, wie Investitionskosten, dar. Ebenso fallen darunter Trends oder Entwicklungen, wie die Entwicklung der Rohölpreise. Auch ein-
Abb. 5.14 Die fünf Phasen eines Szenarioprozesses. (Nach Kosow et al. 2008)
5.3 Definition von Szenarien
245
schlägige Ereignisse, wie der der Krieg in der Ukraine, können als Schlüsselfaktor in den Prozess einfließen. Die Identifikation der Schlüsselfaktoren erfolgt unterschiedlich. Dies reicht von der Analyse empirischer Daten über die Recherche theoretischer Zusammenhänge oder Entwicklungen bis zu interaktiven Workshops oder möglichen Befragungen. In der dritten Phase wird analysiert, wie sich die Schlüsselfaktoren zukünftig zum Projektionszeitpunkt entwickeln und welche Ausprägungen diese annehmen könnten. In der darauffolgenden Phase vier werden die Szenarien generiert. Dazu werden bestimmte Schlüsselfaktoren ausgewählt, die konsistent und aussagekräftig sind. Diese Faktoren werden zu Bündeln zusammengefügt und daraus die Szenarien ausgearbeitet. Häufig werden mehrere Szenarien gefunden bzw. sind theoretisch vorstellbar. Die Anzahl wird schnell unübersichtlich und lässt sich oftmals kognitiv nicht mehr bearbeiten. Aus praktischen Erfahrungen wird empfohlen, für ein Szenariofeld eine Auswahl von maximal vier bis fünf Szenarien zu treffen (Kosow et al. 2008). Werden zu viele Szenarien ausgewählt, wird der Prozess schnell unübersichtlich. Sind es zu wenige, gehen u. U. Perspektiven verloren und mögliche Zukünfte werden nicht betrachtet. Die ausgewählten Szenarien müssen klar voneinander abgrenzbar und unterscheidbar sein, damit diese jeweils eine eigene Interpretation zulassen. Zur Auswahl von Szenarien stehen zwei in der Praxis erprobte Verfahren zur Verfügung (Greeuw et al. 2000). Diese erlauben jeweils eine Auswahl von vier oder mehr Szenarien. Dem ersten Verfahren liegen zwei grundsätzliche Logiken zugrunde (s. Tab. 5.2). Mittels der ersten Logik wird eine Aussage über das aktive Handeln getroffen. Dabei wird von den beiden Rändern, von keine bis sehr viele Maßnahmen initiieren, geschaut. Die zweite Logik sagt etwas über die Entwicklung der Szenariofaktoren aus. Auch hier werden die beiden Ränder betrachtet, in diesem Fall von negativer bis positiver Entwicklung. Ein anderes Verfahren spannt vier Dimensionen mit stark voneinander variierenden Perspektiven auf die zukünftige Entwicklung auf (s. Abb. 5.15). Das Interessante dieser Vorgehensweise liegt darin, dass nach Beendigung des Prozesses vier oder mehr fertige Szenarien vorliegen. Sind die Szenarien entwickelt, ist der Prozess beendet. Von Schritt zwei bis zu Schritt vier stehen sehr unterschiedliche Szenariotechniken zur Verfügung, von denen einige nachfolgend vorgestellt werden. Doch zunächst folgt als optionale Phase fünf, der Szenario-Transfer. In dieser Phase können Szenarien auf ihre Wirkung hin analysiert werden. Ebenso können die Strategien zur Zielerreichung bewertet werden. Denkbar ist auch eine Analyse der Auswirkungen von Szenarien auf die am Markt agierenden Akteure. In unserem Fall erfolgen nach der Konstruktion von Szenarien die mathematische Modellbildung und die folgende Optimierungsrechnung. Nach diesem Schritt können Aussagen über die Wahl der Ausprägung heutiger Schlüsselfaktoren und deren Auswirkungen in der Zukunft gemacht werden. Tab. 5.2 Variation der Perspektive anhand von Maßnahmen und Faktorenentwicklung. (Nach Kosow et al. 2008) Szenariotyp: Perspektive: Grundlogik:
„wait and see“ Maßnahmen wenige bis keine
„just do it“ viele neue
„doom monger“ „Carpe Diem“ Entwicklung Szenariofaktoren positive negativ
246
5 Getting startet
Abb. 5.15 Vier Zukunftsperspektiven. (Nach UNEP 2002)
Kommen wir nun zu den Szenariotechniken, mit denen die Schritte eins bis vier vollzogen werden können und beginnen mit systematisch-formalisierten Szenariotechniken. Die unterschiedlichen Techniken werden nur exemplarisch vorgestellt und auf die vielfältige Literatur verwiesen. Es stehen auch kommerzielle Programmsysteme zur Verfügung, mit denen Szenarien entwickelt werden können. Open Source Programme sind zum jetzigen Zeitpunkt keine bekannt. Systematisch-formalisierte Techniken zur Szenarioentwicklung Hierbei werden Schlüsselfaktoren definiert, variiert und anschließend miteinander kombiniert. Dies ermöglicht das Aufspannen eines Szenariotrichters (s. Abb. 5.16) innerhalb dessen Szenarien generiert werden können. Die Identifikation von Einflussfaktoren erfolgt bei diesen Techniken sowohl anhand quantitativer als auch qualitativer Daten. Die Daten können aus einer Trendanalyse stammen, wie beispielsweise die Entwicklung von Energiepreisen. Es können aber auch qualitative Daten, wie die Akzeptanz der Energiewende, verwendet werden. Anhand dieser
5.3 Definition von Szenarien
247
Abb. 5.16 Aufspannen eines Szenariotrichters. (Kosow et al. 2008)
Daten werden über eine Einflussanalyse die Schlüsselfaktoren herausgearbeitet. Mittels der Einflussanalyse wird das Zusammenwirken der identifizierten Einflussfaktoren analysiert. Dabei steht die Frage im Mittelpunkt, wie sich die Faktoren zueinander verhalten? Dies erfolgt anhand einer Matrix, wie sie in Tab. 5.3 dargestellt ist. In dieser Matrix werden die Faktoren einander gegenübergestellt und überprüft, inwieweit zwischen jedem Faktoren-Paar eine direkte Beziehung wirksam ist (Wilms 2006). Um die Einflüsse bewerten zu können, wird folgende Skala empfohlen (Blasche 2006): • • • •
0 = kein Einfluss 1 = schwache Beziehung 2 = mittlere Beziehung 3 = starke Beziehung
Jede Beziehung kann anhand dieser Skala quantifiziert werden und anschließend eine Summe der Spalten und Zeilen gebildet werden. Die Zeilensumme (ZS) gibt an, wie stark ein Faktor auf einen anderen Faktor wirkt. Die Spaltensumme (SS) ist ein Maß dafür, wie stark ein Faktor von einem anderen Faktor beeinflusst wird. Welche Aussagen können aus dieser Matrix formuliert werden. Dazu hilft es, eine Charakterisierung der Zusammenhänge der Faktoren nach folgender Systematik durchzuführen (Kosow et al. 2008): • Hohe ZS und niedrige SS: Hier spricht man von aktiven bzw. impulsiven Faktoren. In Tab. 5.3 sind dies die Faktoren „Strompreis“ und „Flexibilisierung“. Diese Faktoren werden nicht so sehr vom Szenariofeld beeinflusst, sondern nehmen selbst einen großen Einfluss auf das Szenario-
Faktor A CO2-Preis Faktor B Strompreis Faktor C Akzeptanz Windkraft Faktor D Durchschnittstemperatur der erdnahen Atmosphäre Faktor E Flexibilisierung Industrie Spaltensumme (SS) 0 1
3 4
3
3 6
6
0
3
3
Faktor C Faktor B Akzeptanz Strompreis Windkraft 0 0
0
0
Faktor A CO2- Preis
7
2
1
2
Faktor D Durchschnittstemperatur der erdnahen Atmosphäre 2
3
0
0
3
Faktor E Flexibilisierung Industrie 0
8
7
1
8
Zeilensumme (ZS) 2
Tab. 5.3 Einflussmatrix der identifizierten Einflussfaktoren, beispielhafte Darstellung mit einem Auszug an Einflussfaktoren. (Nach Blasche 2006)
248 5 Getting startet
5.3 Definition von Szenarien
249
feld. Sofern diese Faktoren veränderbar sind, können sie Zukunft-gestaltend eingesetzt werden. Für die zukünftige Entwicklung sind diese Faktoren ein „Hebel“ oder „Schalter“, die etwas bewirken können. • Niedrige ZS und hohe SS: Dies sind so genannte reaktive oder passive Faktoren. In Tab. 5.3 sind dies bspw. die Faktoren „CO2-Preis“ und „Akzeptanz Windkraft“. Die Wirkung auf diese Faktoren ist stärker, als dass sie selbst auf das Szenariofeld wirken. Ein solcher Faktor kann gut eingesetzt werden, um eine Situation zu beobachten. • Hohe ZS und hohe SS: Dies trifft für kritische bzw. dynamische Faktoren zu. Eine starke Beeinflussung geht hier in beide Richtungen. Es tritt somit sowohl eine große Beeinflussung auf das Feld wie auch vom Feld auf den Faktor auf. Dies liegt daran, dass diese Faktoren stark mit anderen Faktoren vernetzt sind. Diese Faktoren müssen unbedingt beobachtet werden. Für das Beispiel in Tab. 5.3 trifft dies auf den Faktor „Durchschnittstemperatur der erdnahen Atmosphäre“ zu. • Niedrige ZS und niedrige SS: Diese Faktoren haben eine puffernde oder träge Wirkung im System. Eine Beeinflussung auf bzw. vom Feld auf den Faktor liegt nur schwach vor. Diese Faktoren sind eher isoliert, sodass kaum Vernetzungen mit anderen Faktoren vorliegen. In Tab. 5.3 trifft dies auf keinen der Faktoren zu. Da es sich bei den Einflussfaktoren in Tab. 5.3 nur um einen Auszug handelt, ist die Wiedergabe der Zusammenhänge anhand der dargestellten Systematik nur beispielhaft und keinesfalls aussagekräftig. Aktive und kritische Faktoren stellen im weiteren Verlauf die gesuchten Schlüsselfaktoren für die Szenariobildung dar. Bei passiven und trägen Faktoren wird angenommen, dass diese nicht auf das System einwirken, weil sie entweder als stabil anzusehen sind oder nur über die Vernetzung mit anderen Faktoren wirken. Ziel ist es, zwischen 10 und maximal 20 Schlüsselfaktoren zu identifizieren (Kosow et al. 2008). In der nächsten Phase erfolgt die Analyse der Schlüsselfaktoren. Dazu werden Annahmen getroffen, wie sich diese Schlüsselfaktoren zukünftig entwickeln können. Annahmen können dabei sehr konservativ oder eher „kreativ“ sein. Was ist noch vorstellbar, was hingegen ist undenkbar? Entsprechend gestalten sich auch die Szenarien. Betrachten wir den Preis für die Rohölsorte Brent. Dieser liegt am 25.05.2023 bei 75,94 $ (https:// www.onvista.de/rohstoffe/Oelpreis-B rent-5 389904?referrer=https%3A%2F%2Fde. search.yahoo.com%2F). Wie wird sich dieser Preis zukünftig entwickeln? Was können wir annehmen? Wird dieser Preis eher weiter sinken oder wieder steigen? Sind 100 $ im Jahr 2050 annehmbar oder eher 200 $? Wo liegt die untere Grenze? Könnte der Ölpreis weiter fallen? Wäre ein Preis von 30,00 $ vorstellbar? Dies sind wesentliche Fragen, die immer auch einen subjektiven Anteil innehaben, abhängig von der Sicht auf die Welt, die jeder einzelne von uns hat.
250
5 Getting startet
Für die Entwicklung konsistenter Szenarien müssen die Faktoren variiert und kombiniert werden. Eine Methode hierfür ist die Konsistenzanalyse (Heinecke 2006). Dieser Prozess ist vor allem für die Deutbarkeit von Szenarien von immenser Wichtigkeit (Gaßner 1992). Aber auch für den Glauben an ein Szenario spielt dieser Prozess eine wichtige Rolle. Im Rahmen der Konsistenzanalyse werden für jeden einzelnen Schlüsselfaktor mehrere unterschiedliche Ausprägungen, wie der Faktor Rohölpreis sinkt um 10 % oder steigt um 10 %, gewählt. Bei diesem Verfahren werden keine Wahrscheinlichkeiten berücksichtigt. Also die Frage, wie wahrscheinlich ist es, dass der Rohölpreis um 10 % sinkt, wird nicht betrachtet. Um die paarweise Konsistenz zu überprüfen, wird ein Faktor mit seiner jeweiligen Ausprägung, hier a) und b), allen anderen Faktoren gegenübergestellt (s. Tab. 5.4). Für jedes Faktoren-Paar erfolgt eine Bewertung der Konsistenz. Dies bedeutet, Faktor A mit seiner Ausprägung a) und b) wird jeweils dem Faktor B ebenfalls in seiner Ausprägung a) und b) gegenübergestellt. Dazu wird nach (Kosow et al. 2008) häufig eine Skala von 1 bis 5 gewählt. Die Ausprägung 5 bedeutet: starke Konsistenz, d. h. Faktoren unterstützen sich gegenseitig stark; 4 drückt eine schwache Konsistenz aus, d. h. Faktoren begünstigen sich gegenseitig; 3 steht für neutral oder unabhängig voneinander; 2 deutet auf eine schwache Inkonsistenz, hier besteht ein Widerspruch zwischen den Faktoren und 1 bringt zum Ausdruck, dass eine starke Inkonsistenz, also ein völliger Widerspruch zwischen den Faktoren besteht. Ist jeder Schlüsselfaktor mit seinen Ausprägungen auf Konsistenz geprüft, können diese kombiniert und zu Bündeln zusammengefasst werden. Es entsteht ein Set an Ausprägungsbündeln, von denen jedes ein Rohszenario darstellen kann. Um nicht zu viele Szenarien zu erhalten, sind durch Priorisierung jene Kombinationen herauszufiltern, deren Konsistenz besonders hoch ist. Hierdurch lässt sich die Zahl der Faktorenbündel reduzieren. Technik der Trendextrapolation zur Konstruktion von Szenarien Zu Beginn wurde ausgesagt, dass es sich bei Szenarien nicht um Prognosen handelt, die auf der Auswertung von Trendextrapolationen beruhen. Jedoch können einzelne quantitative Faktoren durch eine Trendextrapolation oder Trendanalyse variiert werden. Dabei werden bestehende und vergangene Trends betrachtet und in die Zukunft projiziert. Unter einem Trend wird eine langfristige Entwicklung eines Parameters verstanden. In Abb. 5.17 wird dies anhand der Durchschnittstemperatur der erdnahen Atmosphäre dargestellt. Über eine Trendextrapolation werden statistisch erfasste Entwicklungsrichtungen, wie in Abb. 5.17 dargestellt, in die Zukunft fortgeschrieben. Dahingegen beschäftigt sich die Trendanalyse mit der Ermittlung, Quantifizierung und Ursachenergründung sich langfristig abzeichnender Entwicklungen einer Größe. Eine Trendanalyse ist somit umfassender als eine reine Trendextrapolation.
Wie verträgt sich die Ausprägung des Faktors der Zeile mit der Ausprägung des Faktors der Spalte? Faktor A Ausprägung Aa) Ausprägung Ab) Faktor B Ausprägung Ba) Ausprägung Bb) Faktor C Ausprägung Ca) Ausprägung Cb) Faktor D Ausprägung Da) Ausprägung Db)
Faktor B
Faktor C
Faktor C
5 3 3 4 1 3
3
4
4
3
4
1
5
1
5
5
2
4
2
2
1
3
3
5
Ausprägung Ausprägung Ausprägung Ausprägung Ausprägung Ausprägung Ausprägung Ausprägung Aa) Ab) Ba) Bb) Ca) Cb) Da) Db)
Faktor A
Tab. 5.4 Beispielhafte Darstellung einer Konsistenzmatrix. (Nach Gausemeier et al. 1996)
5.3 Definition von Szenarien 251
252
5 Getting startet
Abb. 5.17 Änderung der globalen jährlichen bodennahen Lufttemperatur. (Unter Verwendung von (NASA 2023)
Die Technik der Trendextrapolation beruht auf der Sammlung möglichst langfristiger Daten und Informationen. Unter Verwendung von statistischen Verfahren, wie der Zeitreihenanalyse, werden Trends in die Zukunft projiziert. Diese Technik spannt jedoch keinen Möglichkeitsraum auf, sondern beschreibt eine als am wahrscheinlichsten angenommene Entwicklung bzw. Zukunft (Kosow et al. 2008). Damit ist diese Technik nicht geeignet, um mehrere Zukünfte zu konstruieren. Jedoch kann die Technik sinnvoll sein, um ein Referenzszenario zu etablieren. Andere Szenarien können dann mit diesem Referenzszenario verglichen werden. Bei der Anwendung dieser Technik muss man sich jedoch bewusst sein, dass hier eine Sicherheit über eine zukünftige Entwicklung ausgedrückt wird, die nur einer gewissen Wahrscheinlichkeit unterliegt und damit auch Abweichungen aufgrund nicht vorhergesehener Faktoren entstehen können. Im Energiebereich, der so komplex und mit so vielen Unsicherheiten behaftet ist, wird diese Technik zur Entwicklung von Szenarien nicht empfohlen. Die Weiterentwicklung dieser Methode, die Trend-Impact-Analyse, könnte jedoch eingesetzt werden, um eine Aufspreizung einzelner Schlüsselfaktoren zu erreichen. Über diese Methode ist es möglich, einem Faktor unterschiedliche Ausprägungen zuzuordnen. Dabei wird zunächst über die Trendextrapolation ein „sicherer“ Trendverlauf berechnet (Gordon 1994). Im nächsten Schritt wird dieser Verlauf abgeändert, indem z. B. über Expertenbefragungen zukünftige Ereignisse definiert und deren Auswirkungen auf den „sicheren“ Trendverlauf berechnet werden. Daraus ergeben sich dann alternative Verläufe, wie dies in Abb. 5.18 dargestellt ist. Über diesen Weg ergeben sich ebenfalls alternative Ausprägungen, mit denen in weiteren Schritten im Rahmen der Szenarioentwicklung gearbeitet werden kann.
5.3 Definition von Szenarien
253
Abb. 5.18 Beispielhafte Darstellung der Trend-Impact-Analyse. (Unter Verwendung von (NASA 2023)
Entwicklung von Szenarios mit Hilfe von kreativ-narrativen Techniken In der Anwendung dieser Techniken liegt ein verstärkter Fokus auf dem Szenarioprozess. Sind die Schlüsselfaktoren identifiziert, kann zur folgenden Szenariobildung das Verfahren der vollständigen Permutation gewählt werden (Steinmüller 2002). Hierbei werden alle ausgewählten Schlüsselfaktorausprägungen miteinander kombiniert. Die Anzahl der Schlüsselfaktoren sollte begrenzt werden, um den Prozess überschaubar zu halten. Es wird ein Rasterverfahren angewendet, bei dem die Ausprägungen nach Kriterien, wie hohe oder niedrige Ausprägung oder Überschuss-Defizit, bewertet werden (s. Abb. 5.19). Für die einzelnen Quadranten kann nun anhand des Rasters eine Beschreibung der Zukunft erfolgen. Ausgehend von diesen Beschreibungen können die Schlüsselfaktorausprägungen zu einzelnen Szenarien gebündelt werden. Auch hier ist darauf zu achten, dass nicht zu viele Szenarien entwickelt werden. Eine Anzahl von maximal drei bis vier Szenarien trägt dem Aspekt des Aufzeigens eines unsicheren Zukunftsraumes Rechnung. Sind die Grobszenarien angelegt, müssen diese ausformuliert werden. Die entwickelten Szenarien sind für den folgenden Optimierungsschritt wichtig, um für die zukunftsgerichteten Modellparameter Werte zu erhalten. Erst wenn die Werte für die Modellparameter festgelegt sind, kann mit den Optimierungsrechnungen begonnen werden.
254
5 Getting startet
Abb. 5.19 Aufbau der Raster nach dem Permutationsverfahren. (Nach Steinmüller 2002)
Literatur Blasche, U.G.: Die Szenariotechnik als Modell für komplexe Probleme: Mit Unsicherheiten leben lernen. In: Falko E.P. Wilms (ed.) Szenariotechnik: Vom Umgang mit der Zukunft, pp. 61–92. Haupt Verlag, Bern (2006) Gausemeier, J. et al.: Szenario-Management: Planen und Führen nach Szenarien. 2., bearbeitete Auflage, Hanser Verlag, München, 1996 Gaßner, R.: Plädoyer für mehr Science Fiction in der Zukunftsforschung. In: Burmeister, K., et al. (eds.) Streifzüge ins Übermorgen, pp. 223–232. Beltz Verlag, Weinheim (1992) Gerbert, P. et al.: Klimapfade für Deutschland. Studie im Auftrag des Bundesverbandes der Deutschen Industrie (BDI), Berlin, 2018. https://bdi.eu/publikation/news/klimapfade-fuerdeutschland/. Accessed on 24 Jan 2018 Gordon, T. J.: Trend Impact Analysis. In: Glenn, J. C. et al. (Ed.): Futures Research Methodology Version 3.0. CD-ROM. The Millennium Project, 1994. http://107.22.164.43/millennium/ FRMV3.html. Accessed on 06 June 2018
Literatur
255
Götze, U.: Szenario-Technik in der strategischen Unternehmensplanung. 2., aktualisierte Auflage. Wiesbaden (1993) Greeuw, S. C.H. et al. (Authors); International Centre for Integretive Studies (ICIS): Cloudy Crystal Balls : An assessment of recent European and global Scenario studies and Models. Experts’ corner report. Prospects and scenarios No 4. Environmental issues series no 17, European Environment Agency, Copenhagen, 2000 Grunwald, A.: Technikfolgenabschätzung: Eine Einführung. Edition Sigma, Berlin (2002) Heinecke, A.: Die Anwendung induktiver Verfahren in der Szenario-Technik. In: Falko E. P. Wilms (ed.) Szenariotechnik. Vom Umgang mit der Zukunft, pp. 183–213. Haupt Verlag, Bern (2006) Henning, H.-M. et al.: Phasen der Transformation des Energiesystems. In: Energiewirtschaftliche Tagesfragen, 65. Jahrgang, Heft 1/2; pp. 10–13.EWMedien und Kongresse GmbH, Essen, 2015. http://www.et-energie-online.de/AktuellesHeft/Topthema/tabid/70/NewsId/1230/Phasen-der- Transformation-des-Energiesystems.aspx. Accessed on 28 Feb 2018 Kosow, H. et al.: Methoden der Zukunfts-und Szenarienanalyse: Überblick, Bewertung und Auswahlkriterien. IZT WerkstattBericht Nr. 103. IZT – Institut für Zukunftsstudien und Technologiebewertung, Berlin, 2008. https://www.izt.de/fileadmin/publikationen/IZT_WB103.pdf. Accessed on 24 Jan 2018 Meyer, R. et al.: Diskursprojekt “Szenario Workshops: Zukünfte der Grünen Gentechnik”: Leitfaden “Szenario Workshop”. BMBF Förderkennzeichen: 01GP0774, Karlsruhe et al., 2009. http:// www.itas.kit.edu/pub/v/2009/meua09g.pdf. Accessed on 24 Jan 2018 Nagel, J.: Energie- und Ressourceninnovation: Wegweiser zur Gestaltung der Energiewende. Hanser Verlag, München (2017) NASA National Aeronautics and Space Administration: GISS Surface Temperature Analysis (v4). 2023. Download: https://data.giss.nasa.gov/gistemp/graphs_v4/, accessed on 01.06.2023 Steinmüller, K.: Workshop Zukunftsforschung. Teil 2: Szenarien: Grundlagen und Anwendungen. Z_punkt GmbH, Essen (2002) UNEP—United Nations Environment Programme: GEO 3 Global environment outlook 3. In: Past, Present and Future Perspectives, pp. 2002–2032. UNEP, Nairobi (2002) von Reibnitz, U.: Szenario-Technik: Instrumente für die unternehmerische und persönliche Erfolgsplanung. Wiesbaden (1991) Wilms, F.E.: Szenariotechnik: Vom Umgang mit der Zukunft. Haupt Verlag, Bern (2006)
6
Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF
In diesem Kapitel werden Beispiele zur Modellierung mit dem Tool OEMOF vorgestellt. Aus dem Release v0.1.x wird auf eine Studie der B.A.U.M. Consult Group zurückgegriffen, die u. a. noch die vorangegangene Form eines Transformers verwendet, bei der zwischen der Anzahl der In- und Outputs unterschieden wurde. Es handelt sich hierbei um eine konkrete Modellentwicklung einer Unternehmensberatung. Weitere Studien sind bis heute unter Umständen veröffentlicht worden. Zudem existieren Beispiele auf der Internetseite von OEMOF für die Releases v0.1.x, v0.2.x, v0.3.x und v0.4.x, auf die zurückgegriffen werden kann (s. https://github.com/oemof/oemof-examples/tree/master/oemof_ examples/oemof.solph).
6.1 Studie „Elektrifizierung von Landmaschinen und Einsatz von Photovoltaik“ Die Studie wurde im Rahmen der Verbundprojekte SESAM (FKZ 01ME12124), GridCon (FKZ 01ME14004C) (www.gridcon-project.de) und GridCon2 (FKZ 01ME17007C) im Auftrag des Bundesministeriums für Wirtschaft und Energie durchgeführt (Stöhr 2018a). Eine Forschergruppe bestehend aus John Deere, der TU Kaiserslautern und B.A.U.M. arbeitete in diesem Projekt an der Entwicklung und dem Aufbau elektrifizierter Landmaschinen und ihrer Integration in elektrische Netze (projekt-gridcon 2018). Ziel ist es, mehr Klimaschutz durch Batterien, Photovoltaik und den Einsatz von elektrischen Landmaschinen zu erreichen (baumgroup 2018). Im Rahmen der Studie wird der Einsatz voll elektrischer Landmaschinen im Zusammenspiel mit dem lokalen Stromnetz und der Stromerzeugung durch Photovoltaik betrachtet. Eine Elektrifizierung von Landmaschinen kann durch den Einsatz von Batterien oder den direkten Anschluss der Landmaschine an das lokale Stromnetz durch ein Kabel erfolgen. Der Einsatz elektrifizierter Landmaschinen hat
© Springer Nature Switzerland AG 2023 J. Nagel, Optimierung von Energieversorgungssystemen, https://doi.org/10.1007/978-3-031-36355-9_6
257
258
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF
uswirkungen auf das jeweilige lokale Stromnetz. Aus diesem Grund wurde in die UnterA suchung das zu einem landwirtschaftlichen Betrieb gehörende ländliche Ortsnetz mit einbezogen. Die zukünftige Smart-Grid-Infrastruktur soll einen lokalen Ausgleich zwischen der Erzeugung von elektrischer Energie aus erneuerbaren Energien von einem landwirtschafltichen Betrieb und dem lokalen Verbrauch durch die Landmaschine sowie durch vor Ort stationäre Verbraucher, wie Wohngebäude, ermöglichen. Die Smart-Grid-Infrastruktur wandelt den betrachteten landwirtschaftlichen Betrieb in einen intelligenten Verbraucher im Zusammenspiel mit dem Stromversorgungssystem. Unterstützt wird dies durch die Einbindung stationärer Speicher. Die B.A.U.M. Consult GmbH München hat in diesem Rahmen ein Optimierungsmodell in OEMOF entwickelt, um den wirtschaftlichen und ökologischen Einsatz einer voll elektrischen Landmaschine zu analysieren. Dazu wurden zwei Module in OEMOF erstellt. Ein Modul dient dazu, den ökonomischen Ansatz umzusetzen (economics_BAUM) (Stöhr 2018a). Weiterhin wurde eine Applikation für das Optimierungsmodell aufgesetzt (GridCon_storage) (Stöhr 2018a). Beide Programme beruhen auf dem Release v0.1. Die Applikation optimiert die Kosten für eine Erweiterung des elektrischen Netzes zur allgemeinen Stromversorgung und einen stationären Energiespeicher. Die Zukunftsvision einer voll elektrischen Landmaschine könnte ein selbstfahrendes Fahrgehäuse sein, welches wie bei einem elektrischen Rasenmäher oder Staubsauger über ein Kabel mit einer sich stationär bspw. am Feldrand befindenden Stromsteckdose verbunden ist.
6.1.1 Investitionsberechnung in OEMOF Das Programmsystem OEMOF stellt unter dem Package „tools“ das Modul „economics“ zur Verfügung (GitHub 2018). Hierin wird bei der Wirtschaftlichkeitsbetrachtung die Methode der Annuität zugrundegelegt. Diese wird in OEMOF mittels der Zielfunktion berechnet und ausgegeben. Die Annuität ist eine von mehren Methoden zur Bewertung einer Investition. Es handelt sich dabei um eine dynamische Investitionsmethode. In dynamischen Wirtschaftlichkeitsberechnungen findet die Verzinsung von Kapital Berücksichtigung (Konstantin 2017). Diese werden vorzugsweise bei Investitionen mit langer Laufzeit angewendet, wie dies in der Energiewirtschaft häufig der Fall ist. Eine Besonderheit der Annuitätenmethode ist die Betrachtung von Investitionsgütern unabhängig von der Lebensdauer (Konstantin 2017). Dies ist ein wichtiger Punkt, da die Lebensdauern von Investitionsgütern gerade in der heutigen Energiewirschaft mit den Erneuerbaren Energien sehr unterschiedlich sein können. So haben Batterien heutzutage eine viel geringe Lebensdauer als Windkrafträder. Mit der Annuitätenmethode ist ein finanzmathemtischer Vergleich trotzdem möglich. Dabei wird in OEMOF wie folgt vorgegangen. Es wird von diskontinuierlichen Investitionen ausgegangen (Stöhr 2018a). Diese stehen den sonst eher regelmäßigen Finanzflüssen gegenüber. Ein Vergleich oder ein finanzmathematischer Ansatz mit diesen beiden
6.1 Studie „Elektrifizierung von Landmaschinen und Einsatz von Photovoltaik“
259
Faktoren ist so nicht möglich. Darum wird ein jährlich in gleicher Höhe wiederkehrender Zahlungsfluss berechnet. Dieser Zahlungsfluss stellt die Annuität dar, die nach Gl. 6.1 berechnet werden kann (Stöhr 2018a): i
i
n n æ 1 ö æ 1 ö I 0 + å i =1 I i ç = A å i =1 ç ÷ ÷ è1+ z ø è1+ z ø
(6.1)
mit Io Anfangsinvestition i im Jahr i n Dauer von n Jahren z kalkulatorischer Zinssatz A Annuität Die Gleichung Gl. 6.1 kann wie folgt interpretiert werden: Auf der linken Seite sind die diskontinuierlichen Investitionen dargestellt. Diese Investitionen werden kalkulatorisch so betrachtet, als ob im gleichen Zeitraum regelmäßige jährliche Ausgaben in gleicher Höhe anfallen würden (Stöhr 2018a). Diese jährlichen Ausgaben entsprechen der Annuität. Für æ 1 ö die weitere Umformung wird der Faktor ç ÷ durch den Faktor q substituiert è 1+ z ø (s. Gl. 6.2): q=
1 1+ z
(6.2)
Auf diese Gleichung kann die Summenformel der geometrischen Reihen angewendet werden (s. Gl. 6.3) (Konstantin 2017):
å i =1 q i = n
(
q 1 - qn 1- q
)
(6.3)
In OEMOF werden Investitionsrechnungen unter dem Aspekt durchgeführt, dass im Betrachtungszeitraum nur eine einzige Anfangsinvestition erfolgt. Weitere Investitionen werden nicht durchgeführt. Für diesen Spezialfall kann Gl. 6.1 unter Verwendung von Gl. 6.3 weitergeführt werden zu Gl. 6.4 (Stöhr 2018a): A = I0
1- q
(
q 1 - qn
(1 + z ) n -1 (1 + z ) n
)
= I0 z
(6.4)
Für Optimierungsrechnungen bzgl. der Investitionen wird in OEMOF standardmäßig auf Gl. 6.4 im Modul „economics“ zurückgegriffen. Das Programm gibt dann die Variable „equivalent periodical (annual) costs“ („epc“) einer Investition wieder. Die äquivalenten periodischen Kosten (equivalent periodical cost (epc)) sind die zu optimierende Größe (Zielfunktion) des Optimierungsmodells.
260
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF
Im Modul „economics“ ist dieser Ansatz im Quellcode wie folgt umgesetzt (GitHub 2018). Das Modul wurde zwischenzeitlich leicht weiterentwickelt. Siehe dazu https:// oemof-tools.readthedocs.io/en/latest/_modules/oemof/tools/economics.html: Nachfolgrend wird die vorangegangene Version vorgestellt. # -*- coding: utf-8 -*-
"""Module to collect useful functions for economic calculation. This file is part of project oemof (github.com/oemof/oemof). It's copyrighted by the contributors recorded in the version control history of the file, available from its original location oemof/oemof/tools/economics.py SPDX-License-Identifier: GPL-3.0-or-later """ def annuity(capex, n, wacc): """Calculate the annuity. annuity = capex * (wacc * (1 + wacc) ** n) / ((1 + wacc) ** n - 1) Parameters ---------capex : float Capital expenditure (NPV of investment) n : int Number of years that the investment is used (economic lifetime) wacc : float Weighted average cost of capital Returns ------float : annuity """ return capex * (wacc * (1 + wacc) ** n) / ((1 + wacc) ** n - 1)
6.1.2 Finanzmathemtische Erweiterung der Annuitätenmethode im Modul „economics_BAUM“ In der Studie von B.A:U.M. Consult im Rahmen von GridCON werden mit Hilfe des Moduls „economics_BAUM“ ebenfalls die „equivalent periodical cost (epc)“ einer Investition berechnet. Die Variable „epc“ des Moduls „economics_BAUM“ beinhaltet alle fixen jährlichen Kosten einer wirtschaftlichen Tätigkeit. Der zuvor vorgestellte Ansatz „economics“ von OEMOF reicht für die hier betrachteten Zusammenhänge nicht aus, weshalb das ökonomische Modul „economics_BAUM“ entwickelt wurde. Da in dieser Studie Lithium-Ionen-Batteriespeicher betrachtet werden, ist
6.1 Studie „Elektrifizierung von Landmaschinen und Einsatz von Photovoltaik“
261
der kurzen Lebensdauer im Vergeich zu einem Stromnetz und den rapide sinkenden Investitionskosten dieser Batterien entsprechend Rechnung zu tragen. Die kurze Lebensdauer dieser Batterien führen dazu, dass im Betrachtungszeitraum mehrere Investionen zur Neuanschaffung von Batterien erfolgen müssen. Weiterhin muss die Degression der Kosten durch eine Funktion mit exponentiell fallenden Faktoren berücksichtigt werden. Für die Studie wurde von B.A:U.M. Consult die technische Lebensdauer eines elektrischen Stromnetzes als finanzieller Betrachtungszeitraum gewählt (Stöhr 2018a). Um die genannten Aspekte berücksichtigen zu können, wurde Gl. 6.1 wie folgt erweitert (s. Gl. 6.5) (Stöhr 2018a): m -1
(6.5)
I 0 + å j =1 I 0 (1 - cd ) q ju = Aå i =1 q i n
ju
mit i j m u n
Nummer des Jahres Zähler für Folgeinvestitionen Anzahl der Folgeinvestitionen technische Lebensdauer der Investition in Jahren n = m · u – d. h. das Ende der technischen Lebensdauer der letzten Folgeinvestition fällt mit dem Ende des finanziellen Betrachtungzeitraumes zusammen. I0 Anfangsinvestition I -I cd cd = i i +1 – jährliche Kostendegression Ii Wird die erste Folgebatterie nach u Jahren angeschafft, können deren nominale Kosten anhand Gl. 6.6 berechnet werden: I 0u = I 0 (1 - cd ) u
(6.6)
Betrachtet man jetzt die j-te Folgeinvestition, die nach j · u Jahren erfolgt, ändert sich die Berechnung der Kosten entsprechend Gl. 6.7: I 0ju = I 0 (1 - cd ) ju
(6.7)
Über den Faktor qju werden nominale Werte in kalkulatorische Werte umgewandelt. Nun wird die geometriche Reihe aus Gl. 6.3 auf die Gleichung Gl. 6.5 angewendet. Darüber kommt man zu Gl. 6.8 (Stöhr 2018):
( m -1)u æ u 1 - ( (1 - cd ) q ) ç I 0 1 + ( (1 - cd ) q ) u ç 1 - ( (1 - cd ) q ) è
(
)
ö q 1 - qn ÷=A ÷ 1- q ø
Löst man diese Gleichung nach der Annuität A auf, so folgt Gl. 6.9:
(6.8)
262
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF
A = I0
1- q
(
q 1 - qn
)
×
1 - ( (1 - cd ) q )
mu
(6.9)
1 - ( (1 - cd ) q ) u
1 angesetzt. Desweite1+ z ren wird der Faktor m ⋅ u durch n ersetzt. Darüber kommt man zu Gl. 6.10: Nun wird statt des Faktors q wieder der ursprüngliche Ansatz
n
æ 1 - cd ö 1- ç ÷ (1 + z ) è 1+ z ø × A = I0 z n u (1 + z ) - 1 1 - æ 1 - cd ö ç 1+ z ÷ ø è n
(6.10)
Im Modul „economics_BAUM“ werden die Variablen entsprechend der Begriffe der Finanzrechnung wie folgt umbenannt: I0 capex – Investitionsausgaben z wacc – gewichtete Kapitalkosten (Weighet Average Cost of Capital) cd cost_decrease Der Term an in Gl. 6.11 stellt den Annuitätsfaktor dar:
(1 + z ) an = z n (1 + z ) - 1 n
(6.11)
Unter Einbeziehung der Begriffsumbenennung ergibt sich aus Gl. 6.11 die Gl. 6.12:
(1 + wacc ) an = wacc n (1 + wacc ) - 1 n
(6.12)
Damit folgt aus Gl. 6.10 die Gl. 6.13: n
æ 1 - cd ö 1- ç 1 + wacc ÷ø A = capex × an × è u æ 1 - cd ö 1- ç ÷ è 1 + wacc ø
(6.13)
Bei Bedarf können zu Gl. 6.13 mit dem Faktor „oc“ alle jährlichen fixen Kosten (fixed annual operational costs) hinzuaddiert werden. Der Faktor „oc“ ist im Modell eine weitere Variable. Der aus Gl. 6.13 berechnete Wert wird als „equivalent periodical (annual) costs (epc)“ einer Investition bezeichent. Dieser Wert wird nach der Optimierung von OEMOF zurück-
6.1 Studie „Elektrifizierung von Landmaschinen und Einsatz von Photovoltaik“
263
gegeben sowie bei Bedarf die periodischen „fixed operational costs („oc“)“. Der Ausdruck „capex“ spiegelt die Modifikation der Annuität im Falle sich wiederholender Investitionen wider. Dabei finden die Wiederholungen in fixen Intervallen „u“ mit sinkenden Kosten statt. Handelt es sich um jährliche Perioden, ist der epc identisch mit der Annuität. Die „neue“ Energiewirtschaft mit der umfangreichen Einbeziehung Erneuerbarer Energien ermöglicht es den Energieverbrauchern, selbst Strom zu erzeugen und diesen in das bestehende Stromnetz einzuspreisen. Damit sind herkömmliche Verbraucher nicht mehr nur noch „Consumer“ sondern auch „Producer“, was sie zu so genannten „Prosumern“ macht. Die Besonderheit in der Modellierung liegt nun darin, dass diese „Prosumer“ regelmäßige fixe Einnahmen über den Verkauf von Strom erzielen. Die Berücksichtung regelmäßiger fixer Einnahmen war in OEMOF bisher nicht möglich. Durch das Modul „economics_BAUM“ kann dies zukünftig in neuen Applikationen eingebunden werden. Im Rahmen der GridCON Studie wurden über diesen Ansatz Einnahmen aus der Bereitstellung von Primärregelleistung (PRL) durch einen stationären Energiespeicher berücksichtigt. Dazu wurde im Modell die neue Variable „oc“ dahingehend erweitert, dass sie die Differenz aus fixen jährlichen Kosten und prognostizierten jährlichen Einnahmen aus der PRL-Bereitstellung berechnen kann (Stöhr 2018b). Die Variable gibt die jährlichen fixen „Nettokosten“ nach Abzug prognostizierter fixer Einnahmen wieder. Sie ist damit nicht mehr begrenzt auf die jährlichen fixen Kosten (fixed annual operational costs).
6.1.3 Der Quellcode des Moduls „economics_BAUM“ Nachfolgend wird der Quellcode des Moduls „economics_BAUM“ vorgestellt. Ausführliche Erläuterungen zu den einzelnen Programmschritten befinden sich direkt im Code (Stöhr 2018a): # -*- coding: utf-8 -*""" Module to collect useful functions for economic calculations. """ def epc(capex, n, u, wacc, cost_decrease = 0, oc = 0): """ function represents equivalent periodical costs of an economic activity parameters ---------capex: float capital expenditure for first investment per functional unit
264
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF
(e.g. 1 kWh or 1 kW) n : int number of years investigated; might be an integer multiple of technical lifetime (u) in case of repeated investments; in case of a single investment, n must equal u u : int number of years that a single investment is used, i.e. the technical lifetime of a single investment wacc : float weighted average cost of capital cost_decrease : float annual rate of cost decrease for repeated investments takes the value "0" if not set otherwise, that is in case of a single investment or in case of no cost decrease for repeated investments oc : float fixed annual operational costs per functional unit (e.g. 1 kWh or 1 kW) takes the value "0" if not set otherwise """ annuity_factor = (wacc*(1+wacc)**n)/((1+wacc)**n-1) # the annuity factor is the ratio of the equivalent annual costs of investment # (annuity) and the costs of investment in case of a single initial investment return (annuity_factor*capex*((1-((1-cost_decrease)/(1+wacc))**n) /(1-((1-cost_decrease)/(1+wacc))**u)))+oc # the value returned are the equivalent periodical (annual) costs of an # investment (annuity) plus the periodical fixed operational costs (oc); # the expression behind "capex" reflects the modification of the annuity # in case of repeated investments at fixed intervals u with decreasing costs;
6.1 Studie „Elektrifizierung von Landmaschinen und Einsatz von Photovoltaik“
265
6.1.4 Die Applikation „GridCon_storage“ Als nächstes wird die Applikation „GridCon_storage“ zur Optimierung der Kosten für eine Erweiterung des elektrischen Netzes zur allgemeinen Stromversorgung und einem stationären Energiesspeicher vorgestellt. Der Quellcode folgt dem allgemeinen formalen Aufbau in OEMOF. Hierin befinden sich auch umfangreiche Dokumentationen. ################################################################# ########## # IMPORTS ################################################################# ########## # outputlib from oemof import outputlib # default logger of oemof from oemof.tools import logger from oemof.tools import helpers from oemof.tools import economics_BAUM # economics is a tool to calculate the equivalent periodical cost (epc) # of an investment; # it has been modified by B.A.U.M. Consult GmbH within the frame of the # GridCon project (www.gridcon-project.de) and the modified version # has been called "economics_BAUM"; # it allows now calculting epc of a series of investments with a # defined cost-decrease rate; # it also allows taking into account fixed periodical costs such as # staff cost and offset fixed periodical income; from pyomo import environ import oemof.solph as solph # import oemof base classes to create energy system objects import logging import os import pandas as pd
266
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF
try:
import matplotlib.pyplot as plt except ImportError: plt = None # import load and generation data from csv-file and define timesteps
def optimise_storage_size(filename="GridCon1_Profile.csv", solver='cbc', debug=True, number_timesteps= (96*366), tee_switch=True): # the file "GridCon1_Profile" contains the normalised profile for the # agricultural base load profile L2, a synthetic electrified # agricultural machine load profile, and the PV generation profile ES0; # number_timesteps: one timestep has a duration of 15 minutes, # hence, 96 is the number of timesteps per day; # 366 is the number of days in a leap year, chosen here because # standard load profils of 2016 are used; # the total number of timesteps is therefore 96*366 = 35136; # initialise energysystem, date, time increment logging.info('Initialise the Energysystem') date_time_index = pd.date_range('1/1/2016', periods=number_timesteps, freq='15min') energysystem = solph.EnergySystem(timeindex=date_time_index) time_step = 0.25 # a 15 minutes time step equals 0.25 hours; # read data file full_filename = os.path.join(os.path.dirname(__file__), filename) data = pd.read_csv(full_filename, sep=",") ################################################################# ########## # DEFINITION OF TO-BE-OPTIMISED STRUCTURES ################################################################# ##########
6.1 Studie „Elektrifizierung von Landmaschinen und Einsatz von Photovoltaik“
267
# definition of the investigated (financial) period for which the # optimisation is performed; # needs to be the same for all objects whose costs are taken into account; n = 50 # financial period in years for which equivalent periodical costs of # different options are compared; # definition of the specific investment costs of those objects whose size # is optimised; # here, the electric grid connection and the electrical storage; # the electric grid connection considered here comprises the local mv-lv # transformer plus the respective share of the entire up-stream grid; # as a consequence of oemof allowing to handle only positive flow values, # the grid connection needs to be modelled twice: a "collecting half" for # the electric power flow from the local lv-grid to a far point in the # up-stream grid (it collects electricty generated in areas where # generation exceeds the demand at a given moment), and a "supplying half" # for the inverse flow from that far point to the local grid (it supplies # areas where the demand exceeds the generation at a given moment); invest_grid = 500 # assumed specific investment costs of the electric transformer linking # the low voltage and the medium voltage grid including respective # share of up-stream grid costs in €/kW; # the value is taken from a real price (about 200000 €) paid by an # investor for grid connection of about 400 kW active power provision # capacity (at the low-voltage side of the transformer) set up for a # new large load in a rural area; this amount contains essentially # upstream grid costs; # source: oral communication from a private investor; invest_el_lv_1_storage = 300 # assumed specific investment costs of electric energy storage system # in €/kWh; # figure reflects roughly specific investment costs of lithium-ion # batteries; # source: Sterner/Stadler, Energiespeicher, p. 600 # (indicates 170 - 600 €/kWh)
268
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF
# definition of parameters entering in the calculation of the equivalent # periodical costs (epc) of the electric transformer and the up-stream # grid; wacc = 0.05 # assumed weighted average cost of capital; u_grid = 50 # assumed technical lifetime of electric transformer and up-stream # grid; cost_decrease_grid = 0 # # # # # # #
indicates the relative annual decrease of investment costs; allows calculating the cost of a second or any further investment a certain number of years after the first one; here, only one investment in the electric transformer and up-stream grid is considered to be made here within the financial period; hence, there is no cost decrease and the variable takes the value zero;
oc_rate_grid = 0.02 # percentage of initial investment costs assumed for calculation of # specific annual fixed operational costs of electric transformer and # up-stream grid; oc_grid = oc_rate_grid * invest_grid # specific annual fixed operational costs of electric transformer and # up-stream grid in €/kW of active power provision capacity; # calculation of specific equivalent periodical costs (epc) i.e. the annual # costs equivalent to the investment costs (annuitiy) plus the fixed # operational costs of the electric transformer and the up-stream grid per # kW of active power provision capacity; sepc_grid = economics_BAUM.epc(invest_grid, n, u_grid, wacc, cost_decrease_grid, oc_grid) # specific equivalent periodical costs of transformer and of up-stream # grid in €/kW;
6.1 Studie „Elektrifizierung von Landmaschinen und Einsatz von Photovoltaik“
269
# definition of parameters entering in the calculation of the equivalent # periodical costs of the electric energy storage system; u_el_lv_1_storage = 5 # assumed technical lifetime of electric energy storage system cost_decrease_el_lv_1_storage = 0.1 # assumed annual cost decrease rate for newly installed electric energy # storage systems; reflects roughly learning curve for lithium-ion # battery storage systems in 2010-2016; oc_rate_el_lv_1_storage = 0.02 # percentage of specific initial investment costs assumed for # calculation of specific annual fixed operational costs of electric # storage system; oc_el_lv_1_storage = oc_rate_el_lv_1_storage * invest_el_lv_1_storage # specific annual fixed operational costs of electric storage system in # €/kWh; # c alculation of specific equivalent periodical costs (sepc in €/ kWh/year) # i.e. the specific annual costs equivalent to the investment costs # (annuitiy) plus the fixed operational costs of the electric energy # storage system; sepc_el_lv_1_storage = economics_BAUM.epc(invest_el_lv_1_storage, n, u_el_lv_1_storage, wacc, cost_decrease_el_lv_1_storage, oc_el_lv_1_storage) kS_el = sepc_el_lv_1_storage # equivalent specific annual costs of system in # €/kWh; # refers to nominal storage capacity;
electric
energy
storage
# calculation of income from provision of primary balancing power by # electric energy storage system; income is substracted from equivalent # periodical costs;
270
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF prl_on = 0 # if primary balancing power is planned to be provided by the energy # storage, set value "1", otherwise "0"; prl_weeks = 13 # number of entire weeks for which primary balancing power is planned # to be provided prl_income = 2.4 * prl_on * prl_weeks # corresponds to specific annual income per kWh of nominal electric # energy storage capacity, i.e. expressed in €/kWh, generated by # provision of primary balancing power in Germany at a remuneration of # 3000 €/week by an energy storage with a charge/ discharge rate of at # least 1 MW per MWh of storage capacity operated between 10% and 90% # of its nominal capacity; sepc_el_lv_1_storage = sepc_el_lv_1_storage - prl_income kS_el_netto = sepc_el_lv_1_storage # net specific equivalent periodical costs of electric energy storage # system taking into account income generated from provision of primary # balancing power;
################################################################# ########## # CREATION OF OEMOF STRUCTURE ################################################################# ########## logging.info('Constructing GridCon energy system structure') ################################################################# ########## # CREATION OF BUSES REPRESENTING ENERGY DISTRIBUTION ################################################################# ##########
6.1 Studie „Elektrifizierung von Landmaschinen und Einsatz von Photovoltaik“
271
b_el_mv = solph.Bus(label="b_el_mv") # creates medium voltage electric grid b_el_lv = solph.Bus(label="b_el_lv") # creates low voltage electric grid ################################################################# ########## # CREATION OF SOURCE OBJECTS ################################################################# ########## solph.Source(label='mv_source', outputs={b_el_mv: solph.Flow()}) # represents aggregated electric generators at a far point in the # up-stream grid; here, no limit is considered for this source; solph.Source(label='el_lv_7_pv', outputs={b_el_lv: solph.Flow(actual_value=data['pv'], nominal_value = 1, fixed=True)}) # represents aggregated pv power plants in investigated area which are # looked at as a single source of energy; # "outputs={b_el_lv: ...}" defines that this source is connected to the # low voltage grid; # "solph.Flow ..." defines properties of this connection: actual_value # get the pv generation data for all time intervals from csv-file; # "nominal_value = 1" signifies that pv generation data do not need # further processing, they are already absolute figures in kW; # "fixed=True" signifies that these data are not modified by the # solver; solph.Source(label='el_lv_6_grid_excess', outputs={b_el_lv: solph.Flow( variable_costs = 100000000)}) # # # # #
dummy producer of electric energy connected to low voltage grid; introduced to ensure energy balance in case no other solution is found; extremely high variable costs ensure that source is normally not used;
272
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF
################################################################# ########## # CREATION OF SINK OBJECTS ################################################################# ########## solph.Sink(label='el_mv_sink', inputs={b_el_mv: solph.Flow()}) # represents aggregated consumers at a far point in the up- stream grid; # here, it is assumed that no limit exists for this sink; solph.Sink(label='el_lv_2_base_load', inputs={b_el_lv: solph.Flow(actual_value=data['demand_el'], nominal_value= 1, fixed=True)}) # represents base load in low voltage (lv) electric grid; # "inputs={b_el_lv: ...}" defines that this sink is connected to the # low-voltage electric grid; # "solph.Flow ..." defines properties of this connection: actual_value # gets the base load data for all time intervals from csv-file; # "nominal_value = 1" signifies that base load data do not need further # processing, they are already absolute figures in kW; # "fixed=True" signifies that these data are not modified by the # solver; solph.Sink(label='el_lv_3_machine_load', inputs={b_el_lv: solph.Flow(actual_value=data['machine_load'], nominal_value= 1, fixed=True)}) # # # # # # # # # #
represents electrified agricultural machine connected to lv-grid; "inputs={b_el_lv: ...}" defines that this sink is connected to the low voltage electric grid; "solph.Flow ..." defines properties of this connection: actual_value gets the electrified agricultural machine load data for all time intervals from csv-file; "nominal_value = 1" signifies that base load data do not need further processing, they are already absolute figures in kW; "fixed=True" signifies that these data are not modified by the solver;
6.1 Studie „Elektrifizierung von Landmaschinen und Einsatz von Photovoltaik“
273
cost_electricity_losses = 6.5E-2 # # # # # #
(unit) cost that a farmer or equivalent investor in grid extension and/or electric energy storage pays for 1 kWh of electric energy which is lost; the value of 0.065 €/kWh corresponds to assumed average cost of electricity in a future energy system with predominant generation from PV and wind power plants;
curtailment = solph.Sink(label='el_lv_4_excess_sink', inputs={b_el_lv: solph.Flow(variable_costs = cost_electricity_losses)}) # represents curtailment of electric energy from PV plants, i.e. that # part of possible PV electricity generation which is actually not # generated by tuning the PV power electronics such that the output is # reduced below the instantaneous maximum power; # "inputs={b_el_lv: ...}" defines that this "sink" is connected to the # low voltage electric grid; # "solph.Flow ..." defines properties of this connection: # variable_costs are set at costs of electricity which is lost; ################################################################# ########## # CREATION OF TRANSFORMER OBJECTS ################################################################# ########## # as a consequence of oemof allowing to handle only positive flow # values,the local mv-lv transformer needs to be modelled by two different # objects, one for the electric power flow from the local lv-grid to a far # point in the up-stream grid, one for the inverse flow from that far point # to the local grid; # each (!) of the two objects represents, for the respective power flow # direction, not only the local mv-lv transformer, but the whole grid # infrastructure between a virtuel power supplier/ sink at a far point in # the up-stream grid and the local lv-grid, including all grid lines and # voltage transformation steps;
274
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF grid_loss_rate = 0.0685 # rate of losses within the entire up-stream grid including the local # transformer; # the value of 6.85% reflects average grid losses in Germany from # January to September 2017; # source: https://www.destatis.de/DE/ZahlenFakten/Wirtschaftsbereiche/ # Energie/Erzeugung/Tabellen/BilanzElektrizitaetsversorgung.html # [last retrieved on 16 November 2017]; grid_eff = 1 - grid_loss_rate # effective efficiency of power transmission in the up-stream grid; transformer_mv_to_lv = solph.LinearTransformer(label="transformer_mv_to_lv", inputs={b_el_mv: solph.Flow(variable_costs = (grid_loss_rate * cost_electricity_losses))}, outputs={b_el_lv: solph.Flow(investment=solph.Investment (ep_costs=0.5 * sepc_grid))}, conversion_factors={b_el_lv: grid_eff}) # represents the "supplying half" of the whole up-stream grid including # the "mv-to-lv electric transformer", i.e. that "half" of the # physical local transformer linking the low and medium voltage grid # "in the direction mv -> lv"; # "input" designates source bus of electricity, here: medium-voltage # grid; # variable costs are cost of electricity lost within one time interval # in the up-stream grid and transformer; they are a fraction of the # electricity generated at a far point in the up-stream grid times the # cost of electricity which gets lost; # "output" designates destination bus of electricity, here: low-voltage # grid; # fixed grid costs (epc), i.e. costs of "supplying half" of local # transformer and up-stream grid are attributed to output, because it # is the lv-side whose size has to be determined in the optimisation # process; # the conversion factor defines the ratio between the output flow, here # the electricity flowing from the local transformer into the
6.1 Studie „Elektrifizierung von Landmaschinen und Einsatz von Photovoltaik“
275
# low-voltage grid, and the input flow, here the electricity injected # into the up-stream grid at a far point; transformer_lv_to_mv = solph.LinearTransformer(label="transformer_lv_to_mv", inputs={b_el_lv: solph.Flow(investment = solph.Investment(ep_costs = 0.5 * sepc_grid))}, outputs={b_el_mv: solph.Flow(variable_costs = (cost_electricity_losses*grid_loss_rate/grid_eff))}, conversion_factors = {b_el_mv: grid_eff}) # represents the "collecting half" of the whole up-stream grid # including the local "lv-to-mv electric transformer", i.e. that "half" # of the physical local transformer linking the low and medium voltage # grid "in the direction lv -> mv"; # "input" designates source bus of electricity, here: low- voltage grid; # fixed grid costs (epc), i.e. the epc of the "collecting half" of # local transformer and up-stream grid are attributed to input, because # it is the lv-side whose size needs to match the rest of the modelled # system; # "output" designates the destination bus of electricity, # here: medium-voltage grid; # variable costs are cost of electricity lost within one time interval # in the transformer and up-stream grid; they are a fraction of the # electricity generated in the modelled system and fed into the # up-stream grid times the cost of electricity which gets lost; # the conversion factor defines the ratio between the output flow, here # the electric power flow consumed at a far point in the up-stream # grid, and the input flow, here the electricity flowing from the # low-voltage grid into the transformer; ################################################################# ########## # CREATION OF STORAGE OBJECTS ################################################################# ########## icf = 0.95 ocf = 0.95
276
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF # # # #
charging (icf) and discharging efficiency of the electric energy storage; the values reflect the efficiency of a lithium-ion battery with typical input, respectively output electronic converters;
el_storage_conversion_factor = icf * ocf # approximate term for effective efficiency of electric energy storage # system used for calculating the costs of electricity lost in the # electric energy storage system; for this purpose, and only for this # purpose, self-discharge losses are neglected; solph.Storage(label='el_lv_1_storage', inputs={b_el_lv: solph.Flow(variable_costs = cost_electricity_losses *(1-el_storage_conversion_factor))}, outputs={b_el_lv: solph.Flow()}, capacity_min = 0.1, capacity_max = 0.9, nominal_input_capacity_ratio = 1, nominal_output_capacity_ratio = 1, inflow_conversion_factor = icf, outflow_conversion_factor = ocf, capacity_loss = 0.0000025, investment=solph.Investment(ep_costs = sepc_el_lv_1_storage)) # represents electric energy storage (input and output are electricity) # "input" designates source of electricity charging the storage, here # the low voltage electricity grid, "output" the same for sink of # electricity discharged from the storage; # "capacity_min" and "capacity_max" designate, respectively, the # minimum and maximum state of charge of the storage, related to ist # maximum energy content; # values are typical for operation of lithium-ion batteries in # practical applications; # "inflow_conversion_factor" and "outflow_conversion_factor" designate, # respectively, the efficiency of the charging and discharging process; # "capacity_loss" reflects the self-discharge of the storage per # timestep as a fraction of the energy contained in the storage in the # preceding timestep; # the value 0.0000025 (0.00025%) corresponds to the self-discharge
6.1 Studie „Elektrifizierung von Landmaschinen und Einsatz von Photovoltaik“
277
# within 15 minutes, respectively 0.024% per day; that is in the midth # of the typical range of 0,008-0,041% per day for lithium-ion # batteries source: Sterner/Stadler, Energiespeicher, p. 600; ################################################################# ########## # OPTIMISATION OF THE ENERGY SYSTEM ################################################################# ########## logging.info('Optimise the energysystem') # initialise the operational model om = solph.OperationalModel(energysystem) # adding constraint my_block = environ.Block() def connect_invest_rule(m): expr = (om.InvestmentFlow.invest[b_el_lv, transformer_lv_ to_mv] == om.InvestmentFlow.invest[transformer_mv_to_lv, b_el_lv]) return expr my_block.invest_connect_constr = environ.Constraint( rule=connect_invest_rule) om.add_component('ConnectInvest', my_block) # # # # # #
defines that upper limit for energy flow from electric transformer to medium voltage grid equals upper limit for energy flow from transformer to low voltage; the fact that the maximum is addressed instead of the value in a specific timestep is reflected by the string "invest" in the name of the objects;
# if debug is true an lp-file will be written if debug: filename = os.path.join( helpers.extend_basic_path('lp_files'), 'GridCon.lp') logging.info('Store lp-file in {0}.'.format(filename)) om.write(filename, io_options={'symbolic_solver_labels': True})
278
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF
# if tee_switch is true solver messages will be displayed logging.info('Solve the optimisation problem') om.solve(solver=solver, solve_kwargs={'tee': tee_switch}) # Visualisation of results el_lv_1_storage = energysystem.groups['el_lv_1_storage'] print(' ') print('CAPACITY OF GRID CONNECTION') print('####################################################') print(' ') print('Grid collection capacity: ', energysystem.results [ b_el_lv][transformer_lv_to_mv].invest, 'kW') print('Grid supply capacity: ', energysystem.results [ transformer_mv_to_lv][b_el_lv].invest, 'kW') print(' kN: ', sepc_grid, '€/kW') print(' ') print('CAPACITY OF ELECTRIC ENERGY STORAGE') print('####################################################') print(' ') print('Storage capacity: ', energysystem.results [el_lv_1_storage][el_lv_1_storage].invest, 'kWh') print(' kS: ', kS_el, '€/kWh') print(' PRL income: ', prl_income, '€/kWh') print(' kS_netto: ', kS_el_netto, '€/kWh') print(' ') print('COST BREAKDOWN') print('####################################################') print(' ') print('Fixed grid costs: ', om.InvestmentFlow.investment_costs(),'€') print('Fixed storage costs: ', om.InvestmentStorage.investment_costs(), '€') print('Total fixed costs: ', om.InvestmentFlow.investment_costs() + om.InvestmentStorage.investment_costs(), '€') print(' ') print('Costs of grid losses: ', sum(energysystem.results[b_el_mv] [transformer_mv_to_lv]) * cost_electricity_losses * grid_loss_rate * time_step + sum(energysystem.results[b_el_lv][transformer_ lv_to_mv])
6.1 Studie „Elektrifizierung von Landmaschinen und Einsatz von Photovoltaik“
279
* cost_electricity_losses * grid_loss_rate * time_step,'€') print('Costs of storage losses: ', sum(energysystem.results[b_el_lv] [el_lv_1_storage]) * cost_electricity_losses * (1-el_storage_conversion_factor) * time_ step, '€') print('Costs of curtailment: ', sum(energysystem.results[b_el_lv] [curtailment]) * cost_electricity_losses * time_ step, '€') print('Total variable costs: ',om.Flow.variable_costs(), '€') print(' ') print('Total annual costs ', om.InvestmentFlow.investment_costs() + om.InvestmentStorage.investment_costs() + om.Flow.variable_costs(), '€') print('--------------------------------------------------') print('Objective function: ', energysystem.results.objective, '€') print('Accordance: ', (om.InvestmentFlow.investment_costs() + om.InvestmentStorage.investment_costs() + om.Flow.variable_costs()) / energysystem.results.objective*100, '%') print(' ') print('####################################################') print(' ') print(' ') return energysystem ################################################################# ########## # GENERATION OF CSV-FILE ################################################################# ########## def create_csv(energysystem):
results = outputlib.ResultsDataFrame(energy_system=energysystem) results.bus_balance_to_csv(bus_labels=['b_el_lv'], output_path='results_as_csv_LV_Net') def run_GridCon_example(**kwargs): logger.define_logging()
280
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF esys = optimise_storage_size(**kwargs) if plt is not None: create_csv(esys)
if __name__ == "__main__": run_GridCon_example()
Ergebnisse aus der Studie können (Stöhr 2018a) entnommen werden.
6.2 Modellierungsbeispiele aus OEMOF im Release v0.2.0 In OEMOF werden mehrere Optimierungsprobleme zur Verfügung gestellt, die einen Einblick in die Möglichkeiten bei der Modellierung geben. Die Beispiele befinden sich unter: https://github.com/oemof/oemof-examples. Im Folgenden werden drei Beispiele vorgestellt.
6.2.1 Beispiel „simple_dispatch“ Das Beispiel „simple_dispatch“ zeigt auf einfache Weise, wie mit einem least cost dis patch Ansatz der Einsatz unterschiedlicher Generatoren zur Deckung eines inelastischen Bedarfs modelliert werden kann. Es kommen konventionelle und erneuerbare Energien zum Einsatz. Für die Generatoren mit erneuerbaren Energien werden die Grenzkosten auf Null gesetzt. Desweiteren werden CHP Anlagen modelliert. Das Beispiel befindet sich im Release v0.5.0 unter:https://github.com/oemof/oemof-examples/blob/master/oemof_ examples/oemof.solph/v0.2.x/simple_dispatch/simple_dispatch.py. Erläuterungen befinden sich direkt im Quellcode. -*- coding: utf-8 -*####### Model „simple_dispatch“ ####### """ General description ------------------Data ---input_data.csv
6.2 Modellierungsbeispiele aus OEMOF im Release v0.2.0
281
Installation requirements ------------------------This example requires the latest version of oemof and matplotlib. Install by: pip install oemof pip install matplotlib """ import os import pandas as pd from oemof.solph import (Sink, Source, Transformer, Bus, Flow, Model, EnergySystem) from oemof.outputlib import views import matplotlib.pyplot as plt
solver = 'cbc'
# Create an energy system and optimize the dispatch at least costs. # #################### initialize and provide data ################### datetimeindex = pd.date_range('1/1/2016', periods=24*10, freq='H') energysystem = EnergySystem(timeindex=datetimeindex) filename = os.path.join(os.path.dirname(__file__), 'input_data.csv') data = pd.read_csv(filename, sep=",") # ######################### ################
create
energysystem
# resource buses bcoal = Bus(label='coal', balanced=False) bgas = Bus(label='gas', balanced=False) boil = Bus(label='oil', balanced=False) blig = Bus(label='lignite', balanced=False) # electricity and heat bel = Bus(label='bel') bth = Bus(label='bth') energysystem.add(bcoal, bgas, boil, blig, bel, bth)
components
282
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF
# an excess and a shortage variable can help to avoid infeasible problems energysystem.add(Sink(label='excess_el', inputs={bel: Flow()})) # shortage_el = Source(label='shortage_el', # outputs={bel: Flow(variable_costs=200)}) # sources energysystem.add(Source(
label='wind', outputs={bel: Flow(actual_value=data['wind'], nominal_value=66.3, fixed=True)}))
energysystem.add(Source(
label='pv', outputs={bel: Flow(actual_value=data['pv'], nominal_value=65.3, fixed=True)}))
# demands (electricity/heat) energysystem.add(Sink(label='demand_el', inputs={bel: Flow( nominal_value=85, actual_value=data['demand_el'], fixed=True)})) energysystem.add(Sink(label='demand_th', inputs={bth: Flow(nominal_value=40, actual_value=data['demand_th'], fixed=True)})) # power plants energysystem.add(Transformer( label='pp_coal', inputs={bcoal: Flow()}, outputs={bel: Flow(nominal_value=20.2, variable_costs=25)}, conversion_factors={bel: 0.39})) energysystem.add(Transformer( label='pp_lig', inputs={blig: Flow()}, outputs={bel: Flow(nominal_value=11.8, variable_costs=19)}, conversion_factors={bel: 0.41})) energysystem.add(Transformer( label='pp_gas', inputs={bgas: Flow()}, outputs={bel: Flow(nominal_value=41, variable_costs=40)}, conversion_factors={bel: 0.50}))
6.2 Modellierungsbeispiele aus OEMOF im Release v0.2.0
283
energysystem.add(Transformer( label='pp_oil', inputs={boil: Flow()}, outputs={bel: Flow(nominal_value=5, variable_costs=50)}, conversion_factors={bel: 0.28})) # combined heat and power plant (chp) energysystem.add(Transformer( label='pp_chp', inputs={bgas: Flow()}, outputs={bel: Flow(nominal_value=30, variable_costs=42), bth: Flow(nominal_value=40)}, conversion_factors={bel: 0.3, bth: 0.4})) # heat pump with a coefficient of performance (COP) of 3 b_heat_source = Bus(label='b_heat_source') energysystem.add(b_heat_source) energysystem.add(Source(label='heat_source', outputs={b_heat_source: Flow()})) cop = 3 energysystem.add(Transformer( label='heat_pump', inputs={bel: Flow(), b_heat_source: Flow()}, outputs={bth: Flow(nominal_value=10)}, conversion_factors={bel: 1/3, b_heat_source: (cop-1)/cop})) # ############################# optimization ########################
# create optimization model based on energy_system optimization_model = Model(energysystem=energysystem) # solve problem optimization_model.solve(solver=solver, solve_kwargs={'tee': True, 'keepfiles': False}) # write back results from optimization object to energysystem optimization_model.results()
284
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF
# ################################ ##########
results
######################
# subset of results that includes all flows into and from electrical bus # sequences are stored within a pandas.DataFrames and scalars e.g. # investment values within a pandas.Series object. # in this case the entry data['scalars'] does not exist since no investment # variables are used data = views.node(optimization_model.results(), 'bel') print('Optimization successful. Printing some results:', data['sequences'].info()) # see: https://pandas.pydata.org/pandas-docs/stable/visualization.html node_results_bel = views.node(optimization_model.results(), 'bel') node_results_flows = node_results_bel['sequences'] ax = node_results_flows.plot(kind='bar', stacked=True, linewidth=0, width=1) ax.set_title('Sums for optimization period') ax.legend(loc='upper right', bbox_to_anchor=(1, 1)) ax.set_xlabel('Electrical energy (MWh)') ax.set_ylabel('Flow') plt.tight_layout() dates = node_results_flows.index tick_distance = int(len(dates) / 7) - 1 ax.set_xticks(range(0, len(dates), tick_distance), minor=False) ax.set_xticklabels( [item.strftime('%d-%m-%Y') for item in dates.tolist()[0::tick_distance]], rotation=90, minor=False) plt.show()
node_results_bth = views.node(optimization_model.results(), 'bth') node_results_flows = node_results_bth['sequences'] ax = node_results_flows.plot(kind='bar', stacked=True, linewidth=0, width=1) ax.set_title('Sums for optimization period') ax.legend(loc='upper right', bbox_to_anchor=(1, 1))
6.2 Modellierungsbeispiele aus OEMOF im Release v0.2.0
285
ax.set_xlabel('Thermal energy (MWh)') ax.set_ylabel('Flow') plt.tight_layout() dates = node_results_flows.index tick_distance = int(len(dates) / 7) - 1 ax.set_xticks(range(0, len(dates), tick_distance), minor=False) ax.set_xticklabels( [item.strftime('%d-%m-%Y') for item in dates.tolist()[0::tick_distance]], rotation=90, minor=False) plt.show()
Die Ergebnisse dieses Beispiels „simple_dispatch“ können, wie in Abb. 6.1 und 6.2 dargestellt, grafisch ausgegeben werden. Die plot-Anweisungen befinden sich im Quellcode. Zunächst folgt die Darstellung der stromerzeugenden Anlagen (s. Abb. 6.1). Zudem lassen sich die Lastgänge der wärmeerzeugenden Energieanlagen darstellen (s. Abb. 6.2).
6.2.2 Beispiel Investment Modelle – Variante 1: Investitionskosten für Windkraft, PV und Speicher Im Gegensatz zu dem Model „simple_dispatch“ werden in diesem und im Kapitel Abschn. 6.2.3 zwei Varianten eines Investment Modells vorgestellt. In diesen Modellen geht es um die Optimierung der Kosten und damit um die Frage der Investition in Techno-
Abb. 6.1 Beispielhafte Darstellung der Fahrweise der einzelnen Energieerzeugungsanlagen zur Stromerzeugung
286
6 Modellierung von Optimierungsproblemen im Energiesektor mit OEMOF
Abb. 6.2 Beispielhafte Darstellung der Lastgänge der einzelnen Energieerzeugungsanlagen zur Wärmebereitstellung
logien für die Stromversorgung in einem Energieversorgungssystem. Unter dem Link: https://github.com/oemof/oemof-examples/tree/master/oemof_examples/oemof.solph/ v0.2.x/storage_investment können die Beispiele im Release v0.5 aufgerufen werden. Es befinden sich dort drei weitere Varianten des Investment Modells. Für die hier betrachteten zwei Beispiele ist das zugrunde liegende Energieversorgungsmodell jeweils dasselbe und die betrachteten Elemente in dem System sind die gleichen. Jedoch variiert in den beiden Varianten, ob eine Komponente bereits im System besteht, oder ob neu in diese investiert werden muss. Kernstück der beiden Modelle ist die Betrachtung eines Speichers. Speicher spielen sehr häufig eine wichtige Rolle, da deren wirtschaftlicher Einsatz geklärt werden muss. Im betrachteten Energieversorgungssystem werden Windenergie- und Photovoltaik Anlagen als feste Quellen (fixed Sources) betrachtet. Es existiert eine Ressource Gas, eine Commodity, ein Strombedarf, ein Gaskraftwerk und ein Speicher. Das System lässt sich im Quellcode wie folgt darstelllen: input/output bgas bel | | | | | | wind(FixedSource) |------------------>| | | | pv(FixedSource) |------------------>| | | | gas_resource |--------->| | (Commodity) | | | | | | demand(Sink) |