-
Notifications
You must be signed in to change notification settings - Fork 62
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
base: develop
Are you sure you want to change the base?
Changes from 6 commits
fb7261e
1a08646
c4467d1
d31f837
02b28d8
40601d3
abf213d
5e99548
7e0176b
5363a98
e044b08
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, HEIGHT | ||
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): | ||
""" | ||
|
||
""" | ||
|
||
@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) | ||
index_wt = junction_idx_lookups[junction] | ||
press = density * (water_towers.height_m.values - node_pit[index_wt, HEIGHT]) * \ | ||
GRAVITATION_CONSTANT / P_CONVERSION * water_towers.in_service.values | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @SimonRubenDrauz What exactly is the definition of height_m? My guess was the height of the water column in the tower. However, if the junction's height is subtracted, this could lead to negative height, couldn't it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It actually should be the height above sea level. But I agree. It really seems confusing. It is a convention in SINCAL. But I would change it in our case taking only the height of the water_column. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the adaption! However, thinking about it twice, maybe this leads to a problem regarding communicating vessels. (see my latest comment.) This could be the reason why Sincal uses absolute height. |
||
juncts_p, press_sum, number = _sum_by_group(junction.values, press, | ||
np.ones_like(press, dtype=np.int32)) | ||
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 |
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_reservoir(): | ||
""" | ||
|
||
: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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
v;p | ||
0.123588;2.937630 | ||
;2.442159 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add some information on the pictured model and important limitations and assumptions?
It seems to me like an infinite diameter is assumed (no change in water column height on withdraw or feed-in), is that correct?
How does it differ from an ext_grid, apart from the different formulations of pressure, and to which extend does it overlap?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I will do so.
There are not many differences compared to a ext_grid. The pressure is determined by the height of the water column. It is assumed that the water tower capacity is unlimited and the height of the water column remains constant, correct!
I agree that we could think of making a new parent component for ext_grid and water_tower. But for now I would leave it like this!