Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix disaggregation #674

Merged
merged 19 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,19 @@ instead part of 'eGo' https://github.com/openego/eGo

eTraGo is documented on `readthedocs <https://etrago.readthedocs.io>`_.

.. warning::
From now on eTraGo depends on a sector coupled data-model. This is not published on
the oedb yet, the data has to be created using
`eGon-data <https://github.com/openego/eGon-data>`_.
Not all functions and features are compatible to the sector coupled model yet.

When you want to use eTraGo for optimizations, please use the latest release 0.8.0.

Input data
==========
The grid model data for eTraGo was created with the open source tool
`eGon-data <https://github.com/openego/eGon-data.>`_. The resulting data will
be pubished on the `OpenEnergyPlatform <https://openenergy-platform.org/.>`_.
As long as the data is not published there, a local database is needed.
We published a backup of the required tables and instruction on how to use it
on zenodo:

.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.8376714.svg
:target: https://doi.org/10.5281/zenodo.8376714


Installation
============
Expand Down
8 changes: 0 additions & 8 deletions doc/about.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,6 @@ eGon-data is a further development of the `Data processing <https://github.com/o
The resulting data set serves as an input for the optimization tools `eTraGo <https://github.com/openego/eTraGo>`_, `ding0 <https://github.com/openego/ding0>`_ and `eDisGo <https://github.com/openego/eDisGo>`_ and delivers for example data on grid topologies, demands/demand curves and generation capacities in a high spatial resolution. The outputs of egon-data are published under open source and open data licenses.


ego.io
======

The ego.io serves as a SQLAlchemy Interface to the OpenEnergy database (oedb). The
oedb table ORM objects are defined here and small helpers for io tasks are contained.
`Learn more here <https://github.com/openego/ego.io>`_.


Dingo
=====

Expand Down
7 changes: 3 additions & 4 deletions doc/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,8 @@ environments.

Setup database connection
=========================
The package `ego.io <https://github.com/openego/ego.io>`_ will be installed
automatically when eTraGo is installed. The ``egoio``
gives you python SQL-Alchemy representations of
The eTraGo module `db <https://github.com/openego/eTraGo/blob/dev/etrago/tools/db.py>`_
gives you a python SQL-Alchemy representations of
the `OpenEnergy-Database(oedb) <https://openenergy-platform.org/dataedit/>`_
and access to it by using the
`oedialect <https://github.com/openego/oedialect>`_, which is a SQL-Alchemy binding
Expand All @@ -82,7 +81,7 @@ the oedialect enter the following connection parameter. For <username> and
<token> you have to take your credentials which you obtained by registering
at `openenergy-platform.org/login <http://openenergy-platform.org/login/>`_.

Your API access / login data will be saved in the folder ``.egoio`` in the file
Your API access / login data will be saved in the folder ``.etrago_database`` in the file
``config.ini``. Consequently, in the config.ini you can also change
your connection parameters or add new ones.
In the following you can see how the config.ini looks like when you use the
Expand Down
2 changes: 1 addition & 1 deletion doc/theoretical_background.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ With the argument ‘pf_post_lopf’, after the LOPF a non-linear power flow sim
Complexity Reduction
---------------------

The data model is characterised by a high spatial (abou 8,000 electrical and 600 gas nodes) and temporal resolution (8,760 timesteps). To reduce the complexity of the resulting optimisation problem, several methods can be applied.
The data model is characterised by a high spatial (about 8,000 electrical and 600 gas nodes) and temporal resolution (8,760 timesteps). To reduce the complexity of the resulting optimisation problem, several methods can be applied.


Reduction in spatial dimension:
Expand Down
2 changes: 1 addition & 1 deletion etrago/appl.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ def run_etrago(args, json_path):

# spatial disaggregation
# needs to be adjusted for new sectors
# etrago.disaggregation()
etrago.disaggregation()

# calculate central etrago results
etrago.calc_results()
Expand Down
56 changes: 42 additions & 14 deletions etrago/cluster/disaggregation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@


class Disaggregation:
def __init__(
self, original_network, clustered_network, clustering, skip=()
):
def __init__(self, original_network, clustered_network, busmap, skip=()):
"""
:param original_network: Initial (unclustered) network structure
:param clustered_network: Clustered network used for the optimization
Expand All @@ -25,11 +23,11 @@ def __init__(
"""
self.original_network = original_network
self.clustered_network = clustered_network
self.clustering = clustering
self.busmap = busmap

self.buses = pd.merge(
original_network.buses,
self.clustering.busmap.to_frame(name="cluster"),
busmap.to_frame(name="cluster"),
left_index=True,
right_index=True,
)
Expand Down Expand Up @@ -280,13 +278,15 @@ def solve(self, scenario, solver):
}
profile = cProfile.Profile()
profile = noops

for i, cluster in enumerate(sorted(clusters)):
log.info(f"Decompose {cluster=} ({i + 1}/{n})")
profile.enable()
t = time.time()
partial_network, externals = self.construct_partial_network(
cluster, scenario
)

profile.disable()
self.stats["clusters"].loc[cluster, "decompose"] = time.time() - t
log.info(
Expand Down Expand Up @@ -324,7 +324,9 @@ def solve(self, scenario, solver):
):
log.info(f"Attribute sums, {bt}, clustered - disaggregated:")
cnb = getattr(self.clustered_network, bt)
cnb = cnb[cnb.carrier != "DC"]
onb = getattr(self.original_network, bt)
onb = onb[onb.carrier != "DC"]
log.info(
"{:>{}}: {}".format(
"p_nom_opt",
Expand Down Expand Up @@ -622,6 +624,7 @@ def solve_partial_network(
f" & (bus1 in {index})"
)
pnb = pnb.query(query)

assert not pnb.empty or (
# In some cases, a district heating grid is connected to a
# substation only via a resistive_heater but not e.g. by a
Expand Down Expand Up @@ -649,6 +652,13 @@ def solve_partial_network(
if pnb.empty:
continue

# Exclude DC links from the disaggregation because it does not
# make sense to disaggregated them uniformly.
# A new power flow calculation in the high resolution would
# be required.
if pnb.carrier.iloc[0] == "DC":
continue

if not (
pnb.loc[:, extendable_flag].all()
or not pnb.loc[:, extendable_flag].any()
Expand All @@ -675,6 +685,7 @@ def solve_partial_network(
" it has on the buses of it's partial network."
)

print(clb.iloc[0].carrier)
if clb.iloc[0].at[extendable_flag]:
# That means, `p_nom` got computed via optimization and we
# have to distribute it into the subnetwork first.
Expand Down Expand Up @@ -718,7 +729,12 @@ def solve_partial_network(
for s in bustypes[bustype]["series"]:
if s in self.skip:
continue

filtered = pnb.loc[filters.get(s, slice(None))]

if filtered.empty:
continue

clt = cl_t[s].loc[:, clb.index[0]]
weight = reduce(
multiply,
Expand All @@ -744,6 +760,7 @@ def solve_partial_network(
)
delta = abs((new_columns.sum(axis=1) - clt).sum())
epsilon = 1e-5

ClaraBuettner marked this conversation as resolved.
Show resolved Hide resolved
assert delta < epsilon, (
"Sum of disaggregated time series does not match"
f" aggregated timeseries: {delta=} > {epsilon=}."
Expand All @@ -752,12 +769,23 @@ def solve_partial_network(

def transfer_results(self, *args, **kwargs):
kwargs["bustypes"] = ["generators", "links", "storage_units", "stores"]
kwargs["series"] = {
"generators": {"p"},
"links": {"p0", "p1"},
"storage_units": {"p", "state_of_charge"},
"stores": {"e", "p"},
}

# Only disaggregate reactive power (q) if a pf_post_lopf was performed
# and there is data in resulting q time series
if self.original_network.generators_t.q.empty:
kwargs["series"] = {
"generators": {"p"},
"links": {"p0", "p1"},
"storage_units": {"p", "state_of_charge"},
"stores": {"e", "p"},
}
else:
kwargs["series"] = {
"generators": {"p", "q"},
"links": {"p0", "p1"},
"storage_units": {"p", "q", "state_of_charge"},
"stores": {"e", "p"},
}
return super().transfer_results(*args, **kwargs)


Expand Down Expand Up @@ -799,7 +827,7 @@ def update_constraints(network, externals):

def run_disaggregation(self):
log.debug("Running disaggregation.")
if self.clustering:
if self.args["network_clustering"]["active"]:
disagg = self.args.get("disaggregation")
skip = () if self.args["pf_post_lopf"]["active"] else ("q",)
t = time.time()
Expand All @@ -808,14 +836,14 @@ def run_disaggregation(self):
disaggregation = MiniSolverDisaggregation(
self.disaggregated_network,
self.network,
self.clustering,
self.busmap,
skip=skip,
)
elif disagg == "uniform":
disaggregation = UniformDisaggregation(
original_network=self.disaggregated_network,
clustered_network=self.network,
clustering=self.clustering,
busmap=pd.Series(self.busmap["busmap"]),
skip=skip,
)

Expand Down
14 changes: 7 additions & 7 deletions etrago/cluster/electrical.py
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,11 @@ def run_spatial_clustering(self):
None
"""
if self.args["network_clustering"]["active"]:
if self.args["disaggregation"] is not None:
self.disaggregated_network = self.network.copy()
else:
self.disaggregated_network = self.network.copy(with_time=False)

self.network.generators.control = "PV"

elec_network, weight, n_clusters, busmap_foreign = preprocessing(self)
Expand Down Expand Up @@ -1085,17 +1090,12 @@ def run_spatial_clustering(self):
busmap = pd.Series(dtype=str)
medoid_idx = pd.Series(dtype=str)

self.clustering, busmap = postprocessing(
clustering, busmap = postprocessing(
self, busmap, busmap_foreign, medoid_idx
)
self.update_busmap(busmap)

if self.args["disaggregation"] is not None:
self.disaggregated_network = self.network.copy()
else:
self.disaggregated_network = self.network.copy(with_time=False)

self.network = self.clustering.network
self.network = clustering.network

self.buses_by_country()

Expand Down
1 change: 1 addition & 0 deletions etrago/tools/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,7 @@ def load_shedding(self, temporal_disaggregation=False, **kwargs):
p_nom=p_nom,
carrier="load shedding",
bus=network.buses.index,
control="PQ",
),
index=index,
),
Expand Down