Skip to content

Add BadPointPressureLiftController tutorial notebook for district heating network - #711

Open
JonasPfeiffer123 wants to merge 19 commits into
e2nIEE:developfrom
JonasPfeiffer123:Tutorials-Examples-District-Heating-Controllers-
Open

Add BadPointPressureLiftController tutorial notebook for district heating network#711
JonasPfeiffer123 wants to merge 19 commits into
e2nIEE:developfrom
JonasPfeiffer123:Tutorials-Examples-District-Heating-Controllers-

Conversation

@JonasPfeiffer123

Copy link
Copy Markdown
Contributor

After getting contacted by @Theda-ISE regarding certain features in my pandapipes usage that she had discovered, she asked if I could create a tutorial for some of the features. Therefore, I'd like to share an implementation of a bad-point differential pressure control system that I've been using for a while. Feel free to comment & sharing ideas for improvement.

This pull request introduces a new Jupyter notebook tutorial, BadPointPressureController.ipynb, which provides a comprehensive guide and implementation for a custom pressure controller in district heating networks. The notebook includes detailed explanations, code implementation, and an example usage of the BadPointPressureLiftController.

New Feature: BadPointPressureLiftController

  • Controller Overview: Introduced the BadPointPressureLiftController, a custom controller for district heating networks using pandapipes. It ensures a minimum pressure difference at the network's "worst point" (heat exchanger with the lowest pressure difference).
  • Key Functionalities:
    • Automatic detection of the worst point.
    • Proportional pressure regulation.
    • Standby mode for zero heat flow.
    • Convergence checks for pressure difference.
  • Code Implementation: Added the controller's implementation, including methods for initialization, worst point calculation, time step management, convergence checks, and pump pressure adjustments.

Example Usage and Test Network

  • Example Usage: Demonstrated how to integrate the controller into a simulation loop with a sample district heating network. Includes initialization steps and controller registration.
  • Test Network Initialization: Provided a function to create a test network with junctions, pipes, heat consumers,

@EPrade EPrade left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could also add this controller as a controller in src/control/controller as it is done in pandapower, since it is a common problem in heating grids.
Thoughts? @SimonRubenDrauz @dlohmeier

@JonasPfeiffer123

Copy link
Copy Markdown
Contributor Author

As discussed yesterday in the pandapipes meeting, I added a reference to the new Jupyter Notebook for the controller example.

@EPrade

EPrade commented Nov 3, 2025

Copy link
Copy Markdown
Contributor

As discussed yesterday in the pandapipes meeting, I added a reference to the new Jupyter Notebook for the controller example.

Could you move/add the controller as a controller in pandapipes/control and then use it as in import in this tutorial?

JonasPfeiffer123 and others added 2 commits November 4, 2025 09:57
Move BadPointPressureLiftController from Jupyter notebook to pandapipes.control package:

- Add new controller module at src/pandapipes/control/controller/bad_point_pressure_lift_controller.py
- Export controller from pandapipes.control for easy import
- Update BadPointPressureController.ipynb to import controller from pandapipes instead of defining it inline
- Fix documentation formatting and links in controller_classes.rst
- Add controller subdirectory with proper __init__.py

Documentation improvements:
- Fix title underline length in controller_classes.rst to resolve Sphinx warning
- Correct external link syntax for BadPointPressureController notebook
- Update controller name to BadPointPressureLiftController (actual class name)

The BadPointPressureLiftController maintains minimum pressure difference at the worst point
(Schlechtpunkt) in district heating networks by automatically detecting the heat exchanger
with the lowest pressure difference and adjusting circulation pump pressures accordingly.

Features:
- Automatic worst point detection
- Proportional pressure control
- Standby mode when no heat flow detected
- Convergence checking with configurable tolerance
@JonasPfeiffer123

Copy link
Copy Markdown
Contributor Author

As discussed yesterday in the pandapipes meeting, I added a reference to the new Jupyter Notebook for the controller example.

Could you move/add the controller as a controller in pandapipes/control and then use it as in import in this tutorial?

Added the controller in pandapipes/control/controller. Also updated the reference in the rst-file to solve the previous sphinx error.

JonasPfeiffer123 and others added 3 commits November 4, 2025 10:56
Fix Codacy code style issues by removing trailing whitespace:
- Line 28: Remove trailing space from __init__ method signature
- Line 53: Remove trailing whitespace from empty line after docstring
- Line 136: Remove trailing spaces from pflow_adjustment assignment
- Line 139: Remove trailing whitespace from empty line

No functional changes, code style cleanup only.
@EPrade

EPrade commented Nov 5, 2025

Copy link
Copy Markdown
Contributor

As discussed yesterday in the pandapipes meeting, I added a reference to the new Jupyter Notebook for the controller example.

Could you move/add the controller as a controller in pandapipes/control and then use it as in import in this tutorial?

Added the controller in pandapipes/control/controller. Also updated the reference in the rst-file to solve the previous sphinx error.

We should also a test for the controller.

@EPrade EPrade left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test needed

SimonRubenDrauz and others added 2 commits November 7, 2025 10:47
Add test file with 2 focused test cases covering core controller functionality:

Tests added:
- Controller maintains target pressure difference at worst point in district heating network
- Standby mode activation when no heat demand is present

Additional improvements:
- Fix pandas FutureWarning in controller code by replacing chained assignment
  with proper .loc syntax (net.circ_pump_pressure.loc[:, "column"])

All tests pass successfully with no warnings.
Test location: src/pandapipes/test/api/test_components/test_bad_point_pressure_lift_controller.py
@EPrade

EPrade commented Nov 7, 2025

Copy link
Copy Markdown
Contributor

Test needed

I think the test could be put into a directory test/control/ for a more clear structure

Move BadPointPressureLiftController tests to test/control/

@EPrade EPrade left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. @SimonRubenDrauz I think it could be merged now

Comment on lines +11 to +26
Key Features:
- **Automatic Worst Point Detection:** Identifies the heat exchanger with the lowest pressure difference where heat flow is present.
- **Pressure Regulation:** Adjusts the circulation pump's lift and flow pressures to ensure the pressure difference at the worst point meets a specified minimum target.
- **Proportional Control:** Uses a proportional gain to determine the adjustment magnitude based on the deviation from the target pressure difference.
- **Standby Mode:** If no heat flow is detected, the controller switches the pump to a standby mode with minimum lift and flow pressures.
- **Convergence Check:** Determines if the pressure difference is within a specified tolerance of the target, signaling convergence.

Args:
net (pandapipesNet): The pandapipes network.
circ_pump_pressure_idx (int, optional): Index of the circulation pump. Defaults to 0.
target_dp_min_bar (float, optional): Target minimum pressure difference in bar. Defaults to 1.
tolerance (float, optional): Tolerance for pressure difference. Defaults to 0.2.
proportional_gain (float, optional): Proportional gain for the controller. Defaults to 0.2.
min_plift (float, optional): Minimum lift pressure in bar. Defaults to 1.5.
min_pflow (float, optional): Minimum flow pressure in bar. Defaults to 3.5.
**kwargs: Additional keyword arguments.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you adapt the docstrings to Sphinx / reStructuredText (reST) style

Comment on lines +56 to +68
for idx, qext, p_from, p_to in zip(net.heat_consumer.index, net.heat_consumer["qext_w"],
net.res_heat_consumer["p_from_bar"], net.res_heat_consumer["p_to_bar"]):
if qext != 0:
dp_diff = p_from - p_to
dp.append((dp_diff, idx))

if not dp:
return 0, -1

# Find the minimum delta p where the heat flow is not zero
dp_min, idx_min = min(dp, key=lambda x: x[0])

return dp_min, idx_min

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not using:
diff = net.res_heat_consumer["p_from_bar"] - net.res_heat_consumer["p_to_bar"]
diff.min(), diff.idxmin()

@JonasPfeiffer123 JonasPfeiffer123 Nov 7, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! Old implementation of mine, never optimized it. I'm going to simplify the code to use pandas operations instead of the manual loop:
diff = net.res_heat_consumer["p_from_bar"] - net.res_heat_consumer["p_to_bar"]
Then filtering with active_consumers = qext != 0 and using diff.min() and diff.idxmin().

Comment on lines +123 to +124
net.circ_pump_pressure.loc[:, "plift_bar"] = self.min_plift # Minimum lift pressure
net.circ_pump_pressure.loc[:, "p_flow_bar"] = self.min_pflow # Minimum flow pressure

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why all circ pumps?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I've never had a problem, as I always only use one circ_pump_pressure. I'm going to change it from .loc[:, ...] to .at[self.circ_pump_pressure_idx, ...] so it only affects the single controlled pump, not all pumps in the network.

Comment on lines +138 to +142
new_plift = current_plift_bar + plift_adjustment
new_pflow = current_pflow_bar + pflow_adjustment

net.circ_pump_pressure["plift_bar"].at[self.circ_pump_pressure_idx] = new_plift
net.circ_pump_pressure["p_flow_bar"].at[self.circ_pump_pressure_idx] = new_pflow

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to me a bit random and error prone. Usually you keep one pressure fixed: flow or return and only adapt the pressure lift accordingly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my understanding: For this application we need to adjust both p_flow_bar
and plift_bar together. The reason is that p_flow_bar sets the absolute pressure at the
pump outlet (supply), and plift_bar sets the pressure increase across the pump.

If we only adjust plift_bar while keeping p_flow_bar constant, increasing plift_bar would
actually decrease the return pressure (since p_flow = p_return + plift), which is
would be restricted by the pressure maintenance in place, right?

By adjusting both pressures with the same proportional gain, we make sure, that the return pressure stays in place.

If that's not the case, I'd like to discuss it, before finishing this pull request.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should discuss this first I guess.

@SimonRubenDrauz

Copy link
Copy Markdown
Collaborator

Didn't not have much time but here some ideas.

Address code review feedback:
- Simplify worst point calculation using pandas operations (min/idxmin)
  instead of manual loop for better performance and readability
- Fix standby mode to only affect the controlled pump using .at indexing
  instead of .loc[:, ...] which would affect all pumps in the network

Changes based on review by SimonRubenDrauz.
All tests continue to pass.
Changes based on code review feedback from SimonRubenDrauz.
Implemented two operating modes to provide flexibility in pressure control strategy:

- **fixed_preturn mode (default)**: Adjusts both p_flow_bar and plift_bar
  simultaneously to maintain constant return pressure. Ideal when return
  pressure stability is critical for the overall network.

- **fixed_pflow mode**: Keeps flow pressure constant by adjusting only
  plift_bar. Return pressure may vary but is protected by min_preturn
  parameter. Useful when flow pressure must remain fixed due to supply
  line limitations.

This resolves the implementation question of which control strategy is
more appropriate by allowing users to choose based on their specific
network requirements. Both modes successfully maintain the target
pressure difference at the worst point while respecting different
operational constraints.

Changes:
- Added 'mode' parameter with validation ('fixed_pflow'/'fixed_preturn')
- Added 'min_preturn' parameter to prevent return pressure drops in
  fixed_pflow mode
- Updated control_step() logic to handle both modes
- Extended tests to verify both modes work correctly
- Updated documentation and tutorial notebook with mode comparison
@JonasPfeiffer123

Copy link
Copy Markdown
Contributor Author

I've implemented a dual-mode system for the BadPointPressureLiftController, allowing users to choose between fixed_pflow and fixed_preturn modes based on their specific network requirements. This resolves the discussed question about which control strategy should be implemented by providing both options.

Mode options:

  • fixed_preturn (default): Maintains constant return pressure by adjusting both p_flow and plift
  • fixed_pflow: Maintains constant flow pressure by adjusting only plift (with min_preturn protection)

Both modes successfully maintain the target pressure difference at the worst point while respecting different operational constraints.

This follows discussions with @Theda-ISE and @EPrade regarding the optimal implementation approach for this controller.

@kbensafta

Copy link
Copy Markdown
Contributor

A small suggestion: change the name of the tutorial (and the rest of the files accrodingly) to WorstPointPressureDropController instead of BadPointPressureLiftController.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants