e2nIEE / pandapipes

A pipeflow calculation tool that complements pandapower in the simulation of multi energy grids
https://www.pandapipes.org
Other
140 stars 61 forks source link

Example of heating network with multiple producers #309

Open cvTHM opened 2 years ago

cvTHM commented 2 years ago

Hi all,

has there already been an exampel of a heating network which features multiple producers like in many real district heating networks? I encounter some problems while modelling such a network design: Mainly a network set pressure delivered by pumps at the production sites is known, but not the mass flow given by each production site (at a certain load of consumers). Referring to former issue:

##################### is there another way to fix the pressure in any point in the grid, apart from create_ext_grid ? Originally posted by @ggrrll in https://github.com/e2nIEE/pandapipes/issues/288#issuecomment-879758434 #####################

I came up with the following small example to illustrate my approach to handle multiple producers (feed line and reflux line are calculated separately, unfortunetaly.) Circulation pumps often cause problems in combination with external grids and other circulation pumps. On top of that, modelling consumers merely as heat exchangers does not allow to consider the different pressure drops at consumers given by the network. That's why they are modelled as sinks and sources (consumers=heat exchangers close to production sites would simply feature the highest mass flow whereas those with more distance to a production site receive hardly any mass flow. A a priori-definition of the heat exchangers' loss coefficients is not practicable). I would be glad for any contribution

Code example ``` import pandas as pd import pandapipes as ppi import pandapipes.plotting as ppplot from pandapipes.pipeflow_setup import init_options from pandapipes.component_models import Pipe ''' Feed line: 1) PRODUCERS: Pressure and temperature defined ext_grid type='pt fix (sytem) pressure at producers and (system) temperatures 2) CONSUMERS: Mass flow defined sinks (fix mass flow from defined temperature drop and heat consumption ''' ''' VL/feed line ''' net=ppi.create_empty_network(name='net',fluid='water') ppi.create_junctions(net,10,10*[1],10*[300],geodata=[(0,0), (1,0), (2,0.5), (2,-0.5), (3,-0.5), (1, -1), (1,1), (3,0.5), (2,-2), (2,1)]) ppi.create_pipes_from_parameters(net,[0,1,2,1,3,1,2,2,3,2],[1,2,3,3,4,5,6,7,8,9],10*[0.1],10*[0.05],10*[0.03],sections=10*[5],alpha_w_per_m2k=10*[0.5], text_k=10*[283]) # Producers ppi.create_ext_grid(net,0,6,353,name='ext1',type='pt') ppi.create_ext_grid(net,4,6,353,name='ext2',type='pt') ppi.create_ext_grid(net,9,6,353,name='ext2',type='pt') # Consumers ppi.create_sinks(net,[5,6,7,8],[0.05,0.07,0.02,0.05]) ppi.pipeflow(net,mode='all') ppplot.simple_plot(net, plot_sinks=True, plot_sources=True) ''' Reflux line: 1) PRODUCERS: Mass flow defined partially sinks (fixed mass flow taken from results in feed line calculation) + ONE producer modelled as ext_grid to avoid over-definition of the system 2) CONSUMERS: Mass flow defined partially, temperature defined sources with same mass flow like in feed line calculation + additional ext_grid at each consumer specifying reflux line temperature 3) Pressure definition: Alternatively at one of the producers OR specified at the consumers with worst supply (e.g. 0,5 bar below feed line pressure at this consumer) ''' ''' RL/reflux line''' net2=ppi.create_empty_network(name='net2',fluid='water') ppi.create_junctions(net2,10,10*[1],10*[300], geodata=[(0,0), (1,0), (2,0.5), (2,-0.5), (3,-0.5), (1, -1), (1,1), (3,0.5), (2,-2), (2,1)]) ppi.create_pipes_from_parameters(net2,[0,1,2,1,3,1,2,2,3,2],[1,2,3,3,4,5,6,7,8,9],10*[0.1],10*[0.05],10*[0.03],sections=10*[5],alpha_w_per_m2k=10*[0.5], text_k=10*[283]) # Producers ppi.create_ext_grid(net2,0,4.5,310,type='p') # ppi.create_ext_grid(net2,4,4.5,310,type='t',in_service=False) ppi.create_sink(net2,4,abs(list(net.res_ext_grid['mdot_kg_per_s'])[1]) ) ppi.create_sink(net2,9,abs(list(net.res_ext_grid['mdot_kg_per_s'])[2]) ) # ppi.create_sinks(net2,[4,9],[abs(mm) for mm in net.res_ext_grid['mdot_kg_per_s']] ) # Consumers ppi.create_sources(net2,[5,6,7,8],[0.05,0.07,0.02,0.05]) ppi.create_ext_grid(net2,5,4.5,net.res_junction.loc[5,'t_k']-30,type='t') ppi.create_ext_grid(net2,6,4.5,net.res_junction.loc[6,'t_k']-30,type='t') ppi.create_ext_grid(net2,7,4.5,net.res_junction.loc[7,'t_k']-30,type='t') ppi.create_ext_grid(net2,8,4.5,net.res_junction.loc[8,'t_k']-30,type='t') # ppi.create_ext_grid(net2,4,4.5,net.res_junction.loc[4,'t_k']-30,type='t') ppi.pipeflow(net2,mode='all') print(net.res_junction) print(net2.res_junction) pipe_results=Pipe.get_internal_results(net, [0]) Pipe.plot_pipe(net, 0, pipe_results) ppplot.simple_plot(net2, plot_sinks=True, plot_sources=True) ```
ggrrll commented 2 years ago

(Hi, just fyi, I do not work anymore on this topic -- good luck anyways)

dlohmeier commented 1 year ago

Hello @cvTHM , I would like to come back to this issue, as we haven't been able to work on this for a long time. We have started addressing many of the aspects that you mentioned. We defined flow controllers that can work as consumers now in combination with heat exchangers. They produce the pressure drop needed between flow and return system. With respect to circulation pumps, we are currently working on further improvements to ensure that different circulation pumps can work together at least when implementing a control strategy. In such cases, we need to ensure that mass flow balances work out as expected. As far as I understood, you are not working on this topic anymore, right? If you do continue with this, your input would be highly welcome. Kind regards!

Toecan commented 1 year ago

Hello @dlohmeier , I've been working with pandapipes for a few years now and I'm happy about the extensions. The modeling of the consumers works quite well, although I have to estimate the flow temperature for the heat exchanger if a certain heat demand is given.

Besides, it would be great to be able to connect several heat generators to the network in the future. My understanding is that it should be possible to install a circulation pump pressure, which provides the grid pressure and several "circulation pump mass" in combination with heat exchangers, which pump a certain mass flow at a higher temperature from the return into the supply pipe. Best regards!

dlohmeier commented 1 year ago

Hi @Toecan , thanks for your feedback. The task of enabling to connect several pumps with their respective control strategies is indeed very important as one of the next steps to improve pandapipes for district heating simulations. I believe that the approach you described should be possible, have you tried it? If you could provide some insights to what you try to model and how the control scheme works in your case, that could be very helpful in the further development. Best regards

Toecan commented 1 year ago

Hello @dlohmeier ,

here you can see my test grid:

image

`

Import Section

import pandapipes as pp import pandapipes.plotting as plot import pandas as pd

kelvin = 273.15 cp = 4.2 #kJ/kg/K

Grid Components

net = pp.create_empty_network(fluid ="water")

j0 = pp.create_junction(net, pn_bar=5, tfluid_k=90+kelvin, geodata=(0,5), name="junction 0") j1 = pp.create_junction(net, pn_bar=5, tfluid_k=90+kelvin, geodata=(2,5), name="junction 1") j2 = pp.create_junction(net, pn_bar=5, tfluid_k=90+kelvin, geodata=(4,5), name="junction 2") j3 = pp.create_junction(net, pn_bar=5, tfluid_k=60+kelvin, geodata=(4,1), name="junction 3") j4 = pp.create_junction(net, pn_bar=5, tfluid_k=60+kelvin, geodata=(2,1), name="junction 4") j5 = pp.create_junction(net, pn_bar=5, tfluid_k=60+kelvin, geodata=(0,1), name="junction 5") j6 = pp.create_junction(net, pn_bar=5, tfluid_k=90+kelvin, geodata=(4,3), name="junction 6") j10 = pp.create_junction(net, pn_bar=5, tfluid_k=90+kelvin, geodata=(2,3), name="junction 10")

pp.create_pipe_from_parameters(net, from_junction=j0, to_junction=j1, length_km=1, diameter_m=0.2, k_mm=.1, alpha_w_per_m2k=0, sections = 5, text_k=283) pp.create_pipe_from_parameters(net, from_junction=j1, to_junction=j2, length_km=1, diameter_m=0.2, k_mm=.1, alpha_w_per_m2k=0, sections = 5, text_k=283) pp.create_pipe_from_parameters(net, from_junction=j3, to_junction=j4, length_km=1, diameter_m=0.2, k_mm=.1, alpha_w_per_m2k=0, sections = 5, text_k=283) pp.create_pipe_from_parameters(net, from_junction=j4, to_junction=j5, length_km=1, diameter_m=0.2, k_mm=.1, alpha_w_per_m2k=0, sections = 5, text_k=283)

Generation

pp.create_circ_pump_const_pressure(net, return_junction=j5, flow_junction=j0, p_flow_bar=9, plift_bar=8.5, t_flow_k=90+kelvin, type='pt')

heat = 500 #kW flow_temp = 130 # Celsius return_temp = 70 # Celsius m_flow = heat/cp/(flow_temp-return_temp) # kg/s pp.create_circ_pump_const_mass_flow(net, return_junction=j4, flow_junction=j10, mdot_flow_kg_per_s= m_flow, t_flow_k=90+kelvin, type='t', p_flow_bar=7) pp.create_heat_exchanger(net, from_junction=j10, to_junction=j1, diameter_m=0.2, qext_w=-heat*1000)

Load

heat = 3000 #kW flow_temp = 130 # Celsius return_temp = 70 # Celsius m_flow = heat/cp/(flow_temp-return_temp) # kg/s pp.create_flow_control(net, from_junction=j2, to_junction=j6, controlled_mdot_kg_per_s=m_flow, diameter_m=0.2) pp.create_heat_exchanger(net, from_junction=j6, to_junction=j3, diameter_m=0.2, qext_w=heat*1000)

Simulation

pp.pipeflow(net, mode='all') plot.simple_plot(net) `

The additional heat generation should feed 500 kW into the grid and the Slack the missing 2500 kW. Unfortunately the calculation does not converge. Without the second heat generation the calculation converges. Is there another way to model this small grid?

Best regards

dlohmeier commented 1 year ago

Hi @Toecan, thanks a lot for the example. Does the hydraulic simulation fail, or is it just the temperature calculation? That should be easy to identify from the traceback. I can imagine that the definition of fixed temperature and pressure nodes in a mesh that are closely linked with the typically strict flow definition for the heating grid is a problem in the pump setup. We will have to consider that in the future as well, so that it will be possible to define two pumps as primary / secondary wrt. their control logic. Would it be an option for you to use a flow controller instead of the circ_pump_mass? It is not really obvious, but the flow controller is just a unit that ensures a certain flow, which means that it can also increase the pressure at the outlet node. You would not have any control over the outlet pressure or temperature, but this way the solver would probably be able to converge. Best regards

cvTHM commented 1 year ago

Hello together,

I can provide a simple example of a heating network with multiple heat generators which makes use of flow controllers. I came up with the topology to ensure that there is at least one mesh in the grid. One heat generator (left side, between feed line junction 0 and reflux line junction 17) is used as a peak load producer which fixes pressure and temperature (circ_pump_const_pressure), the other one (between feed line junction 15 and reflux line juntion 32) is simply modelled as a combination of a heat exchanger and a flow controller. As stated by @dlohmeier before the pressure and the mixing temperature at the second producer's outlet junction into the feed line are results of the conditions in the entire rest of the network. The second heat generator's heating power is fixed - thus, I use this setup to define the first heat generator as a peak load producer which provides all thermal power that cannot be fed in by the second heat generator. The second heat generator is interpreted as a base load producer whose thermal ouput power is either known a priori (e. g. in maximum load case when simulating the network) or can be adjusted according to a time series. All heat consumers are modelled as a combination of flow controller with a set mass flow according to a temperature difference and their heat demand and a heat exchanger which eventually draws the thermal power of the heat demand from the network. The network can be seen in the ecnlosed figure fetauring a qualitative color scheme for the mass flows in feed line and reflux line. The simulation converges both in hydraulic and thermal mode and gives plausible results. I have tried several combinations of pump models to simulate a heating network with multiple heat generators, but this setup was the most successful I came up with so far. What is more, the optimized value for pressure lift of the peak load producer (funcitoning as pressure-driven master in the network) could be found by an iterative start of the simulation and adapting the pressure lift so that a minimum pressure difference at all flow controllers representing the heat consumers is ensured (usually 0,5-0,7 bar at maximum load). This would also be helpful when extending the simulation to time-series simulation on an hourly basis (e. g. within an external loop).

Maybe this is a bit of a help for some users.

Best regards

heating_network_example_ppi_20230728
import pandapipes as ppi
import pandapipes.plotting as ppplot
import numpy as np

# %% Create coordinates for network

xVL = np.array([
    0,
    1, 1, 1, 1,
    2,2,2,2,
    3,3,3,3,
    4,4,
    5,
    6
])

xInter = np.array([
    1,1,
    2,2,
    3,3,
    4,6
]) + 0.25

yInter = np.array([
    1,-2,
    1,-2,
    1,-2,
    -2,
    -1
]) + 0.25

yVL = np.array([
    0,
    1,0,-1,-2,
    1,0,-1,-2,
    1,0,-1,-2,
    -1,-2,
    -1,
    -1
])

xRL = xVL + 0.5
yRL = yVL + 0.5

nr_junctions = len(xVL) + len(xInter) + len(xRL)

from_js = np.array([
        0,
        1, 2,3,
        5,6,7,
        9,10,11,
        13,
        2,3,
        6,7,
        11,
        13,
        15,
    17,
    18,19,20,
    22,23,24,
    26,27,28,
    30,
    19,20,
    23,24,
    28,
    30,
    32
])

to_js = np.array([
        2,
        2,3,4,
        6,7,8,
        10,11,12,
        14,
        6,7,
        10,11,
        13,15,
        16,
    19,
    19,20,21,
    23,24,25,
    27,28,29,
    31,
    23,24,
    27,28,
    30,
    32,
    33
])

net3 = ppi.create_empty_network(
    name = 'net',
    fluid = 'water'
)

ppi.create_junctions(
    net3,
    nr_junctions = nr_junctions,
    pn_bar = 5,
    tfluid_k = 330,
    geodata = [(x,y) for x,y in np.vstack((np.concatenate((xVL, xRL, xInter)), np.concatenate((yVL, yRL, yInter)))).T],
    ID = np.arange(nr_junctions)
)

ppi.create_pipes_from_parameters(
    net3,
    from_junctions = from_js,
    to_junctions = to_js,
    length_km = 0.2,
    diameter_m = 0.05,
    alpha_w_per_m2k = 0.8
)

net3.pipe['ID'] = np.arange(len(net3.pipe))

ppi.create_circ_pump_const_pressure(
    net3,
    flow_junction = 0,
    return_junction = 17,
    p_flow_bar = 5,
    plift_bar = 2,
    t_flow_k = 330
)

ppi.create_flow_controls(
    net3,
    from_junctions = [1,4,5,8,9,12,14,16],
    to_junctions = [34,35,36,37,38,39,40,41],
    diameter_m = 0.03,
    controlled_mdot_kg_per_s = 0.04
)

# Erzeuger 2 als flow_control + heat_exchanger modelliert

ppi.create_junction(
    net3,
    pn_bar = 5,
    tfluid_k = 330,
    geodata = (5.125, -1+0.125)
)

ppi.create_flow_control(
    net3,
    from_junction = 32,
    to_junction = len(net3.junction)-1,
    diameter_m = 0.03,
    controlled_mdot_kg_per_s = 10000/4190/30 # 10kW th. power
)

ppi.create_heat_exchanger(
    net3,
    from_junction = len(net3.junction)-1,
    to_junction = 15,
    qext_w = -10000,
    diameter_m = 0.3
)

for f, t in [(34,18), (35,21), (36,22), (37,25), (38,26), (39,29), (40,31), (41,33) ]:
    ppi.create_heat_exchanger(
        net3,
        from_junction = f,
        to_junction = t,
        qext_w = 4000,
        diameter_m = 0.03,
        loss_coefficient = 0
    )

ppi.pipeflow(net3, mode='hydraulics')
ppplot.simple_plot(net3)

ppi.pipeflow(net3, mode = 'all')
Toecan commented 1 year ago

Thanks for your answers!

Replacing the "circ_pump_const_massflow" with a flow controller solved my problem.

Modelling loads as well as generations with heat exchangers plus flow controller works fine.

JanStock1 commented 6 months ago

Hello everyone, I would like to comment on this issue as I came up with the same problem of several producers in a district heating network. The solution proposed by @cvTHM worked well for me (additional producers modelled by _heatexchanger and _flowcontroller). However, since the heat supply and the mass flow are prescribed in this modelling approach (interpreted as base load producer), the supply temperature at these producers is not fixed but depends on the return temperature (temperature difference is fixed). Therefore, the supply temperature at such a producer varies depending on the return temperature, i.e. on the load situation in the district heating network. Thus, the supply temperature at the producers modelled with _heatexchanger and _flowcontroller is quite low at some load times.

I have tested an alternative approach that ensures a constant supply temperature at the additional producers, but the actual heat supply at these producers differs slightly from the calculated value. The first producer in the district heating network is still modelled as a _circ_pumppressure component. The _circ_pumpmass component, which prescribes a certain mass flow, can be used (since pandapipes 0.9.0) to specify the temperature at the supply node of additional producers. A heat flow can be calculated using the specified mass flow and the known design temperature difference between supply and return (later more on that). The pressure at the supply node of the _circ_pumpmass component must be specified, otherwise there may be no mass flow at the supply node (depending on the pressure conditions in the network). However, if a pressure value is set at the supply nodes of the additional producers (_circ_pumpmass) and at the first producer (_circ_pumppressure), the resulting mass flow in the network may not be as desired, as the mass flow follows the fixed pressure conditions in the network. Therefore, a _flowcontroller is required downstream of the supply node of a _circ_pumpmass component, which regulates the pressure conditions and ensures the desired mass flow, which is also set to the _circ_pumpmass component but is not yet reached due to the first producer, i.e. the fixed pressure at the _circ_pumppressure component.

The main advantage of this proposed modelling approach (_circ_pumpmass and _flowcontroller) is that the supply temperature at the additional producers can be set. However, as the return temperature at the producers is not known in advance, but the supply temperature and the mass flow are specified, the actually supplied heat at these producers deviates slightly from the calculated value. By varying the set mass flows at the producers depending on the supply condition and the estimated heat losses in the network, the actually supplied heat can be approximated to the aimed value.

sad15919 commented 5 months ago

Hello everyone, I have tried doing the way as @JanStock1 provided, which works to some extent. What I saw in the network is, the other plants (using circ_pump_mass + flow control), dont have control over their own supply pressure. For example if my circ_pump_pressure has a supply pressure of 4 bars and my other producer which is (circ_pump_mass + flow_control) has supply pressure of 6 bars, then after the simulation the supply pressure of the latter producer becomes 4 bars or almost near to 4 bars, whatever the flowrate i have defined in this producer. Can someone give me a solution for this? Because in this way, the other producers with circ_pump_mass and flow_control, they dont have their own supply pressure, then what is the point of using them as other producer in the network.

hoping for an answer !! :)

JonasPfeiffer123 commented 5 months ago

Hello everyone, I've been also thinking about implementing multi-producer networks for a while now and the required logic for that. @JanStock1 's approach sounds like something I was also aiming at. A controller would be required to set the mass flow for a given heat supply according to the current state of the network (aka the return temperatures). The only "problem" I currently have with implementing more than one producer is, that I have to create some sort of controller which controlls the allocation of the needed heat supply based on the required heat of the heat exchangers in the network to the different heat supplies.

@sad15919 why would you want a higher pressure at the second producer? The "first" producer in the network, the circ_pump_pressure-component, is literally there for setting the pressure in the system in my understanding. I would want to add multiple producers to simulate different producer locations and different producer behaviours, for example I could set the produced heat with results from a simulation of a solar thermal producer.

sad15919 commented 5 months ago

Hello @JonasPfeiffer123 , thank you very much for replying. I am very new in DH networks so i dont have much idea. Can you please eloborate you answer 'I would want to add multiple producers to simulate different producer locations and different producer behaviours, for example I could set the produced heat with results from a simulation of a solar thermal producer.' ? It would be very helpful for my understanding.

In my case, I am doing steady state simulation and I have 3 producers who has supply pressure of about 7, 4, and 3 bars respectively with also a specific p_lift. I have set the circ_pump_pressure component in the producer who has the supply pressure pressure of 4 bars. Am I wrong doing so? Should i have put that in the producer with max supply pressure? Because if the pressure pump is setting the pressure then how would i use the supply pressure of the other producers? And since we are circ_pump_mass, we are defining the input mass flow rate by using the flow control as well. Is it right because i read in real life, you choose the pump based on pump curve which we dont have here as a circulation pump.