Components
Components are objects representing different elements of the network stored as edges in the network graph. A differentiation is made in PyDHN between branch and edge components (see here) and between ideal and real components. These categorizations are used to speed up the hydraulic convergence, currently based on a modified loop method. The difference between ideal and real components is that the former enforce a setpoint, such as a certain mass flow rate, while the latter define a relationship between pressure losses and mass flow.
Component
The class Component
provides a blueprint for all component classes and defines the main methods that are used during simulations. Each component has its own attributes, stored as a dictionary within the property _attrs
. By default, in addition to all the attributes that are enforced as mandatory inputs, kwargs
are also stored there:
>>> from pydhn.components import Component
>>> comp = Component(test_attr=5)
>>> print(comp._attrs['test_attr'])
5
Attributes however are not meant to be accessed by directly calling _attrs
, but with the built-it __getitem__()
method:
>>> from pydhn.components import Component
>>> comp = Component(test_attr=5)
>>> print(comp['test_attr'])
5
and can be changed via the set()
method:
>>> from pydhn.components import Component
>>> comp = Component(test_attr=5)
>>> comp.set('test_attr', 10)
>>> print(comp['test_attr'])
10
In addition, the properties _type
, with the unique name of the component type, _class
(either "branch_component"
or "leaf_component"
) and _is_ideal
(either True
or False
) should be specified when creating a new component class:
>>> from pydhn.components import Component
>>> # Create custom class
>>> class MyComp(Component):
... def __init__(self, **kwargs):
... super(MyComp, self).__init__()
... # Component class and type
... self._class = "branch_component"
... self._type = "my_custom_component"
... self._is_ideal = True
... # Add new inputs
... self._attrs.update(kwargs)
...
>>> # Instantiate a MyComp object
>>> my_comp = MyComp()
>>> # Print component_type
>>> print(my_comp['component_type'])
my_custom_component
>>> # Print component_class
>>> print(my_comp['component_class'])
branch_component
>>> # Print is_ideal
>>> print(my_comp['is_ideal'])
True
More complex logic can also be implemented by modifying the method _run_control_logic()
, which defines return rules for one or more attributes:
>>> from pydhn.components import Component
>>>
>>> # Create custom class
>>> class MyComp(Component):
... def __init__(self, test_value, **kwargs):
... super(MyComp, self).__init__()
... # Component class and type
... self._class = "branch_component"
... self._type = "my_custom_component"
... self._is_ideal = True
... # Add new inputs
... input_dict = {
... "test_value": test_value
... }
... self._attrs.update(input_dict)
... self._attrs.update(kwargs)
...
... # Implement the _run_control_logic method
... def _run_control_logic(self, key):
... if key == "test_value":
... return self._attrs["test_value"]**2
... return None
...
>>> # Instantiate a MyComp object
>>> my_comp = MyComp(test_value=5)
>>> # Print test_value
>>> print(my_comp._run_control_logic('test_value'))
25
>>> # Check that the original value was not modified
>>> print(my_comp._attrs['test_value'])
5
Finally, for each component two private methods defining the functioning during simulations need to be implemented: _compute_delta_p()
and _compute_temperatures()
.
Pipe
Component type |
Component class |
Is ideal |
---|---|---|
base_pipe |
branch_component |
False |
Pipe
is the base implementation for steady-state pipes. It has the following main attributes:
Input |
Symbol |
Documentation |
Default |
Unit |
---|---|---|---|---|
diameter |
\(D_p\) |
0.0204 |
\(m\) |
|
length |
\(L_p\) |
1.0 |
\(m\) |
|
roughness |
\(\epsilon\) |
0.045 |
\(mm\) |
|
depth |
\(\delta\) |
1.0 |
\(m\) |
|
k_insulation |
\(k_{ins}\) |
0.026 |
\(W/(m·K)\) |
|
insulation_thickness |
\(t_{ins}\) |
0.034 |
\(m\) |
|
k_internal_pipe |
\(k_{ip}\) |
45.0 |
\(W/(m·K)\) |
|
internal_pipe_thickness |
\(t_{ip}\) |
0.023 |
\(m\) |
|
k_casing |
\(k_{cas}\) |
0.4 |
\(W/(m·K)\) |
|
casing_thickness |
\(t_{cas}\) |
0.003 |
\(m\) |
|
discretization |
\(-\) |
10 |
\(m\) |
Hydraulics
The base Pipe implements the following formula to compute pressure losses:
Plus eventually the hydrostatic pressure:
The friction factor \(f_D\) is computed as a function of the Reynolds number \(Re\). For laminar flow (\(Re \leq 2300\)) the friction factor is computed as:
For turbulent flow (\(Re \geq 4000\)) Haaland equation is used:
Finally, for the transition regimen (\(2320 < Re < 4000\)), the relationship given in [HaZa21] is used.
Thermal
The base Pipe implements a simple steady-state model. The outlet temperature, limited by the soil temperature \(\theta_{s}\), is computed as:
The thermal losses \(Q\) (Wh) are computed as:
where \(\Delta p _{fr}\) is the frictional component of the pressure loss and \(R_{ip}\), \(R_{ins}\) and \(R_{cas}\) are the thermal resistances of the internal pipe, insulation, casing respectively:
And:
Finally, \(R_{s}\) is the thermal resistance of the soil computed as:
For the thermal simulation, base Pipes can be discretized in segments of a given length for increasing the accuracy at the cost of computational speed.
Lagrangian Pipe
Component type |
Component class |
Is ideal |
---|---|---|
lagrangian_pipe |
branch_component |
False |
LagrangianPipe
is the implementation of a dynamic pipe based on the Lagrangian approach described in [DeAl19]. It has the following main attributes:
Input |
Symbol |
Documentation |
Default |
Unit |
---|---|---|---|---|
diameter |
\(D_p\) |
0.0204 |
\(m\) |
|
length |
\(L_p\) |
1.0 |
\(m\) |
|
roughness |
\(\epsilon\) |
0.045 |
\(mm\) |
|
depth |
\(\delta\) |
1.0 |
\(m\) |
|
k_insulation |
\(k_{ins}\) |
0.026 |
\(W/(m·K)\) |
|
insulation_thickness |
\(t_{ins}\) |
0.034 |
\(m\) |
|
k_internal_pipe |
\(k_{ip}\) |
45.0 |
\(W/(m·K)\) |
|
internal_pipe_thickness |
\(t_{ip}\) |
0.023 |
\(m\) |
|
k_casing |
\(k_{cas}\) |
0.4 |
\(W/(m·K)\) |
|
casing_thickness |
\(t_{cas}\) |
0.003 |
\(m\) |
|
rho_wall |
\(\rho_p\) |
940.0 |
\(kg/m^3\) |
|
cp_wall |
\(cp_p\) |
2000.0 |
\(J/(kg·K)\) |
|
h_ext |
\(h_{ext}\) |
0.0 |
\(W/(m^2·K)\) |
|
stepsize |
\(\Delta s\) |
5.0 |
\(s\) |
Each pipe is initialized with a single volume of fluid at a temperature of 50.0
°C. At each time-step, a new volume is inserted in the pipe, pushing all existing volumes.
Hydraulics
The Lagrangian Pipe implements the same hydraulics of the Pipe
class described here
Thermal
The equations used for the thermal simulation in the LagrangianPipe
class are described in detail in [DeAl19]. The outlet temperature is computed as the weighted average between the volumes exiting the pipe. If the mass flow is zero, the outlet temperature is the temperature at the outlet section of the pipe. Currently, the function used to compute the temperatures is not vectorized, so the simulation loop will call the method _compute_temperatures()
for each element of this tipe separately using a for loop.
Note
The stepsize is defined for each pipe separately and there is no mechanism in place to check that all dynamic components share the same stepsize.
The component keeps an internal memory of the moving volumes of fluid and their temperatures:
>>> from pydhn.components import LagrangianPipe
>>> from pydhn import ConstantWater
>>> from pydhn import Soil
>>> comp = LagrangianPipe(length=100)
>>> fluid = ConstantWater()
>>> soil = Soil()
>>> # Print list of internal volumes
>>> print(comp._volumes)
[0.03268513]
>>> # Print list of internal temperatures
>>> print(comp._temperatures)
[50.]
>>> # Set a mass flow of 1 kg/s
>>> comp.set("mass_flow", 1.)
>>> # Simulate one time-step with inlet temperature of 45°C
>>> _ = comp._compute_temperatures(fluid=fluid, soil=soil, t_in=45)
>>> # Print list of internal volumes
>>> print(comp._volumes)
[0.00505051 0.02763462]
>>> # Print list of internal temperatures
>>> print(comp._temperatures)
[45. 49.9998273]
Producer
Component type |
Component class |
Is ideal |
---|---|---|
base_producer |
leaf_component |
True |
Producer
models a simple heat source where hydraulics and thermal setpoints are enforced. It has the following main attributes:
Input |
Symbol |
Documentation |
Default |
Unit |
---|---|---|---|---|
static_pressure |
\(p_s\) |
nan |
\(Pa\) |
|
setpoint_type_hx |
\(-\) |
t_out |
\(-\) |
|
setpoint_value_hx |
\(\theta_{out}\) or \(\Delta T\) or \(Q\) |
80.0 |
\(°C\) or \(K\) or \(Wh\) |
|
setpoint_type_hx_rev |
\(-\) |
delta_t |
\(-\) |
|
setpoint_value_hx_rev |
\(\theta_{out}\) or \(\Delta T\) or \(Q\) |
0.0 |
\(°C\) or \(K\) or \(Wh\) |
|
power_max_hx |
\(Q_{max}\) |
nan |
\(Wh\) |
|
t_out_min_hx |
\(\theta_{min}\) |
nan |
\(°C\) |
|
setpoint_type_hyd |
\(-\) |
pressure |
\(-\) |
|
setpoint_value_hyd |
\(\Delta p_{set}\) or \(\dot m_{set}\) |
-1000000.0 |
\(Pa\) or \(kg/s\) |
Commonly, one producer is used as the “main” component, enforcing a differential pressure to the network (using a 'pressure'
setpoint), while the other producers, if present, have an imposed mass flow.
Hydraulics
The base producer has two modes of operations: either the mass flow \(\dot m\) (kg/s) or pressure difference \(\Delta p\) (Pa) can be imposed. This is done by setting the relevant setpoint_type_hyd
, respectively 'mass_flow'
or 'pressure'
.
Note
A negative pressure difference indicates a pressure lift along the positive direction of the edge.
>>> from pydhn.components import Producer
>>> from pydhn import ConstantWater
>>> comp = Producer(setpoint_type_hyd='pressure', setpoint_value_hyd=-50000)
>>> fluid = ConstantWater()
>>> delta_p, mdot = comp._compute_delta_p(fluid)
>>> print(delta_p)
-50000
Thermal
The base producer can enforce three different types of setpoints for the thermal simulation depending on the value given to setpoint_type_hx
the outlet temperature 't_out'
, the injected energy 'delta_q'
or the temperature difference 'delta_t'
. The value of the chosen setpoint is then given by setpoint_value_hx
. Regardless of the setpoint type, a limitation can be further imposed by limiting the outlet temperature - specifying a value for t_out_min_hx
- or the maximum power - specifying a value for power_max_hx
.
Warning
power_max_hx
comes from an old version of PyDHN where time-steps were assumed to be hourly. What it is actually limiting is the energy and not the power of the heat source, according to the formula:
\[\begin{equation}\label{dt_prod} \theta_{out} = min\left\{ \theta _{set}, \frac{ Q_{max}}{\dot m c_p} + \theta_{in}\right\} \end{equation}\]
This behaviour will change in a future version.
Note
While the base producer allows different types of setpoints, it is advisable to use at least one 't_out'
, as in most cases the solver might not converge in the absence of a fixed nodal temperature in the network.
setpoint_type_hx_rev
and setpoint_value_hx_rev
are used to specify what should happen in case of reverse flow (IE: negative mass flow). The values and usage are the same as the setpoint_type_hx
and setpoint_value_hx
.
>>> from pydhn.components import Producer
>>> from pydhn import ConstantWater
>>> from pydhn import Soil
>>> comp = Producer(setpoint_type_hx='t_out', setpoint_value_hx=70,
... setpoint_type_hx_rev='delta_t', setpoint_value_hx_rev=0.)
>>> fluid = ConstantWater()
>>> soil = Soil()
>>> # Set a mass flow of 10 kg/s
>>> comp.set("mass_flow", 10)
>>> # Simulate one time-step with inlet temperature of 45°C
>>> t_out = comp._compute_temperatures(fluid=fluid, soil=soil, t_in=45)[1]
>>> # Print the outlet temperature
>>> print(t_out)
70.0
>>> # Set a mass flow of -1 kg/s
>>> comp.set("mass_flow", -1)
>>> # Simulate one time-step with inlet temperature of 45°C
>>> t_out = comp._compute_temperatures(fluid=fluid, soil=soil, t_in=45)[1]
>>> # Print the outlet temperature
>>> print(t_out)
45.0
Consumer
Component type |
Component class |
Is ideal |
---|---|---|
base_consumer |
leaf_component |
True |
Consumer
models a simple consumer where hydraulics and thermal setpoints are enforced. It has the following main attributes:
Input |
Symbol |
Documentation |
Default |
Unit |
---|---|---|---|---|
setpoint_type_hx |
\(-\) |
t_out |
\(-\) |
|
setpoint_value_hx |
\(\theta_{out}\) or \(\Delta T\) or \(Q\) |
80.0 |
\(°C\) or \(K\) or \(Wh\) |
|
setpoint_type_hx_rev |
\(-\) |
delta_t |
\(-\) |
|
setpoint_value_hx_rev |
\(\theta_{out}\) or \(\Delta T\) or \(Q\) |
0.0 |
\(°C\) or \(K\) or \(Wh\) |
|
power_max_hx |
\(Q_{max}\) |
nan |
\(Wh\) |
|
t_out_min_hx |
\(\theta_{min}\) |
nan |
\(°C\) |
|
setpoint_type_hyd |
\(-\) |
pressure |
\(-\) |
|
setpoint_value_hyd |
\(\Delta p_{set}\) or \(\dot m_{set}\) |
-1000000.0 |
\(Pa\) or \(kg/s\) |
|
control_type |
\(-\) |
energy |
\(-\) |
|
design_delta_t |
\(\Delta T_{des}\) |
-30.0 |
\(K\) |
|
heat_demand |
\(Q_{dem}\) |
5000.0 |
\(Wh\) |
Hydraulics
The base consumer has two possible control types that can be selected by setting the argument control_type
as either 'mass_flow'
or 'energy'
. The former behaves exactly like the hydraulic implementation of Producer
class described here.
Warning
Note that selecting 'mass_flow'
as control_type
also allows to use the differential pressure as setpoint. Furthermore, it requires to se the additional attribute setpoint_type_hyd
, that can again be 'mass_flow'
. This is indeed confusing and will be changed in a future version.
The control type 'energy'
imposes again a mass flow setpoint, with the difference that the value is computed from the heat_demand
and design_delta_t
values as:
Note
Note that the signs of \(Q _{dem}\) and \(\Delta T_{des}\) have to generally be opposite: the demand is normally positive, as it is the energy requested, while the temperature difference is negative, as it represent the (expected) difference between the outlet and inlet temperature of the heat exchanger.
>>> from pydhn.components import Consumer
>>> from pydhn import ConstantWater
>>> comp = Consumer(control_type='energy', heat_demand=5000.,
... design_delta_t=-30.)
>>> # Check the hydraulic setpoint value
>>> comp['setpoint_value_hyd'] # it should be 5000/(30*4182) = 0.03985
0.03985333970986769
Thermal
The Consumer
class implements the same method for _compute_temperatures()
of the Producer
class described here. Commonly, the consumers are given setpoint_type_hx
as either delta_t
or delta_q
.
Note
Consumer
has the additional argument heat_demand
, but this is only used to compute the mass flow setpoint in case control_type
is set to 'energy'
: it has no influence on the thermal simulation.
Branch Valve
Component type |
Component class |
Is ideal |
---|---|---|
base_branch_valve |
branch_component |
False |
BranchValve
implements a valve controlled by the flow coefficient \(K_v\). The model assumes no temperature changes in the fluid across the valve. It has the following main attributes:
Input |
Symbol |
Documentation |
Default |
Unit |
---|---|---|---|---|
kv |
\(K_v\) |
0.0 |
\(m^3/h\) |
Hydraulics
The base valve implementation computes the pressure loss according to the following formula:
Thermal
The base valve does not introduce any heat loss, and the outlet temperature is equal to the inlet temperature.
Branch Pump
Component type |
Component class |
Is ideal |
---|---|---|
branch_pump |
branch_component |
True |
BranchPump
implements a branch pump imposing a certain \(\Delta p\). The model assumes no temperature changes in the fluid across the pump. It has the following main attributes:
Input |
Symbol |
Documentation |
Default |
Unit |
---|---|---|---|---|
setpoint_value_hyd |
\(\Delta p\) |
|
|
\(Pa\) |
Hydraulics
The base pump imposes the \(\Delta p\) value specified by the attribute setpoint_type_hyd
regardless of the mass flow.
Thermal
The branch pump does not introduce any heat loss, and the outlet temperature is equal to the inlet temperature.
Bypass Pipe
Component type |
Component class |
Is ideal |
---|---|---|
base_bypass_pipe |
leaf_component |
False |
BypassPipe
is the implementation of Pipe
as a leaf component. It has the following main attributes:
Input |
Symbol |
Documentation |
Default |
Unit |
---|---|---|---|---|
diameter |
\(D_p\) |
0.0372 |
\(m\) |
|
length |
\(L_p\) |
1.0 |
\(m\) |
|
roughness |
\(\epsilon\) |
0.045 |
\(mm\) |
|
depth |
\(\delta\) |
1.0 |
\(m\) |
|
k_insulation |
\(k_{ins}\) |
0.026 |
\(W/(m·K)\) |
|
insulation_thickness |
\(t_{ins}\) |
0.031 |
\(m\) |
|
k_internal_pipe |
\(k_{ip}\) |
45.0 |
\(W/(m·K)\) |
|
internal_pipe_thickness |
\(t_{ip}\) |
0.0052 |
\(m\) |
|
k_casing |
\(k_{cas}\) |
0.4 |
\(W/(m·K)\) |
|
casing_thickness |
\(t_{cas}\) |
0.0054 |
\(m\) |
|
discretization |
\(-\) |
10 |
\(m\) |
Hydraulics
See here.
Thermal
See here.