Merge branch 'master' into retrofit-gas-pipelines

This commit is contained in:
Fabian Neumann 2021-11-02 19:03:26 +01:00 committed by GitHub
commit 37e2e53486
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 2707 additions and 2141 deletions

4
.gitignore vendored
View File

@ -11,11 +11,8 @@ gurobi.log
/benchmarks
/logs
/notebooks
/data/timezone_mappings.csv
/data/urban_percent.csv
/data/links_p_nom.csv
/data/*totals.csv
/data/*Jensen.csv
/data/biomass*
/data/emobility/
/data/eea*
@ -28,6 +25,7 @@ gurobi.log
/data/.nfs*
/data/Industrial_Database.csv
/data/retro/tabula-calculator-calcsetbuilding.csv
/data/nuts*
*.org

14
.syncignore-receive Normal file
View File

@ -0,0 +1,14 @@
.snakemake
.git
.pytest_cache
.ipynb_checkpoints
.vscode
.DS_Store
__pycache__
*.pyc
*.pyo
*.ipynb
data
notebooks
benchmarks
*.nc

14
.syncignore-send Normal file
View File

@ -0,0 +1,14 @@
.snakemake
.git
.pytest_cache
.ipynb_checkpoints
.vscode
.DS_Store
__pycache__
*.pyc
*.pyo
*.ipynb
notebooks
benchmarks
resources
results

View File

@ -1,674 +1,20 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
MIT License
Copyright 2017-2021 The PyPSA-Eur Authors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -9,21 +9,24 @@
**WARNING**: This model is under construction and contains serious
problems that distort the results. See the github repository
[issues](https://github.com/PyPSA/pypsa-eur-sec/issues) for some of
the problems (please feel free to help or make suggestions). There is
neither documentation nor a paper yet, but we hope to have a preprint
out by autumn 2021. We cannot support this model if you choose to use
it.
**WARNING**: This model is under construction and contains serious problems that
distort the results. See the github repository
[issues](https://github.com/PyPSA/pypsa-eur-sec/issues) for some of the problems
(please feel free to help or make suggestions). There is neither a full
documentation nor a paper yet, but we hope to have a preprint out by the end of 2021.
You can find out more about the model capabilities in [a recent
presentation at EMP-E](https://nworbmot.org/energy/brown-empe.pdf) or the
following [preprint with a description of the industry
sector](https://arxiv.org/abs/2109.09563). We cannot support this model if you
choose to use it.
PyPSA-Eur-Sec builds on the electricity generation and transmission
model [PyPSA-Eur](https://github.com/PyPSA/pypsa-eur) to add demand
and supply for the following sectors: transport, space and water
heating, biomass, industry and industrial feedstocks. This completes
the energy system and includes all greenhouse gas emitters except
waste management, agriculture, forestry and land use.
heating, biomass, industry and industrial feedstocks, agriculture,
forestry and fishing. This completes the energy system and includes
all greenhouse gas emitters except waste management and land use.
Please see the [documentation](https://pypsa-eur-sec.readthedocs.io/)
for installation instructions and other useful information about the snakemake workflow.
@ -65,6 +68,6 @@ the additional sectors.
# Licence
The code in PyPSA-Eur-Sec is released as free software under the
[GPLv3](http://www.gnu.org/licenses/gpl-3.0.en.html), see LICENSE.txt.
[MIT License](https://opensource.org/licenses/MIT), see `LICENSE.txt`.
However, different licenses and terms of use may apply to the various
input data.

View File

@ -1,4 +1,7 @@
from snakemake.remote.HTTP import RemoteProvider as HTTPRemoteProvider
HTTP = HTTPRemoteProvider()
configfile: "config.yaml"
@ -6,7 +9,6 @@ wildcard_constraints:
lv="[a-z0-9\.]+",
simpl="[a-zA-Z0-9]*",
clusters="[0-9]+m?",
sectors="[+a-zA-Z0-9]+",
opts="[-+a-zA-Z0-9]*",
sector_opts="[-+a-zA-Z0-9\.\s]*"
@ -21,7 +23,6 @@ subworkflow pypsaeur:
snakefile: "../pypsa-eur/Snakefile"
configfile: "../pypsa-eur/config.yaml"
rule all:
input: SDIR + '/graphs/costs.pdf'
@ -167,6 +168,7 @@ rule build_energy_totals:
co2="data/eea/UNFCCC_v23.csv",
swiss="data/switzerland-sfoe/switzerland-new_format.csv",
idees="data/jrc-idees-2015",
district_heat_share='data/district_heat_share.csv',
eurostat=input_eurostat
output:
energy_name='resources/energy_totals.csv',
@ -180,16 +182,37 @@ rule build_energy_totals:
rule build_biomass_potentials:
input:
jrc_potentials="data/biomass/JRC Biomass Potentials.xlsx"
enspreso_biomass=HTTP.remote("https://cidportal.jrc.ec.europa.eu/ftp/jrc-opendata/ENSPRESO/ENSPRESO_BIOMASS.xlsx", keep_local=True),
nuts2="data/nuts/NUTS_RG_10M_2013_4326_LEVL_2.geojson", # https://gisco-services.ec.europa.eu/distribution/v2/nuts/download/#nuts21
regions_onshore=pypsaeur("resources/regions_onshore_elec_s{simpl}_{clusters}.geojson"),
nuts3_population="../pypsa-eur/data/bundle/nama_10r_3popgdp.tsv.gz",
swiss_cantons="../pypsa-eur/data/bundle/ch_cantons.csv",
swiss_population="../pypsa-eur/data/bundle/je-e-21.03.02.xls",
country_shapes=pypsaeur('resources/country_shapes.geojson')
output:
biomass_potentials_all='resources/biomass_potentials_all.csv',
biomass_potentials='resources/biomass_potentials.csv'
biomass_potentials_all='resources/biomass_potentials_all_s{simpl}_{clusters}.csv',
biomass_potentials='resources/biomass_potentials_s{simpl}_{clusters}.csv'
threads: 1
resources: mem_mb=1000
benchmark: "benchmarks/build_biomass_potentials"
benchmark: "benchmarks/build_biomass_potentials_s{simpl}_{clusters}"
script: 'scripts/build_biomass_potentials.py'
if config["sector"]["biomass_transport"]:
rule build_biomass_transport_costs:
input:
transport_cost_data=HTTP.remote("publications.jrc.ec.europa.eu/repository/bitstream/JRC98626/biomass potentials in europe_web rev.pdf", keep_local=True)
output:
biomass_transport_costs="resources/biomass_transport_costs.csv",
threads: 1
resources: mem_mb=1000
benchmark: "benchmarks/build_biomass_transport_costs"
script: 'scripts/build_biomass_transport_costs.py'
build_biomass_transport_costs_output = rules.build_biomass_transport_costs.output
else:
build_biomass_transport_costs_output = {}
rule build_ammonia_production:
input:
usgs="data/myb1-2017-nitro.xls"
@ -230,10 +253,10 @@ rule build_industrial_production_per_country_tomorrow:
input:
industrial_production_per_country="resources/industrial_production_per_country.csv"
output:
industrial_production_per_country_tomorrow="resources/industrial_production_per_country_tomorrow.csv"
industrial_production_per_country_tomorrow="resources/industrial_production_per_country_tomorrow_{planning_horizons}.csv"
threads: 1
resources: mem_mb=1000
benchmark: "benchmarks/build_industrial_production_per_country_tomorrow"
benchmark: "benchmarks/build_industrial_production_per_country_tomorrow_{planning_horizons}"
script: 'scripts/build_industrial_production_per_country_tomorrow.py'
@ -253,25 +276,25 @@ rule build_industrial_distribution_key:
rule build_industrial_production_per_node:
input:
industrial_distribution_key="resources/industrial_distribution_key_elec_s{simpl}_{clusters}.csv",
industrial_production_per_country_tomorrow="resources/industrial_production_per_country_tomorrow.csv"
industrial_production_per_country_tomorrow="resources/industrial_production_per_country_tomorrow_{planning_horizons}.csv"
output:
industrial_production_per_node="resources/industrial_production_elec_s{simpl}_{clusters}.csv"
industrial_production_per_node="resources/industrial_production_elec_s{simpl}_{clusters}_{planning_horizons}.csv"
threads: 1
resources: mem_mb=1000
benchmark: "benchmarks/build_industrial_production_per_node/s{simpl}_{clusters}"
benchmark: "benchmarks/build_industrial_production_per_node/s{simpl}_{clusters}_{planning_horizons}"
script: 'scripts/build_industrial_production_per_node.py'
rule build_industrial_energy_demand_per_node:
input:
industry_sector_ratios="resources/industry_sector_ratios.csv",
industrial_production_per_node="resources/industrial_production_elec_s{simpl}_{clusters}.csv",
industrial_production_per_node="resources/industrial_production_elec_s{simpl}_{clusters}_{planning_horizons}.csv",
industrial_energy_demand_per_node_today="resources/industrial_energy_demand_today_elec_s{simpl}_{clusters}.csv"
output:
industrial_energy_demand_per_node="resources/industrial_energy_demand_elec_s{simpl}_{clusters}.csv"
industrial_energy_demand_per_node="resources/industrial_energy_demand_elec_s{simpl}_{clusters}_{planning_horizons}.csv"
threads: 1
resources: mem_mb=1000
benchmark: "benchmarks/build_industrial_energy_demand_per_node/s{simpl}_{clusters}"
benchmark: "benchmarks/build_industrial_energy_demand_per_node/s{simpl}_{clusters}_{planning_horizons}"
script: 'scripts/build_industrial_energy_demand_per_node.py'
@ -334,7 +357,7 @@ rule prepare_sector_network:
clustered_gas_network="resources/gas_network_elec_s{simpl}_{clusters}.csv",
traffic_data_KFZ="data/emobility/KFZ__count",
traffic_data_Pkw="data/emobility/Pkw__count",
biomass_potentials='resources/biomass_potentials.csv',
biomass_potentials='resources/biomass_potentials_s{simpl}_{clusters}.csv',
heat_profile="data/heat_load_profile_BDEW.csv",
costs=CDIR + "costs_{planning_horizons}.csv",
profile_offwind_ac=pypsaeur("resources/profile_offwind-ac.nc"),
@ -344,7 +367,7 @@ rule prepare_sector_network:
busmap=pypsaeur("resources/busmap_elec_s{simpl}_{clusters}.csv"),
clustered_pop_layout="resources/pop_layout_elec_s{simpl}_{clusters}.csv",
simplified_pop_layout="resources/pop_layout_elec_s{simpl}.csv",
industrial_demand="resources/industrial_energy_demand_elec_s{simpl}_{clusters}.csv",
industrial_demand="resources/industrial_energy_demand_elec_s{simpl}_{clusters}_{planning_horizons}.csv",
heat_demand_urban="resources/heat_demand_urban_elec_s{simpl}_{clusters}.nc",
heat_demand_rural="resources/heat_demand_rural_elec_s{simpl}_{clusters}.nc",
heat_demand_total="resources/heat_demand_total_elec_s{simpl}_{clusters}.nc",
@ -363,7 +386,8 @@ rule prepare_sector_network:
solar_thermal_total="resources/solar_thermal_total_elec_s{simpl}_{clusters}.nc",
solar_thermal_urban="resources/solar_thermal_urban_elec_s{simpl}_{clusters}.nc",
solar_thermal_rural="resources/solar_thermal_rural_elec_s{simpl}_{clusters}.nc",
**build_retro_cost_output
**build_retro_cost_output,
**build_biomass_transport_costs_output
output: RDIR + '/prenetworks/elec_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}_{planning_horizons}.nc'
threads: 1
resources: mem_mb=2000

View File

@ -1,4 +1,4 @@
version: 0.5.0
version: 0.6.0
logging_level: INFO
@ -21,15 +21,17 @@ scenario:
opts: # only relevant for PyPSA-Eur
- ''
sector_opts: # this is where the main scenario settings are
- Co2L0-3H-T-H-B-I-solar+p3-dist1
- Co2L0-3H-T-H-B-I-A-solar+p3-dist1
# to really understand the options here, look in scripts/prepare_sector_network.py
# Co2Lx specifies the CO2 target in x% of the 1990 values; default will give default (5%);
# Co2L0p25 will give 25% CO2 emissions; Co2Lm0p05 will give 5% negative emissions
# xH is the temporal resolution; 3H is 3-hourly, i.e. one snapshot every 3 hours
# single letters are sectors: T for land transport, H for building heating,
# B for biomass supply, I for industry, shipping and aviation
# B for biomass supply, I for industry, shipping and aviation,
# A for agriculture, forestry and fishing
# solar+c0.5 reduces the capital cost of solar to 50\% of reference value
# solar+p3 multiplies the available installable potential by factor 3
# co2 stored+e2 multiplies the potential of CO2 sequestration by a factor 2
# dist{n} includes distribution grids with investment cost of n times cost in data/costs.csv
# for myopic/perfect foresight cb states the carbon budget in GtCO2 (cumulative
# emissions throughout the transition path in the timeframe determined by the
@ -71,7 +73,8 @@ electricity:
# regulate what components with which carriers are kept from PyPSA-Eur;
# some technologies are removed because they are implemented differently
# or have different year-dependent costs in PyPSA-Eur-Sec
# (e.g. battery or H2 storage) or have different year-dependent costs
# in PyPSA-Eur-Sec
pypsa_eur:
Bus:
- AC
@ -97,28 +100,28 @@ energy:
biomass:
year: 2030
scenario: Med
scenario: ENS_Med
classes:
solid biomass:
- Primary agricultural residues
- Forestry energy residue
- Secondary forestry residues
- Secondary Forestry residues sawdust
- Forestry residues from landscape care biomass
- Agricultural waste
- Fuelwood residues
- Secondary Forestry residues - woodchips
- Sawdust
- Residues from landscape care
- Municipal waste
not included:
- Bioethanol sugar beet biomass
- Rapeseeds for biodiesel
- sunflower and soya for Biodiesel
- Starchy crops biomass
- Grassy crops biomass
- Willow biomass
- Poplar biomass potential
- Roundwood fuelwood
- Roundwood Chips & Pellets
- Sugar from sugar beet
- Rape seed
- "Sunflower, soya seed "
- Bioethanol barley, wheat, grain maize, oats, other cereals and rye
- Miscanthus, switchgrass, RCG
- Willow
- Poplar
- FuelwoodRW
- C&P_RW
biogas:
- Manure biomass potential
- Sludge biomass
- Manure solid, liquid
- Sludge
solar_thermal:
@ -139,8 +142,16 @@ existing_capacities:
sector:
central: true
central_fraction: 0.6
district_heating:
potential: 0.6 # maximum fraction of urban demand which can be supplied by district heating
# increase of today's district heating demand to potential maximum district heating share
# progress = 0 means today's district heating share, progress = 1 means maximum fraction of urban demand is supplied by district heating
progress: 1
# 2020: 0.0
# 2030: 0.3
# 2040: 0.6
# 2050: 1.0
district_heating_loss: 0.15
bev_dsm_restriction_value: 0.75 #Set to 0 for no restriction on BEV DSM
bev_dsm_restriction_time: 7 #Time at which SOC of BEV has to be dsm_restriction_value
transport_heating_deadband_upper: 20.
@ -149,7 +160,6 @@ sector:
ICE_upper_degree_factor: 1.6
EV_lower_degree_factor: 0.98
EV_upper_degree_factor: 0.63
district_heating_loss: 0.15
bev_dsm: true #turns on EV battery
bev_availability: 0.5 #How many cars do smart charging
bev_energy: 0.05 #average battery size in MWh
@ -160,34 +170,46 @@ sector:
bev_avail_mean: 0.8
v2g: true #allows feed-in to grid from EV battery
#what is not EV or FCEV is oil-fuelled ICE
land_transport_fuel_cell_share: # 1 means all FCEVs
2020: 0
2030: 0.05
2040: 0.1
2050: 0.15
land_transport_electric_share: # 1 means all EVs
2020: 0
2030: 0.25
2040: 0.6
2050: 0.85
land_transport_fuel_cell_share: 0.15 # 1 means all FCEVs
# 2020: 0
# 2030: 0.05
# 2040: 0.1
# 2050: 0.15
land_transport_electric_share: 0.85 # 1 means all EVs
# 2020: 0
# 2030: 0.25
# 2040: 0.6
# 2050: 0.85
transport_fuel_cell_efficiency: 0.5
transport_internal_combustion_efficiency: 0.3
agriculture_machinery_electric_share: 0
agriculture_machinery_fuel_efficiency: 0.7 # fuel oil per use
agriculture_machinery_electric_efficiency: 0.3 # electricity per use
shipping_average_efficiency: 0.4 #For conversion of fuel oil to propulsion in 2011
shipping_hydrogen_liquefaction: false # whether to consider liquefaction costs for shipping H2 demands
shipping_hydrogen_share: 1 # 1 means all hydrogen FC
# 2020: 0
# 2025: 0
# 2030: 0.05
# 2035: 0.15
# 2040: 0.3
# 2045: 0.6
# 2050: 1
time_dep_hp_cop: true #time dependent heat pump coefficient of performance
heat_pump_sink_T: 55. # Celsius, based on DTU / large area radiators; used in build_cop_profiles.py
# conservatively high to cover hot water and space heating in poorly-insulated buildings
reduce_space_heat_exogenously: true # reduces space heat demand by a given factor (applied before losses in DH)
# this can represent e.g. building renovation, building demolition, or if
# the factor is negative: increasing floor area, increased thermal comfort, population growth
reduce_space_heat_exogenously_factor: # per unit reduction in space heat demand
reduce_space_heat_exogenously_factor: 0.29 # per unit reduction in space heat demand
# the default factors are determined by the LTS scenario from http://tool.european-calculator.eu/app/buildings/building-types-area/?levers=1ddd4444421213bdbbbddd44444ffffff11f411111221111211l212221
2020: 0.10 # this results in a space heat demand reduction of 10%
2025: 0.09 # first heat demand increases compared to 2020 because of larger floor area per capita
2030: 0.09
2035: 0.11
2040: 0.16
2045: 0.21
2050: 0.29
# 2020: 0.10 # this results in a space heat demand reduction of 10%
# 2025: 0.09 # first heat demand increases compared to 2020 because of larger floor area per capita
# 2030: 0.09
# 2035: 0.11
# 2040: 0.16
# 2045: 0.21
# 2050: 0.29
retrofitting : # co-optimises building renovation to reduce space heat demand
retro_endogen: false # co-optimise space heat savings
cost_factor: 1.0 # weight costs for building renovation
@ -212,7 +234,8 @@ sector:
co2_vent: true
SMR: true
co2_sequestration_potential: 200 #MtCO2/a sequestration potential for Europe
co2_sequestration_cost: 20 #EUR/tCO2 for transport and sequestration of CO2
co2_sequestration_cost: 10 #EUR/tCO2 for sequestration of CO2
co2_network: false
cc_fraction: 0.9 # default fraction of CO2 captured with post-combustion capture
hydrogen_underground_storage: true
use_fischer_tropsch_waste_heat: true
@ -228,25 +251,61 @@ sector:
H2_retrofit_capacity_per_CH4: 0.6 # ratio for H2 capacity per original CH4 capacity of retrofitted pipelines
gas_distribution_grid: true
gas_distribution_grid_cost_factor: 1.0 #multiplies cost in data/costs.csv
biomass_transport: false # biomass transport between nodes
conventional_generation: # generator : carrier
OCGT: gas
industry:
St_primary_fraction: 0.3 # fraction of steel produced via primary route (DRI + EAF) versus secondary route (EAF); today fraction is 0.6
St_primary_fraction: 0.3 # fraction of steel produced via primary route versus secondary route (scrap+EAF); today fraction is 0.6
# 2020: 0.6
# 2025: 0.55
# 2030: 0.5
# 2035: 0.45
# 2040: 0.4
# 2045: 0.35
# 2050: 0.3
DRI_fraction: 1 # fraction of the primary route converted to DRI + EAF
# 2020: 0
# 2025: 0
# 2030: 0.05
# 2035: 0.2
# 2040: 0.4
# 2045: 0.7
# 2050: 1
H2_DRI: 1.7 #H2 consumption in Direct Reduced Iron (DRI), MWh_H2,LHV/ton_Steel from 51kgH2/tSt in Vogl et al (2018) doi:10.1016/j.jclepro.2018.08.279
elec_DRI: 0.322 #electricity consumption in Direct Reduced Iron (DRI) shaft, MWh/tSt HYBRIT brochure https://ssabwebsitecdn.azureedge.net/-/media/hybrit/files/hybrit_brochure.pdf
Al_primary_fraction: 0.2 # fraction of aluminium produced via the primary route versus scrap; today fraction is 0.4
# 2020: 0.4
# 2025: 0.375
# 2030: 0.35
# 2035: 0.325
# 2040: 0.3
# 2045: 0.25
# 2050: 0.2
MWh_CH4_per_tNH3_SMR: 10.8 # 2012's demand from https://ec.europa.eu/docsroom/documents/4165/attachments/1/translations/en/renditions/pdf
MWh_elec_per_tNH3_SMR: 0.7 # same source, assuming 94-6% split methane-elec of total energy demand 11.5 MWh/tNH3
MWh_H2_per_tNH3_electrolysis: 6.5 # from https://doi.org/10.1016/j.joule.2018.04.017, around 0.197 tH2/tHN3 (>3/17 since some H2 lost and used for energy)
MWh_elec_per_tNH3_electrolysis: 1.17 # from https://doi.org/10.1016/j.joule.2018.04.017 Table 13 (air separation and HB)
NH3_process_emissions: 24.5 # in MtCO2/a from SMR for H2 production for NH3 from UNFCCC for 2015 for EU28
petrochemical_process_emissions: 25.5 # in MtCO2/a for petrochemical and other from UNFCCC for 2015 for EU28
HVC_primary_fraction: 1.0 #fraction of current non-ammonia basic chemicals produced via primary route
HVC_primary_fraction: 1. # fraction of today's HVC produced via primary route
HVC_mechanical_recycling_fraction: 0. # fraction of today's HVC produced via mechanical recycling
HVC_chemical_recycling_fraction: 0. # fraction of today's HVC produced via chemical recycling
HVC_production_today: 52. # MtHVC/a from DECHEMA (2017), Figure 16, page 107; includes ethylene, propylene and BTX
MWh_elec_per_tHVC_mechanical_recycling: 0.547 # from SI of https://doi.org/10.1016/j.resconrec.2020.105010, Table S5, for HDPE, PP, PS, PET. LDPE would be 0.756.
MWh_elec_per_tHVC_chemical_recycling: 6.9 # Material Economics (2019), page 125; based on pyrolysis and electric steam cracking
chlorine_production_today: 9.58 # MtCl/a from DECHEMA (2017), Table 7, page 43
MWh_elec_per_tCl: 3.6 # DECHEMA (2017), Table 6, page 43
MWh_H2_per_tCl: -0.9372 # DECHEMA (2017), page 43; negative since hydrogen produced in chloralkali process
methanol_production_today: 1.5 # MtMeOH/a from DECHEMA (2017), page 62
MWh_elec_per_tMeOH: 0.167 # DECHEMA (2017), Table 14, page 65
MWh_CH4_per_tMeOH: 10.25 # DECHEMA (2017), Table 14, page 65
hotmaps_locate_missing: false
reference_year: 2015
# references:
# DECHEMA (2017): https://dechema.de/dechema_media/Downloads/Positionspapiere/Technology_study_Low_carbon_energy_and_feedstock_for_the_European_chemical_industry-p-20002750.pdf
# Material Economics (2019): https://materialeconomics.com/latest-updates/industrial-transformation-2050
costs:
lifetime: 25 #default lifetime
@ -360,6 +419,7 @@ plotting:
- solar thermal collector
- central solar thermal collector
tech_colors:
# wind
onwind: "#235ebc"
onshore wind: "#235ebc"
offwind: "#6895dd"
@ -368,117 +428,161 @@ plotting:
offshore wind (AC): "#6895dd"
offwind-dc: "#74c6f2"
offshore wind (DC): "#74c6f2"
wave: '#004444'
hydro: '#3B5323'
hydro reservoir: '#3B5323'
ror: '#78AB46'
run of river: '#78AB46'
hydroelectricity: '#006400'
# water
hydro: '#298c81'
hydro reservoir: '#298c81'
ror: '#3dbfb0'
run of river: '#3dbfb0'
hydroelectricity: '#298c81'
PHS: '#51dbcc'
wave: '#a7d4cf'
# solar
solar: "#f9d002"
solar PV: "#f9d002"
solar thermal: coral
solar rooftop: '#ffef60'
OCGT: wheat
OCGT marginal: sandybrown
OCGT-heat: '#ee8340'
gas boiler: '#ee8340'
gas boilers: '#ee8340'
gas boiler marginal: '#ee8340'
gas-to-power/heat: '#ee8340'
gas: brown
Gas pipeline : brown
natural gas: brown
SMR: '#4F4F2F'
SMR CC: '#6f6f42'
oil: '#B5A642'
oil boiler: '#B5A677'
lines: k
transmission lines: k
H2: m
hydrogen storage: m
battery: slategray
battery storage: slategray
home battery: '#614700'
home battery storage: '#614700'
Nuclear: r
Nuclear marginal: r
nuclear: r
uranium: r
Coal: k
coal: k
Coal marginal: k
Lignite: grey
lignite: grey
Lignite marginal: grey
CCGT: '#ee8340'
CCGT marginal: '#ee8340'
heat pumps: '#76EE00'
heat pump: '#76EE00'
air heat pump: '#76EE00'
ground heat pump: '#40AA00'
power-to-heat: '#40AA00'
resistive heater: pink
Sabatier: '#FF1493'
methanation: '#FF1493'
power-to-gas: '#FF1493'
power-to-liquid: '#FFAAE9'
helmeth: '#7D0552'
DAC: '#E74C3C'
co2 stored: '#123456'
CO2 sequestration: '#123456'
CC: k
co2: '#123456'
co2 vent: '#654321'
solid biomass for industry co2 from atmosphere: '#654321'
solid biomass for industry co2 to stored: '#654321'
gas for industry co2 to atmosphere: '#654321'
gas for industry co2 to stored: '#654321'
Fischer-Tropsch: '#44DD33'
kerosene for aviation: '#44BB11'
naphtha for industry: '#44FF55'
land transport oil: '#44DD33'
water tanks: '#BBBBBB'
hot water storage: '#BBBBBB'
hot water charging: '#BBBBBB'
hot water discharging: '#999999'
CHP: r
CHP heat: r
CHP electric: r
PHS: g
Ambient: k
Electric load: b
Heat load: r
heat: darkred
rural heat: '#880000'
central heat: '#b22222'
decentral heat: '#800000'
low-temperature heat for industry: '#991111'
process heat: '#FF3333'
heat demand: darkred
electric demand: k
Li ion: grey
district heating: '#CC4E5C'
retrofitting: purple
building retrofitting: purple
BEV charger: grey
V2G: grey
land transport EV: grey
electricity: k
gas for industry: '#333333'
gas for industry CC: '#404040'
solid biomass for industry: '#555555'
solid biomass for industry CC: '#555555'
industry electricity: '#222222'
industry new electricity: '#222222'
solar thermal: '#ffbf2b'
solar rooftop: '#ffea80'
# gas
OCGT: '#e0986c'
OCGT marginal: '#e0986c'
OCGT-heat: '#e0986c'
gas boiler: '#db6a25'
gas boilers: '#db6a25'
gas boiler marginal: '#db6a25'
gas: '#e05b09'
natural gas: '#e05b09'
CCGT: '#a85522'
CCGT marginal: '#a85522'
gas for industry co2 to atmosphere: '#692e0a'
gas for industry co2 to stored: '#8a3400'
gas for industry: '#853403'
gas for industry CC: '#692e0a'
gas pipeline: '#ebbca0'
Gas pipeline: '#ebbca0'
# oil
oil: '#c9c9c9'
oil boiler: '#adadad'
agriculture machinery oil: '#949494'
shipping oil: "#808080"
land transport oil: '#afafaf'
# nuclear
Nuclear: '#ff8c00'
Nuclear marginal: '#ff8c00'
nuclear: '#ff8c00'
uranium: '#ff8c00'
# coal
Coal: '#545454'
coal: '#545454'
Coal marginal: '#545454'
solid: '#545454'
Lignite: '#826837'
lignite: '#826837'
Lignite marginal: '#826837'
# biomass
biogas: '#e3d37d'
biomass: '#baa741'
solid biomass: '#baa741'
solid biomass transport: '#baa741'
solid biomass for industry: '#7a6d26'
solid biomass for industry CC: '#47411c'
solid biomass for industry co2 from atmosphere: '#736412'
solid biomass for industry co2 to stored: '#47411c'
# power transmission
lines: '#6c9459'
transmission lines: '#6c9459'
electricity distribution grid: '#97ad8c'
# electricity demand
Electric load: '#110d63'
electric demand: '#110d63'
electricity: '#110d63'
industry electricity: '#2d2a66'
industry new electricity: '#2d2a66'
agriculture electricity: '#494778'
# battery + EVs
battery: '#ace37f'
battery storage: '#ace37f'
home battery: '#80c944'
home battery storage: '#80c944'
BEV charger: '#baf238'
V2G: '#e5ffa8'
land transport EV: '#baf238'
Li ion: '#baf238'
# hot water storage
water tanks: '#e69487'
hot water storage: '#e69487'
hot water charging: '#e69487'
hot water discharging: '#e69487'
# heat demand
Heat load: '#cc1f1f'
heat: '#cc1f1f'
heat demand: '#cc1f1f'
rural heat: '#ff5c5c'
central heat: '#cc1f1f'
decentral heat: '#750606'
low-temperature heat for industry: '#8f2727'
process heat: '#ff0000'
agriculture heat: '#d9a5a5'
# heat supply
heat pumps: '#2fb537'
heat pump: '#2fb537'
air heat pump: '#36eb41'
ground heat pump: '#2fb537'
Ambient: '#98eb9d'
CHP: '#8a5751'
CHP CC: '#634643'
CHP heat: '#8a5751'
CHP electric: '#8a5751'
district heating: '#e8beac'
resistive heater: '#d8f9b8'
retrofitting: '#8487e8'
building retrofitting: '#8487e8'
# hydrogen
H2 for industry: "#f073da"
H2 for shipping: "#ebaee0"
H2: '#bf13a0'
hydrogen: '#bf13a0'
SMR: '#870c71'
SMR CC: '#4f1745'
H2 liquefaction: '#d647bd'
hydrogen storage: '#bf13a0'
H2 storage: '#bf13a0'
land transport fuel cell: '#6b3161'
H2 pipeline: '#f081dc'
H2 Fuel Cell: '#c251ae'
H2 Electrolysis: '#ff29d9'
# syngas
Sabatier: '#9850ad'
methanation: '#c44ce6'
methane: '#c44ce6'
helmeth: '#e899ff'
# synfuels
Fischer-Tropsch: '#25c49a'
liquid: '#25c49a'
kerosene for aviation: '#a1ffe6'
naphtha for industry: '#57ebc4'
# co2
CC: '#f29dae'
CCS: '#f29dae'
CO2 sequestration: '#f29dae'
DAC: '#ff5270'
co2 stored: '#f2385a'
co2: '#f29dae'
co2 vent: '#ffd4dc'
CO2 pipeline: '#f5627f'
# emissions
process emissions CC: '#000000'
process emissions: '#222222'
process emissions to stored: '#444444'
process emissions to atmosphere: '#888888'
process emissions: '#222222'
process emissions CC: '#484848'
oil emissions: '#666666'
land transport oil emissions: '#666666'
land transport fuel cell: '#AAAAAA'
biogas: '#800000'
solid biomass: '#DAA520'
today: '#D2691E'
shipping: '#6495ED'
electricity distribution grid: '#333333'
oil emissions: '#aaaaaa'
shipping oil emissions: "#555555"
land transport oil emissions: '#777777'
agriculture machinery oil emissions: '#333333'
# other
shipping: '#03a2ff'
power-to-heat: '#2fb537'
power-to-gas: '#c44ce6'
power-to-H2: '#ff29d9'
power-to-liquid: '#25c49a'
gas-to-power/heat: '#ee8340'
waste: '#e3d37d'
other: '#000000'

View File

@ -0,0 +1,34 @@
country,share to satisfy heat demand (residential) in percent,capacity[MWth]
AT,14,11200
BG,16,6162
BA,8,
HR,6.3,2221
CZ,40,
DK,65,
FI,38,23390
FR,5,
DE,13.8,
HU,7.92875588637399,8549
IS,90,8079000
IE,0.8,
IT,3,8727
LV,73,2254
LT,56,
MK,23.7745607009008,636
NO,4,3400
PL,42,54912
PT,0.070754716981132,34
RS,25,5821
SI,8.86,1739
ES,0.251589260787732,1273
SE,50.4,
UK,2,
BY,70,
EE,52,5406
KO,3,207
RO,23,9962
SK,54,15000
NL,4,9800
CH,4,2792
AL,0,
ME,0,
1 country share to satisfy heat demand (residential) in percent capacity[MWth]
2 AT 14 11200
3 BG 16 6162
4 BA 8
5 HR 6.3 2221
6 CZ 40
7 DK 65
8 FI 38 23390
9 FR 5
10 DE 13.8
11 HU 7.92875588637399 8549
12 IS 90 8079000
13 IE 0.8
14 IT 3 8727
15 LV 73 2254
16 LT 56
17 MK 23.7745607009008 636
18 NO 4 3400
19 PL 42 54912
20 PT 0.070754716981132 34
21 RS 25 5821
22 SI 8.86 1739
23 ES 0.251589260787732 1273
24 SE 50.4
25 UK 2
26 BY 70
27 EE 52 5406
28 KO 3 207
29 RO 23 9962
30 SK 54 15000
31 NL 4 9800
32 CH 4 2792
33 AL 0
34 ME 0

View File

@ -0,0 +1,25 @@
hour,weekday,weekend
0,0.9181438689,0.9421512708
1,0.9172359071,0.9400891069
2,0.9269464481,0.9461062015
3,0.9415047932,0.9535084941
4,0.9656299507,0.9651094993
5,1.0221166443,0.9834676747
6,1.1553090493,1.0124171051
7,1.2093411031,1.0446615927
8,1.1470295942,1.088203419
9,1.0877191341,1.1110334576
10,1.0418327372,1.0926752822
11,1.0062977133,1.055488209
12,0.9837030359,1.0251266112
13,0.9667570278,0.9990015154
14,0.9548320932,0.9782897278
15,0.9509232061,0.9698167237
16,0.9636973319,0.974288587
17,0.9799372563,0.9886456216
18,1.0046501848,1.0084159643
19,1.0079452419,1.0171243296
20,0.9860566481,0.9994722379
21,0.9705228074,0.982761591
22,0.9586485819,0.9698167237
23,0.9335023778,0.9515079292
1 hour weekday weekend
2 0 0.9181438689 0.9421512708
3 1 0.9172359071 0.9400891069
4 2 0.9269464481 0.9461062015
5 3 0.9415047932 0.9535084941
6 4 0.9656299507 0.9651094993
7 5 1.0221166443 0.9834676747
8 6 1.1553090493 1.0124171051
9 7 1.2093411031 1.0446615927
10 8 1.1470295942 1.088203419
11 9 1.0877191341 1.1110334576
12 10 1.0418327372 1.0926752822
13 11 1.0062977133 1.055488209
14 12 0.9837030359 1.0251266112
15 13 0.9667570278 0.9990015154
16 14 0.9548320932 0.9782897278
17 15 0.9509232061 0.9698167237
18 16 0.9636973319 0.974288587
19 17 0.9799372563 0.9886456216
20 18 1.0046501848 1.0084159643
21 19 1.0079452419 1.0171243296
22 20 0.9860566481 0.9994722379
23 21 0.9705228074 0.982761591
24 22 0.9586485819 0.9698167237
25 23 0.9335023778 0.9515079292

30
data/urban_percent.csv Normal file
View File

@ -0,0 +1,30 @@
AT,66
BA,40
BE,98
BG,74
CH,74
CZ,73
DE,75
DK,88
EE,68
ES,80
FI,84
FR,80
GB,83
GR,78
HR,59
HU,71
IE,63
IT,69
LT,67
LU,90
LV,67
NL,90
NO,80
PL,61
PT,63
RO,55
RS,56
SE,86
SI,50
SK,54
1 AT 66
2 BA 40
3 BE 98
4 BG 74
5 CH 74
6 CZ 73
7 DE 75
8 DK 88
9 EE 68
10 ES 80
11 FI 84
12 FR 80
13 GB 83
14 GR 78
15 HR 59
16 HU 71
17 IE 63
18 IT 69
19 LT 67
20 LU 90
21 LV 67
22 NL 90
23 NO 80
24 PL 61
25 PT 63
26 RO 55
27 RS 56
28 SE 86
29 SI 50
30 SK 54

View File

@ -62,17 +62,17 @@ master_doc = 'index'
# General information about the project.
project = u'PyPSA-Eur-Sec'
copyright = u'2019-2020 Tom Brown (KIT), Marta Victoria (Aarhus University), Lisa Zeyen (KIT)'
author = u'2019-2020 Tom Brown (KIT), Marta Victoria (Aarhus University), Lisa Zeyen (KIT)'
copyright = u'2019-2021 Tom Brown (KIT, TUB), Marta Victoria (Aarhus University), Lisa Zeyen (KIT, TUB), Fabian Neumann (TUB)'
author = u'2019-2021 Tom Brown (KIT, TUB), Marta Victoria (Aarhus University), Lisa Zeyen (KIT, TUB), Fabian Neumann (TUB)'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = u'0.5'
version = u'0.6'
# The full version, including alpha/beta/rc tags.
release = u'0.5.0'
release = u'0.6.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@ -2,11 +2,11 @@ description,file/folder,licence,source
JRC IDEES database,jrc-idees-2015/,CC BY 4.0,https://ec.europa.eu/jrc/en/potencia/jrc-idees
urban/rural fraction,urban_percent.csv,unknown,unknown
JRC biomass potentials,biomass/,unknown,https://doi.org/10.2790/39014
JRC ENSPRESO biomass potentials,remote,CC BY 4.0,https://data.jrc.ec.europa.eu/dataset/74ed5a04-7d74-4807-9eab-b94774309d9f
EEA emission statistics,eea/UNFCCC_v23.csv,EEA standard re-use policy,https://www.eea.europa.eu/data-and-maps/data/national-emissions-reported-to-the-unfccc-and-to-the-eu-greenhouse-gas-monitoring-mechanism-16
Eurostat Energy Balances,eurostat-energy_balances-*/,Eurostat,https://ec.europa.eu/eurostat/web/energy/data/energy-balances
Swiss energy statistics from Swiss Federal Office of Energy,switzerland-sfoe/,unknown,http://www.bfe.admin.ch/themen/00526/00541/00542/02167/index.html?dossier_id=02169
BASt emobility statistics,emobility/,unknown,http://www.bast.de/DE/Verkehrstechnik/Fachthemen/v2-verkehrszaehlung/Stundenwerte.html?nn=626916
timezone mappings,timezone_mappings.csv,CC BY 4.0,Tom Brown
BDEW heating profile,heat_load_profile_BDEW.csv,unknown,https://github.com/oemof/demandlib
heating profiles for Aarhus,heat_load_profile_DK_AdamJensen.csv,unknown,Adam Jensen MA thesis at Aarhus University
George Lavidas wind/wave costs,WindWaveWEC_GLTB.xlsx,unknown,George Lavidas
@ -24,3 +24,6 @@ Comparative level investment,comparative_level_investment.csv,Eurostat,https://e
Electricity taxes,electricity_taxes_eu.csv,Eurostat,https://appsso.eurostat.ec.europa.eu/nui/show.do?dataset=nrg_pc_204&lang=en
Building topologies and corresponding standard values,tabula-calculator-calcsetbuilding.csv,unknown,https://episcope.eu/fileadmin/tabula/public/calc/tabula-calculator.xlsx
Retrofitting thermal envelope costs for Germany,retro_cost_germany.csv,unkown,https://www.iwu.de/forschung/handlungslogiken/kosten-energierelevanter-bau-und-anlagenteile-bei-modernisierung/
District heating most countries,jrc-idees-2015/,CC BY 4.0,https://ec.europa.eu/jrc/en/potencia/jrc-idees,,
District heating missing countries,district_heat_share.csv,unkown,https://www.euroheat.org/knowledge-hub/country-profiles,,

Can't render this file because it has a wrong number of fields in line 27.

View File

@ -29,6 +29,11 @@ heating, biomass, industry and industrial feedstocks. This completes
the energy system and includes all greenhouse gas emitters except
waste management, agriculture, forestry and land use.
.. note::
More about the current model capabilities and preliminary results
can be found in `a recent presentation at EMP-E <https://nworbmot.org/energy/brown-empe.pdf>`_
and the the following `preprint with a description of the industry sector <https://arxiv.org/abs/2109.09563>`_.
This diagram gives an overview of the sectors and the links between
them:
@ -61,9 +66,25 @@ PyPSA-Eur-Sec is the different extra_functionality required to build
storage and CHP constraints.
PyPSA-Eur-Sec is designed to be imported into the open toolbox `PyPSA <https://www.pypsa.org>`_ for which `documentation <https://pypsa.org/doc>`_ is available as well.
PyPSA-Eur-Sec is designed to be imported into the open toolbox `PyPSA
<https://www.pypsa.org>`_ for which `documentation <https://pypsa.org/doc>`_ is
available as well.
This project is maintained by the `Energy System Modelling group <https://www.iai.kit.edu/english/2338.php>`_ at the `Institute for Automation and Applied Informatics <https://www.iai.kit.edu/english/index.php>`_ at the `Karlsruhe Institute of Technology <http://www.kit.edu/english/index.php>`_. The group is funded by the `Helmholtz Association <https://www.helmholtz.de/en/>`_ until 2024. Previous versions were developed by the `Renewable Energy Group <https://fias.uni-frankfurt.de/physics/schramm/renewable-energy-system-and-network-analysis/>`_ at `FIAS <https://fias.uni-frankfurt.de/>`_ to carry out simulations for the `CoNDyNet project <http://condynet.de/>`_, financed by the `German Federal Ministry for Education and Research (BMBF) <https://www.bmbf.de/en/index.html>`_ as part of the `Stromnetze Research Initiative <http://forschung-stromnetze.info/projekte/grundlagen-und-konzepte-fuer-effiziente-dezentrale-stromnetze/>`_.
This project is currently maintained by the `Department of Digital
Transformation in Energy Systems <https://tub-ensys.github.io>`_ at the
`Technical University of Berlin <https://www.tu.berlin>`_. Previous versions
were developed by the `Energy System Modelling group
<https://www.iai.kit.edu/english/2338.php>`_ at the `Institute for Automation
and Applied Informatics <https://www.iai.kit.edu/english/index.php>`_ at the
`Karlsruhe Institute of Technology <http://www.kit.edu/english/index.php>`_
which was funded by the `Helmholtz Association <https://www.helmholtz.de/en/>`_,
and by the `Renewable Energy Group
<https://fias.uni-frankfurt.de/physics/schramm/renewable-energy-system-and-network-analysis/>`_
at `FIAS <https://fias.uni-frankfurt.de/>`_ to carry out simulations for the
`CoNDyNet project <http://condynet.de/>`_, financed by the `German Federal
Ministry for Education and Research (BMBF) <https://www.bmbf.de/en/index.html>`_
as part of the `Stromnetze Research Initiative
<http://forschung-stromnetze.info/projekte/grundlagen-und-konzepte-fuer-effiziente-dezentrale-stromnetze/>`_.
Documentation
@ -134,7 +155,7 @@ it.
Licence
=======
The code in PyPSA-Eur-Sec is released as free software under the `GPLv3
<http://www.gnu.org/licenses/gpl-3.0.en.html>`_, see
The code in PyPSA-Eur-Sec is released as free software under the
`MIT license <https://opensource.org/licenses/MIT>`_, see
`LICENSE <https://github.com/PyPSA/pypsa-eur-sec/blob/master/LICENSE.txt>`_.
However, different licenses and terms of use may apply to the various input data.

View File

@ -66,15 +66,15 @@ Data requirements
=================
Small data files are included directly in the git repository, while
larger ones are archived in a data bundle. The data bundle's size is
around 640 MB.
larger ones are archived in a data bundle on zenodo (`10.5281/zenodo.5546517 <https://doi.org/10.5281/zenodo.5546517>`_).
The data bundle's size is around 640 MB.
To download and extract the data bundle on the command line:
.. code:: bash
projects/pypsa-eur-sec/data % wget "https://nworbmot.org/pypsa-eur-sec-data-bundle-210418.tar.gz"
projects/pypsa-eur-sec/data % tar xvzf pypsa-eur-sec-data-bundle-210418.tar.gz
`
projects/pypsa-eur-sec/data % wget "https://zenodo.org/record/5546517/files/pypsa-eur-sec-data-bundle.tar.gz"
projects/pypsa-eur-sec/data % tar xvzf pypsa-eur-sec-data-bundle.tar.gz
The data licences and sources are given in the following table.
@ -89,10 +89,8 @@ The data licences and sources are given in the following table.
Set up the default configuration
================================
First make your own copy of the ``config.yaml``. For overnight
scenarios, use ``config.default.yaml``. For a pathway optimization
with myopic foresight (which is still experimental), use
``config.myopic.yaml``. For example:
First make your own copy of the ``config.yaml`` based on
``config.default.yaml``. For example:
.. code:: bash

View File

@ -6,61 +6,192 @@ Future release
==============
.. note::
This unreleased version currently requires the master branches of PyPSA, PyPSA-Eur, and the technology-data repository.
This unreleased version currently may require the master branches of PyPSA, PyPSA-Eur, and the technology-data repository.
PyPSA-Eur-Sec 0.6.0 (4 October 2021)
====================================
This release includes
improvements regarding the basic chemical production,
the addition of plastics recycling,
the addition of the agriculture, forestry and fishing sector,
more regionally resolved biomass potentials,
CO2 pipeline transport and storage, and
more options in setting exogenous transition paths,
besides many performance improvements.
This release is known to work with `PyPSA-Eur
<https://github.com/PyPSA/pypsa-eur>`_ Version 0.4.0, `Technology Data
<https://github.com/PyPSA/technology-data>`_ Version 0.3.0 and
`PyPSA <https://github.com/PyPSA/PyPSA>`_ Version 0.18.0.
Please note that the data bundle has also been updated.
**General**
* With this release, we change the license from copyleft GPLv3 to the more
liberal MIT license with the consent of all contributors.
**New features and functionality**
* Distinguish costs for home battery storage and inverter from utility-scale
battery costs.
* Separate basic chemicals into HVC (high-value chemicals), chlorine, methanol and ammonia
[`#166 <https://github.com/PyPSA/PyPSA-Eur-Sec/pull/166>`_].
* Add option to specify reuse, primary production, and mechanical and chemical
recycling fraction of platics
[`#166 <https://github.com/PyPSA/PyPSA-Eur-Sec/pull/166>`_].
* Include energy demands and CO2 emissions for the agriculture, forestry and fishing sector.
It is included by default through the option ``A`` in the ``sector_opts`` wildcard.
Part of the emissions (1.A.4.c) was previously assigned to "industry non-elec" in the ``co2_totals.csv``.
Hence, excluding the agriculture sector will now lead to a tighter CO2 limit.
Energy demands are taken from the JRC IDEES database (missing countries filled with eurostat data)
and are split into
electricity (lighting, ventilation, specific electricity uses, pumping devices (electric)),
heat (specific heat uses, low enthalpy heat)
machinery oil (motor drives, farming machine drives, pumping devices (diesel)).
Heat demand is assigned at "services rural heat" buses.
Electricity demands are added to low-voltage buses.
Time series for demands are constant and distributed inside countries by population
[`#147 <https://github.com/PyPSA/PyPSA-Eur-Sec/pull/147>`_].
* Include today's district heating shares in myopic optimisation and add option
to specify exogenous path for district heating share increase under ``sector:
district_heating:`` [`#149 <https://github.com/PyPSA/PyPSA-Eur-Sec/pull/149>`_].
* Added option for hydrogen liquefaction costs for hydrogen demand in shipping.
This introduces a new ``H2 liquid`` bus at each location. It is activated via
``sector: shipping_hydrogen_liquefaction: true``.
* The share of shipping transformed into hydrogen fuel cell can be now defined
for different years in the ``config.yaml`` file. The carbon emission from the
remaining share is treated as a negative load on the atmospheric carbon dioxide
bus, just like aviation and land transport emissions.
* The transformation of the Steel and Aluminium production can be now defined
for different years in the ``config.yaml`` file.
* Include the option to alter the maximum energy capacity of a store via the
``carrier+factor`` in the ``{sector_opts}`` wildcard. This can be useful for
sensitivity analyses. Example: ``co2 stored+e2`` multiplies the ``e_nom_max`` by
factor 2. In this example, ``e_nom_max`` represents the CO2 sequestration
potential in Europe.
* Use `JRC ENSPRESO database <https://data.jrc.ec.europa.eu/dataset/74ed5a04-7d74-4807-9eab-b94774309d9f>`_ to
spatially disaggregate biomass potentials to PyPSA-Eur regions based on
overlaps with NUTS2 regions from ENSPRESO (proportional to area) (`#151
<https://github.com/PyPSA/pypsa-eur-sec/pull/151>`_).
* Add option to regionally disaggregate biomass potential to individual nodes
(previously given per country, then distributed by population density within)
and allow the transport of solid biomass. The transport costs are determined
based on the `JRC-EU-Times Bioenergy report
<http://dx.doi.org/10.2790/01017>`_ in the new optional rule
``build_biomass_transport_costs``. Biomass transport can be activated with the
setting ``sector: biomass_transport: true``.
* Add option to regionally resolve CO2 storage and add CO2 pipeline transport
because geological storage potential,
CO2 utilisation sites and CO2 capture sites may be separated. The CO2 network
is built from zero based on the topology of the electricity grid (greenfield).
Pipelines are assumed to be bidirectional and lossless. Furthermore, neither
retrofitting of natural gas pipelines (required pressures are too high, 80-160
bar vs <80 bar) nor other modes of CO2 transport (by ship, road or rail) are
considered. The regional representation of CO2 is activated with the config
setting ``sector: co2_network: true`` but is deactivated by default. The
global limit for CO2 sequestration now applies to the sum of all CO2 stores
via an ``extra_functionality`` constraint.
* The myopic option can now be used together with different clustering for the
generators and the network. The existing renewable capacities are split evenly
among the regions in every country [`#144 <https://github.com/PyPSA/PyPSA-Eur-Sec/pull/144>`_].
* Add optional function to use ``geopy`` to locate entries of the Hotmaps
database of industrial sites with missing location based on city and country,
which reduces missing entries by half. It can be activated by setting
``industry: hotmaps_locate_missing: true``, takes a few minutes longer, and
should only be used if spatial resolution is coarser than city level.
**Performance and Structure**
* Extended use of ``multiprocessing`` for much better performance
(from up to 20 minutes to less than one minute).
* Compatibility with ``atlite>=0.2``. Older versions of ``atlite`` will no longer work.
* Handle most input files (or base directories) via ``snakemake.input``.
* Use of ``mock_snakemake`` from PyPSA-Eur.
* Update ``solve_network`` rule to match implementation in PyPSA-Eur by using ``n.ilopf()`` and remove outdated code using ``pyomo``.
Allows the new setting to skip iterated impedance updates with ``solving: options: skip_iterations: true``.
* Update ``solve_network`` rule to match implementation in PyPSA-Eur by using
``n.ilopf()`` and remove outdated code using ``pyomo``.
Allows the new setting to skip iterated impedance updates with ``solving:
options: skip_iterations: true``.
* The component attributes that are to be overridden are now stored in the folder
``data/override_component_attrs`` analogous to ``pypsa/component_attrs``.
This reduces verbosity and also allows circumventing the ``n.madd()`` hack
for individual components with non-default attributes.
This data is also tracked in the Snakefile.
A function ``helper.override_component_attrs`` was added that loads this data
and can pass the overridden component attributes into ``pypsa.Network()``:
>>> from helper import override_component_attrs
>>> overrides = override_component_attrs(snakemake.input.overrides)
>>> n = pypsa.Network("mynetwork.nc", override_component_attrs=overrides)
and can pass the overridden component attributes into ``pypsa.Network()``.
* Add various parameters to ``config.default.yaml`` which were previously hardcoded inside the scripts
(e.g. energy reference years, BEV settings, solar thermal collector models, geomap colours).
* Removed stale industry demand rules ``build_industrial_energy_demand_per_country``
and ``build_industrial_demand``. These are superseded with more regionally resolved rules.
* Use simpler and shorter ``gdf.sjoin()`` function to allocate industrial sites
from the Hotmaps database to onshore regions.
This change also fixes a bug:
The previous version allocated sites to the closest bus,
but at country borders (where Voronoi cells are distorted by the borders),
this had resulted in e.g. a Spanish site close to the French border
being wrongly allocated to the French bus if the bus center was closer.
* Bugfix: Corrected calculation of "gas for industry" carbon capture efficiency.
* Retrofitting rule is now only triggered if endogeneously optimised.
* Show progress in build rules with ``tqdm`` progress bars.
* Reduced verbosity of ``Snakefile`` through directory prefixes.
* Improve legibility of ``config.default.yaml`` and remove unused options.
* Add optional function to use ``geopy`` to locate entries of the Hotmaps database of industrial sites
with missing location based on city and country, which reduces missing entries by half. It can be
activated by setting ``industry: hotmaps_locate_missing: true``, takes a few minutes longer,
and should only be used if spatial resolution is coarser than city level.
* Use the country-specific time zone mappings from ``pytz`` rather than a manual mapping.
* A function ``add_carrier_buses()`` was added to the ``prepare_network`` rule to reduce code duplication.
* In the ``prepare_network`` rule the cost and potential adjustment was moved into an
own function ``maybe_adjust_costs_and_potentials()``.
* Use ``matplotlibrc`` to set the default plotting style and backend``.
* Added benchmark files for each rule.
* Implements changes to ``n.snapshot_weightings`` in upcoming PyPSA version (cf. `PyPSA/#227 <https://github.com/PyPSA/PyPSA/pull/227>`_).
* New dependencies: ``tqdm``, ``atlite>=0.2.4``, ``pytz`` and ``geopy`` (optional).
These are included in the environment specifications of PyPSA-Eur.
* Consistent use of ``__main__`` block and further unspecific code cleaning.
* Distinguish costs for home battery storage and inverter from utility-scale battery costs.
* Use ``matplotlibrc`` to set the default plotting style and backend.
* Added benchmark files for each rule.
* Consistent use of ``__main__`` block and further unspecific code cleaning.
* Updated data bundle and moved data bundle to zenodo.org (`10.5281/zenodo.5546517 <https://doi.org/10.5281/zenodo.5546517>`_).
**Bugfixes and Compatibility**
* Compatibility with ``atlite>=0.2``. Older versions of ``atlite`` will no longer work.
* Corrected calculation of "gas for industry" carbon capture efficiency.
* Implemented changes to ``n.snapshot_weightings`` in PyPSA v0.18.0.
* Compatibility with ``xarray`` version 0.19.
* New dependencies: ``tqdm``, ``atlite>=0.2.4``, ``pytz`` and ``geopy`` (optional).
These are included in the environment specifications of PyPSA-Eur v0.4.0.
Many thanks to all who contributed to this release!
PyPSA-Eur-Sec 0.5.0 (21st May 2021)
@ -242,4 +373,4 @@ To make a new release of the data bundle, make an archive of the files in ``data
.. code:: bash
data % tar pczf pypsa-eur-sec-data-bundle-YYMMDD.tar.gz eea/UNFCCC_v23.csv switzerland-sfoe biomass eurostat-energy_balances-* jrc-idees-2015 emobility urban_percent.csv timezone_mappings.csv heat_load_profile_DK_AdamJensen.csv WindWaveWEC_GLTB.xlsx myb1-2017-nitro.xls Industrial_Database.csv retro/tabula-calculator-calcsetbuilding.csv
data % tar pczf pypsa-eur-sec-data-bundle.tar.gz eea/UNFCCC_v23.csv switzerland-sfoe biomass eurostat-energy_balances-* jrc-idees-2015 emobility WindWaveWEC_GLTB.xlsx myb1-2017-nitro.xls Industrial_Database.csv retro/tabula-calculator-calcsetbuilding.csv nuts/NUTS_RG_10M_2013_4326_LEVL_2.geojson

View File

@ -44,11 +44,13 @@ Hydrogen network: nodal.
Methane network: single node for Europe, since future demand is so
low and no bottlenecks are expected.
Solid biomass: single node for Europe, until transport costs can be
incorporated.
Solid biomass: choice between single node for Europe and nodal where biomass
potential is regionally disaggregated (currently given per country,
then distributed by population density within)
and transport of solid biomass is possible.
CO2: single node for Europe, but a transport and storage cost is added for
sequestered CO2.
sequestered CO2. Optionally: nodal, with CO2 transport via pipelines.
Liquid hydrocarbons: single node for Europe, since transport costs for
liquids are low.

View File

@ -43,7 +43,7 @@ Heat demand is split into:
* ``urban central``: large-scale district heating networks in urban areas with dense heat demand
* ``residential/services urban decentral``: heating for individual buildings in urban areas
* ``residential/services rural``: heating for individual buildings in rural areas
* ``residential/services rural``: heating for individual buildings in rural areas, agriculture heat uses
Heat supply
@ -183,13 +183,13 @@ Solid biomass provides process heat up to 500 Celsius in industry, as well as fe
Solid biomass supply
=====================
Only wastes and residues from the JRC biomass dataset.
Only wastes and residues from the JRC ENSPRESO biomass dataset.
Oil product demand
=====================
Transport fuels and naphtha as a feedstock for the chemicals industry.
Transport fuels, agriculture machinery and naphtha as a feedstock for the chemicals industry.
Oil product supply
======================

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 300 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 114 KiB

View File

@ -28,7 +28,7 @@ def add_build_year_to_new_assets(n, baseyear):
# Give assets with lifetimes and no build year the build year baseyear
for c in n.iterate_components(["Link", "Generator", "Store"]):
assets = c.df.index[~c.df.lifetime.isna() & c.df.build_year.isna()]
assets = c.df.index[~c.df.lifetime.isna() & c.df.build_year==0]
c.df.loc[assets, "build_year"] = baseyear
# add -baseyear to name
@ -155,6 +155,11 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas
# assign clustered bus
busmap_s = pd.read_csv(snakemake.input.busmap_s, index_col=0, squeeze=True)
busmap = pd.read_csv(snakemake.input.busmap, index_col=0, squeeze=True)
inv_busmap = {}
for k, v in busmap.iteritems():
inv_busmap[v] = inv_busmap.get(v, []) + [k]
clustermaps = busmap_s.map(busmap)
clustermaps.index = clustermaps.index.astype(int)
@ -193,22 +198,52 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas
if generator in ['solar', 'onwind', 'offwind']:
rename = {"offwind": "offwind-ac"}
p_max_pu=n.generators_t.p_max_pu[capacity.index + ' ' + rename.get(generator, generator) + '-' + str(baseyear)]
suffix = '-ac' if generator == 'offwind' else ''
name_suffix = f' {generator}{suffix}-{baseyear}'
n.madd("Generator",
capacity.index,
suffix=' ' + generator +"-"+ str(grouping_year),
bus=capacity.index,
carrier=generator,
p_nom=capacity,
marginal_cost=costs.at[generator, 'VOM'],
capital_cost=costs.at[generator, 'fixed'],
efficiency=costs.at[generator, 'efficiency'],
p_max_pu=p_max_pu.rename(columns=n.generators.bus),
build_year=grouping_year,
lifetime=costs.at[generator, 'lifetime']
)
if 'm' in snakemake.wildcards.clusters:
for ind in capacity.index:
# existing capacities are split evenly among regions in every country
inv_ind = [i for i in inv_busmap[ind]]
# for offshore the spliting only inludes coastal regions
inv_ind = [i for i in inv_ind if (i + name_suffix) in n.generators.index]
p_max_pu = n.generators_t.p_max_pu[[i + name_suffix for i in inv_ind]]
p_max_pu.columns=[i + name_suffix for i in inv_ind ]
n.madd("Generator",
[i + name_suffix for i in inv_ind],
bus=ind,
carrier=generator,
p_nom=capacity[ind] / len(inv_ind), # split among regions in a country
marginal_cost=costs.at[generator,'VOM'],
capital_cost=costs.at[generator,'fixed'],
efficiency=costs.at[generator, 'efficiency'],
p_max_pu=p_max_pu,
build_year=grouping_year,
lifetime=costs.at[generator,'lifetime']
)
else:
p_max_pu = n.generators_t.p_max_pu[capacity.index + name_suffix]
n.madd("Generator",
capacity.index,
suffix=' ' + generator +"-"+ str(grouping_year),
bus=capacity.index,
carrier=generator,
p_nom=capacity,
marginal_cost=costs.at[generator, 'VOM'],
capital_cost=costs.at[generator, 'fixed'],
efficiency=costs.at[generator, 'efficiency'],
p_max_pu=p_max_pu.rename(columns=n.generators.bus),
build_year=grouping_year,
lifetime=costs.at[generator, 'lifetime']
)
else:
@ -410,7 +445,8 @@ if __name__ == "__main__":
simpl='',
clusters=45,
lv=1.0,
sector_opts='Co2L0-168H-T-H-B-I-solar3-dist1',
opts='',
sector_opts='Co2L0-168H-T-H-B-I-solar+p3-dist1',
planning_horizons=2020,
)

View File

@ -1,55 +1,194 @@
import pandas as pd
rename = {"UK" : "GB", "BH" : "BA"}
import geopandas as gpd
def build_biomass_potentials():
def build_nuts_population_data(year=2013):
config = snakemake.config['biomass']
year = config["year"]
scenario = config["scenario"]
pop = pd.read_csv(
snakemake.input.nuts3_population,
sep=r'\,| \t|\t',
engine='python',
na_values=[":"],
index_col=1
)[str(year)]
df = pd.read_excel(snakemake.input.jrc_potentials,
"Potentials (PJ)",
index_col=[0,1])
# only countries
pop.drop("EU28", inplace=True)
df.rename(columns={"Unnamed: 18": "Municipal waste"}, inplace=True)
df.drop(columns="Total", inplace=True)
df.replace("-", 0., inplace=True)
# mapping from Cantons to NUTS3
cantons = pd.read_csv(snakemake.input.swiss_cantons)
cantons = cantons.set_index(cantons.HASC.str[3:]).NUTS
cantons = cantons.str.pad(5, side='right', fillchar='0')
column = df.iloc[:,0]
countries = column.where(column.str.isalpha()).pad()
countries = [rename.get(ct, ct) for ct in countries]
countries_i = pd.Index(countries, name='country')
df.set_index(countries_i, append=True, inplace=True)
# get population by NUTS3
swiss = pd.read_excel(snakemake.input.swiss_population, skiprows=3, index_col=0).loc["Residents in 1000"]
swiss = swiss.rename(cantons).filter(like="CH")
df.drop(index='MS', level=0, inplace=True)
# aggregate also to higher order NUTS levels
swiss = [swiss.groupby(swiss.index.str[:i]).sum() for i in range(2, 6)]
# convert from PJ to MWh
df = df / 3.6 * 1e6
# merge Europe + Switzerland
pop = pd.DataFrame(pop.append(swiss), columns=["total"])
df.to_csv(snakemake.output.biomass_potentials_all)
# add missing manually
pop["AL"] = 2893
pop["BA"] = 3871
pop["RS"] = 7210
# solid biomass includes:
# Primary agricultural residues (MINBIOAGRW1),
# Forestry energy residue (MINBIOFRSF1),
# Secondary forestry residues (MINBIOWOOW1),
# Secondary Forestry residues sawdust (MINBIOWOO1a)',
# Forestry residues from landscape care biomass (MINBIOFRSF1a),
# Municipal waste (MINBIOMUN1)',
pop["ct"] = pop.index.str[:2]
# biogas includes:
# Manure biomass potential (MINBIOGAS1),
# Sludge biomass (MINBIOSLU1),
return pop
df = df.loc[year, scenario, :]
grouper = {v: k for k, vv in config["classes"].items() for v in vv}
df = df.groupby(grouper, axis=1).sum()
def enspreso_biomass_potentials(year=2020, scenario="ENS_Low"):
"""
Loads the JRC ENSPRESO biomass potentials.
df.index.name = "MWh/a"
Parameters
----------
year : int
The year for which potentials are to be taken.
Can be {2010, 2020, 2030, 2040, 2050}.
scenario : str
The scenario. Can be {"ENS_Low", "ENS_Med", "ENS_High"}.
df.to_csv(snakemake.output.biomass_potentials)
Returns
-------
pd.DataFrame
Biomass potentials for given year and scenario
in TWh/a by commodity and NUTS2 region.
"""
glossary = pd.read_excel(
str(snakemake.input.enspreso_biomass),
sheet_name="Glossary",
usecols="B:D",
skiprows=1,
index_col=0
)
df = pd.read_excel(
str(snakemake.input.enspreso_biomass),
sheet_name="ENER - NUTS2 BioCom E",
usecols="A:H"
)
df["group"] = df["E-Comm"].map(glossary.group)
df["commodity"] = df["E-Comm"].map(glossary.description)
to_rename = {
"NUTS2 Potential available by Bio Commodity": "potential",
"NUST2": "NUTS2",
}
df.rename(columns=to_rename, inplace=True)
# fill up with NUTS0 if NUTS2 is not given
df.NUTS2 = df.apply(lambda x: x.NUTS0 if x.NUTS2 == '-' else x.NUTS2, axis=1)
# convert PJ to TWh
df.potential /= 3.6
df.Unit = "TWh/a"
dff = df.query("Year == @year and Scenario == @scenario")
bio = dff.groupby(["NUTS2", "commodity"]).potential.sum().unstack()
# currently Serbia and Kosovo not split, so aggregate
bio.loc["RS"] += bio.loc["XK"]
bio.drop("XK", inplace=True)
return bio
def disaggregate_nuts0(bio):
"""
Some commodities are only given on NUTS0 level.
These are disaggregated here using the NUTS2
population as distribution key.
Parameters
----------
bio : pd.DataFrame
from enspreso_biomass_potentials()
Returns
-------
pd.DataFrame
"""
pop = build_nuts_population_data()
# get population in nuts2
pop_nuts2 = pop.loc[pop.index.str.len() == 4]
by_country = pop_nuts2.total.groupby(pop_nuts2.ct).sum()
pop_nuts2["fraction"] = pop_nuts2.total / pop_nuts2.ct.map(by_country)
# distribute nuts0 data to nuts2 by population
bio_nodal = bio.loc[pop_nuts2.ct]
bio_nodal.index = pop_nuts2.index
bio_nodal = bio_nodal.mul(pop_nuts2.fraction, axis=0)
# update inplace
bio.update(bio_nodal)
return bio
def build_nuts2_shapes():
"""
- load NUTS2 geometries
- add RS, AL, BA country shapes (not covered in NUTS 2013)
- consistently name ME, MK
"""
nuts2 = gpd.GeoDataFrame(gpd.read_file(snakemake.input.nuts2).set_index('id').geometry)
countries = gpd.read_file(snakemake.input.country_shapes).set_index('name')
missing = countries.loc[["AL", "RS", "BA"]]
nuts2.rename(index={"ME00": "ME", "MK00": "MK"}, inplace=True)
return nuts2.append(missing)
def area(gdf):
"""Returns area of GeoDataFrame geometries in square kilometers."""
return gdf.to_crs(epsg=3035).area.div(1e6)
def convert_nuts2_to_regions(bio_nuts2, regions):
"""
Converts biomass potentials given in NUTS2 to PyPSA-Eur regions based on the
overlay of both GeoDataFrames in proportion to the area.
Parameters
----------
bio_nuts2 : gpd.GeoDataFrame
JRC ENSPRESO biomass potentials indexed by NUTS2 shapes.
regions : gpd.GeoDataFrame
PyPSA-Eur clustered onshore regions
Returns
-------
gpd.GeoDataFrame
"""
# calculate area of nuts2 regions
bio_nuts2["area_nuts2"] = area(bio_nuts2)
overlay = gpd.overlay(regions, bio_nuts2)
# calculate share of nuts2 area inside region
overlay["share"] = area(overlay) / overlay["area_nuts2"]
# multiply all nuts2-level values with share of nuts2 inside region
adjust_cols = overlay.columns.difference({"name", "area_nuts2", "geometry", "share"})
overlay[adjust_cols] = overlay[adjust_cols].multiply(overlay["share"], axis=0)
bio_regions = overlay.groupby("name").sum()
bio_regions.drop(["area_nuts2", "share"], axis=1, inplace=True)
return bio_regions
if __name__ == "__main__":
@ -57,12 +196,28 @@ if __name__ == "__main__":
from helper import mock_snakemake
snakemake = mock_snakemake('build_biomass_potentials')
config = snakemake.config['biomass']
year = config["year"]
scenario = config["scenario"]
# This is a hack, to be replaced once snakemake is unicode-conform
enspreso = enspreso_biomass_potentials(year, scenario)
solid_biomass = snakemake.config['biomass']['classes']['solid biomass']
if 'Secondary Forestry residues sawdust' in solid_biomass:
solid_biomass.remove('Secondary Forestry residues sawdust')
solid_biomass.append('Secondary Forestry residues sawdust')
enspreso = disaggregate_nuts0(enspreso)
build_biomass_potentials()
nuts2 = build_nuts2_shapes()
df_nuts2 = gpd.GeoDataFrame(nuts2.geometry).join(enspreso)
regions = gpd.read_file(snakemake.input.regions_onshore)
df = convert_nuts2_to_regions(df_nuts2, regions)
df.to_csv(snakemake.output.biomass_potentials_all)
grouper = {v: k for k, vv in config["classes"].items() for v in vv}
df = df.groupby(grouper, axis=1).sum()
df *= 1e6 # TWh/a to MWh/a
df.index.name = "MWh/a"
df.to_csv(snakemake.output.biomass_potentials)

View File

@ -0,0 +1,90 @@
"""
Reads biomass transport costs for different countries of the JRC report
"The JRC-EU-TIMES model.
Bioenergy potentials
for EU and neighbouring countries."
(2015)
converts them from units 'EUR per km/ton' -> 'EUR/ (km MWh)'
assuming as an approximation energy content of wood pellets
@author: bw0928
"""
import pandas as pd
import tabula as tbl
ENERGY_CONTENT = 4.8 # unit MWh/t (wood pellets)
def get_countries():
pandas_options = dict(
skiprows=range(6),
header=None,
index_col=0
)
return tbl.read_pdf(
str(snakemake.input.transport_cost_data),
pages="145",
multiple_tables=False,
pandas_options=pandas_options
)[0].index
def get_cost_per_tkm(page, countries):
pandas_options = dict(
skiprows=range(6),
header=0,
sep=' |,',
engine='python',
index_col=False,
)
sc = tbl.read_pdf(
str(snakemake.input.transport_cost_data),
pages=page,
multiple_tables=False,
pandas_options=pandas_options
)[0]
sc.index = countries
sc.columns = sc.columns.str.replace("", "EUR")
return sc
def build_biomass_transport_costs():
countries = get_countries()
sc1 = get_cost_per_tkm(146, countries)
sc2 = get_cost_per_tkm(147, countries)
# take mean of both supply chains
to_concat = [sc1["EUR/km/ton"], sc2["EUR/km/ton"]]
transport_costs = pd.concat(to_concat, axis=1).mean(axis=1)
# convert tonnes to MWh
transport_costs /= ENERGY_CONTENT
transport_costs.name = "EUR/km/MWh"
# rename country names
to_rename = {
"UK": "GB",
"XK": "KO",
"EL": "GR"
}
transport_costs.rename(to_rename, inplace=True)
# add missing Norway with data from Sweden
transport_costs["NO"] = transport_costs["SE"]
transport_costs.to_csv(snakemake.output[0])
if __name__ == "__main__":
build_biomass_transport_costs()

View File

@ -117,6 +117,7 @@ to_ipcc = {
"total energy": "1 - Energy",
"industrial processes": "2 - Industrial Processes and Product Use",
"agriculture": "3 - Agriculture",
"agriculture, forestry and fishing": '1.A.4.c - Agriculture/Forestry/Fishing',
"LULUCF": "4 - Land Use, Land-Use Change and Forestry",
"waste management": "5 - Waste management",
"other": "6 - Other Sector",
@ -182,7 +183,7 @@ def idees_per_country(ct, year):
ct_idees = idees_rename.get(ct, ct)
fn_residential = f"{base_dir}/JRC-IDEES-2015_Residential_{ct_idees}.xlsx"
fn_services = f"{base_dir}/JRC-IDEES-2015_Tertiary_{ct_idees}.xlsx"
fn_tertiary = f"{base_dir}/JRC-IDEES-2015_Tertiary_{ct_idees}.xlsx"
fn_transport = f"{base_dir}/JRC-IDEES-2015_Transport_{ct_idees}.xlsx"
# residential
@ -212,9 +213,15 @@ def idees_per_country(ct, year):
assert df.index[47] == "Electricity"
ct_totals["electricity residential"] = df[47]
assert df.index[46] == "Derived heat"
ct_totals["derived heat residential"] = df[46]
assert df.index[50] == 'Thermal uses'
ct_totals["thermal uses residential"] = df[50]
# services
df = pd.read_excel(fn_services, "SER_hh_fec", index_col=0)[year]
df = pd.read_excel(fn_tertiary, "SER_hh_fec", index_col=0)[year]
ct_totals["total services space"] = df["Space heating"]
@ -231,7 +238,7 @@ def idees_per_country(ct, year):
assert df.index[31] == "Electricity"
ct_totals["electricity services cooking"] = df[31]
df = pd.read_excel(fn_services, "SER_summary", index_col=0)[year]
df = pd.read_excel(fn_tertiary, "SER_summary", index_col=0)[year]
row = "Energy consumption by fuel - Eurostat structure (ktoe)"
ct_totals["total services"] = df[row]
@ -239,6 +246,41 @@ def idees_per_country(ct, year):
assert df.index[50] == "Electricity"
ct_totals["electricity services"] = df[50]
assert df.index[49] == "Derived heat"
ct_totals["derived heat services"] = df[49]
assert df.index[53] == 'Thermal uses'
ct_totals["thermal uses services"] = df[53]
# agriculture, forestry and fishing
start = "Detailed split of energy consumption (ktoe)"
end = "Market shares of energy uses (%)"
df = pd.read_excel(fn_tertiary, "AGR_fec", index_col=0).loc[start:end, year]
rows = [
"Lighting",
"Ventilation",
"Specific electricity uses",
"Pumping devices (electric)"
]
ct_totals["total agriculture electricity"] = df[rows].sum()
rows = ["Specific heat uses", "Low enthalpy heat"]
ct_totals["total agriculture heat"] = df[rows].sum()
rows = [
"Motor drives",
"Farming machine drives (diesel oil incl. biofuels)",
"Pumping devices (diesel oil incl. biofuels)",
]
ct_totals["total agriculture machinery"] = df[rows].sum()
row = "Agriculture, forestry and fishing"
ct_totals["total agriculture"] = df[row]
# transport
df = pd.read_excel(fn_transport, "TrRoad_ene", index_col=0)[year]
@ -342,6 +384,7 @@ def build_idees(countries, year):
with mp.Pool(processes=nprocesses) as pool:
totals_list = list(tqdm(pool.imap(func, countries), **tqdm_kwargs))
totals = pd.concat(totals_list, axis=1)
# convert ktoe to TWh
@ -351,6 +394,13 @@ def build_idees(countries, year):
# convert TWh/100km to kWh/km
totals.loc["passenger car efficiency"] *= 10
# district heating share
district_heat = totals.loc[["derived heat residential",
"derived heat services"]].sum()
total_heat = totals.loc[["thermal uses residential",
"thermal uses services"]].sum()
totals.loc["district heat share"] = district_heat.div(total_heat)
return totals.T
@ -502,6 +552,14 @@ def build_energy_totals(countries, eurostat, swiss, idees):
ratio = df.at["BA", "total residential"] / df.at["RS", "total residential"]
df.loc['BA', missing] = ratio * df.loc["RS", missing]
# Missing district heating share
dh_share = pd.read_csv(snakemake.input.district_heat_share,
index_col=0, usecols=[0, 1])
# make conservative assumption and take minimum from both data sets
df["district heat share"] = (pd.concat([df["district heat share"],
dh_share.reindex(index=df.index)/100],
axis=1).min(axis=1))
return df
@ -540,10 +598,13 @@ def build_eea_co2(year=1990):
"international aviation",
"domestic navigation",
"international navigation",
"agriculture, forestry and fishing"
]
emissions["industrial non-elec"] = emissions["total energy"] - emissions[to_subtract].sum(axis=1)
to_drop = ["total energy", "total wL", "total woL"]
emissions["agriculture"] += emissions["agriculture, forestry and fishing"]
to_drop = ["total energy", "total wL", "total woL", "agriculture, forestry and fishing"]
emissions.drop(columns=to_drop, inplace=True)
# convert from Gg to Mt
@ -588,7 +649,7 @@ def build_co2_totals(countries, eea_co2, eurostat_co2):
# does not include industrial process emissions or fuel processing/refining
"industrial non-elec": (ct, "+", "Industry"),
# does not include non-energy emissions
"agriculture": (ct, "+", "+", "Agriculture / Forestry"),
"agriculture": (eurostat_co2.index.get_level_values(0) == ct) & eurostat_co2.index.isin(["Agriculture / Forestry", "Fishing"], level=3),
}
for i, mi in mappings.items():

View File

@ -103,6 +103,7 @@ def add_ammonia_energy_demand(demand):
demand['Basic chemicals (without ammonia)'] = demand["Basic chemicals"] - demand["Ammonia"]
demand['Basic chemicals (without ammonia)'].clip(lower=0, inplace=True)
demand.drop(columns='Basic chemicals', inplace=True)
return demand
@ -114,6 +115,11 @@ def add_non_eu28_industrial_energy_demand(demand):
fn = snakemake.input.industrial_production_per_country
production = pd.read_csv(fn, index_col=0) / 1e3
#recombine HVC, Chlorine and Methanol to Basic chemicals (without ammonia)
chemicals = ["HVC", "Chlorine", "Methanol"]
production["Basic chemicals (without ammonia)"] = production[chemicals].sum(axis=1)
production.drop(columns=chemicals, inplace=True)
eu28_production = production.loc[eu28].sum()
eu28_energy = demand.groupby(level=1).sum()
eu28_averages = eu28_energy / eu28_production

View File

@ -179,8 +179,8 @@ def industry_production(countries):
return demand
def add_ammonia_demand_separately(demand):
"""Include ammonia demand separately and remove ammonia from basic chemicals."""
def separate_basic_chemicals(demand):
"""Separate basic chemicals into ammonia, chlorine, methanol and HVC."""
ammonia = pd.read_csv(snakemake.input.ammonia_production, index_col=0)
@ -189,7 +189,7 @@ def add_ammonia_demand_separately(demand):
print("Following countries have no ammonia demand:", missing)
demand.insert(2, "Ammonia", 0.)
demand["Ammonia"] = 0.
demand.loc[there, "Ammonia"] = ammonia.loc[there, str(year)]
@ -198,9 +198,13 @@ def add_ammonia_demand_separately(demand):
# EE, HR and LT got negative demand through subtraction - poor data
demand['Basic chemicals'].clip(lower=0., inplace=True)
to_rename = {"Basic chemicals": "Basic chemicals (without ammonia)"}
demand.rename(columns=to_rename, inplace=True)
# assume HVC, methanol, chlorine production proportional to non-ammonia basic chemicals
distribution_key = demand["Basic chemicals"] / demand["Basic chemicals"].sum()
demand["HVC"] = config["HVC_production_today"] * 1e3 * distribution_key
demand["Chlorine"] = config["chlorine_production_today"] * 1e3 * distribution_key
demand["Methanol"] = config["methanol_production_today"] * 1e3 * distribution_key
demand.drop(columns=["Basic chemicals"], inplace=True)
if __name__ == '__main__':
if 'snakemake' not in globals():
@ -211,12 +215,14 @@ if __name__ == '__main__':
year = snakemake.config['industry']['reference_year']
config = snakemake.config["industry"]
jrc_dir = snakemake.input.jrc
eurostat_dir = snakemake.input.eurostat
demand = industry_production(countries)
add_ammonia_demand_separately(demand)
separate_basic_chemicals(demand)
fn = snakemake.output.industrial_production_per_country
demand.to_csv(fn, float_format='%.2f')

View File

@ -2,6 +2,8 @@
import pandas as pd
from prepare_sector_network import get
if __name__ == '__main__':
if 'snakemake' not in globals():
from helper import mock_snakemake
@ -9,31 +11,42 @@ if __name__ == '__main__':
config = snakemake.config["industry"]
investment_year = int(snakemake.wildcards.planning_horizons)
fn = snakemake.input.industrial_production_per_country
production = pd.read_csv(fn, index_col=0)
keys = ["Integrated steelworks", "Electric arc"]
total_steel = production[keys].sum(axis=1)
st_primary_fraction = get(config["St_primary_fraction"], investment_year)
dri_fraction = get(config["DRI_fraction"], investment_year)
int_steel = production["Integrated steelworks"].sum()
fraction_persistent_primary = config["St_primary_fraction"] * total_steel.sum() / int_steel
fraction_persistent_primary = st_primary_fraction * total_steel.sum() / int_steel
dri = fraction_persistent_primary * production["Integrated steelworks"]
dri = dri_fraction * fraction_persistent_primary * production["Integrated steelworks"]
production.insert(2, "DRI + Electric arc", dri)
production["Electric arc"] = total_steel - production["DRI + Electric arc"]
production["Integrated steelworks"] = 0.
not_dri = (1 - dri_fraction)
production["Integrated steelworks"] = not_dri * fraction_persistent_primary * production["Integrated steelworks"]
production["Electric arc"] = total_steel - production["DRI + Electric arc"] - production["Integrated steelworks"]
keys = ["Aluminium - primary production", "Aluminium - secondary production"]
total_aluminium = production[keys].sum(axis=1)
key_pri = "Aluminium - primary production"
key_sec = "Aluminium - secondary production"
fraction_persistent_primary = config["Al_primary_fraction"] * total_aluminium.sum() / production[key_pri].sum()
al_primary_fraction = get(config["Al_primary_fraction"], investment_year)
fraction_persistent_primary = al_primary_fraction * total_aluminium.sum() / production[key_pri].sum()
production[key_pri] = fraction_persistent_primary * production[key_pri]
production[key_sec] = total_aluminium - production[key_pri]
production["Basic chemicals (without ammonia)"] *= config['HVC_primary_fraction']
production["HVC (mechanical recycling)"] = get(config["HVC_mechanical_recycling_fraction"], investment_year) * production["HVC"]
production["HVC (chemical recycling)"] = get(config["HVC_chemical_recycling_fraction"], investment_year) * production["HVC"]
production["HVC"] *= get(config['HVC_primary_fraction'], investment_year)
fn = snakemake.output.industrial_production_per_country_tomorrow
production.to_csv(fn, float_format='%.2f')

View File

@ -9,7 +9,11 @@ sector_mapping = {
'Integrated steelworks': 'Iron and steel',
'DRI + Electric arc': 'Iron and steel',
'Ammonia': 'Chemical industry',
'Basic chemicals (without ammonia)': 'Chemical industry',
'HVC': 'Chemical industry',
'HVC (mechanical recycling)': 'Chemical industry',
'HVC (chemical recycling)': 'Chemical industry',
'Methanol': 'Chemical industry',
'Chlorine': 'Chemical industry',
'Other chemicals': 'Chemical industry',
'Pharmaceutical products etc.': 'Chemical industry',
'Cement': 'Cement',

View File

@ -279,7 +279,7 @@ def chemicals_industry():
df = pd.DataFrame(index=index)
# Basid chemicals
# Basic chemicals
sector = "Basic chemicals"
@ -374,52 +374,82 @@ def chemicals_industry():
# putting in ammonia demand for H2 and electricity separately
s_emi = idees["emi"][3:57]
s_out = idees["out"][8:9]
assert s_emi.index[0] == sector
assert sector in str(s_out.index)
ammonia = pd.read_csv(snakemake.input.ammonia_production, index_col=0)
# ktNH3/a
ammonia_total = ammonia.loc[ammonia.index.intersection(eu28), str(year)].sum()
s_out -= ammonia_total
# convert from MtHVC/a to ktHVC/a
s_out = config["HVC_production_today"] * 1e3
# tCO2/t material
df.loc["process emission", sector] += (
s_emi["Process emissions"]
- config["petrochemical_process_emissions"] * 1e3
- config["NH3_process_emissions"] * 1e3
) / s_out.values
) / s_out
# emissions originating from feedstock, could be non-fossil origin
# tCO2/t material
df.loc["process emission from feedstock", sector] += (
config["petrochemical_process_emissions"] * 1e3
) / s_out.values
) / s_out
# convert from ktoe/a to GWh/a
sources = ["elec", "biomass", "methane", "hydrogen", "heat", "naphtha"]
df.loc[sources, sector] *= toe_to_MWh
# subtract ammonia energy demand (in ktNH3/a)
ammonia = pd.read_csv(snakemake.input.ammonia_production, index_col=0)
ammonia_total = ammonia.loc[ammonia.index.intersection(eu28), str(year)].sum()
df.loc["methane", sector] -= ammonia_total * config["MWh_CH4_per_tNH3_SMR"]
df.loc["elec", sector] -= ammonia_total * config["MWh_elec_per_tNH3_SMR"]
# MWh/t material
df.loc[sources, sector] = df.loc[sources, sector] / s_out.values
# subtract chlorine demand
chlorine_total = config["chlorine_production_today"]
df.loc["hydrogen", sector] -= chlorine_total * config["MWh_H2_per_tCl"]
df.loc["elec", sector] -= chlorine_total * config["MWh_elec_per_tCl"]
to_rename = {sector: f"{sector} (without ammonia)"}
df.rename(columns=to_rename, inplace=True)
# subtract methanol demand
methanol_total = config["methanol_production_today"]
df.loc["methane", sector] -= methanol_total * config["MWh_CH4_per_tMeOH"]
df.loc["elec", sector] -= methanol_total * config["MWh_elec_per_tMeOH"]
# MWh/t material
df.loc[sources, sector] = df.loc[sources, sector] / s_out
df.rename(columns={sector: "HVC"}, inplace=True)
# HVC mechanical recycling
sector = "HVC (mechanical recycling)"
df[sector] = 0.0
df.loc["elec", sector] = config["MWh_elec_per_tHVC_mechanical_recycling"]
# HVC chemical recycling
sector = "HVC (chemical recycling)"
df[sector] = 0.0
df.loc["elec", sector] = config["MWh_elec_per_tHVC_chemical_recycling"]
# Ammonia
sector = "Ammonia"
df[sector] = 0.0
df.loc["hydrogen", sector] = config["MWh_H2_per_tNH3_electrolysis"]
df.loc["elec", sector] = config["MWh_elec_per_tNH3_electrolysis"]
# Chlorine
sector = "Chlorine"
df[sector] = 0.0
df.loc["hydrogen", sector] = config["MWh_H2_per_tCl"]
df.loc["elec", sector] = config["MWh_elec_per_tCl"]
# Methanol
sector = "Methanol"
df[sector] = 0.0
df.loc["methane", sector] = config["MWh_CH4_per_tMeOH"]
df.loc["elec", sector] = config["MWh_elec_per_tMeOH"]
# Other chemicals
sector = "Other chemicals"

View File

@ -90,8 +90,8 @@ if __name__ == '__main__':
for key, pop in pop_cells.items():
ycoords = ('y', cutout.coords['y'])
xcoords = ('x', cutout.coords['x'])
ycoords = ('y', cutout.coords['y'].data)
xcoords = ('x', cutout.coords['x'].data)
values = pop.values.reshape(cutout.shape)
layout = xr.DataArray(values, [ycoords, xcoords])

View File

@ -5,7 +5,8 @@ files = [
"config.yaml",
"Snakefile",
"scripts/solve_network.py",
"scripts/prepare_sector_network.py"
"scripts/prepare_sector_network.py",
"../pypsa-eur/config.yaml"
]
if __name__ == '__main__':

View File

@ -19,9 +19,11 @@ def rename_techs_tyndp(tech):
tech = rename_techs(tech)
if "heat pump" in tech or "resistive heater" in tech:
return "power-to-heat"
elif tech in ["methanation", "hydrogen storage", "helmeth"]:
elif tech in ["H2 Electrolysis", "methanation", "helmeth", "H2 liquefaction"]:
return "power-to-gas"
elif tech in ["OCGT", "CHP", "gas boiler"]:
elif tech == "H2":
return "H2 storage"
elif tech in ["OCGT", "CHP", "gas boiler", "H2 Fuel Cell"]:
return "gas-to-power/heat"
elif "solar" in tech:
return "solar"
@ -29,6 +31,8 @@ def rename_techs_tyndp(tech):
return "power-to-liquid"
elif "offshore wind" in tech:
return "offshore wind"
elif "CC" in tech or "sequestration" in tech:
return "CCS"
else:
return tech
@ -286,7 +290,7 @@ def plot_h2_map(network):
l2 = ax.legend(
handles, labels,
loc="upper left",
bbox_to_anchor=(0.01, 1.01),
bbox_to_anchor=(-0.03, 1.01),
labelspacing=1.0,
frameon=False,
title='Electrolyzer capacity',
@ -662,7 +666,8 @@ def plot_series(network, carrier="AC", name="test"):
supply = pd.DataFrame(index=n.snapshots)
for c in n.iterate_components(n.branch_components):
for i in range(2):
n_port = 4 if c.name=='Link' else 2
for i in range(n_port):
supply = pd.concat((supply,
(-1) * c.pnl["p" + str(i)].loc[:,
c.df.index[c.df["bus" + str(i)].isin(buses)]].groupby(c.df.carrier,
@ -831,10 +836,11 @@ if __name__ == "__main__":
snakemake = mock_snakemake(
'plot_network',
simpl='',
clusters=48,
lv=1.0,
sector_opts='Co2L0-168H-T-H-B-I-solar3-dist1',
planning_horizons=2050,
clusters=45,
lv=1.5,
opts='',
sector_opts='Co2L0-168H-T-H-B-I-solar+p3-dist1',
planning_horizons=2030,
)
overrides = override_component_attrs(snakemake.input.overrides)

View File

@ -34,9 +34,11 @@ def rename_techs(label):
rename_if_contains_dict = {
"water tanks": "hot water storage",
"retrofitting": "building retrofitting",
"H2": "hydrogen storage",
# "H2 Electrolysis": "hydrogen storage",
# "H2 Fuel Cell": "hydrogen storage",
# "H2 pipeline": "hydrogen storage",
"battery": "battery storage",
"CC": "CC"
# "CC": "CC"
}
rename = {
@ -88,6 +90,7 @@ preferred_order = pd.Index([
"offshore wind (DC)",
"solar PV",
"solar thermal",
"solar rooftop",
"solar",
"building retrofitting",
"ground heat pump",

View File

@ -19,7 +19,6 @@ from helper import override_component_attrs
import logging
logger = logging.getLogger(__name__)
from types import SimpleNamespace
spatial = SimpleNamespace()
@ -38,6 +37,40 @@ def define_spatial(nodes):
spatial.nodes = nodes
# biomass
spatial.biomass = SimpleNamespace()
if options["biomass_transport"]:
spatial.biomass.nodes = nodes + " solid biomass"
spatial.biomass.locations = nodes
spatial.biomass.industry = nodes + " solid biomass for industry"
spatial.biomass.industry_cc = nodes + " solid biomass for industry CC"
else:
spatial.biomass.nodes = ["EU solid biomass"]
spatial.biomass.locations = ["EU"]
spatial.biomass.industry = ["solid biomass for industry"]
spatial.biomass.industry_cc = ["solid biomass for industry CC"]
spatial.biomass.df = pd.DataFrame(vars(spatial.biomass), index=nodes)
# co2
spatial.co2 = SimpleNamespace()
if options["co2_network"]:
spatial.co2.nodes = nodes + " co2 stored"
spatial.co2.locations = nodes
spatial.co2.vents = nodes + " co2 vent"
else:
spatial.co2.nodes = ["co2 stored"]
spatial.co2.locations = ["EU"]
spatial.co2.vents = ["co2 vent"]
spatial.co2.df = pd.DataFrame(vars(spatial.co2), index=nodes)
# gas
spatial.gas = SimpleNamespace()
if options["gas_network"]:
@ -56,6 +89,10 @@ def define_spatial(nodes):
spatial.gas.df = pd.DataFrame(vars(spatial.gas), index=nodes)
from types import SimpleNamespace
spatial = SimpleNamespace()
def emission_sectors_from_opts(opts):
sectors = ["electricity"]
@ -78,6 +115,10 @@ def emission_sectors_from_opts(opts):
"domestic navigation",
"international navigation"
]
if "A" in opts:
sectors += [
"agriculture"
]
return sectors
@ -90,6 +131,40 @@ def get(item, investment_year=None):
return item
def create_network_topology(n, prefix, connector=" -> "):
"""
Create a network topology like the power transmission network.
Parameters
----------
n : pypsa.Network
prefix : str
connector : str
Returns
-------
pd.DataFrame with columns bus0, bus1 and length
"""
ln_attrs = ["bus0", "bus1", "length"]
lk_attrs = ["bus0", "bus1", "length", "underwater_fraction"]
candidates = pd.concat([
n.lines[ln_attrs],
n.links.loc[n.links.carrier == "DC", lk_attrs]
]).fillna(0)
positive_order = candidates.bus0 < candidates.bus1
candidates_p = candidates[positive_order]
swap_buses = {"bus0": "bus1", "bus1": "bus0"}
candidates_n = candidates[~positive_order].rename(columns=swap_buses)
candidates = pd.concat([candidates_p, candidates_n])
topo = candidates.groupby(["bus0", "bus1"], as_index=False).mean()
topo.index = topo.apply(lambda c: prefix + c.bus0 + connector + c.bus1, axis=1)
return topo
def co2_emissions_year(countries, opts, year):
"""
Calculate CO2 emissions in one specific year (e.g. 1990 or 2018).
@ -139,9 +214,6 @@ def build_carbon_budget(o, fn):
#emissions at the beginning of the path (last year available 2018)
e_0 = co2_emissions_year(countries, opts, year=2018)
#emissions in 2019 and 2020 assumed equal to 2018 and substracted
carbon_budget -= 2 * e_0
planning_horizons = snakemake.config['scenario']['planning_horizons']
t_0 = planning_horizons[0]
@ -180,6 +252,53 @@ def add_lifetime_wind_solar(n, costs):
n.generators.loc[gen_i, "lifetime"] = costs.at[carrier, 'lifetime']
def create_network_topology(n, prefix, connector=" -> ", bidirectional=True):
"""
Create a network topology like the power transmission network.
Parameters
----------
n : pypsa.Network
prefix : str
connector : str
bidirectional : bool, default True
True: one link for each connection
False: one link for each connection and direction (back and forth)
Returns
-------
pd.DataFrame with columns bus0, bus1 and length
"""
ln_attrs = ["bus0", "bus1", "length"]
lk_attrs = ["bus0", "bus1", "length", "underwater_fraction"]
candidates = pd.concat([
n.lines[ln_attrs],
n.links.loc[n.links.carrier == "DC", lk_attrs]
]).fillna(0)
positive_order = candidates.bus0 < candidates.bus1
candidates_p = candidates[positive_order]
swap_buses = {"bus0": "bus1", "bus1": "bus0"}
candidates_n = candidates[~positive_order].rename(columns=swap_buses)
candidates = pd.concat([candidates_p, candidates_n])
def make_index(c):
return prefix + c.bus0 + connector + c.bus1
topo = candidates.groupby(["bus0", "bus1"], as_index=False).mean()
topo.index = topo.apply(make_index, axis=1)
if not bidirectional:
topo_reverse = topo.copy()
topo_reverse.rename(columns=swap_buses, inplace=True)
topo_reverse.index = topo_reverse.apply(make_index, axis=1)
topo = topo.append(topo_reverse)
return topo
# TODO merge issue with PyPSA-Eur
def update_wind_solar_costs(n, costs):
"""
@ -312,6 +431,9 @@ def patch_electricity_network(n):
update_wind_solar_costs(n, costs)
n.loads["carrier"] = "electricity"
n.buses["location"] = n.buses.index
# remove trailing white space of load index until new PyPSA version after v0.18.
n.loads.rename(lambda x: x.strip(), inplace=True)
n.loads_t.p_set.rename(lambda x: x.strip(), axis=1, inplace=True)
def add_co2_tracking(n, options):
@ -338,26 +460,26 @@ def add_co2_tracking(n, options):
)
# this tracks CO2 stored, e.g. underground
n.add("Bus",
"co2 stored",
location="EU",
n.madd("Bus",
spatial.co2.nodes,
location=spatial.co2.locations,
carrier="co2 stored"
)
n.add("Store",
"co2 stored",
n.madd("Store",
spatial.co2.nodes,
e_nom_extendable=True,
e_nom_max=options['co2_sequestration_potential'] * 1e6,
e_nom_max=np.inf,
capital_cost=options['co2_sequestration_cost'],
carrier="co2 stored",
bus="co2 stored"
bus=spatial.co2.nodes
)
if options['co2_vent']:
n.add("Link",
"co2 vent",
bus0="co2 stored",
n.madd("Link",
spatial.co2.vents,
bus0=spatial.co2.nodes,
bus1="co2 atmosphere",
carrier="co2 vent",
efficiency=1.,
@ -365,6 +487,28 @@ def add_co2_tracking(n, options):
)
def add_co2_network(n, costs):
logger.info("Adding CO2 network.")
co2_links = create_network_topology(n, "CO2 pipeline ")
cost_onshore = (1 - co2_links.underwater_fraction) * costs.at['CO2 pipeline', 'fixed'] * co2_links.length
cost_submarine = co2_links.underwater_fraction * costs.at['CO2 submarine pipeline', 'fixed'] * co2_links.length
capital_cost = cost_onshore + cost_submarine
n.madd("Link",
co2_links.index,
bus0=co2_links.bus0.values + " co2 stored",
bus1=co2_links.bus1.values + " co2 stored",
p_min_pu=-1,
p_nom_extendable=True,
length=co2_links.length.values,
capital_cost=capital_cost.values,
carrier="CO2 pipeline",
lifetime=costs.at['CO2 pipeline', 'lifetime']
)
def add_dac(n, costs):
heat_carriers = ["urban central heat", "services urban decentral heat"]
@ -375,10 +519,9 @@ def add_dac(n, costs):
efficiency3 = -(costs.at['direct air capture', 'heat-input'] - costs.at['direct air capture', 'compression-heat-output'])
n.madd("Link",
locations,
suffix=" DAC",
heat_buses.str.replace(" heat", " DAC"),
bus0="co2 atmosphere",
bus1="co2 stored",
bus1=spatial.co2.df.loc[locations, "nodes"].values,
bus2=locations.values,
bus3=heat_buses,
carrier="DAC",
@ -522,6 +665,8 @@ def prepare_data(n):
nodal_energy_totals = energy_totals.loc[pop_layout.ct].fillna(0.)
nodal_energy_totals.index = pop_layout.index
# district heat share not weighted by population
district_heat_share = nodal_energy_totals["district heat share"].round(2)
nodal_energy_totals = nodal_energy_totals.multiply(pop_layout.fraction, axis=0)
# copy forward the daily average heat demand into each hour, so it can be multipled by the intraday profile
@ -644,7 +789,7 @@ def prepare_data(n):
)
return nodal_energy_totals, heat_demand, ashp_cop, gshp_cop, solar_thermal, transport, avail_profile, dsm_profile, nodal_transport_data
return nodal_energy_totals, heat_demand, ashp_cop, gshp_cop, solar_thermal, transport, avail_profile, dsm_profile, nodal_transport_data, district_heat_share
# TODO checkout PyPSA-Eur script
@ -772,8 +917,9 @@ def insert_electricity_distribution_grid(n, costs):
capital_cost=costs.at['electricity distribution grid', 'fixed'] * cost_factor
)
# this catches regular electricity load and "industry electricity"
loads = n.loads.index[n.loads.carrier.str.contains("electricity")]
# this catches regular electricity load and "industry electricity" and
# "agriculture machinery electric" and "agriculture electricity"
loads = n.loads.index[n.loads.carrier.str.contains("electric")]
n.loads.loc[loads, "bus"] += " low voltage"
bevs = n.links.index[n.links.carrier == "BEV charger"]
@ -814,7 +960,8 @@ def insert_electricity_distribution_grid(n, costs):
marginal_cost=n.generators.loc[solar, 'marginal_cost'],
capital_cost=costs.at['solar-rooftop', 'fixed'],
efficiency=n.generators.loc[solar, 'efficiency'],
p_max_pu=n.generators_t.p_max_pu[solar]
p_max_pu=n.generators_t.p_max_pu[solar],
lifetime=costs.at['solar-rooftop', 'lifetime']
)
n.add("Carrier", "home battery")
@ -947,7 +1094,7 @@ def add_storage(n, costs):
)
# hydrogen stored overground (where not already underground)
h2_capital_cost = costs.at["hydrogen storage tank", "fixed"]
h2_capital_cost = costs.at["hydrogen storage tank incl. compressor", "fixed"]
nodes_overground = cavern_nodes.index.symmetric_difference(nodes)
n.madd("Store",
@ -982,9 +1129,9 @@ def add_storage(n, costs):
p_min_pu=-1,
p_nom_extendable=True,
length=h2_links.length.values,
capital_cost=costs.at['H2 pipeline', 'fixed'] * h2_links.length.values,
capital_cost=costs.at['H2 (g) pipeline', 'fixed'] * h2_links.length.values,
carrier="H2 pipeline",
lifetime=costs.at['H2 pipeline', 'lifetime']
lifetime=costs.at['H2 (g) pipeline', 'lifetime']
)
if options["gas_network"]:
@ -1120,25 +1267,27 @@ def add_storage(n, costs):
if options['methanation']:
n.madd("Link",
nodes + " Sabatier",
spatial.nodes,
suffix=" Sabatier",
bus0=nodes + " H2",
bus1=spatial.gas.nodes,
bus2="co2 stored",
bus2=spatial.co2.nodes,
p_nom_extendable=True,
carrier="Sabatier",
efficiency=costs.at["methanation", "efficiency"],
efficiency2=-costs.at["methanation", "efficiency"] * costs.at['gas', 'CO2 intensity'],
capital_cost=costs.at["methanation", "fixed"],
capital_cost=costs.at["methanation", "fixed"] * costs.at["methanation", "efficiency"], # costs given per kW_gas
lifetime=costs.at['methanation', 'lifetime']
)
if options['helmeth']:
n.madd("Link",
nodes + " helmeth",
spatial.nodes,
suffix=" helmeth",
bus0=nodes,
bus1=spatial.gas.nodes,
bus2="co2 stored",
bus2=spatial.co2.nodes,
carrier="helmeth",
p_nom_extendable=True,
efficiency=costs.at["helmeth", "efficiency"],
@ -1151,11 +1300,12 @@ def add_storage(n, costs):
if options['SMR']:
n.madd("Link",
nodes + " SMR CC",
spatial.nodes,
suffix=" SMR CC",
bus0=spatial.gas.nodes,
bus1=nodes + " H2",
bus2="co2 atmosphere",
bus3="co2 stored",
bus3=spatial.co2.nodes,
p_nom_extendable=True,
carrier="SMR CC",
efficiency=costs.at["SMR CC", "efficiency"],
@ -1294,8 +1444,8 @@ def add_land_transport(n, costs):
co2 = ice_share / ice_efficiency * transport[nodes].sum().sum() / 8760 * costs.at["oil", 'CO2 intensity']
n.madd("Load",
["land transport oil emissions"],
n.add("Load",
"land transport oil emissions",
bus="co2 atmosphere",
carrier="land transport oil emissions",
p_set=-co2
@ -1308,12 +1458,11 @@ def add_heat(n, costs):
sectors = ["residential", "services"]
nodes = create_nodes_for_heat_sector()
nodes, dist_fraction, urban_fraction = create_nodes_for_heat_sector()
#NB: must add costs of central heating afterwards (EUR 400 / kWpeak, 50a, 1% FOM from Fraunhofer ISE)
urban_fraction = options['central_fraction'] * pop_layout["urban"] / pop_layout[["urban", "rural"]].sum(axis=1)
# exogenously reduce space heat demand
if options["reduce_space_heat_exogenously"]:
dE = get(options["reduce_space_heat_exogenously_factor"], investment_year)
@ -1344,15 +1493,22 @@ def add_heat(n, costs):
## Add heat load
for sector in sectors:
# heat demand weighting
if "rural" in name:
factor = 1 - urban_fraction[nodes[name]]
elif "urban" in name:
factor = urban_fraction[nodes[name]]
elif "urban central" in name:
factor = dist_fraction[nodes[name]]
elif "urban decentral" in name:
factor = urban_fraction[nodes[name]] - \
dist_fraction[nodes[name]]
else:
raise NotImplementedError(f" {name} not in " f"heat systems: {heat_systems}")
if sector in name:
heat_load = heat_demand[[sector + " water",sector + " space"]].groupby(level=1,axis=1).sum()[nodes[name]].multiply(factor)
if name == "urban central":
heat_load = heat_demand.groupby(level=1,axis=1).sum()[nodes[name]].multiply(urban_fraction[nodes[name]] * (1 + options['district_heating_loss']))
heat_load = heat_demand.groupby(level=1,axis=1).sum()[nodes[name]].multiply(factor * (1 + options['district_heating']['district_heating_loss']))
n.madd("Load",
nodes[name],
@ -1502,7 +1658,7 @@ def add_heat(n, costs):
bus1=nodes[name],
bus2=nodes[name] + " urban central heat",
bus3="co2 atmosphere",
bus4="co2 stored",
bus4=spatial.co2.df.loc[nodes[name], "nodes"].values,
carrier="urban central gas CHP CC",
p_nom_extendable=True,
capital_cost=costs.at['central gas CHP', 'fixed']*costs.at['central gas CHP', 'efficiency'] + costs.at['biomass CHP capture', 'fixed']*costs.at['gas', 'CO2 intensity'],
@ -1633,47 +1789,60 @@ def create_nodes_for_heat_sector():
# urban are areas with high heating density
# urban can be split into district heating (central) and individual heating (decentral)
ct_urban = pop_layout.urban.groupby(pop_layout.ct).sum()
# distribution of urban population within a country
pop_layout["urban_ct_fraction"] = pop_layout.urban / pop_layout.ct.map(ct_urban.get)
sectors = ["residential", "services"]
nodes = {}
urban_fraction = pop_layout.urban / pop_layout[["rural", "urban"]].sum(axis=1)
for sector in sectors:
nodes[sector + " rural"] = pop_layout.index
nodes[sector + " urban decentral"] = pop_layout.index
if options["central"]:
# TODO: this looks hardcoded, move to config
urban_decentral_ct = pd.Index(["ES", "GR", "PT", "IT", "BG"])
nodes[sector + " urban decentral"] = pop_layout.index[pop_layout.ct.isin(urban_decentral_ct)]
else:
nodes[sector + " urban decentral"] = pop_layout.index
# maximum potential of urban demand covered by district heating
central_fraction = options['district_heating']["potential"]
# district heating share at each node
dist_fraction_node = district_heat_share * pop_layout["urban_ct_fraction"] / pop_layout["fraction"]
nodes["urban central"] = dist_fraction_node.index
# if district heating share larger than urban fraction -> set urban
# fraction to district heating share
urban_fraction = pd.concat([urban_fraction, dist_fraction_node],
axis=1).max(axis=1)
# difference of max potential and today's share of district heating
diff = (urban_fraction * central_fraction) - dist_fraction_node
progress = get(options["district_heating"]["progress"], investment_year)
dist_fraction_node += diff * progress
print(
"The current district heating share compared to the maximum",
f"possible is increased by a progress factor of\n{progress}",
f"resulting in a district heating share of\n{dist_fraction_node}"
)
# for central nodes, residential and services are aggregated
nodes["urban central"] = pop_layout.index.symmetric_difference(nodes["residential urban decentral"])
return nodes
return nodes, dist_fraction_node, urban_fraction
def add_biomass(n, costs):
print("adding biomass")
# biomass distributed at country level - i.e. transport within country allowed
countries = n.buses.country.dropna().unique()
biomass_potentials = pd.read_csv(snakemake.input.biomass_potentials, index_col=0)
# potential per node distributed within country by population
biogas_pot = (biomass_potentials.loc[pop_layout.ct]
.set_index(pop_layout.index)
.mul(pop_layout.fraction, axis="index")
.rename(index=lambda x: x + " biogas")
)["biogas"]
# need to aggregate potentials if gas not nodally resolved
if not options["gas_network"]:
biogas_pot = biogas_pot.sum()
if options["gas_network"]:
biogas_potentials_spatial = biomass_potentials["biogas"].rename(index=lambda x: x + " biogas")
else:
biogas_potentials_spatial = biomass_potentials["biogas"].sum()
if options["biomass_transport"]:
solid_biomass_potentials_spatial = biomass_potentials["solid biomass"].rename(index=lambda x: x + " solid biomass")
else:
solid_biomass_potentials_spatial = biomass_potentials["solid biomass"].sum()
n.add("Carrier", "biogas")
n.add("Carrier", "solid biomass")
n.madd("Bus",
@ -1682,9 +1851,9 @@ def add_biomass(n, costs):
carrier="biogas"
)
n.add("Bus",
"EU solid biomass",
location="EU",
n.madd("Bus",
spatial.biomass.nodes,
location=spatial.biomass.locations,
carrier="solid biomass"
)
@ -1692,18 +1861,18 @@ def add_biomass(n, costs):
spatial.gas.biogas,
bus=spatial.gas.biogas,
carrier="biogas",
e_nom=biogas_pot,
e_nom=biogas_potentials_spatial,
marginal_cost=costs.at['biogas', 'fuel'],
e_initial=biogas_pot
e_initial=biogas_potentials_spatial
)
n.add("Store",
"EU solid biomass",
bus="EU solid biomass",
n.madd("Store",
spatial.biomass.nodes,
bus=spatial.biomass.nodes,
carrier="solid biomass",
e_nom=biomass_potentials.loc[countries, "solid biomass"].sum(),
e_nom=solid_biomass_potentials_spatial,
marginal_cost=costs.at['solid biomass', 'fuel'],
e_initial=biomass_potentials.loc[countries, "solid biomass"].sum()
e_initial=solid_biomass_potentials_spatial
)
n.madd("Link",
@ -1718,6 +1887,32 @@ def add_biomass(n, costs):
p_nom_extendable=True
)
if options["biomass_transport"]:
transport_costs = pd.read_csv(
snakemake.input.biomass_transport_costs,
index_col=0,
squeeze=True
)
# add biomass transport
biomass_transport = create_network_topology(n, "biomass transport ", bidirectional=False)
# costs
bus0_costs = biomass_transport.bus0.apply(lambda x: transport_costs[x[:2]])
bus1_costs = biomass_transport.bus1.apply(lambda x: transport_costs[x[:2]])
biomass_transport["costs"] = pd.concat([bus0_costs, bus1_costs], axis=1).mean(axis=1)
n.madd("Link",
biomass_transport.index,
bus0=biomass_transport.bus0 + " solid biomass",
bus1=biomass_transport.bus1 + " solid biomass",
p_nom_extendable=True,
length=biomass_transport.length.values,
marginal_cost=biomass_transport.costs * biomass_transport.length.values,
capital_cost=1,
carrier="solid biomass transport"
)
#AC buses with district heating
urban_central = n.buses.index[n.buses.carrier == "urban central heat"]
@ -1728,7 +1923,7 @@ def add_biomass(n, costs):
n.madd("Link",
urban_central + " urban central solid biomass CHP",
bus0="EU solid biomass",
bus0=spatial.biomass.df.loc[urban_central, "nodes"].values,
bus1=urban_central,
bus2=urban_central + " urban central heat",
carrier="urban central solid biomass CHP",
@ -1742,11 +1937,11 @@ def add_biomass(n, costs):
n.madd("Link",
urban_central + " urban central solid biomass CHP CC",
bus0="EU solid biomass",
bus0=spatial.biomass.df.loc[urban_central, "nodes"].values,
bus1=urban_central,
bus2=urban_central + " urban central heat",
bus3="co2 atmosphere",
bus4="co2 stored",
bus4=spatial.co2.df.loc[urban_central, "nodes"].values,
carrier="urban central solid biomass CHP CC",
p_nom_extendable=True,
capital_cost=costs.at[key, 'fixed'] * costs.at[key, 'efficiency'] + costs.at['biomass CHP capture', 'fixed'] * costs.at['solid biomass', 'CO2 intensity'],
@ -1776,34 +1971,39 @@ def add_industry(n, costs):
solid_biomass_by_country = industrial_demand["solid biomass"].groupby(pop_layout.ct).sum()
n.add("Bus",
"solid biomass for industry",
location="EU",
n.madd("Bus",
spatial.biomass.industry,
location=spatial.biomass.locations,
carrier="solid biomass for industry"
)
n.add("Load",
"solid biomass for industry",
bus="solid biomass for industry",
if options["biomass_transport"]:
p_set = industrial_demand.loc[spatial.biomass.locations, "solid biomass"].rename(index=lambda x: x + " solid biomass for industry") / 8760
else:
p_set = industrial_demand["solid biomass"].sum() / 8760
n.madd("Load",
spatial.biomass.industry,
bus=spatial.biomass.industry,
carrier="solid biomass for industry",
p_set=solid_biomass_by_country.sum() / 8760
p_set=p_set
)
n.add("Link",
"solid biomass for industry",
bus0="EU solid biomass",
bus1="solid biomass for industry",
n.madd("Link",
spatial.biomass.industry,
bus0=spatial.biomass.nodes,
bus1=spatial.biomass.industry,
carrier="solid biomass for industry",
p_nom_extendable=True,
efficiency=1.
)
n.add("Link",
"solid biomass for industry CC",
bus0="EU solid biomass",
bus1="solid biomass for industry",
n.madd("Link",
spatial.biomass.industry_cc,
bus0=spatial.biomass.nodes,
bus1=spatial.biomass.industry,
bus2="co2 atmosphere",
bus3="co2 stored",
bus3=spatial.co2.nodes,
carrier="solid biomass for industry CC",
p_nom_extendable=True,
capital_cost=costs.at["cement capture", "fixed"] * costs.at['solid biomass', 'CO2 intensity'],
@ -1841,7 +2041,7 @@ def add_industry(n, costs):
bus0=spatial.gas.nodes,
bus1=spatial.gas.industry,
bus2="co2 atmosphere",
bus3="co2 stored",
bus3=spatial.co2.nodes,
carrier="gas for industry CC",
p_nom_extendable=True,
capital_cost=costs.at["cement capture", "fixed"] * costs.at['gas', 'CO2 intensity'],
@ -1859,18 +2059,66 @@ def add_industry(n, costs):
p_set=industrial_demand.loc[nodes, "hydrogen"] / 8760
)
if options["shipping_hydrogen_liquefaction"]:
n.madd("Bus",
nodes,
suffix=" H2 liquid",
carrier="H2 liquid",
location=nodes
)
n.madd("Link",
nodes + " H2 liquefaction",
bus0=nodes + " H2",
bus1=nodes + " H2 liquid",
carrier="H2 liquefaction",
efficiency=costs.at["H2 liquefaction", 'efficiency'],
capital_cost=costs.at["H2 liquefaction", 'fixed'],
p_nom_extendable=True,
lifetime=costs.at['H2 liquefaction', 'lifetime']
)
shipping_bus = nodes + " H2 liquid"
else:
shipping_bus = nodes + " H2"
all_navigation = ["total international navigation", "total domestic navigation"]
efficiency = options['shipping_average_efficiency'] / costs.at["fuel cell", "efficiency"]
p_set = nodal_energy_totals.loc[nodes, all_navigation].sum(axis=1) * 1e6 * efficiency / 8760
shipping_hydrogen_share = get(options['shipping_hydrogen_share'], investment_year)
p_set = shipping_hydrogen_share * nodal_energy_totals.loc[nodes, all_navigation].sum(axis=1) * 1e6 * efficiency / 8760
n.madd("Load",
nodes,
suffix=" H2 for shipping",
bus=nodes + " H2",
bus=shipping_bus,
carrier="H2 for shipping",
p_set=p_set
)
if shipping_hydrogen_share < 1:
shipping_oil_share = 1 - shipping_hydrogen_share
p_set = shipping_oil_share * nodal_energy_totals.loc[nodes, all_navigation].sum(axis=1) * 1e6 / 8760.
n.madd("Load",
nodes,
suffix=" shipping oil",
bus="EU oil",
carrier="shipping oil",
p_set=p_set
)
co2 = shipping_oil_share * nodal_energy_totals.loc[nodes, all_navigation].sum().sum() * 1e6 / 8760 * costs.at["oil", "CO2 intensity"]
n.add("Load",
"shipping oil emissions",
bus="co2 atmosphere",
carrier="shipping oil emissions",
p_set=-co2
)
if "EU oil" not in n.buses.index:
n.add("Bus",
@ -1902,7 +2150,7 @@ def add_industry(n, costs):
if options["oil_boilers"]:
nodes_heat = create_nodes_for_heat_sector()
nodes_heat = create_nodes_for_heat_sector()[0]
for name in ["residential rural", "services rural", "residential urban decentral", "services urban decentral"]:
@ -1923,7 +2171,7 @@ def add_industry(n, costs):
nodes + " Fischer-Tropsch",
bus0=nodes + " H2",
bus1="EU oil",
bus2="co2 stored",
bus2=spatial.co2.nodes,
carrier="Fischer-Tropsch",
efficiency=costs.at["Fischer-Tropsch", 'efficiency'],
capital_cost=costs.at["Fischer-Tropsch", 'fixed'],
@ -2012,11 +2260,12 @@ def add_industry(n, costs):
)
#assume enough local waste heat for CC
n.add("Link",
"process emissions CC",
n.madd("Link",
spatial.co2.locations,
suffix=" process emissions CC",
bus0="process emissions",
bus1="co2 atmosphere",
bus2="co2 stored",
bus2=spatial.co2.nodes,
carrier="process emissions CC",
p_nom_extendable=True,
capital_cost=costs.at["cement capture", "fixed"],
@ -2046,6 +2295,71 @@ def add_waste_heat(n):
n.links.loc[urban_central + " H2 Fuel Cell", "efficiency2"] = 0.95 - n.links.loc[urban_central + " H2 Fuel Cell", "efficiency"]
def add_agriculture(n, costs):
logger.info('Add agriculture, forestry and fishing sector.')
nodes = pop_layout.index
# electricity
n.madd("Load",
nodes,
suffix=" agriculture electricity",
bus=nodes,
carrier='agriculture electricity',
p_set=nodal_energy_totals.loc[nodes, "total agriculture electricity"] * 1e6 / 8760
)
# heat
n.madd("Load",
nodes,
suffix=" agriculture heat",
bus=nodes + " services rural heat",
carrier="agriculture heat",
p_set=nodal_energy_totals.loc[nodes, "total agriculture heat"] * 1e6 / 8760
)
# machinery
electric_share = get(options["agriculture_machinery_electric_share"], investment_year)
assert electric_share <= 1.
ice_share = 1 - electric_share
machinery_nodal_energy = nodal_energy_totals.loc[nodes, "total agriculture machinery"]
if electric_share > 0:
efficiency_gain = options["agriculture_machinery_fuel_efficiency"] / options["agriculture_machinery_electric_efficiency"]
n.madd("Load",
nodes,
suffix=" agriculture machinery electric",
bus=nodes,
carrier="agriculture machinery electric",
p_set=electric_share / efficiency_gain * machinery_nodal_energy * 1e6 / 8760,
)
if ice_share > 0:
n.add("Load",
"agriculture machinery oil",
bus="EU oil",
carrier="agriculture machinery oil",
p_set=ice_share * machinery_nodal_energy.sum() * 1e6 / 8760
)
co2 = ice_share * machinery_nodal_energy.sum() * 1e6 / 8760 * costs.at["oil", 'CO2 intensity']
n.add("Load",
"agriculture machinery oil emissions",
bus="co2 atmosphere",
carrier="agriculture machinery oil emissions",
p_set=-co2
)
def decentral(n):
"""Removes the electricity transmission system."""
n.lines.drop(n.lines.index, inplace=True)
@ -2070,14 +2384,19 @@ def maybe_adjust_costs_and_potentials(n, opts):
suptechs = map(lambda c: c.split("-", 2)[0], carrier_list)
if oo[0].startswith(tuple(suptechs)):
carrier = oo[0]
attr_lookup = {"p": "p_nom_max", "c": "capital_cost"}
attr_lookup = {"p": "p_nom_max", "e": "e_nom_max", "c": "capital_cost"}
attr = attr_lookup[oo[1][0]]
factor = float(oo[1][1:])
#beware if factor is 0 and p_nom_max is np.inf, 0*np.inf is nan
if carrier == "AC": # lines do not have carrier
n.lines[attr] *= factor
else:
comps = {"Generator", "Link", "StorageUnit"} if attr == 'p_nom_max' else {"Generator", "Link", "StorageUnit", "Store"}
if attr == 'p_nom_max':
comps = {"Generator", "Link", "StorageUnit"}
elif attr == 'e_nom_max':
comps = {"Store"}
else:
comps = {"Generator", "Link", "StorageUnit", "Store"}
for c in n.iterate_components(comps):
if carrier=='solar':
sel = c.df.carrier.str.contains(carrier) & ~c.df.carrier.str.contains("solar rooftop")
@ -2094,17 +2413,18 @@ def limit_individual_line_extension(n, maxext):
hvdc = n.links.index[n.links.carrier == 'DC']
n.links.loc[hvdc, 'p_nom_max'] = n.links.loc[hvdc, 'p_nom'] + maxext
#%%
if __name__ == "__main__":
if 'snakemake' not in globals():
from helper import mock_snakemake
snakemake = mock_snakemake(
'prepare_sector_network',
simpl='',
clusters=48,
opts="",
clusters="37",
lv=1.0,
sector_opts='Co2L0-168H-T-H-B-I-solar3-dist1',
planning_horizons=2020,
planning_horizons="2020",
)
logging.basicConfig(level=snakemake.config['logging_level'])
@ -2129,6 +2449,8 @@ if __name__ == "__main__":
patch_electricity_network(n)
define_spatial(pop_layout.index)
if snakemake.config["foresight"] == 'myopic':
add_lifetime_wind_solar(n, costs)
@ -2152,11 +2474,13 @@ if __name__ == "__main__":
if o[:4] == "dist":
options['electricity_distribution_grid'] = True
options['electricity_distribution_grid_cost_factor'] = float(o[4:].replace("p", ".").replace("m", "-"))
if o == "biomasstransport":
options["biomass_transport"] = True
nodal_energy_totals, heat_demand, ashp_cop, gshp_cop, solar_thermal, transport, avail_profile, dsm_profile, nodal_transport_data = prepare_data(n)
nodal_energy_totals, heat_demand, ashp_cop, gshp_cop, solar_thermal, transport, avail_profile, dsm_profile, nodal_transport_data, district_heat_share = prepare_data(n)
if "nodistrict" in opts:
options["central"] = False
options["district_heating"]["progress"] = 0.0
if "T" in opts:
add_land_transport(n, costs)
@ -2173,6 +2497,9 @@ if __name__ == "__main__":
if "I" in opts and "H" in opts:
add_waste_heat(n)
if "A" in opts: # requires H and I
add_agriculture(n, costs)
if options['dac']:
add_dac(n, costs)
@ -2182,6 +2509,9 @@ if __name__ == "__main__":
if "noH2network" in opts:
remove_h2_network(n)
if options["co2_network"]:
add_co2_network(n, costs)
for o in opts:
m = re.match(r'^\d+h$', o, re.IGNORECASE)
if m is not None:

View File

@ -3,6 +3,7 @@
import pypsa
import numpy as np
import pandas as pd
from pypsa.linopt import get_var, linexpr, define_constraints
@ -19,11 +20,46 @@ pypsa.pf.logger.setLevel(logging.WARNING)
def add_land_use_constraint(n):
if 'm' in snakemake.wildcards.clusters:
_add_land_use_constraint_m(n)
else:
_add_land_use_constraint(n)
def _add_land_use_constraint(n):
#warning: this will miss existing offwind which is not classed AC-DC and has carrier 'offwind'
for carrier in ['solar', 'onwind', 'offwind-ac', 'offwind-dc']:
existing = n.generators.loc[n.generators.carrier == carrier, "p_nom"].groupby(n.generators.bus.map(n.buses.location)).sum()
existing = n.generators.loc[n.generators.carrier==carrier,"p_nom"].groupby(n.generators.bus.map(n.buses.location)).sum()
existing.index += " " + carrier + "-" + snakemake.wildcards.planning_horizons
n.generators.loc[existing.index, "p_nom_max"] -= existing
n.generators.loc[existing.index,"p_nom_max"] -= existing
n.generators.p_nom_max.clip(lower=0, inplace=True)
def _add_land_use_constraint_m(n):
# if generators clustering is lower than network clustering, land_use accounting is at generators clusters
planning_horizons = snakemake.config["scenario"]["planning_horizons"]
grouping_years = snakemake.config["existing_capacities"]["grouping_years"]
current_horizon = snakemake.wildcards.planning_horizons
for carrier in ['solar', 'onwind', 'offwind-ac', 'offwind-dc']:
existing = n.generators.loc[n.generators.carrier==carrier,"p_nom"]
ind = list(set([i.split(sep=" ")[0] + ' ' + i.split(sep=" ")[1] for i in existing.index]))
previous_years = [
str(y) for y in
planning_horizons + grouping_years
if y < int(snakemake.wildcards.planning_horizons)
]
for p_year in previous_years:
ind2 = [i for i in ind if i + " " + carrier + "-" + p_year in existing.index]
sel_current = [i + " " + carrier + "-" + current_horizon for i in ind2]
sel_p_year = [i + " " + carrier + "-" + p_year for i in ind2]
n.generators.loc[sel_current, "p_nom_max"] -= existing.loc[sel_p_year].rename(lambda x: x[:-4] + current_horizon)
n.generators.p_nom_max.clip(lower=0, inplace=True)
@ -150,7 +186,6 @@ def add_chp_constraints(n):
define_constraints(n, lhs, "<=", 0, 'chplink', 'backpressure')
def add_pipe_retrofit_constraint(n):
"""Add constraint for retrofitting existing CH4 pipelines to H2 pipelines."""
@ -173,10 +208,27 @@ def add_pipe_retrofit_constraint(n):
define_constraints(n, lhs, "=", pipe_capacity, 'Link', 'pipe_retrofit')
def add_co2_sequestration_limit(n, sns):
co2_stores = n.stores.loc[n.stores.carrier=='co2 stored'].index
if co2_stores.empty or ('Store', 'e') not in n.variables.index:
return
vars_final_co2_stored = get_var(n, 'Store', 'e').loc[sns[-1], co2_stores]
lhs = linexpr((1, vars_final_co2_stored)).sum()
rhs = n.config["sector"].get("co2_sequestration_potential", 200) * 1e6
name = 'co2_sequestration_limit'
define_constraints(n, lhs, "<=", rhs, 'GlobalConstraint',
'mu', axes=pd.Index([name]), spec=name)
def extra_functionality(n, snapshots):
add_chp_constraints(n)
add_battery_constraints(n)
add_pipe_retrofit_constraint(n)
add_co2_sequestration_limit(n, snapshots)
def solve_network(n, config, opts='', **kwargs):