Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d0ffb08
Replace nominal_value with nominal_capacity
jnettels Jun 24, 2026
2575bcb
Use more explicit condition
jnettels Jun 24, 2026
639e82b
Unify code structure
jnettels Jun 24, 2026
a4b7dfe
Create opposite directions for existing pipes
jnettels Jun 24, 2026
bbccf71
Set undefined values for attribute 'existing'
jnettels Jun 24, 2026
058e8b3
Implement new setting 'return_existing'
jnettels Jun 24, 2026
367a94b
Update settings and attributes in documentation
jnettels Jun 24, 2026
3c0d9ee
Make weld_segments() compatible with existing pipes
jnettels Jun 24, 2026
178dfda
Fix flake8 format issues
jnettels Jun 24, 2026
423b8a9
Fix flake8 format issues
jnettels Jun 24, 2026
f546a37
Fix flake8 format issues
jnettels Jun 24, 2026
d9a52ed
Fix flake8 format issues
jnettels Jun 24, 2026
d5d8dba
Always derive flow direction from flow attribute
jnettels Jul 6, 2026
3349ea2
Fix typo
jnettels Jul 8, 2026
a330571
Merge branch 'dev' into features/update_existing_pipes
jnettels Jul 16, 2026
8129f98
Add FutureWarning for replacing welding with simplify
jnettels Jul 16, 2026
915eeec
Update changelog
jnettels Jul 16, 2026
cbf0dc0
Add test for investment optimization workflow
jnettels Jul 28, 2026
20209bf
Rename optimization test result file
jnettels Jul 28, 2026
ac30fef
Change if condition
jnettels Jul 28, 2026
890f5ee
Fix oemof.solph error when using nonconvex=0
jnettels Jul 28, 2026
69ca9fb
Add test for new pipes and nonconvex=0
jnettels Jul 28, 2026
f265ec6
Use deprecated argument to increase code coverage
jnettels Jul 28, 2026
334069c
Change more options in test to increase coverage
jnettels Jul 28, 2026
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
2 changes: 2 additions & 0 deletions docs/_static/opti_results.csv
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ attribute,type,unit,description,status
**length**,float,m,Length of pipe (see :ref:`pipes of network<edges_csv>`),Input
**hp_type**,object,n/a,Label of pipe which got selected from :ref:`network/pipes.csv <invest_pipes>`,Result
**capacity**,float,kW,Installed pipe capacity,Result
**flow**,float,kW,Flow through the pipe. For new pipes *capacity = flow + losses*. For existing pipes *capacity >= flow + losses*,Result
**direction**,float,-1/0/1,Flow direction of pipe: 1 if direction corresponds to the *from_node*/*to_node* notation. -1: opposite direction. 0: no investment. This works only if the setting option *bidirectional_pipes* is set *False*.,Result
**costs**,float,Eur,Total cost of pipe element.,Result
**losses**,float,kW,Total losses of pipe element.,Result
**existing**,bool,True/False,"*True* if pipe is existing, *False* if pipe is new. Attribute is only included if the optimization setting *return_existing* is *True*.",Result
2 changes: 2 additions & 0 deletions docs/_static/opti_settings.csv
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ attribute,type,default,description
**dump_name**,str,dump.oemof,Name of dump file
**print_logging_info**,bool,*False*,There are still some helpful print statements.
**write_lp_file**,bool,*False*,Option of writing lp-file. The lp-file is stored in 'User/.oemof/lp_files/DHNx.lp'
**allow_nonoptimal**,bool,*False*,"*False*: If no optimal solution is found, an error will be raised. *True*: If no optimal solution is found, there will be a warning."
**return_existing**,bool,*False*,"Return existing pipes in the resulting network, but only those that receive a flow. Only applies if *existing=1* was used for any input pipe segments."
24 changes: 24 additions & 0 deletions docs/whatsnew/v0-1-0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,29 @@ API changes
We reccomend using PandaPipes (typically) or TESPy
(e.g. if phase changes need to be considered) instead.

* Renamed argument ``welding`` of ``process_geometry()`` to ``simplify``
to reflect the extended functionality (not only merging line segments and
loose ends, but also removing unnecessary detours)

* The result ``network.results.optimization['components']['pipes']`` of
the optimization now contains the following additional column:

* ``flow``: Thermal power flow through the pipe.
For new pipes ``capacity = flow + losses``.
For existing pipes ``capacity >= flow + losses``.

* ``network.optimize_investment()`` accepts new arguments:

* ``return_existing``: If ``True``, this will return existing pipes in the
resulting network, but only those that receive a flow.
Only applies if ``existing=1`` was used for any input pipe segments.
Default is ``False``, in which case the previous behaviour
is preserved, where existing pipe segments are not included in the
resulting network (despite being used in the optimization).
* ``allow_nonoptimal``: ``False`` (default): If no optimal solution is
found, an error will be raised. ``True``: If no optimal solution is
found, there will be a warning.


New features
^^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -54,6 +77,7 @@ Other changes

* Updated build system to pyproject.toml
* Improved simplifying pre-processing
* Fix several issues related to the workflow with existing pipes


Contributors
Expand Down
19 changes: 15 additions & 4 deletions src/dhnx/gistools/connect_points.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
print("Need to install shapely to process geometry.")

import logging
import warnings

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -550,6 +551,7 @@ def process_geometry(
n_conn=1,
n_conn_prod=1,
simplify=True,
**kwargs,
):
"""
This function connects the consumers and producers to the line network,
Expand Down Expand Up @@ -595,10 +597,11 @@ def process_geometry(
the nearest line segments in the street network. This allows the
placement of the connection lines to be part of the optimization
process. The default is 1.
welding : bool, optional
Weld continuous line segments together and cut loose ends. This
can improve the performance of the optimization, as it decreases
the total number of line elements. Default is True.
simplify : bool, optional
Merge continuous line segments together, cut loose ends and remove
unnecessary detours. This can improve the performance of the
optimization, as it decreases the total number of line elements
and branches. Default is True.

Returns
-------
Expand All @@ -613,6 +616,14 @@ def process_geometry(
"supported. Use 'reset_index=True'."
)

if "welding" in kwargs:
warnings.warn(
"Argument 'welding' is deprecated and now overwrites 'simplify'. "
"Please use 'simplify' instead.",
FutureWarning,
)
simplify = kwargs["welding"]

# Copies of the original polygons are needed for method 'boundary'
consumers_poly = go.check_crs(consumers, crs=projected_crs).copy()
producers_poly = go.check_crs(producers, crs=projected_crs).copy()
Expand Down
2 changes: 1 addition & 1 deletion src/dhnx/gistools/geometry_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def match_multipoint(point_wkt):
[wkt.loads(x) for x in lines["b1_wkt"] if not match_multipoint(x)]
)
gdf_errors = gpd.GeoDataFrame(geometry=errors, crs=lines.crs)
ax = lines.plot()
ax = lines.plot(color='blue')
gdf_errors.plot(ax=ax, color="red", label="Point(s) causing error")
plt.legend()
plt.show()
Expand Down
5 changes: 2 additions & 3 deletions src/dhnx/optimization/add_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,11 +457,10 @@ def add_heatpipes(it, labels, bidirectional, length, b_in, b_out, nodes):
# definition of tag3 of label -> type of pipe
labels["l_3"] = t["label_3"]

epc_p = t["capex_pipes"] * length
epc_fix = t["fix_costs"] * length

# Heatpipe with binary variable
nc = bool(t["nonconvex"])
epc_p = t["capex_pipes"] * length
epc_fix = t["fix_costs"] * length if nc else 0

# bidirectional heatpipelines yes or no
flow_bi_args = {"minimum": -1} if bidirectional else {}
Expand Down
44 changes: 24 additions & 20 deletions src/dhnx/optimization/dhs_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
SPDX-License-Identifier: MIT
"""


import oemof.solph as solph

import dhnx.optimization.add_components as ac
Expand Down Expand Up @@ -75,7 +74,7 @@ def add_nodes_dhs(opti_network, gd, nodes, busd):
d_labels["l_1"] = "infrastructure"
d_labels["l_2"] = "heat"

if q["existing"]:
if q["existing"] == 1:

# terminate the first label
l_1_in = "infrastructure"
Expand Down Expand Up @@ -127,7 +126,6 @@ def add_nodes_dhs(opti_network, gd, nodes, busd):
)

elif (typ_from == "forks") and (typ_to == "forks"):

start = q["from_node"]
end = q["to_node"]
b_in = busd[(l_1_in, d_labels["l_2"], "bus", start)]
Expand All @@ -137,6 +135,18 @@ def add_nodes_dhs(opti_network, gd, nodes, busd):
pipe_data, d_labels, gd, q, b_in, b_out, nodes
)

if not gd["bidirectional_pipes"]:
# the heatpipes from fork to fork need to be created in
# both directions, in case of bidirectional == False
start = q["to_node"]
end = q["from_node"]
b_in = busd[(l_1_in, d_labels["l_2"], "bus", start)]
b_out = busd[(l_1_out, d_labels["l_2"], "bus", end)]
d_labels["l_4"] = start + "-" + end
nodes = ac.add_heatpipes_exist(
pipe_data, d_labels, gd, q, b_in, b_out, nodes
)

else:
raise ValueError("Something wrong!")

Expand Down Expand Up @@ -208,14 +218,11 @@ def add_nodes_dhs(opti_network, gd, nodes, busd):
elif (q["from_node"].split("-")[0] == "forks") and (
q["to_node"].split("-")[0] == "forks"
):

b_in = busd[
(d_labels["l_1"], d_labels["l_2"], "bus", q["from_node"])
]
b_out = busd[
(d_labels["l_1"], d_labels["l_2"], "bus", q["to_node"])
]
d_labels["l_4"] = q["from_node"] + "-" + q["to_node"]
start = q["from_node"]
end = q["to_node"]
b_in = busd[(d_labels["l_1"], d_labels["l_2"], "bus", start)]
b_out = busd[(d_labels["l_1"], d_labels["l_2"], "bus", end)]
d_labels["l_4"] = start + "-" + end

nodes = ac.add_heatpipes(
pipe_data,
Expand All @@ -229,19 +236,16 @@ def add_nodes_dhs(opti_network, gd, nodes, busd):

if not gd["bidirectional_pipes"]:
# the heatpipes from fork to fork need to be created in
# both directions in this case bidiretional = False
# both directions, in case of bidirectional == False
start = q["to_node"]
end = q["from_node"]
b_in = busd[
(d_labels["l_1"], d_labels["l_2"], "bus", q["to_node"])
(d_labels["l_1"], d_labels["l_2"], "bus", start)
]
b_out = busd[
(
d_labels["l_1"],
d_labels["l_2"],
"bus",
q["from_node"],
)
(d_labels["l_1"], d_labels["l_2"], "bus", end)
]
d_labels["l_4"] = q["to_node"] + "-" + q["from_node"]
d_labels["l_4"] = start + "-" + end

nodes = ac.add_heatpipes(
pipe_data,
Expand Down
28 changes: 14 additions & 14 deletions src/dhnx/optimization/oemof_heatpipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def __init__(
"the results. Hopefully, you know what you are doing."
)
else:
self._set_nominal_value()
self._set_nominal_capacity()

def _check_flows_invest(self):
# for flow in self.inputs.values():
Expand All @@ -148,15 +148,15 @@ def _set_flows(self):
for flow in self.inputs.values():
flow.investment = Investment()

# set nominal values of in- and outflow equal in case of
# set nominal capacities of in- and outflow equal in case of
# invest_group = False
def _set_nominal_value(self):
def _set_nominal_capacity(self):
i = list(self.inputs.keys())[0]
o = list(self.outputs.keys())[0]
if self.outputs[o].nominal_value is not None:
self.inputs[i].nominal_value = self.outputs[o].nominal_value
elif self.inputs[i].nominal_value is not None:
self.outputs[o].nominal_value = self.inputs[i].nominal_value
if self.outputs[o].nominal_capacity is not None:
self.inputs[i].nominal_capacity = self.outputs[o].nominal_capacity
elif self.inputs[i].nominal_capacity is not None:
self.outputs[o].nominal_capacity = self.inputs[i].nominal_capacity

def constraint_group(self):
if self._invest_group is True:
Expand Down Expand Up @@ -191,8 +191,8 @@ class HeatPipelineBlock(ScalarBlock): # pylint: disable=too-many-ancestors
":math:`\dot{Q}_{in}(t)`", ":py:obj:`flow[i, n, t]`", "V", "Heat input"
":math:`\dot{Q}_{loss}(t)`", ":py:obj:`heat_loss[n, t]`", "P", "Heat
loss of heat pipeline"
":math:`\dot{Q}_{nominal}`", ":py:obj:`flows[n, o].nominal_value`", "
P", "Nominal capacity of heating pipeline"
":math:`\dot{Q}_{nominal}`", ":py:obj:`flows[n, o].nominal_capacity`
", "P", "Nominal capacity of heating pipeline"
":math:`f_{loss}(t)`", ":py:obj:`heat_loss_factor`", "P", "Specific
heat loss factor for pipeline"
":math:`l`", ":py:obj:`length`", "P", "Length of heating pipeline"
Expand Down Expand Up @@ -242,13 +242,13 @@ def _create(self, group=None):

def _heat_loss_rule_fix(block, n, t):
"""Rule definition for the heat loss depending on the nominal
capacity for fix fix heat loss.
capacity for fix heat loss.
"""
o = list(n.outputs.keys())[0]

expr = 0
expr += -block.heat_loss[n, t]
expr += n.heat_loss_factor[t] * m.flows[n, o].nominal_value
expr += n.heat_loss_factor[t] * m.flows[n, o].nominal_capacity
expr += n.heat_loss_factor_fix[t]
return expr == 0

Expand All @@ -265,7 +265,7 @@ def _heat_loss_rule_on_off(block, n, t):
expr = 0
expr += -block.heat_loss[n, t]
expr += (
n.heat_loss_factor[t] * m.flows[n, o].nominal_value
n.heat_loss_factor[t] * m.flows[n, o].nominal_capacity
+ n.heat_loss_factor_fix[t]
) * m.NonConvexFlowBlock.status[n, o, t]
return expr == 0
Expand Down Expand Up @@ -325,8 +325,8 @@ class HeatPipelineInvestBlock(
":math:`\dot{Q}_{in}(t)`", ":py:obj:`flow[i, n, t]`", "V", "Heat input"
":math:`\dot{Q}_{loss}(t)`", ":py:obj:`heat_loss[n, t]`", "V", "Heat
loss of heat pipeline"
":math:`\dot{Q}_{nominal}`", ":py:obj:`flows[n, o].nominal_value`", "
V", "Nominal capacity of heating pipeline"
":math:`\dot{Q}_{nominal}`", ":py:obj:`flows[n, o].nominal_capacity`
", "V", "Nominal capacity of heating pipeline"
":math:`f_{loss}(t)`", ":py:obj:`heat_loss_factor`", "P", "Specific
heat loss factor for pipeline"
":math:`l`", ":py:obj:`length`", "P", "Length of heating pipeline"
Expand Down
Loading
Loading