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

Water Tower Component #107

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
1 change: 1 addition & 0 deletions pandapipes/component_models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
from pandapipes.component_models.pump_component import *
from pandapipes.component_models.circulation_pump_mass_component import *
from pandapipes.component_models.circulation_pump_pressure_component import *
from pandapipes.component_models.water_tower_component import *
140 changes: 140 additions & 0 deletions pandapipes/component_models/water_tower_component.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Copyright (c) 2020 by Fraunhofer Institute for Energy Economics
# and Energy System Technology (IEE), Kassel. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

import numpy as np
from numpy import dtype

from pandapipes.component_models.abstract_models import NodeElementComponent
from pandapipes.constants import GRAVITATION_CONSTANT, P_CONVERSION
from pandapipes.idx_branch import FROM_NODE, TO_NODE, LOAD_VEC_NODES
from pandapipes.idx_node import PINIT, LOAD, NODE_TYPE, P, EXT_GRID_OCCURENCE
from pandapipes.internals_toolbox import _sum_by_group
from pandapipes.pipeflow_setup import get_lookup

try:
import pplog as logging
except ImportError:
import logging

logger = logging.getLogger(__name__)


class WaterTower(NodeElementComponent):
"""
The water tower is similiar to an external grid. Based on the water column height the relative pressure
is determined. This pressure is fixed, i.e. the capacity of the water tower is infinitem and its level is constant.
"""

@classmethod
def table_name(cls):
return "water_tower"

@classmethod
def sign(cls):
return 1.

@classmethod
def create_pit_node_entries(cls, net, node_pit, node_name):
"""
Function which creates pit node entries.

:param net: The pandapipes network
:type net: pandapipesNet
:param node_pit:
:type node_pit:
:return: No Output.
"""
water_towers = net[cls.table_name()]
density = net.fluid.get_density(water_towers.t_k.values)
junction_idx_lookups = get_lookup(net, "node", "index")[node_name]
junction = cls.get_connected_junction(net)
press = density * water_towers.height_m.values * \
GRAVITATION_CONSTANT / P_CONVERSION * water_towers.in_service.values
juncts_p, press_sum, number = _sum_by_group(junction.values, press,
np.ones_like(press, dtype=np.int32))
Copy link
Collaborator

Choose a reason for hiding this comment

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

I am not sure whether this is physically possible. The effect of communicating vessels (dt: "kommunizierende Röhren") is not considered, is it?
I'd suggest limiting the number of water towers to 1 per net or raise a warning when towers with different water height are connected.

index_p = junction_idx_lookups[juncts_p]
node_pit[index_p, PINIT] = press_sum / number
node_pit[index_p, NODE_TYPE] = P
node_pit[index_p, EXT_GRID_OCCURENCE] += number

net["_lookups"]["water_tower"] = \
np.array(list(set(np.concatenate([net["_lookups"]["water_tower"], index_p])))) if \
"water_tower" in net['_lookups'] else index_p
return water_towers, press

@classmethod
def extract_results(cls, net, options, node_name):
"""
Function that extracts certain results.

:param net: The pandapipes network
:type net: pandapipesNet
:param options:
:type options:
:return: No Output.
"""
water_towers = net[cls.table_name()]

if len(water_towers) == 0:
return

res_table = super().extract_results(net, options, node_name)

branch_pit = net['_pit']['branch']
node_pit = net["_pit"]["node"]

junction = cls.get_connected_junction(net)
index_juncts = junction.values
junct_uni = np.array(list(set(index_juncts)))
index_nodes = get_lookup(net, "node", "index")[node_name][junct_uni]
eg_from_branches = np.isin(branch_pit[:, FROM_NODE], index_nodes)
eg_to_branches = np.isin(branch_pit[:, TO_NODE], index_nodes)
from_nodes = branch_pit[eg_from_branches, FROM_NODE]
to_nodes = branch_pit[eg_to_branches, TO_NODE]
mass_flow_from = branch_pit[eg_from_branches, LOAD_VEC_NODES]
mass_flow_to = branch_pit[eg_to_branches, LOAD_VEC_NODES]
press = node_pit[index_nodes, PINIT]
loads = node_pit[index_nodes, LOAD]
counts = node_pit[index_nodes, EXT_GRID_OCCURENCE]
all_index_nodes = np.concatenate([from_nodes, to_nodes, index_nodes])
all_mass_flows = np.concatenate([-mass_flow_from, mass_flow_to, -loads])
nodes, sum_mass_flows = _sum_by_group(all_index_nodes, all_mass_flows)

# positive results mean that the ext_grid feeds in, negative means that the ext grid
# extracts (like a load)
res_table["mdot_kg_per_s"].values[:] = np.repeat(cls.sign() * sum_mass_flows / counts,
counts.astype(int))
res_table["p_bar"].values[:] = press
return res_table, water_towers, index_nodes, node_pit, branch_pit

@classmethod
def get_connected_junction(cls, net):
junction = net[cls.table_name()].junction
return junction

@classmethod
def get_component_input(cls):
"""

:return:
"""

return [("name", dtype(object)),
("junction", "u4"),
("height_m", "f8"),
("t_k", "f8"),
("in_service", "bool"),
('type', dtype(object))]

@classmethod
def get_result_table(cls, net):
"""

:param net: The pandapipes network
:type net: pandapipesNet
:return: (columns, all_float) - the column names and whether they are all float type. Only
if False, returns columns as tuples also specifying the dtypes
:rtype: (list, bool)
"""
return ["mdot_kg_per_s", "p_bar"], True
61 changes: 56 additions & 5 deletions pandapipes/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@

import numpy as np
import pandas as pd
from pandapower.auxiliary import get_free_id, _preserve_dtypes

from pandapipes.component_models import Junction, Sink, Source, Pump, Pipe, ExtGrid, \
HeatExchanger, Valve, CirculationPumpPressure, CirculationPumpMass, WaterTower
from pandapipes.component_models.auxiliaries.component_toolbox import add_new_component
from pandapipes.pandapipes_net import pandapipesNet, get_default_pandapipes_structure
from pandapipes.properties import call_lib, add_fluid_to_net
from pandapower.auxiliary import get_free_id, _preserve_dtypes
from pandapipes.properties.fluids import Fluid
from pandapipes.std_types.std_type import PumpStdType, add_basic_std_types, add_pump_std_type, \
load_std_type
from pandapipes.std_types.std_type_toolbox import regression_function
from pandapipes.component_models import Junction, Sink, Source, Pump, Pipe, ExtGrid, \
HeatExchanger, Valve, CirculationPumpPressure, CirculationPumpMass

try:
import pplog as logging
Expand Down Expand Up @@ -303,6 +304,56 @@ def create_ext_grid(net, junction, p_bar, t_k, name=None, in_service=True, index
return index


def create_water_tower(net, junction, height_m, t_k=293.15, name=None, in_service=True, index=None,
type='water_tower', **kwargs):
"""

:param net: The net that the water tower should be connected to
:type net: pandapipesNet
:param junction: The junction to which the water tower is connected to
:type junction: int
:param h_m: The height of the water column relative to its surrounding
:type h_m: float
:param t_k: The fixed temperature of the water in the water tower
:type t_k: float, default 293.15
:param name: A name tag for this water tower
:type name: str, default None
:param in_service: True for in service, False for out of service
:type in_service: bool, default True
:param index: Force a specified ID if it is available. If None, the index is set one higher than the \
highest already existing index is selected.
:return: index - The unique ID of the created element
:rtype: int

:Example: create_water_tower(net, junction1, h_m=3, name="Grid reservoir")

"""
add_new_component(net, WaterTower)

if junction not in net["junction"].index.values:
raise UserWarning("Cannot attach to junction %s, junction does not exist" % junction)

if index is not None and index in net["water_tower"].index:
raise UserWarning("An water tower with index %s already exists" % index)

if index is None:
index = get_free_id(net["water_tower"])

# store dtypes
dtypes = net.water_tower.dtypes

cols = ["name", "junction", "height_m", "t_k", "in_service", "type"]
vals = [name, junction, height_m, t_k, bool(in_service), type]
all_values = {col: val for col, val in zip(cols, vals)}
all_values.update(**kwargs)
for col, val in all_values.items():
net.water_tower.at[index, col] = val

# and preserve dtypes
_preserve_dtypes(net.water_tower, dtypes)
return index


def create_heat_exchanger(net, from_junction, to_junction, diameter_m, qext_w, loss_coefficient=0,
name=None, index=None, in_service=True, type="heat_exchanger", **kwargs):
"""
Expand Down Expand Up @@ -823,8 +874,8 @@ def create_circ_pump_const_pressure(net, from_junction, to_junction, p_bar, plif
for b in [from_junction, to_junction]:
if b not in net["junction"].index.values:
raise UserWarning(
"CirculationPumpPressure %s tries to attach to non-existing junction %s"
% (name, b))
"CirculationPumpPressure %s tries to attach to non-existing junction %s"
% (name, b))

if index is None:
index = get_free_id(net["circ_pump_pressure"])
Expand Down
45 changes: 45 additions & 0 deletions pandapipes/test/api/test_components/test_water_tower.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright (c) 2020 by Fraunhofer Institute for Energy Economics
# and Energy System Technology (IEE), Kassel. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

import os

import numpy as np
import pandas as pd

import pandapipes as pp
from pandapipes.test.pipeflow_internals import internals_data_path


def test_water_tower():
"""

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

junction1 = pp.create_junction(net, pn_bar=1.0, tfluid_k=293.15, name="Connection to Water Tower")
junction2 = pp.create_junction(net, pn_bar=1.0, tfluid_k=293.15, name="Junction 2")

pp.create_water_tower(net, junction1, height_m=30, name="Water Tower")

pp.create_pipe_from_parameters(net, from_junction=junction1, to_junction=junction2, length_km=10, diameter_m=0.075,
name="Pipe 1")

pp.create_sink(net, junction=junction2, mdot_kg_per_s=0.545, name="Sink 1")

pp.pipeflow(net, stop_condition="tol", iter=3, friction_model="nikuradse",
mode="hydraulics", transient=False, nonlinear_method="automatic",
tol_p=1e-4,
tol_v=1e-4)

data = pd.read_csv(os.path.join(internals_data_path, "test_water_tower.csv"), sep=';')

res_junction = net.res_junction.p_bar.values
res_pipe = net.res_pipe.v_mean_m_per_s.values

p_diff = np.abs(1 - res_junction / data['p'].dropna().values)
v_diff = np.abs(1 - res_pipe / data['v'].dropna().values)

assert np.all(p_diff < 0.01)
assert np.all(v_diff < 0.01)
3 changes: 3 additions & 0 deletions pandapipes/test/pipeflow_internals/data/test_water_tower.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
v;p
0.123588;2.937630
;2.442159