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

Transient heat transfer wops #533

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d939857
add minimal example of first time series function including pass func…
Nov 25, 2022
60ddbc2
First minimal example running with transient heat transfer mode
Jan 10, 2023
1f1799c
test:: initial changes to init file for new dynamic valve
Jan 11, 2023
24be771
Implementation of dynamic valve and pump, variable speed curves, vari…
Jan 13, 2023
437181a
Implementation of dynamic valve and pump, variable speed curves, vari…
Jan 13, 2023
ceb3316
Merge remote-tracking branch 'origin/transient_heat_transfer_wops' in…
Jan 13, 2023
656cf7a
Minimal example running with transient heat transfer mode and differe…
Jan 26, 2023
33e5336
Merge branch 'transient_heat_transfer_wops' of github.com:Theda-ISE/p…
Jan 26, 2023
29ff553
External Reset PID changes, Collector control class for multi-logic s…
Jan 26, 2023
e1a3b24
External Reset PID changes, Collector control class for multi-logic s…
Feb 2, 2023
92fa4a8
PP_Public updates merged for WOps
Feb 2, 2023
843661d
Merged updates re-intergrated with new circ pump components
Feb 8, 2023
ae277af
Updates from PP_public
Feb 13, 2023
fb18d3f
dynamic pump adaption after derivatives
Feb 14, 2023
de672a4
dynamic pump static working
Feb 16, 2023
1bbe905
transient vectors without division by zero, small adaptions to transi…
Feb 9, 2023
4b5c716
updates to PID pump & Valve param results extraction
Feb 16, 2023
c911091
minor changes to dynamics- temp-pid-vlv
Feb 16, 2023
2f3f731
minor changes to PID, collector, valve
Feb 19, 2023
b03dbe3
differential PID mods, minor changes to vlv
Feb 21, 2023
8dd5427
Transient heat transfer equations multiplied with length
Feb 21, 2023
a4ab22b
Merge branch 'transient_heat_transfer_wops' of github.com:Theda-ISE/p…
Feb 21, 2023
401942d
UniSim density chart, hex - record power values
Feb 21, 2023
5a3b9bd
Mod to Pipeflow initialisation of transient-mode
Feb 23, 2023
cbd6972
mod to active branches in transient sim
Feb 27, 2023
94d94e3
changes to dt in PID and lag functions, edit to valve area for veloci…
Feb 28, 2023
75a0f33
Updates in Vlv for multi-component arrays, Additional Pump Curves
Mar 7, 2023
1fb08c1
new std types,minor changes to dynamic controllers
Mar 16, 2023
bb63c1d
changes to lag functions, update of pipes + dt param set before runti…
Mar 30, 2023
6143b66
updates to controllers, stdtypes, density cross ref
Apr 5, 2023
8d2b613
minor updates to controllers, new logic controller built
Apr 8, 2023
7cec103
new pump curves
Apr 24, 2023
d1822f4
minor updates and code cleaning
Apr 24, 2023
8ab877b
Updated indexing for sections in OutputWriter
May 3, 2023
7a3690e
Merge branch 'transient_heat_transfer_wops' of github.com:Theda-ISE/p…
May 3, 2023
6cfd07c
deleted unnecessary files
Theda-ISE May 10, 2023
500eedb
Merge branch 'transient_heat_transfer' into transient_heat_transfer_wops
dlohmeier May 11, 2023
ebd9566
corrected some imports from pandapower and one keyword
dlohmeier May 11, 2023
ce5564f
restricted usage of plotly -> ignore import errors, just raise a warn…
dlohmeier May 11, 2023
cbc6098
added init file to controller package
dlohmeier May 11, 2023
5640d79
commented out some code for special cases
dlohmeier May 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pandapipes/component_models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
from pandapipes.component_models.junction_component import *
from pandapipes.component_models.pipe_component import *
from pandapipes.component_models.valve_component import *
from pandapipes.component_models.dynamic_valve_component import *
from pandapipes.component_models.dynamic_circulation_pump_component import *
from pandapipes.component_models.dynamic_pump_component import *
from pandapipes.component_models.ext_grid_component import *
from pandapipes.component_models.sink_component import *
from pandapipes.component_models.source_component import *
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ def create_pit_branch_entries(cls, net, branch_pit):
circ_pump_pit[:, AREA] = circ_pump_pit[:, D] ** 2 * np.pi / 4
circ_pump_pit[:, ACTIVE] = False

return circ_pump_pit

@classmethod
def calculate_temperature_lift(cls, net, pipe_pit, node_pit):
raise NotImplementedError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ def create_pit_node_entries(cls, net, node_pit):
set_fixed_node_entries(net, node_pit, junction, circ_pump.type.values, p_in, None,
cls.get_connected_node_type(), "p")




@classmethod
def calculate_temperature_lift(cls, net, pipe_pit, node_pit):
pass
20 changes: 20 additions & 0 deletions pandapipes/component_models/component_toolbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,26 @@ def set_fixed_node_entries(net, node_pit, junctions, eg_types, p_values, t_value
node_pit[index, type_col] = typ
node_pit[index, eg_count_col] += number

def update_fixed_node_entries(net, node_pit, junctions, eg_types, p_values, t_values, node_comp,
mode="all"):
junction_idx_lookups = get_lookup(net, "node", "index")[node_comp.table_name()]
for eg_type in ("p", "t"):
if eg_type not in mode and mode != "all":
continue
if eg_type == "p":
val_col, type_col, eg_count_col, typ, valid_types, values = \
PINIT, NODE_TYPE, EXT_GRID_OCCURENCE, P, ["p", "pt"], p_values
else:
val_col, type_col, eg_count_col, typ, valid_types, values = \
TINIT, NODE_TYPE_T, EXT_GRID_OCCURENCE_T, T, ["t", "pt"], t_values
mask = np.isin(eg_types, valid_types)
if not np.any(mask):
continue
use_numba = get_net_option(net, "use_numba")
juncts, press_sum, number = _sum_by_group(use_numba, junctions[mask], values[mask],
np.ones_like(values[mask], dtype=np.int32))
index = junction_idx_lookups[juncts]
node_pit[index, val_col] = press_sum

def get_mass_flow_at_nodes(net, node_pit, branch_pit, eg_nodes, comp):
node_uni, inverse_nodes, counts = np.unique(eg_nodes, return_counts=True, return_inverse=True)
Expand Down
306 changes: 306 additions & 0 deletions pandapipes/component_models/dynamic_circulation_pump_component.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
# Copyright (c) 2020-2022 by Fraunhofer Institute for Energy Economics
# and Energy System Technology (IEE), Kassel, and University of 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 operator import itemgetter
from pandapipes.component_models.junction_component import Junction
from pandapipes.component_models.abstract_models.circulation_pump import CirculationPump
from pandapipes.component_models.component_toolbox import set_fixed_node_entries, update_fixed_node_entries
from pandapipes.idx_node import PINIT, NODE_TYPE, P, EXT_GRID_OCCURENCE
from pandapipes.pf.pipeflow_setup import get_lookup, get_net_option
from pandapipes.idx_branch import STD_TYPE, VINIT, D, AREA, ACTIVE, LOSS_COEFFICIENT as LC, FROM_NODE, \
TINIT, PL, ACTUAL_POS, DESIRED_MV, RHO, TO_NODE, JAC_DERIV_DP, JAC_DERIV_DP1, JAC_DERIV_DV, LOAD_VEC_BRANCHES
from pandapipes.idx_node import PINIT, PAMB, TINIT as TINIT_NODE, HEIGHT, RHO as RHO_node
from pandapipes.constants import NORMAL_TEMPERATURE, NORMAL_PRESSURE, P_CONVERSION, GRAVITATION_CONSTANT
from pandapipes.properties.fluids import get_fluid
from pandapipes.component_models.component_toolbox import p_correction_height_air
from pandapipes.component_models.component_toolbox import set_fixed_node_entries, \
get_mass_flow_at_nodes
from pandapipes.pf.result_extraction import extract_branch_results_without_internals

try:
import pandaplan.core.pplog as logging
except ImportError:
import logging

logger = logging.getLogger(__name__)


class DynamicCirculationPump(CirculationPump):

# class attributes
kwargs = None
prev_act_pos = None
time_step = 0

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

@classmethod
def get_connected_node_type(cls):
return Junction

@classmethod
def active_identifier(cls):
return "in_service"

@classmethod
def create_pit_node_entries(cls, net, node_pit):
"""
Function which creates pit node entries.
:param net: The pandapipes network
:type net: pandapipesNet
:param node_pit:
:type node_pit:
:return: No Output.
"""
# Sets the discharge pressure, otherwise known as the starting node in the system
dyn_circ_pump, press = super().create_pit_node_entries(net, node_pit)

# SET SUCTION PRESSURE
junction = dyn_circ_pump[cls.from_to_node_cols()[0]].values
p_in = dyn_circ_pump.p_static_bar.values
set_fixed_node_entries(net, node_pit, junction, dyn_circ_pump.type.values, p_in, None,
cls.get_connected_node_type(), "p")


@classmethod
def create_pit_branch_entries(cls, net, branch_pit):
"""
Function which creates pit branch entries with a specific table.
:param net: The pandapipes network
:type net: pandapipesNet
:param branch_pit:
:type branch_pit:
:return: No Output.
"""
dyn_circ_pump_pit = super().create_pit_branch_entries(net, branch_pit)
dyn_circ_pump_pit[:, ACTIVE] = False

@classmethod
def plant_dynamics(cls, dt, desired_mv, dyn_pump_tbl):
"""
Takes in the desired valve position (MV value) and computes the actual output depending on
equipment lag parameters.
Returns Actual valve position
"""

"""
Takes in the desired valve position (MV value) and computes the actual output depending on
equipment lag parameters.
Returns Actual valve position
"""

if dyn_pump_tbl.__contains__("time_const_s"):
time_const_s = dyn_pump_tbl.time_const_s.values
else:
print("No actuator time constant set, default lag is now 5s.")
time_const_s = 5

a = np.divide(dt, time_const_s + dt)
actual_pos = (1 - a) * cls.prev_act_pos + a * desired_mv
cls.prev_act_pos = actual_pos

return actual_pos

# Issue with getting array values for different types of valves!! Assume all First Order!
# if cls.kwargs.__contains__("act_dynamics"):
# typ = cls.kwargs['act_dynamics']
# else:
# # default to instantaneous
# return desired_mv
#
# # linear
# if typ == "l":
#
# # TODO: equation for linear
# actual_pos = desired_mv
#
# # first order
# elif typ == "fo":
#
# a = np.divide(dt, cls.kwargs['time_const_s'] + dt)
# actual_pos = (1 - a) * cls.prev_act_pos + a * desired_mv
#
# cls.prev_act_pos = actual_pos
#
# # second order
# elif typ == "so":
# # TODO: equation for second order
# actual_pos = desired_mv
#
# else:
# # instantaneous - when incorrect option selected
# actual_pos = desired_mv
#
# return actual_pos

@classmethod
def adaption_before_derivatives_hydraulic(cls, net, branch_pit, node_pit, idx_lookups, options):
dt = net['_options']['dt']
circ_pump_tbl = net[cls.table_name()]
junction_lookup = get_lookup(net, "node", "index")[ cls.get_connected_node_type().table_name()]
fn_col, tn_col = cls.from_to_node_cols()
# get indices in internal structure for flow_junctions in circ_pump tables which are
# "active"
return_junctions = circ_pump_tbl[fn_col].values
return_node = junction_lookup[return_junctions]
rho = node_pit[return_node, RHO_node]
flow_junctions = circ_pump_tbl[tn_col].values
flow_nodes = junction_lookup[flow_junctions]
in_service = circ_pump_tbl.in_service.values
p_grids = np.isin(circ_pump_tbl.type.values, ["p", "pt"]) & in_service
sum_mass_flows, inverse_nodes, counts = get_mass_flow_at_nodes(net, node_pit, branch_pit,
flow_nodes[p_grids], cls)
q_kg_s = - (sum_mass_flows / counts)[inverse_nodes]
vol_m3_s = np.divide(q_kg_s, rho)
vol_m3_h = vol_m3_s * 3600
desired_mv = circ_pump_tbl.desired_mv.values
cur_actual_pos = circ_pump_tbl.actual_pos.values

#if not np.isnan(desired_mv) and get_net_option(net, "time_step") == cls.time_step:
if get_net_option(net, "time_step") == cls.time_step:
# a controller timeseries is running
actual_pos = cls.plant_dynamics(dt, desired_mv, circ_pump_tbl)
# Account for nan's when FCE are in manual
update_pos = np.where(np.isnan(actual_pos))
actual_pos[update_pos] = cur_actual_pos[update_pos]
circ_pump_tbl.actual_pos = actual_pos
cls.time_step += 1

else: # Steady state analysis
actual_pos = circ_pump_tbl.actual_pos.values

std_types_lookup = np.array(list(net.std_types['dynamic_pump'].keys()))
std_type, pos = np.where(net[cls.table_name()]['std_type'].values
== std_types_lookup[:, np.newaxis])
std_types = np.array(list(net.std_types['dynamic_pump'].keys()))[std_type]
fcts = itemgetter(*std_types)(net['std_types']['dynamic_pump'])
fcts = [fcts] if not isinstance(fcts, tuple) else fcts
m_head = np.array(list(map(lambda x, y, z: x.get_m_head(y, z), fcts, vol_m3_s, actual_pos))) # m head
prsr_lift = np.divide((rho * GRAVITATION_CONSTANT * m_head), P_CONVERSION)[0] # bar
circ_pump_tbl.p_lift = prsr_lift
circ_pump_tbl.m_head = m_head

# Now: Update the Discharge pressure node (Also known as the starting PT node)
# And the discharge temperature from the suction temperature (neglecting pump temp)
circ_pump_tbl = net[cls.table_name()][net[cls.table_name()][cls.active_identifier()].values]

junction = net[cls.table_name()][cls.from_to_node_cols()[1]].values

# TODO: there should be a warning, if any p_bar value is not given or any of the types does
# not contain "p", as this should not be allowed for this component

t_flow_k = node_pit[return_node, TINIT_NODE]
p_static = circ_pump_tbl.p_static_bar.values

# update the 'FROM' node i.e: discharge node temperature and pressure lift updates
update_fixed_node_entries(net, node_pit, junction, circ_pump_tbl.type.values, (prsr_lift + p_static), t_flow_k,
cls.get_connected_node_type(), "pt")


@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_flow_kg_per_s", "deltap_bar", "desired_mv", "actual_pos", "p_lift", "m_head", "rho", "t_from_k",
"t_to_k", "p_static_bar", "p_flow_bar"], True

@classmethod
def get_component_input(cls):
"""
:return:
:rtype:
"""
return [("name", dtype(object)),
("return_junction", "u4"),
("flow_junction", "u4"),
("p_flow_bar", "f8"),
("t_flow_k", "f8"),
("p_lift", "f8"),
('m_head', "f8"),
("p_static_bar", "f8"),
("actual_pos", "f8"),
("in_service", 'bool'),
("std_type", dtype(object)),
("type", dtype(object))]

@classmethod
def calculate_temperature_lift(cls, net, pipe_pit, node_pit):
pass


@classmethod
def extract_results(cls, net, options, branch_results, nodes_connected, branches_connected):
"""
Function that extracts certain results.
:param nodes_connected:
:type nodes_connected:
:param branches_connected:
:type branches_connected:
:param branch_results:
:type branch_results:
:param net: The pandapipes network
:type net: pandapipesNet
:param options:
:type options:
:return: No Output.
"""
circ_pump_tbl = net[cls.table_name()]

if len(circ_pump_tbl) == 0:
return

res_table = net["res_" + cls.table_name()]

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

junction_lookup = get_lookup(net, "node", "index")[
cls.get_connected_node_type().table_name()]
fn_col, tn_col = cls.from_to_node_cols()
# get indices in internal structure for flow_junctions in circ_pump tables which are
# "active"
flow_junctions = circ_pump_tbl[tn_col].values
flow_nodes = junction_lookup[flow_junctions]
in_service = circ_pump_tbl.in_service.values
p_grids = np.isin(circ_pump_tbl.type.values, ["p", "pt"]) & in_service
sum_mass_flows, inverse_nodes, counts = get_mass_flow_at_nodes(net, node_pit, branch_pit,
flow_nodes[p_grids], cls)

# positive results mean that the circ_pump feeds in, negative means that the ext grid
# extracts (like a load)
res_table["mdot_flow_kg_per_s"].values[p_grids] = - (sum_mass_flows / counts)[inverse_nodes]

return_junctions = circ_pump_tbl[fn_col].values
return_node = junction_lookup[return_junctions]


#res_table["vdot_norm_m3_per_s"] = np.divide(- (sum_mass_flows / counts)[inverse_nodes], rho)

return_junctions = circ_pump_tbl[fn_col].values
return_nodes = junction_lookup[return_junctions]

deltap_bar = node_pit[flow_nodes, PINIT] - node_pit[return_nodes, PINIT]
res_table["p_static_bar"].values[in_service] = circ_pump_tbl.p_static_bar.values
res_table["p_flow_bar"].values[in_service] = node_pit[flow_nodes, PINIT]
res_table["deltap_bar"].values[in_service] = deltap_bar[in_service]
res_table["t_from_k"].values[p_grids] = node_pit[return_node, TINIT]
res_table["t_to_k"].values[p_grids] = node_pit[flow_nodes, TINIT]
res_table["rho"].values[p_grids] = node_pit[return_node, RHO_node]
res_table["p_lift"].values[p_grids] = circ_pump_tbl.p_lift.values
res_table["m_head"].values[p_grids] = circ_pump_tbl.m_head.values
res_table["actual_pos"].values[p_grids] = circ_pump_tbl.actual_pos.values
res_table["desired_mv"].values[p_grids] = circ_pump_tbl.desired_mv.values
Loading