From 2e41cb551fc401f21a5e22f68980a458254b665a Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 30 Jan 2018 15:32:48 +0100 Subject: [PATCH 01/80] Update README.rst Removed typo --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index d21c04b04..ad0b7a794 100644 --- a/README.rst +++ b/README.rst @@ -31,7 +31,7 @@ If you want to use the latest features, you might want to install the **develope Examples ======== -For a short introduction on how TESPy works and how you can use it, we provid a short `Introduction ' `_.You can download the python scripts of the example plants from the tespy.examples folder. +For a short introduction on how TESPy works and how you can use it, we provid a short `Introduction `_.You can download the python scripts of the example plants from the tespy.examples folder. License ======= From 6ebc7e6498fc2328166e7885ef7c6d48060aad7c Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 5 Jul 2018 15:39:27 +0200 Subject: [PATCH 02/80] added first tries for custom variables --- tespy/components/components.py | 81 +++++++++++++++++++++++++--------- tespy/networks.py | 45 +++++++++++++++---- 2 files changed, 96 insertions(+), 30 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index 24583849f..f255e54bc 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -168,18 +168,18 @@ def set_attr(self, **kwargs): self.get_attr(key).set_attr(val=kwargs[key]) self.get_attr(key).set_attr(is_set=True) - elif isinstance(kwargs[key], str): - self.get_attr(key).set_attr(val=kwargs[key]) + elif kwargs[key] == 'var': + self.get_attr(key).set_attr(val=1) self.get_attr(key).set_attr(is_set=True) + self.get_attr(key).set_attr(is_var=True) - elif isinstance(kwargs[key], dict): + elif isinstance(kwargs[key], str) and kwargs[key] != 'var': self.get_attr(key).set_attr(val=kwargs[key]) self.get_attr(key).set_attr(is_set=True) - elif kwargs[key] == 'var': - self.get_attr(key).set_attr(val=1) + elif isinstance(kwargs[key], dict): + self.get_attr(key).set_attr(val=kwargs[key]) self.get_attr(key).set_attr(is_set=True) - self.get_attr(key).set_attr(is_var=True) # invalid datatype for keyword else: @@ -243,7 +243,12 @@ def get_attr(self, key): return None def comp_init(self, nw): - return + self.num_c_vars = 0 + for var in self.attr(): + if isinstance(self.attr_prop()[var], dc_cp): + if self.get_attr(var).is_var: + self.get_attr(var).var_pos = self.num_c_vars + self.num_c_vars += 1 def attr(self): return [] @@ -449,7 +454,7 @@ def fluid_deriv(self, inl, outl): num_fl = len(inl[0].fluid.val) if len(self.inlets()) == 1 and len(self.outlets()) == 1: - mat_deriv = np.zeros((num_fl, num_i + num_o, 3 + num_fl)) + mat_deriv = np.zeros((num_fl, 2 + self.num_c_vars, 3 + num_fl)) i = 0 for fluid, x in inl[0].fluid.val.items(): mat_deriv[i, 0, i + 3] = 1 @@ -458,7 +463,7 @@ def fluid_deriv(self, inl, outl): return mat_deriv.tolist() if isinstance(self, heat_exchanger): - mat_deriv = np.zeros((num_fl * 2, num_i + num_o, 3 + num_fl)) + mat_deriv = np.zeros((num_fl * 2, 4 + self.num_c_vars, 3 + num_fl)) i = 0 for fluid in inl[0].fluid.val.keys(): mat_deriv[i, 0, i + 3] = 1 @@ -618,7 +623,8 @@ def mass_flow_deriv(self, inl, outl): isinstance(self, combustion_chamber_stoich) or isinstance(self, drum) or (len(self.inlets()) == 1 and len(self.outlets()) == 1)): - mat_deriv = np.zeros((1, num_i + num_o, num_fl + 3)) + mat_deriv = np.zeros((1, num_i + num_o + self.num_c_vars, + num_fl + 3)) j = 0 for i in inl: mat_deriv[0, j, 0] = 1 @@ -631,7 +637,8 @@ def mass_flow_deriv(self, inl, outl): if (isinstance(self, subsys_interface) or isinstance(self, heat_exchanger)): - mat_deriv = np.zeros((num_i, num_i + num_o, num_fl + 3)) + mat_deriv = np.zeros((num_i, num_i + num_o + self.num_c_vars, + num_fl + 3)) for i in range(num_i): mat_deriv[i, i, 0] = 1 for j in range(num_o): @@ -696,7 +703,7 @@ def ddx_func(self, inl, outl, func, dx, pos): deriv += [exp / (2 * (dm + dp + dh + df))] - else: + elif dx in ['m', 'p', 'h']: exp = 0 (inl + outl)[pos].m.val_SI += dm (inl + outl)[pos].p.val_SI += dp @@ -713,6 +720,18 @@ def ddx_func(self, inl, outl, func, dx, pos): (inl + outl)[pos].p.val_SI += dp (inl + outl)[pos].h.val_SI += dh + else: + d = 1e-5 + exp = 0 + self.get_attr(dx).val += d + exp += func(inl, outl) + + self.get_attr(dx).val -= 2 * d + exp -= func(inl, outl) + deriv = exp / (2 * d) + + self.get_attr(dx).val += d + return deriv # %% @@ -4471,27 +4490,27 @@ def derivatives(self, nw): inl, outl = (nw.comps.loc[self].i.tolist(), nw.comps.loc[self].o.tolist()) - num_i, num_o = len(inl), len(outl) num_fl = len(nw.fluids) mat_deriv = [] mat_deriv += self.fluid_deriv(inl, outl) mat_deriv += self.mass_flow_deriv(inl, outl) - h_deriv = np.zeros((num_i + num_o - 1, num_i + num_o, num_fl + 3)) - for k in range(num_i + num_o - 1): - h_deriv[k, 0, 2] = 1 - h_deriv[k, k + 1, 2] = -1 + h_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) + h_deriv[0, 0, 2] = 1 + h_deriv[0, 1, 2] = -1 mat_deriv += h_deriv.tolist() if self.pr.is_set: - pr_deriv = np.zeros((1, num_i + num_o, num_fl + 3)) + pr_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) pr_deriv[0, 0, 1] = self.pr.val pr_deriv[0, 1, 1] = -1 + if self.pr.is_var: + pr_deriv[0, 2 + self.pr.var_pos, 0] = inl[0].p.val_SI mat_deriv += pr_deriv.tolist() if self.zeta.is_set: - zeta_deriv = np.zeros((1, num_i + num_o, num_fl + 3)) + zeta_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) for i in range(2): if i == 0: zeta_deriv[0, i, 0] = ( @@ -4500,6 +4519,9 @@ def derivatives(self, nw): self.ddx_func(inl, outl, self.zeta_func, 'p', i)) zeta_deriv[0, i, 2] = ( self.ddx_func(inl, outl, self.zeta_func, 'h', i)) + if self.zeta.is_var: + zeta_deriv[0, 2 + self.zeta.var_pos, 0] = ( + self.ddx_func(inl, outl, self.zeta_func, 'zeta', i)) mat_deriv += zeta_deriv.tolist() return np.asarray(mat_deriv) @@ -4644,6 +4666,8 @@ class heat_exchanger_simple(component): def comp_init(self, nw): + component.comp_init(self, nw) + if self.kA_char.func is None: method = self.kA_char.method x = self.kA_char.x @@ -4802,16 +4826,20 @@ def derivatives(self, nw): mat_deriv += self.mass_flow_deriv(inl, outl) if self.Q.is_set: - Q_deriv = np.zeros((1, num_i + num_o, num_fl + 3)) + Q_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) Q_deriv[0, 0, 0] = outl[0].h.val_SI - inl[0].h.val_SI Q_deriv[0, 0, 2] = -inl[0].m.val_SI Q_deriv[0, 1, 2] = inl[0].m.val_SI + if self.Q.is_var: + Q_deriv[0, 2 + self.Q.var_pos, 0] = -1 mat_deriv += Q_deriv.tolist() if self.pr.is_set: - pr_deriv = np.zeros((1, num_i + num_o, num_fl + 3)) + pr_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) pr_deriv[0, 0, 1] = self.pr.val pr_deriv[0, 1, 1] = -1 + if self.pr.is_var: + pr_deriv[0, 2 + self.pr.var_pos, 0] = inl[0].p.val_SI mat_deriv += pr_deriv.tolist() if self.zeta.is_set: @@ -4832,7 +4860,7 @@ def derivatives(self, nw): else: func = self.lamb_func - deriv = np.zeros((1, num_i + num_o, num_fl + 3)) + deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) for i in range(2): if i == 0: deriv[0, i, 0] = ( @@ -4841,6 +4869,15 @@ def derivatives(self, nw): self.ddx_func(inl, outl, func, 'p', i)) deriv[0, i, 2] = ( self.ddx_func(inl, outl, func, 'h', i)) + if self.D.is_var: + deriv[0, 2 + self.D.var_pos, 0] = ( + self.ddx_func(inl, outl, func, 'D', i)) + if self.L.is_var: + deriv[0, 2 + self.L.var_pos, 0] = ( + self.ddx_func(inl, outl, func, 'L', i)) + if self.ks.is_var: + deriv[0, 2 + self.ks.var_pos, 0] = ( + self.ddx_func(inl, outl, func, 'ks', i)) mat_deriv += deriv.tolist() if self.kA_group.is_set: diff --git a/tespy/networks.py b/tespy/networks.py index 8ec741ad2..590e67553 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -1188,6 +1188,22 @@ def solve_control(self): j += 1 i += 1 + c_vars = 0 + for cp in self.comps.index: + if cp.num_c_vars > 0: + for var in cp.attr(): + if isinstance(cp.attr_prop()[var], hlp.dc_cp): + if cp.get_attr(var).is_var: + pos = cp.get_attr(var).var_pos + cp.get_attr(var).val += self.vec_z[ + self.num_vars * len(self.conns) + + c_vars + pos] * self.relax + if var == 'D': + print(cp.get_attr(var).val) + if cp.get_attr(var).val <= 0: + cp.get_attr(var).val = 0.01 + c_vars += cp.num_c_vars + # check properties for consistency if self.iter < 3: for cp in self.comps.index: @@ -1298,8 +1314,9 @@ def solve_components(self): - search a way to speed up locating the data within the matrix """ - self.mat_deriv = np.zeros((len(self.conns) * (self.num_vars), - len(self.conns) * (self.num_vars))) + num_cols = len(self.conns) * self.num_vars + self.mat_deriv = np.zeros((num_cols + self.num_c_vars, + num_cols + self.num_c_vars,)) if self.parallel: data = self.solve_parallelize(network.solve_comp, self.comps_split) @@ -1313,6 +1330,7 @@ def solve_components(self): self.vec_res += [it for ls in data[part][0].tolist() for it in ls] k = 0 + c_var = 0 for cp in self.comps_split[part].index: if (not isinstance(cp, cmp.source) and @@ -1330,6 +1348,14 @@ def solve_components(self): (loc + 1) * self.num_vars] = ( data[part][1].iloc[k][0][:, i]) i += 1 + + for j in range(cp.num_c_vars): + self.mat_deriv[sum_eq:sum_eq + num_eq, + num_cols + c_var] = ( + data[part][1].iloc[k][0] + [:, i + j, :1].transpose()[0]) + c_var += 1 + sum_eq += num_eq k += 1 @@ -1795,8 +1821,11 @@ def solve_determination(self): :returns: no return value :raises: :code:`MyNetworkError` """ + self.num_c_vars = 0 n = 0 for cp in self.comps.index: + + self.num_c_vars += cp.num_c_vars n += len(cp.equations(self)) for c in self.conns.index: @@ -1810,15 +1839,15 @@ def solve_determination(self): for b in self.busses: n += [b.P_set].count(True) - if n > self.num_vars * len(self.conns.index): + if n > self.num_vars * len(self.conns.index) + self.num_c_vars: msg = ('You have provided too many parameters:', - self.num_vars * len(self.conns.index), 'required, ', - n, 'given.') + self.num_vars * len(self.conns.index) + self.num_c_vars, + 'required, ', n, 'given.') raise hlp.MyNetworkError(msg) - elif n < self.num_vars * len(self.conns.index): + elif n < self.num_vars * len(self.conns.index) + self.num_c_vars: msg = ('You have not provided enough parameters:', - self.num_vars * len(self.conns.index), 'required, ', - n, 'given.') + self.num_vars * len(self.conns.index) + self.num_c_vars, + 'required, ', n, 'given.') raise hlp.MyNetworkError(msg) else: return From 0863129b829a1b39fa3c3e05e1e428354ebe2a03 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 11 Jul 2018 10:45:20 +0200 Subject: [PATCH 03/80] added derivatives for more parameters and generic derivative calculation for grouped parameters --- tespy/components/components.py | 56 ++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index f255e54bc..1909377b3 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -243,12 +243,14 @@ def get_attr(self, key): return None def comp_init(self, nw): + self.vars = {} self.num_c_vars = 0 for var in self.attr(): if isinstance(self.attr_prop()[var], dc_cp): if self.get_attr(var).is_var: self.get_attr(var).var_pos = self.num_c_vars self.num_c_vars += 1 + self.vars[self.get_attr(var)] = var def attr(self): return [] @@ -721,7 +723,7 @@ def ddx_func(self, inl, outl, func, dx, pos): (inl + outl)[pos].h.val_SI += dh else: - d = 1e-5 + d = self.get_attr(dx).d exp = 0 self.get_attr(dx).val += d exp += func(inl, outl) @@ -3131,7 +3133,7 @@ def derivatives(self, nw): mat_deriv += lamb_deriv.tolist() if self.ti.is_set: - # derivatives for specified lambda + # derivatives for specified thermal input ti_deriv = np.zeros((1, num_i + num_o, num_fl + 3)) for i in range(num_i): ti_deriv[0, i, 0] = ( @@ -4725,8 +4727,11 @@ def attr(self): def attr_prop(self): return {'Q': dc_cp(), 'pr': dc_cp(), 'zeta': dc_cp(), - 'D': dc_cp(), 'L': dc_cp(), 'ks': dc_cp(), - 'kA': dc_cp(), 't_a': dc_cp(), 't_a_design': dc_cp(), + 'D': dc_cp(min_val=1e-4, max_val=5, d=1e-5), + 'L': dc_cp(min_val=1e-3, d=1e-3), + 'ks': dc_cp(min_val=1e-7, max_val=1e-4, d=1e-7), + 'kA': dc_cp(min_val=100, d=1), + 't_a': dc_cp(), 't_a_design': dc_cp(), 'kA_char': dc_cc(method='HE_HOT', param='m'), 'SQ1': dc_cp(), 'SQ2': dc_cp(), 'Sirr': dc_cp(), 'hydro_group': dc_gcp(), 'kA_group': dc_gcp()} @@ -4843,7 +4848,7 @@ def derivatives(self, nw): mat_deriv += pr_deriv.tolist() if self.zeta.is_set: - zeta_deriv = np.zeros((1, num_i + num_o, num_fl + 3)) + zeta_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) for i in range(2): if i == 0: zeta_deriv[0, i, 0] = ( @@ -4852,6 +4857,9 @@ def derivatives(self, nw): self.ddx_func(inl, outl, self.zeta_func, 'p', i)) zeta_deriv[0, i, 2] = ( self.ddx_func(inl, outl, self.zeta_func, 'h', i)) + if self.zeta.is_var: + zeta_deriv[0, 2 + self.zeta.var_pos, 0] = ( + self.ddx_func(inl, outl, self.zeta_func, 'zeta', i)) mat_deriv += zeta_deriv.tolist() if self.hydro_group.is_set: @@ -4869,26 +4877,27 @@ def derivatives(self, nw): self.ddx_func(inl, outl, func, 'p', i)) deriv[0, i, 2] = ( self.ddx_func(inl, outl, func, 'h', i)) - if self.D.is_var: - deriv[0, 2 + self.D.var_pos, 0] = ( - self.ddx_func(inl, outl, func, 'D', i)) - if self.L.is_var: - deriv[0, 2 + self.L.var_pos, 0] = ( - self.ddx_func(inl, outl, func, 'L', i)) - if self.ks.is_var: - deriv[0, 2 + self.ks.var_pos, 0] = ( - self.ddx_func(inl, outl, func, 'ks', i)) + for var in self.hydro_group.elements: + if var.is_var: + deriv[0, 2 + var.var_pos, 0] = ( + self.ddx_func(inl, outl, func, + self.vars[var], i)) mat_deriv += deriv.tolist() if self.kA_group.is_set: - - kA_deriv = np.zeros((1, num_i + num_o, num_fl + 3)) + kA_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) kA_deriv[0, 0, 0] = self.ddx_func(inl, outl, self.kA_func, 'm', 0) for i in range(2): kA_deriv[0, i, 1] = ( self.ddx_func(inl, outl, self.kA_func, 'p', i)) kA_deriv[0, i, 2] = ( self.ddx_func(inl, outl, self.kA_func, 'h', i)) + # this does not work atm, as t_a.val_SI is used instead of t_a.val! + for var in self.kA_group.elements: + if var.is_var: + kA_deriv[0, 2 + var.var_pos, 0] = ( + self.ddx_func(inl, outl, self.kA_func, + self.vars[var], i)) mat_deriv += kA_deriv.tolist() return np.asarray(mat_deriv) @@ -5124,6 +5133,21 @@ def initialise_target(self, c, key): else: return 0 + def convergence_check(self, nw): + r""" + prevent bad values for fluid properties in calculation + + :param nw: network using this component object + :type nw: tespy.networks.network + :returns: no return value + """ + + for var in self.vars.keys(): + if var.val < var.min_val: + var.val = (var.min_val + var.max_val) / 2 + if var.val > var.max_val: + var.val = (var.min_val + var.max_val) / 2 + def calc_parameters(self, nw, mode): inl, outl = (nw.comps.loc[self].i.tolist(), From 89bd2d93797fdeb08341ed33d3822632c69d5c37 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 11 Jul 2018 10:45:41 +0200 Subject: [PATCH 04/80] adjusted custom variables handling --- tespy/networks.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/tespy/networks.py b/tespy/networks.py index 590e67553..730f1e2e8 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -1190,19 +1190,12 @@ def solve_control(self): c_vars = 0 for cp in self.comps.index: - if cp.num_c_vars > 0: - for var in cp.attr(): - if isinstance(cp.attr_prop()[var], hlp.dc_cp): - if cp.get_attr(var).is_var: - pos = cp.get_attr(var).var_pos - cp.get_attr(var).val += self.vec_z[ - self.num_vars * len(self.conns) + - c_vars + pos] * self.relax - if var == 'D': - print(cp.get_attr(var).val) - if cp.get_attr(var).val <= 0: - cp.get_attr(var).val = 0.01 - c_vars += cp.num_c_vars + for var in cp.vars.keys(): + pos = var.var_pos + var.val += self.vec_z[ + self.num_vars * len(self.conns) + + c_vars + pos] * self.relax + c_vars += cp.num_c_vars # check properties for consistency if self.iter < 3: From 7de4bd9e6c8c56192177d8c9ca34cbb67dc35334 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 11 Jul 2018 10:46:11 +0200 Subject: [PATCH 05/80] added additional parameters for component parameter data container --- tespy/helpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tespy/helpers.py b/tespy/helpers.py index 9eee96876..dc6a50a06 100644 --- a/tespy/helpers.py +++ b/tespy/helpers.py @@ -148,7 +148,8 @@ class dc_cp(data_container): val will be used as starting value """ def attr(self): - return {'val': 0, 'val_SI': 0, 'is_set': False, 'is_var': False} + return {'val': 0, 'val_SI': 0, 'is_set': False, + 'd': 1e-4, 'min_val': 0, 'max_val': 1e12, 'is_var': False} class dc_cc(data_container): From fa929a6ac65fe5ec14e3015285eef9f0614b87fc Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 12 Jul 2018 16:01:43 +0200 Subject: [PATCH 06/80] changed unit for pipe diameter to mm for improved convergence, still very slow --- tespy/components/components.py | 14 +++++++------- tespy/networks.py | 3 +++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index 1909377b3..edbfa41fb 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -4727,7 +4727,7 @@ def attr(self): def attr_prop(self): return {'Q': dc_cp(), 'pr': dc_cp(), 'zeta': dc_cp(), - 'D': dc_cp(min_val=1e-4, max_val=5, d=1e-5), + 'D': dc_cp(min_val=10, max_val=5000, d=1), 'L': dc_cp(min_val=1e-3, d=1e-3), 'ks': dc_cp(min_val=1e-7, max_val=1e-4, d=1e-7), 'kA': dc_cp(min_val=100, d=1), @@ -4823,7 +4823,6 @@ def derivatives(self, nw): inl, outl = (nw.comps.loc[self].i.tolist(), nw.comps.loc[self].o.tolist()) - num_i, num_o = len(inl), len(outl) num_fl = len(nw.fluids) mat_deriv = [] @@ -4882,6 +4881,7 @@ def derivatives(self, nw): deriv[0, 2 + var.var_pos, 0] = ( self.ddx_func(inl, outl, func, self.vars[var], i)) + mat_deriv += deriv.tolist() if self.kA_group.is_set: @@ -4931,13 +4931,13 @@ def lamb_func(self, inl, outl): i, o = inl[0].to_flow(), outl[0].to_flow() visc_i, visc_o = visc_mix_ph(i), visc_mix_ph(o) v_i, v_o = v_mix_ph(i), v_mix_ph(o) - re = 4 * inl[0].m.val_SI / (math.pi * self.D.val * + re = 4 * inl[0].m.val_SI / (math.pi * self.D.val / 1000 * (visc_i + visc_o) / 2) return ((inl[0].p.val_SI - outl[0].p.val_SI) - 8 * inl[0].m.val_SI ** 2 * (v_i + v_o) / 2 * self.L.val * - lamb(re, self.ks.val, self.D.val) / - (math.pi ** 2 * self.D.val ** 5)) + lamb(re, self.ks.val, self.D.val / 1000) / + (math.pi ** 2 * (self.D.val / 1000) ** 5)) def hw_func(self, inl, outl): r""" @@ -5144,9 +5144,9 @@ def convergence_check(self, nw): for var in self.vars.keys(): if var.val < var.min_val: - var.val = (var.min_val + var.max_val) / 2 + var.val = var.min_val if var.val > var.max_val: - var.val = (var.min_val + var.max_val) / 2 + var.val = var.max_val def calc_parameters(self, nw, mode): diff --git a/tespy/networks.py b/tespy/networks.py index 730f1e2e8..9b706accc 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -1197,6 +1197,9 @@ def solve_control(self): c_vars + pos] * self.relax c_vars += cp.num_c_vars + if cp.num_c_vars > 0: + cp.convergence_check(self) + # check properties for consistency if self.iter < 3: for cp in self.comps.index: From efbc03989cd1b8b6797cd01c60a62cd09233372a Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 12 Jul 2018 16:04:41 +0200 Subject: [PATCH 07/80] added example for custom variables --- examples/custom_vars.py | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 examples/custom_vars.py diff --git a/examples/custom_vars.py b/examples/custom_vars.py new file mode 100644 index 000000000..c0b7f314c --- /dev/null +++ b/examples/custom_vars.py @@ -0,0 +1,63 @@ +from tespy import nwk, con, cmp, hlp +import numpy as np +from matplotlib import pyplot as plt + +nw = nwk.network(['water'], p_unit='bar', T_unit='C', h_unit='kJ / kg', + p_range=[0.5, 2], T_range=[10, 100], + h_range=[5,500]) + +# %% components +pipe = cmp.pipe('pipe') +pipe2 = cmp.pipe('pipe2') +pipe3 = cmp.pipe('pipe3') +pipe4 = cmp.pipe('pipe4') +pipe5 = cmp.pipe('pipe5') +pipe6 = cmp.pipe('pipe6') +sink = cmp.sink('sink') +source = cmp.source('source') + +# %% connections + +a = con.connection(source, 'out1', pipe, 'in1') +b = con.connection(pipe, 'out1', pipe2, 'in1') +c = con.connection(pipe2, 'out1', pipe3, 'in1') +d = con.connection(pipe3, 'out1', pipe4, 'in1') +e = con.connection(pipe4, 'out1', pipe5, 'in1') +f = con.connection(pipe5, 'out1', pipe6, 'in1') +g = con.connection(pipe6, 'out1', sink, 'in1') + +nw.add_conns(a, b, c, d, e, f, g) + +# %% connection parameters + +a.set_attr(h=40, fluid={'water': 1}, p=1, m=10) +b.set_attr(h=40) +c.set_attr(h=40) +d.set_attr(h=40) +e.set_attr(h=40) +f.set_attr(h=40) +g.set_attr(h=40) + + +# %% component parameters + +pipe.set_attr(pr=0.99, ks=1e-4, L=20, D='var') +pipe2.set_attr(pr=0.95, ks=1e-4, L=20, D='var') +pipe3.set_attr(pr=0.95, ks=1e-4, L=20, D='var') + +pipe4.set_attr(pr=0.99, ks=1e-4, L=30, D='var') +pipe5.set_attr(pr=0.95, ks=1e-4, L=20, D='var') +pipe6.set_attr(pr=0.95, ks=1e-4, L=20, D='var') +pipe.D.val=10 +pipe2.D.val=10 +pipe3.D.val=10 + +pipe4.D.val=10 +pipe5.D.val=10 +pipe6.D.val=10 + +# %% solve + +nw.solve(mode='design') +nw.print_results() +nw.save('test') From 669884080b2ad752fb1343b70c754bbb9faf64d6 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Fri, 13 Jul 2018 13:24:48 +0200 Subject: [PATCH 08/80] value range adjustments, removing residual --- tespy/components/components.py | 4 +++- tespy/networks.py | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index edbfa41fb..786970a6f 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -1185,6 +1185,8 @@ class pump(turbomachine): def comp_init(self, nw): + component.comp_init(self, nw) + if self.flow_char.func is None: method = self.flow_char.method x = self.flow_char.x @@ -4727,7 +4729,7 @@ def attr(self): def attr_prop(self): return {'Q': dc_cp(), 'pr': dc_cp(), 'zeta': dc_cp(), - 'D': dc_cp(min_val=10, max_val=5000, d=1), + 'D': dc_cp(min_val=1, max_val=5000, d=1e-1), 'L': dc_cp(min_val=1e-3, d=1e-3), 'ks': dc_cp(min_val=1e-7, max_val=1e-4, d=1e-7), 'kA': dc_cp(min_val=100, d=1), diff --git a/tespy/networks.py b/tespy/networks.py index 9b706accc..cf35c6868 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -1204,8 +1204,6 @@ def solve_control(self): if self.iter < 3: for cp in self.comps.index: cp.convergence_check(self) - - if self.iter < 3: for c in self.conns.index: self.solve_check_properties(c) From 62344483fba4be4fda1dab6d0306e37926bae20d Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Mon, 27 Aug 2018 16:37:08 +0200 Subject: [PATCH 09/80] updated version number --- VERSION | 2 +- doc/conf.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 81f0fdecc..efcac6abd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -__version__ = "0.0.4" +__version__ = "0.0.4 dev" diff --git a/doc/conf.py b/doc/conf.py index ba2b5852e..c9d82f572 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -65,7 +65,7 @@ # The short X.Y version. version = '0.0.4' # The full version, including alpha/beta/rc tags. -release = '0.0.4 master' +release = '0.0.4 dev' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index f6d4048b2..f1fcf73ed 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ def read(fname): setup(name='TESPy', - version='0.0.4', + version='0.0.4 dev', description='Thermal Engineering Systems in Python (TESPy)', url='http://github.com/oemof/tespy', author='Francesco Witte', From 3c73baa9c7f1a0e5924cabe352090fd71a7e4087 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 28 Aug 2018 14:57:19 +0200 Subject: [PATCH 10/80] updated requirements for beautiful printouts --- requirements_rtd.py | 1 + setup.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements_rtd.py b/requirements_rtd.py index cf7084239..bc133373e 100644 --- a/requirements_rtd.py +++ b/requirements_rtd.py @@ -3,3 +3,4 @@ numpy pandas scipy +tabulate diff --git a/setup.py b/setup.py index f1fcf73ed..8128fb5cc 100644 --- a/setup.py +++ b/setup.py @@ -20,4 +20,5 @@ def read(fname): 'matplotlib >= 2.0.2', 'numpy >= 1.13.3', 'pandas >= 0.19.2', - 'scipy >= 0.19.1']) + 'scipy >= 0.19.1', + 'tabulate >= 0.8.2']) From 4ba8f600c2c955a584829e333075d59ac79b3ec5 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 28 Aug 2018 14:58:51 +0200 Subject: [PATCH 11/80] renamed attr_prop()-method to attr()-method, removed old attr()-method --- tespy/components/components.py | 110 ++++++++++----------------------- 1 file changed, 31 insertions(+), 79 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index d4ebfc054..38b8fd740 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -81,7 +81,7 @@ class component: :param label: label for component :type label: str - :param kwargs: for the keyword arguments see :code:`component.attr()` + :param kwargs: for the keyword arguments see :code:`component.attr(self)` :returns: no return value :raises: - :code:`TypeError`, if label is not of type str components @@ -130,22 +130,19 @@ def __init__(self, label, **kwargs): self.offdesign = self.default_offdesign() # add container for components attributes - var = self.attr_prop() + var = self.attr() for key in var.keys(): self.__dict__.update({key: var[key]}) self.set_attr(**kwargs) -# print('Created ', self, '.') -# print(self.__dict__) - def set_attr(self, **kwargs): """ sets, resets or unsets attributes of a connection, for the keyword arguments, return values and errors see object initialisation """ - var = self.attr() + var = self.attr().keys() # set specified values for key in kwargs: @@ -185,7 +182,8 @@ def set_attr(self, **kwargs): # invalid datatype for keyword else: - msg = 'Bad datatype for keyword argument ' + str(key) + msg = ('Bad datatype for keyword argument ' + key + + ' at ' + self.label + '.') raise TypeError(msg) elif (isinstance(self.get_attr(key), dc_cc) or @@ -196,26 +194,29 @@ def set_attr(self, **kwargs): # invalid datatype for keyword else: - msg = 'Bad datatype for keyword argument ' + str(key) + msg = ('Bad datatype for keyword argument ' + key + + ' at ' + self.label + '.') raise TypeError(msg) elif key == 'design' or key == 'offdesign': if not isinstance(kwargs[key], list): - msg = 'Please provide the design parameters as list!' + msg = ('Please provide the design parameters as list at ' + + self.label + '.') raise ValueError(msg) - if set(kwargs[key]).issubset(list(self.attr())): + if set(kwargs[key]).issubset(list(var)): self.__dict__.update({key: kwargs[key]}) else: - msg = ('Available parameters for (off-)design' + msg = ('Available parameters for (off-)design ' 'specification are: ' + - str(self.attr()) + '.') + str(list(var)) + ' at ' + self.label + '.') raise ValueError(msg) elif key == 'mode': if kwargs[key] in ['man', 'auto']: self.__dict__.update({key: kwargs[key]}) else: - msg = 'mode must be \'man\' or \'auto\'.' + msg = ('Mode must be \'man\' or \'auto\' at ' + + self.label + '.') raise TypeError(msg) # invalid keyword @@ -244,17 +245,14 @@ def get_attr(self, key): def comp_init(self, nw): self.vars = {} self.num_c_vars = 0 - for var in self.attr(): - if isinstance(self.attr_prop()[var], dc_cp): + for var in self.attr().keys(): + if isinstance(self.attr()[var], dc_cp): if self.get_attr(var).is_var: self.get_attr(var).var_pos = self.num_c_vars self.num_c_vars += 1 self.vars[self.get_attr(var)] = var def attr(self): - return [] - - def attr_prop(self): return {} def inlets(self): @@ -806,10 +804,8 @@ class turbomachine(component): - in1 - out1 """ - def attr(self): - return ['P', 'eta_s', 'pr', 'eta_s_char', 'Sirr'] - def attr_prop(self): + def attr(self): return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), 'eta_s_char': dc_cc(), 'Sirr': dc_cp()} @@ -1153,9 +1149,6 @@ def component(self): return 'pump' def attr(self): - return ['P', 'eta_s', 'pr', 'Sirr', 'eta_s_char', 'flow_char'] - - def attr_prop(self): return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), 'Sirr': dc_cp(), 'eta_s_char': dc_cc(x=[0, 1, 2, 3], y=[1, 1, 1, 1]), 'flow_char': dc_cc(x=[0, 1, 2, 3], y=[1, 1, 1, 1])} @@ -1503,9 +1496,6 @@ def component(self): return 'compressor' def attr(self): - return ['P', 'eta_s', 'pr', 'vigv', 'char_map', 'Sirr'] - - def attr_prop(self): return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), 'vigv': dc_cp(), 'Sirr': dc_cp(), 'char_map': dc_cc(func=cmp_char.compressor(), @@ -1916,9 +1906,6 @@ def component(self): return 'turbine' def attr(self): - return ['P', 'eta_s', 'pr', 'eta_s_char', 'cone', 'Sirr'] - - def attr_prop(self): return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), 'Sirr': dc_cp(), 'eta_s_char': dc_cc(method='GENERIC', param='m'), @@ -2242,11 +2229,9 @@ class split(component): :alt: alternative text :align: center """ - def attr(self): - return ['num_out'] - def attr_prop(self): - return {'num_out': dc_cp()} + def attr(self): + return {'num_out': dc_cp(printout=False)} def inlets(self): return ['in1'] @@ -2519,11 +2504,9 @@ class merge(component): :alt: alternative text :align: center """ - def attr(self): - return ['num_in'] - def attr_prop(self): - return {'num_in': dc_cp()} + def attr(self): + return {'num_in': dc_cp(printout=False)} def inlets(self): if self.num_in.is_set: @@ -2744,10 +2727,8 @@ def outlets(self): return ['out1'] def attr(self): - return ['fuel', 'lamb', 'ti', 'S'] - - def attr_prop(self): - return {'fuel': dc_cp(), 'lamb': dc_cp(), 'ti': dc_cp(), 'S': dc_cp()} + return {'fuel': dc_cp(printout=False), 'lamb': dc_cp(), 'ti': dc_cp(), + 'S': dc_cp()} def fuels(self): return ['methane', 'ethane', 'propane', 'butane', @@ -3546,13 +3527,11 @@ def outlets(self): return ['out1'] def attr(self): - return ['fuel', 'fuel_alias', 'air', 'air_alias', 'path', - 'lamb', 'ti', 'S'] - - def attr_prop(self): - return {'fuel': dc_cp(), 'fuel_alias': dc_cp(), - 'air': dc_cp(), 'air_alias': dc_cp(), - 'path': dc_cp(), + return {'fuel': dc_cp(printout=False), + 'fuel_alias': dc_cp(printout=False), + 'air': dc_cp(printout=False), + 'air_alias': dc_cp(printout=False), + 'path': dc_cp(printout=False), 'lamb': dc_cp(), 'ti': dc_cp(), 'S': dc_cp()} def fuels(self): @@ -4194,9 +4173,6 @@ class vessel(component): """ def attr(self): - return ['pr', 'zeta', 'Sirr'] - - def attr_prop(self): return {'pr': dc_cp(), 'zeta': dc_cp(), 'Sirr': dc_cp()} def default_design(self): @@ -4489,12 +4465,6 @@ def comp_init(self, nw): self.kA_group.set_attr(is_set=False) def attr(self): - return ['Q', 'pr', 'zeta', 'D', 'L', 'ks', - 'kA', 't_a', 't_a_design', 'kA_char', - 'SQ1', 'SQ2', 'Sirr', - 'hydro_group', 'kA_group'] - - def attr_prop(self): return {'Q': dc_cp(), 'pr': dc_cp(), 'zeta': dc_cp(), 'D': dc_cp(min_val=1, max_val=5000, d=1e-1), 'L': dc_cp(min_val=1e-3, d=1e-3), @@ -5207,12 +5177,6 @@ def comp_init(self, nw): self.energy_group.set_attr(is_set=False) def attr(self): - return ['Q', 'pr', 'zeta', 'D', 'L', 'ks', - 'E', 'lkf_lin', 'lkf_quad', 'A', 't_a', - 'SQ', - 'hydro_group', 'energy_group'] - - def attr_prop(self): return {'Q': dc_cp(), 'pr': dc_cp(), 'zeta': dc_cp(), 'D': dc_cp(), 'L': dc_cp(), 'ks': dc_cp(), 'E': dc_cp(), 'lkf_lin': dc_cp(), 'lkf_quad': dc_cp(), @@ -5411,12 +5375,7 @@ def comp_init(self, nw): self.kA_char2.func = cmp_char.heat_ex(method=method, x=x, y=y) def attr(self): - return ['Q', 'kA', 'td_log', 'kA_char1', 'kA_char2', - 'ttd_u', 'ttd_l', - 'pr1', 'pr2', 'zeta1', 'zeta2', - 'SQ1', 'SQ2', 'Sirr'] - - def attr_prop(self): + # derivatives for logarithmic temperature difference not implemented return {'Q': dc_cp(), 'kA': dc_cp(), 'td_log': dc_cp(), 'kA_char1': dc_cc(method='HE_HOT', param='m'), 'kA_char2': dc_cc(method='HE_COLD', param='m'), @@ -5424,10 +5383,6 @@ def attr_prop(self): 'pr1': dc_cp(), 'pr2': dc_cp(), 'zeta1': dc_cp(), 'zeta2': dc_cp(), 'SQ1': dc_cp(), 'SQ2': dc_cp(), 'Sirr': dc_cp()} - # derivatives for logarithmic temperature difference not implemented -# return (component.attr(self) + -# ['Q', 'kA', 'td_log', 'ttd_u', 'ttd_l', -# 'pr1', 'pr2', 'zeta1', 'zeta2']) def inlets(self): return ['in1', 'in2'] @@ -6188,7 +6143,7 @@ class condenser(heat_exchanger): def component(self): return 'condenser' - def attr_prop(self): + def attr(self): return {'Q': dc_cp(), 'kA': dc_cp(), 'td_log': dc_cp(), 'kA_char1': dc_cc(method='COND_HOT', param='m'), 'kA_char2': dc_cc(method='COND_COLD', param='m'), @@ -6703,10 +6658,7 @@ class subsys_interface(component): """ def attr(self): - return ['num_inter'] - - def attr_prop(self): - return {'num_inter': dc_cp()} + return {'num_inter': dc_cp(printout=False)} def inlets(self): if self.num_inter.is_set: From 2e795c2af35a9b6ad3af1834cfc9b7c01f9da3a4 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 28 Aug 2018 14:59:39 +0200 Subject: [PATCH 12/80] added printout flag for component property data container --- tespy/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tespy/helpers.py b/tespy/helpers.py index 5a941fe2e..ea3140249 100644 --- a/tespy/helpers.py +++ b/tespy/helpers.py @@ -148,7 +148,7 @@ class dc_cp(data_container): val will be used as starting value """ def attr(self): - return {'val': 0, 'val_SI': 0, 'is_set': False, + return {'val': 0, 'val_SI': 0, 'is_set': False, 'printout': True, 'd': 1e-4, 'min_val': 0, 'max_val': 1e12, 'is_var': False} From 9465be65fc74403c6f4d07c068a4727ef4149e77 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 28 Aug 2018 15:00:22 +0200 Subject: [PATCH 13/80] adjusted printouts, adjustments following renaming of attr_prop() to attr() --- tespy/networks.py | 52 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/tespy/networks.py b/tespy/networks.py index 0ddfaf0d0..04ea64429 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -8,6 +8,7 @@ import math import pandas as pd +from tabulate import tabulate from multiprocessing import cpu_count, Pool, freeze_support import numpy as np @@ -2018,13 +2019,36 @@ def print_results(self): msg = 'Do you want to print the components parammeters?' if hlp.query_yes_no(msg): - cp_sort = self.comps + cp_sort = self.comps.copy() cp_sort['cp'] = cp_sort.apply(network.get_class_base, axis=1) + cp_sort['label'] = cp_sort.apply(network.get_props, axis=1, + args=('label',)) + cp_sort.drop('i', axis=1, inplace=True) + cp_sort.drop('o', axis=1, inplace=True) + cp_sort = cp_sort[cp_sort['cp'] != 'source'] + cp_sort = cp_sort[cp_sort['cp'] != 'sink'] pd.options.mode.chained_assignment = None for c in cp_sort.cp.unique(): df = cp_sort[cp_sort['cp'] == c] - df.apply(network.print_components, axis=1, args=(self,)) + + cols = [] + for col, val in df.index[0].attr().items(): + if isinstance(val, hlp.dc_cp): + if val.get_attr('printout'): + cols += [col] + + if len(cols) > 0: + print('##### RESULTS (' + c + ') #####') + for col in cols: + df[col] = df.apply(network.print_components, + axis=1, args=(col,)) + + df.set_index('label', inplace=True) + df.drop('cp', axis=1, inplace=True) + + print(tabulate(df, headers='keys', tablefmt='psql', + floatfmt='.2e')) msg = 'Do you want to print the connections parammeters?' if hlp.query_yes_no(msg): @@ -2034,15 +2058,16 @@ def print_results(self): 'T / (' + self.T_unit + ')']) for c in self.conns.index: df.loc[c.s.label + ' -> ' + c.t.label] = ( - ['{:.2e}'.format(c.m.val_SI / self.m[self.m_unit]), - '{:.2e}'.format(c.p.val_SI / self.p[self.p_unit]), - '{:.4e}'.format(c.h.val_SI / self.h[self.h_unit]), - '{:.2e}'.format(c.T.val_SI / self.T[self.T_unit][1] - - self.T[self.T_unit][0])] + [c.m.val_SI / self.m[self.m_unit], + c.p.val_SI / self.p[self.p_unit], + c.h.val_SI / self.h[self.h_unit], + c.T.val_SI / self.T[self.T_unit][1] - + self.T[self.T_unit][0]] ) - print(df) + print(tabulate(df, headers='keys', tablefmt='psql', + floatfmt='.3e')) - def print_components(cols, nw): + def print_components(c, *args): """ postprocessing: calculate components attributes and print them to prompt @@ -2051,8 +2076,7 @@ def print_components(cols, nw): :type cols: landas dataframe index object :returns: no return value """ - - cols.name.print_parameters(nw) + return c.name.get_attr(args[0]).val def plot_convergence(self): """ @@ -2245,7 +2269,7 @@ def save_components(self, path): """ # create / overwrite csv file - cp_sort = self.comps + cp_sort = self.comps.copy() cp_sort['cp'] = cp_sort.apply(network.get_class_base, axis=1) cp_sort['busses'] = cp_sort.apply(network.get_busses, axis=1, args=(self.busses,)) @@ -2262,7 +2286,7 @@ def save_components(self, path): df[col] = df.apply(network.get_props, axis=1, args=(col,)) - for col, dc in df.index[0].attr_prop().items(): + for col, dc in df.index[0].attr().items(): if isinstance(dc, hlp.dc_cc): df[col] = df.apply(network.get_props, axis=1, args=(col, 'func')).astype(str) @@ -2333,7 +2357,7 @@ def save_characteristics(self, fn): for c in cp_sort.cp.unique(): df = cp_sort[cp_sort['cp'] == c] - for col, dc in df.index[0].attr_prop().items(): + for col, dc in df.index[0].attr().items(): if isinstance(dc, hlp.dc_cc): chars += df.apply(network.get_props, axis=1, args=(col, 'func')).tolist() From cbbc1a1438d250074b1a556df4ec21424eb53d34 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 28 Aug 2018 15:24:49 +0200 Subject: [PATCH 14/80] more adjustments to the printouts --- tespy/components/components.py | 14 +------------- tespy/networks.py | 29 ++++++++++++++++------------- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index 38b8fd740..f83bfbc96 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -4466,7 +4466,7 @@ def comp_init(self, nw): def attr(self): return {'Q': dc_cp(), 'pr': dc_cp(), 'zeta': dc_cp(), - 'D': dc_cp(min_val=1, max_val=5000, d=1e-1), + 'D': dc_cp(min_val=1e-2, max_val=2, d=1e-4), 'L': dc_cp(min_val=1e-3, d=1e-3), 'ks': dc_cp(min_val=1e-7, max_val=1e-4, d=1e-7), 'kA': dc_cp(min_val=100, d=1), @@ -4984,18 +4984,6 @@ def calc_parameters(self, nw, mode): print(msg) nw.errors += [self] - def print_parameters(self, nw): - - print('##### ', self.label, ' #####') - print('Q = ', self.Q.val, 'W; ' - 'pr = ', self.pr.val, '; ' - 'zeta = ', self.zeta.val, 'kg / m^4 * s; ' - 'SQ1 = ', self.SQ1.val, 'W / K; ') - if self.t_a.is_set or self.t_a_design.is_set: - print('SQ2 = ', self.SQ2.val, 'W / K; ' - 'Sirr = ', self.Sirr.val, 'W / K; ' - 'kA = ', self.kA.val, 'W / (m^2 * K)') - # %% diff --git a/tespy/networks.py b/tespy/networks.py index 04ea64429..a43a10dac 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -1078,7 +1078,7 @@ def solve(self, mode, init_file=None, design_file=None, dec='.', self.conns_split = [self.conns] start_time = time.time() - self.solve_loop() + errmsg = self.solve_loop() end_time = time.time() if self.iterinfo: @@ -1096,6 +1096,9 @@ def solve(self, mode, init_file=None, design_file=None, dec='.', str(round(self.iter / (end_time - start_time), 2))) print(msg) + if self.nwkwarn: + print(errmsg) + if self.lin_dep: if self.nwkerr: msg = ('##### ERROR #####\n' @@ -1195,26 +1198,26 @@ def solve_loop(self): # stop calculation after rediculous amount of iterations if self.iter > 3 and self.res[-1] < hlp.err ** (1 / 2): - break + return '' if self.iter > 15: if (all(self.res[(self.iter - 3):] >= self.res[-2]) and self.res[-1] >= self.res[-2]): - if self.nwkwarn: - print('##### WARNING #####\n' - 'Convergence is making no progress, calculation ' - 'stopped, residual value is ' - '{:.2e}'.format(norm(self.vec_res))) - break + msg = ('##### WARNING #####\n' + 'Convergence is making no progress, calculation ' + 'stopped, residual value is ' + '{:.2e}'.format(norm(self.vec_res))) + return msg if self.lin_dep: - break + return '' if self.iter == self.max_iter - 1: - print('##### WARNING #####\n' - 'Reached maximum iteration count, calculation ' - 'stopped, residual value is ' - '{:.2e}'.format(norm(self.vec_res))) + msg = ('##### WARNING #####\n' + 'Reached maximum iteration count, calculation ' + 'stopped, residual value is ' + '{:.2e}'.format(norm(self.vec_res))) + return msg def solve_control(self): """ From b2c08977d10b11c370089f87d5838fd4ff59c906 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 28 Aug 2018 15:25:13 +0200 Subject: [PATCH 15/80] more adjustments to printouts --- tespy/components/components.py | 144 +++++++-------------------------- 1 file changed, 31 insertions(+), 113 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index f83bfbc96..0234719c9 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -308,9 +308,6 @@ def initialise_target(self, c, key): def calc_parameters(self, nw, mode): return - def print_parameters(self, nw): - return - def initialise_fluids(self, nw): return @@ -1073,14 +1070,6 @@ def calc_parameters(self, nw, mode): s_mix_ph(self.outl[0].to_flow()) - s_mix_ph(self.inl[0].to_flow())) - def print_parameters(self, nw): - - print('##### ', self.label, ' #####') - print('P = ', self.P.val, 'W; ' - 'eta_s = ', self.eta_s.val, '; ' - 'pr = ', self.pr.val, '; ' - 'Sirr = ', self.Sirr.val, '; ') - # %% @@ -1819,34 +1808,34 @@ def calc_parameters(self, nw, mode): # print('Creating characteristics for component ', self) # self.char_map.func = cmp_char.compressor() - def print_parameters(self, nw): - - turbomachine.print_parameters(self, nw) - - i1 = self.inl[0].to_flow() - o1 = self.outl[0].to_flow() - - if self.char_map.is_set: - n = math.sqrt(T_mix_ph(self.i0)) / math.sqrt(T_mix_ph(i1)) - m = ( - (i1[0] * math.sqrt(T_mix_ph(i1)) / i1[1]) / - (self.i0[0] * math.sqrt(T_mix_ph(self.i0)) / self.i0[1]) - ) - vigv = self.char_map.func.get_vigv(n, m, (o1[1] * self.i0[1]) / - (i1[1] * self.o0[1])) - if abs(self.vigv.val - vigv) > err and nw.compwarn: - msg = ('##### WARNING #####\n' - 'Selected inlet guide vane angle is not feasible.') - if self.vigv.val > vigv: - msg += ('calculated maximum angle: ' + str(vigv) + - ' selected: ' + str(self.vigv.val)) - else: - msg += ('calculated minimum angle: ' + str(vigv) + - ' selected: ' + str(self.vigv.val)) - print(msg) - - else: - print('vigv =', self.vigv.val) +# def print_parameters(self, nw): +# +# turbomachine.print_parameters(self, nw) +# +# i1 = self.inl[0].to_flow() +# o1 = self.outl[0].to_flow() +# +# if self.char_map.is_set: +# n = math.sqrt(T_mix_ph(self.i0)) / math.sqrt(T_mix_ph(i1)) +# m = ( +# (i1[0] * math.sqrt(T_mix_ph(i1)) / i1[1]) / +# (self.i0[0] * math.sqrt(T_mix_ph(self.i0)) / self.i0[1]) +# ) +# vigv = self.char_map.func.get_vigv(n, m, (o1[1] * self.i0[1]) / +# (i1[1] * self.o0[1])) +# if abs(self.vigv.val - vigv) > err and nw.compwarn: +# msg = ('##### WARNING #####\n' +# 'Selected inlet guide vane angle is not feasible.') +# if self.vigv.val > vigv: +# msg += ('calculated maximum angle: ' + str(vigv) + +# ' selected: ' + str(self.vigv.val)) +# else: +# msg += ('calculated minimum angle: ' + str(vigv) + +# ' selected: ' + str(self.vigv.val)) +# print(msg) +# +# else: +# print('vigv =', self.vigv.val) # %% @@ -2416,21 +2405,7 @@ def initialise_target(self, c, key): else: return 0 - def print_parameters(self, nw): - - print('##### ', self.label, ' #####') - print('m_in = ', self.inl[0].m.val_SI, 'kg / s; ') - i = 1 - for o in self.outl: - print('m_out' + str(i) + ' = ', o.m.val_SI, 'kg / s; ') - i += 1 - if isinstance(self, separator): - print('; fluid_in:', self.inl[0].fluid.val, '; ') - i = 1 - for o in self.outl: - print('fluid_out' + str(i) + ' = ', o.fluid.val) - i += 1 - +# %% class splitter(split): """ @@ -2456,6 +2431,8 @@ class splitter(split): def component(self): return 'splitter' +# %% + class separator(split): """ @@ -2666,15 +2643,6 @@ def initialise_target(self, c, key): else: return 0 - def print_parameters(self, nw): - - print('##### ', self.label, ' #####') - j = 1 - for i in self.inl: - print('m_in' + str(j) + ' = ', i.m.val_SI, 'kg / s; ') - j += 1 - print('m_out = ', self.outl[0].m.val_SI, 'kg / s; ') - # %% @@ -3446,18 +3414,6 @@ def calc_parameters(self, nw, mode): self.lamb.val = n_oxygen / (n_fuel * (self.n['C'] + self.n['H'] / 4)) - def print_parameters(self, nw): - - print('##### ', self.label, ' #####') - print('thermal input = ', self.ti.val, - 'lambda = ', self.lamb.val, - 'S = ', self.S.val) - j = 1 - for i in self.inl: - print('m_in' + str(j) + ' = ', i.m.val_SI, 'kg / s; ') - j += 1 - print('m_out = ', self.outl[0].m.val_SI, 'kg / s; ') - # %% @@ -4333,13 +4289,6 @@ def calc_parameters(self, nw, mode): s_mix_ph(self.outl[0].to_flow()) - s_mix_ph(self.inl[0].to_flow())) - def print_parameters(self, nw): - - print('##### ', self.label, ' #####') - print('pr = ', self.pr.val, '; ' - 'zeta = ', self.zeta.val, 'kg / m^4 * s ; ' - 'Sirr = ', self.Sirr.val, 'W / K') - # %% @@ -4475,10 +4424,6 @@ def attr(self): 'SQ1': dc_cp(), 'SQ2': dc_cp(), 'Sirr': dc_cp(), 'hydro_group': dc_gcp(), 'kA_group': dc_gcp()} - def printouts(self): - return ['Q', 'pr', 'zeta', 'D', 'L', 'ks', - 'kA', 'SQ1', 'SQ2', 'Sirr'] - def default_design(self): return ['pr'] @@ -5282,17 +5227,6 @@ def calc_parameters(self, nw, mode): (v_mix_ph(self.inl[0].to_flow()) + v_mix_ph(self.outl[0].to_flow())) / 2)) - def print_parameters(self, nw): - - print('##### ', self.label, ' #####') - print('Q = ', self.Q.val, 'W; ' - 'pr = ', self.pr.val, '; ' - 'zeta = ', self.zeta.val, 'kg / m^4 * s; ' - 'SQ = ', self.SQ.val, 'W / K; ') - if self.energy_group.is_set: - print('E = ', self.E.val, 'W / m^2; ' - 'A = ', self.A.val, 'm^2') - # %% @@ -6057,22 +5991,6 @@ def calc_parameters(self, nw, mode): print(msg) nw.errors += [self] - def print_parameters(self, nw): - - print('##### ', self.label, ' #####') - print('Q = ', self.Q.val, 'W; ' - 'ttd_u = ', self.ttd_u.val, 'K; ' - 'ttd_l = ', self.ttd_l.val, 'K; ' - 'td_log = ', self.td_log.val, 'K; ' - 'kA = ', self.kA.val, 'W / K; ' - 'pr1 = ', self.pr1.val, '; ' - 'pr2 = ', self.pr2.val, '; ' - 'zeta1 = ', self.zeta1.val, '; ' - 'zeta2 = ', self.zeta2.val, '; ' - 'SQ1 = ', self.SQ1.val, 'W / K; ' - 'SQ2 = ', self.SQ2.val, 'W / K; ' - 'Sirr = ', self.Sirr.val, 'W / K; ') - # %% From 173fdbbc05506149fab1e16eba56eb1298c15dee Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 18 Sep 2018 16:10:00 +0200 Subject: [PATCH 16/80] removed double component initialisation --- tespy/networks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tespy/networks.py b/tespy/networks.py index a43a10dac..248d5edc3 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -553,7 +553,6 @@ def init_components(self, comps): labels = [] for comp in self.comps.index: - comp.comp_init(self) s = self.conns[self.conns.s == comp] s = s.s_id.sort_values().index t = self.conns[self.conns.t == comp] From 45119e4e5378a69e9bea277867ec12eaa1e3eea7 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Fri, 21 Sep 2018 11:01:25 +0200 Subject: [PATCH 17/80] added derivatives for volume to pressure and to enthalpy --- tespy/helpers.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tespy/helpers.py b/tespy/helpers.py index dc587db41..a7cda5b82 100644 --- a/tespy/helpers.py +++ b/tespy/helpers.py @@ -1069,6 +1069,50 @@ def d_ph(p, h, fluid): else: return CPPSI('D', 'P', p, 'H', h, fluid) + +def dv_mix_dph(flow): + r""" + calculates partial derivate of volume to pressure at + constant enthalpy and fluid composition + + :param flow: vector containing [mass flow, pressure, enthalpy, fluid] + :type flow: list + :returns: dv / dp (float) - derivative in m^3 / (Pa * kg) + + .. math:: + + \frac{\partial v_{mix}}{\partial p} = \frac{v_{mix}(p+d,h)- + v_{mix}(p-d,h)}{2 \cdot d} + """ + d = 1 + u = flow.copy() + l = flow.copy() + u[1] += d + l[1] -= d + return (v_mix_ph(u) - v_mix_ph(l)) / (2 * d) + + +def dv_mix_pdh(flow): + r""" + method to calculate partial derivate of volume to enthalpy at + constant pressure and fluid composition + + :param flow: vector containing [mass flow, pressure, enthalpy, fluid] + :type flow: list + :returns: dv / dh (float) - derivative in m^3 / J + + .. math:: + + \frac{\partial v_{mix}}{\partial h} = \frac{v_{mix}(p,h+d)- + v_{mix}(p,h-d)}{2 \cdot d} + """ + d = 1 + u = flow.copy() + l = flow.copy() + u[2] += d + l[2] -= d + return (v_mix_ph(u) - v_mix_ph(l)) / (2 * d) + # %% From 5558816899e1778e2110ff22ae60a5c930183470 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Mon, 24 Sep 2018 09:08:39 +0200 Subject: [PATCH 18/80] added volumetric flow to connection parameters --- tespy/connections.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tespy/connections.py b/tespy/connections.py index 981f5c9a5..6e4d84a2d 100644 --- a/tespy/connections.py +++ b/tespy/connections.py @@ -41,6 +41,7 @@ class connection: - h (*numeric*, *ref object*), h0 (*numeric*) - T (*numeric*, *ref object*) - x (*numeric*) + - v (*numeric*) - fluid (*dict*), fluid_balance (*bool*) - design (*list*), offdesign (*list*) @@ -225,7 +226,7 @@ def attr(self): :returns: list object """ return {'m': dc_prop(), 'p': dc_prop(), 'h': dc_prop(), 'T': dc_prop(), - 'x': dc_prop(), + 'x': dc_prop(), 'v': dc_prop(), 'fluid': dc_flu()} def to_flow(self): From 63db64de7fc5a26eb4d36b94bdd67fc3a4f7f715 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Mon, 24 Sep 2018 12:04:06 +0200 Subject: [PATCH 19/80] added volumetric flow to connection parameters --- tespy/networks.py | 156 ++++++++++++++++++++++------------------------ 1 file changed, 74 insertions(+), 82 deletions(-) diff --git a/tespy/networks.py b/tespy/networks.py index fce6ff180..3ab9c3e11 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -115,11 +115,18 @@ def __init__(self, fluids, **kwargs): 'F': [459.67, 5 / 9], 'K': [0, 1] } + self.v = { + 'm3 / s': 1, + 'l / s': 1e-3, + 'm3 / h': 3.6e-3, + 'l / h': 3.6e-6 + } self.SI_units = { 'm': 'kg / s', 'p': 'Pa', 'h': 'J / kg', - 'T': 'K' + 'T': 'K', + 'v': 'm3 / s' } # printoptions @@ -131,6 +138,7 @@ def __init__(self, fluids, **kwargs): self.p_unit = self.SI_units['p'] self.h_unit = self.SI_units['h'] self.T_unit = self.SI_units['T'] + self.v_unit = self.SI_units['v'] # standard value range self.p_range = [2e3, 300e5] @@ -146,47 +154,7 @@ def __init__(self, fluids, **kwargs): if key in self.attr(): self.__dict__.update({key: kwargs[key]}) - # unit sets - if self.m_unit not in self.m.keys(): - msg = ('Allowed units for mass flow are: ' + - str(self.m.keys())) - raise hlp.MyNetworkError(msg) - - if self.p_unit not in self.p.keys(): - msg = ('Allowed units for pressure are: ' + - str(self.p.keys())) - raise hlp.MyNetworkError(msg) - - if self.h_unit not in self.h.keys(): - msg = ('Allowed units for enthalpy are: ' + - str(self.h.keys())) - raise hlp.MyNetworkError(msg) - - if self.T_unit not in self.T.keys(): - msg = ('Allowed units for temperature are: ' + - str(self.T.keys())) - raise hlp.MyNetworkError(msg) - - # value ranges - if not isinstance(self.p_range, list): - msg = ('Specify the value range as list: [p_min, p_max]') - raise TypeError(msg) - else: - self.p_range_SI = np.array(self.p_range) * self.p[self.p_unit] - - if not isinstance(self.h_range, list): - msg = ('Specify the value range as list: [h_min, h_max]') - raise TypeError(msg) - else: - self.h_range_SI = np.array(self.h_range) * self.h[self.h_unit] - - if not isinstance(self.T_range, list): - msg = ('Specify the value range as list: [T_min, T_max]') - raise TypeError(msg) - else: - self.T_range_SI = ((np.array(self.T_range) + - self.T[self.T_unit][0]) * - self.T[self.T_unit][1]) + self.set_attr(**kwargs) def __getstate__(self): """ @@ -228,6 +196,11 @@ def set_attr(self, **kwargs): str(self.T.keys())) raise hlp.MyNetworkError(msg) + if self.v_unit not in self.v.keys(): + msg = ('Allowed units for volumetric flow are: ' + + str(self.v.keys())) + raise hlp.MyNetworkError(msg) + # value ranges if not isinstance(self.p_range, list): msg = ('Specify the value range as list: [p_min, p_max]') @@ -773,15 +746,15 @@ def init_properties(self): """ # fluid properties for c in self.conns.index: - for key in ['m', 'p', 'h', 'T', 'x']: + for key in ['m', 'p', 'h', 'T', 'x', 'v']: if not c.get_attr(key).unit_set and key != 'x': c.get_attr(key).unit = self.get_attr(key + '_unit') - if key not in ['T', 'x'] and not c.get_attr(key).val_set: + if key not in ['T', 'x', 'v'] and not c.get_attr(key).val_set: self.init_val0(c, key) c.get_attr(key).val_SI = ( c.get_attr(key).val0 * self.get_attr(key)[c.get_attr(key).unit]) - elif key not in ['T', 'x'] and c.get_attr(key).val_set: + elif key not in ['T', 'x', 'v'] and c.get_attr(key).val_set: c.get_attr(key).val_SI = ( c.get_attr(key).val * self.get_attr(key)[c.get_attr(key).unit]) @@ -790,6 +763,8 @@ def init_properties(self): self.T[c.T.unit][1]) elif key == 'x' and c.x.val_set: c.x.val_SI = c.x.val + elif key == 'v' and c.v.val_set: + c.v.val_SI = c.v.val * self.v[c.v.unit] else: continue @@ -1118,11 +1093,13 @@ def solve(self, mode, init_file=None, design_file=None, dec='.', for c in self.conns.index: c.T.val_SI = hlp.T_mix_ph(c.to_flow()) + c.v.val_SI = hlp.v_mix_ph(c.to_flow()) * c.m.val_SI c.T.val = (c.T.val_SI / self.T[c.T.unit][1] - self.T[c.T.unit][0]) c.m.val = c.m.val_SI / self.m[c.m.unit] c.p.val = c.p.val_SI / self.p[c.p.unit] c.h.val = c.h.val_SI / self.h[c.h.unit] + c.v.val = c.v.val_SI / self.v[c.v.unit] c.T.val0 = c.T.val c.m.val0 = c.m.val c.p.val0 = c.p.val @@ -1240,7 +1217,10 @@ def solve_control(self): if not c.m.val_set: c.m.val_SI += self.vec_z[i * (self.num_vars)] * self.relax if not c.p.val_set: - c.p.val_SI += self.vec_z[i * (self.num_vars) + 1] * self.relax + # this prevents negative pressures + relax = max(1, -self.vec_z[i * (self.num_vars) + 1] / + (0.5 * c.p.val_SI)) + c.p.val_SI += self.vec_z[i * (self.num_vars) + 1] / relax if not c.h.val_set: c.h.val_SI += self.vec_z[i * (self.num_vars) + 2] * self.relax @@ -1453,8 +1433,8 @@ def solve_connections(self): # write data in residual vector and jacobian matrix sum_eq = len(self.vec_res) - var = {0: 'm', 1: 'p', 2: 'h', 3: 'T', 4: 'x', - 5: 'm', 6: 'p', 7: 'h', 8: 'T'} + var = {0: 'm', 1: 'p', 2: 'h', 3: 'T', 4: 'x', 5: 'v', + 6: 'm', 7: 'p', 8: 'h', 9: 'T'} for part in range(self.partit): self.vec_res += [it for ls in data[part][0].tolist() @@ -1526,6 +1506,7 @@ def solve_conn_eq(c, nw): nw.solve_prop_eq(c.name, 'h'), nw.solve_prop_eq(c.name, 'T'), nw.solve_prop_eq(c.name, 'x'), + nw.solve_prop_eq(c.name, 'v'), nw.solve_prop_ref_eq(c.name, 'm'), nw.solve_prop_ref_eq(c.name, 'p'), nw.solve_prop_ref_eq(c.name, 'h'), @@ -1537,6 +1518,7 @@ def solve_conn_deriv(c, nw): nw.solve_prop_deriv(c.name, 'h'), nw.solve_prop_deriv(c.name, 'T'), nw.solve_prop_deriv(c.name, 'x'), + nw.solve_prop_deriv(c.name, 'v'), nw.solve_prop_ref_deriv(c.name, 'm'), nw.solve_prop_ref_deriv(c.name, 'p'), nw.solve_prop_ref_deriv(c.name, 'h'), @@ -1580,7 +1562,8 @@ def solve_busses(self): def solve_prop_eq(self, c, var): r""" calculate residuals for given mass flow, - pressure, enthalpy, temperature and vapour mass fraction + pressure, enthalpy, temperature, volumetric flow and + vapour mass fraction :param c: connections object to apply calculations on :type c: tespy.connections.connection @@ -1590,55 +1573,26 @@ def solve_prop_eq(self, c, var): **mass flow, pressure and enthalpy** - **equation for numeric values** - .. math:: 0 = 0 - **derivative for numeric values** - - .. math:: - J\left(\frac{\partial f_{i}}{\partial m_{j}}\right) = 1\\ - \text{for equation i, connection j}\\ - \text{pressure and enthalpy analogously} - **temperatures** - **equation for numeric values** - .. math:: 0 = T_{j} - T \left( p_{j}, h_{j}, fluid_{j} \right) - **derivative for numeric values** + **volumetric flow** .. math:: - J\left(\frac{\partial f_{i}}{\partial p_{j}}\right) = - -\frac{dT_{j}}{dp_{j}}\\ - J(\left(\frac{\partial f_{i}}{\partial h_{j}}\right) = - -\frac{dT_{j}}{dh_{j}}\\ - J\left(\frac{\partial f_{i}}{\partial fluid_{j,k}}\right) = - - \frac{dT_{j}}{dfluid_{j,k}} - \; , \forall k \in \text{fluid components}\\ - \text{for equation i, connection j} + 0 = v_{j} - v \left( p_{j}, h_{j} \right) \cdot \dot{m}_j **vapour mass fraction** .. note:: works with pure fluids only! - **equation for numeric values** - .. math:: 0 = h_{j} - h \left( p_{j}, x_{j}, fluid_{j} \right) - - **derivative for numeric values** - - .. math:: - J\left(\frac{\partial f_{i}}{\partial p_{j}}\right) = - -\frac{\partial h \left( p_{j}, x_{j}, fluid_{j} \right)} - {\partial p_{j}}\\ - J(\left(\frac{\partial f_{i}}{\partial h_{j}}\right) = 1\\ - \text{for equation i, connection j, x: vapour mass fraction} """ if var in ['m', 'p', 'h']: @@ -1656,6 +1610,14 @@ def solve_prop_eq(self, c, var): else: return None + elif var == 'v': + + if c.v.val_set: + flow = c.to_flow() + return c.v.val_SI - hlp.v_mix_ph(flow) * c.m.val_SI + else: + return None + else: if c.x.val_set: flow = c.to_flow() @@ -1710,7 +1672,8 @@ def solve_prop_ref_eq(self, c, var): def solve_prop_deriv(self, c, var): r""" calculate derivatives for given mass flow, - pressure, enthalpy, temperature and vapour mass fraction + pressure, enthalpy, temperature, volumetric flow and + vapour mass fraction :param c: connections object to apply calculations on :type c: tespy.connections.connection @@ -1737,6 +1700,19 @@ def solve_prop_deriv(self, c, var): \; , \forall k \in \text{fluid components}\\ \text{for equation i, connection j} + **volumetric flow** + + .. math:: + J\left(\frac{\partial f_{i}}{\partial m_{j}}\right) = + -v \left( p_{j}, h_{j} \right)\\ + J\left(\frac{\partial f_{i}}{\partial p_{j}}\right) = + -\frac{dv_{j}}{dp_{j}} \cdot \dot{m}_j\\ + J(\left(\frac{\partial f_{i}}{\partial h_{j}}\right) = + -\frac{dv_{j}}{dh_{j}} \cdot \dot{m}_j\\ + + \; , \forall k \in \text{fluid components}\\ + \text{for equation i, connection j} + **vapour mass fraction** .. note:: @@ -1778,6 +1754,22 @@ def solve_prop_deriv(self, c, var): else: return None + elif var == 'v': + + if c.v.val_set: + flow = c.to_flow() + deriv = np.zeros((1, 1, self.num_vars)) + # dv / dm + deriv[0, 0, 0] = -hlp.v_mix_ph(flow) + # dv / dp + deriv[0, 0, 1] = -hlp.dv_mix_dph(flow) * c.m.val_SI + # dv / dh + deriv[0, 0, 2] = -hlp.dv_mix_pdh(flow) * c.m.val_SI + return deriv + + else: + return None + else: if c.x.val_set: @@ -1876,7 +1868,7 @@ def solve_determination(self): for c in self.conns.index: n += [c.m.val_set, c.p.val_set, c.h.val_set, - c.T.val_set, c.x.val_set].count(True) + c.T.val_set, c.x.val_set, c.v.val_set].count(True) n += [c.m.ref_set, c.p.ref_set, c.h.ref_set, c.T.ref_set].count(True) n += list(c.fluid.val_set.values()).count(True) From 1e55e36b5aa9bbfda15537cc305379f5198edeca Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 25 Sep 2018 14:16:50 +0200 Subject: [PATCH 20/80] fixed printouts for turbomachines --- tespy/components/components.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index 893c741cc..7825ee5f8 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -1422,10 +1422,10 @@ def calc_parameters(self, nw, mode): if (mode == 'pre' and 'eta_s' in self.offdesign) or mode == 'post': self.eta_s.val = ((self.h_os('post') - self.inl[0].h.val_SI) / (self.outl[0].h.val_SI - self.inl[0].h.val_SI)) - if self.eta_s.val > 1 or self.eta_s.val <= 0 and nw.comperr: + if (self.eta_s.val > 1 or self.eta_s.val <= 0) and nw.comperr: msg = ('##### ERROR #####\n' 'Invalid value for isentropic efficiency: ' - 'eta_s =', self.eta_s.val) + 'eta_s =' + str(self.eta_s.val)) print(msg) nw.errors += [self] @@ -1794,10 +1794,10 @@ def calc_parameters(self, nw, mode): if (mode == 'pre' and 'eta_s' in self.offdesign) or mode == 'post': self.eta_s.val = ((self.h_os('post') - self.inl[0].h.val_SI) / (self.outl[0].h.val_SI - self.inl[0].h.val_SI)) - if self.eta_s.val > 1 or self.eta_s.val <= 0 and nw.comperr: + if (self.eta_s.val > 1 or self.eta_s.val <= 0) and nw.comperr: msg = ('##### ERROR #####\n' 'Invalid value for isentropic efficiency: ' - 'eta_s =', self.eta_s.val) + 'eta_s =' + str(self.eta_s.val)) print(msg) nw.errors += [self] @@ -2170,10 +2170,10 @@ def calc_parameters(self, nw, mode): if (mode == 'pre' and 'eta_s' in self.offdesign) or mode == 'post': self.eta_s.val = ((self.outl[0].h.val_SI - self.inl[0].h.val_SI) / (self.h_os('post') - self.inl[0].h.val_SI)) - if self.eta_s.val > 1 or self.eta_s.val <= 0 and nw.comperr: + if (self.eta_s.val > 1 or self.eta_s.val <= 0) and nw.comperr: msg = ('##### ERROR #####\n' 'Invalid value for isentropic efficiency: ' - 'eta_s =', self.eta_s.val) + 'eta_s =' + str(self.eta_s.val)) print(msg) nw.errors += [self] From 809ec25fda2e833cf12cceca78192b9b581859ff Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 25 Sep 2018 14:51:59 +0200 Subject: [PATCH 21/80] fixed v_unit bug --- tespy/networks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tespy/networks.py b/tespy/networks.py index 3ab9c3e11..6aa1deed9 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -118,8 +118,8 @@ def __init__(self, fluids, **kwargs): self.v = { 'm3 / s': 1, 'l / s': 1e-3, - 'm3 / h': 3.6e-3, - 'l / h': 3.6e-6 + 'm3 / h': 1 / 3600, + 'l / h': 1 / 3.6 } self.SI_units = { 'm': 'kg / s', @@ -231,7 +231,7 @@ def get_attr(self, key): return None def attr(self): - return ['m_unit', 'p_unit', 'h_unit', 'T_unit', + return ['m_unit', 'p_unit', 'h_unit', 'T_unit', 'v_unit', 'p_range', 'h_range', 'T_range'] def set_printoptions(self, **kwargs): From 10f28d42a09a8911f8618774c09b46c135fef615 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 26 Sep 2018 15:32:43 +0200 Subject: [PATCH 22/80] remodeled the compressor characteristic map --- tespy/components/characteristics.py | 525 ++++++++++++---------------- 1 file changed, 227 insertions(+), 298 deletions(-) diff --git a/tespy/components/characteristics.py b/tespy/components/characteristics.py index 1b72251df..0a22a26f5 100644 --- a/tespy/components/characteristics.py +++ b/tespy/components/characteristics.py @@ -15,300 +15,9 @@ from scipy.interpolate import interp1d import numpy as np -import matplotlib.pyplot as plt -import pandas as pd -import copy import math -def find_length(xs, ys): - dx = np.diff(xs) - dy = np.diff(ys) - return np.sum(np.sqrt(dx ** 2 + dy ** 2)) - - -class compressor: - r""" - - generic characteristic map for axial compressors - - - links mass flow to pressure rise and isentropic efficiency - - includes a vigv angle - - the map can be plotted using :code:`map.plot()` - - **literature** - - compressor map: - - - Marcin Plis, Henryk Rusinowski (2016): Mathematical modeling of an - axial compressor in a gas turbine cycle. Journal of Power - Technologies 96 (3), pp. 194-199. - - vigv: - - - GasTurb GmbH (2015): GasTurb 12. - """ - - def __init__(self, ** kwargs): - - self.raw_data = None - self.beta = None - - for key in kwargs: - if key in self.keywordargs(): - self.__dict__.update({key: kwargs[key]}) - - for key in self.keywordargs(): - if self.__dict__[key] is None: - self.__dict__.update({key: self.default(key)}) - - self.pr = copy.deepcopy(self.raw_data) - self.eta = copy.deepcopy(self.raw_data) - -# create lut for each speedline - self.pr_lut = {} - self.eta_lut = {} - for key in sorted(self.raw_data): -# pressure ratio - x = [it[0] for it in self.raw_data[key]] - y = [it[1] for it in self.raw_data[key]] -# sorted values of x and y - self.pr[key] = interp1d(x, y, 'linear') - y = self.pr[key].y - x = self.pr[key].x - l = np.insert(np.cumsum(np.sqrt(np.diff(x) ** 2 + - np.diff(y) ** 2)), 0, 0) - self.pr_lut[key] = pd.DataFrame({'x': x, 'y': y}, index=l) - -# add values for beta lines - for i in np.linspace(0, self.pr_lut[key].index[-1], self.beta): - if i not in self.pr_lut[key].index: - self.pr_lut[key].loc[i] = [np.nan, np.nan] - - self.pr_lut[key] = self.pr_lut[key].sort_index() - self.pr_lut[key] = self.pr_lut[key].interpolate(method='index') - -# isentropic efficiency - x = [it[0] for it in self.raw_data[key]] - y = [it[2] for it in self.raw_data[key]] -# sorted values of x and y - self.eta[key] = interp1d(x, y, 'linear') - y = self.eta[key].y - x = self.eta[key].x - l = np.insert(np.cumsum(np.sqrt(np.diff(x) ** 2 + - np.diff(y) ** 2)), 0, 0) - self.eta_lut[key] = pd.DataFrame({'x': x, 'y': y}, index=l) - -# add values for beta lines - for i in np.linspace(0, self.eta_lut[key].index[-1], self.beta): - if i not in self.eta_lut[key].index: - self.eta_lut[key].loc[i] = [np.nan, np.nan] - - self.eta_lut[key] = self.eta_lut[key].sort_index() - self.eta_lut[key] = self.eta_lut[key].interpolate(method='index') - - self.pr_beta = {} - self.eta_beta = {} - for i in range(self.beta): - x = [] - y = [] - for key in sorted(self.raw_data): - x += [self.pr_lut[key].loc[np.linspace( - 0, self.pr_lut[key].index[-1], self.beta - )[i]].x] - y += [self.pr_lut[key].loc[np.linspace( - 0, self.pr_lut[key].index[-1], self.beta - )[i]].y] - self.pr_beta[i] = pd.DataFrame({'x': x, 'y': y}, - index=sorted(self.raw_data)) - x = [] - y = [] - for key in sorted(self.raw_data): - x += [self.eta_lut[key].loc[np.linspace( - 0, self.eta_lut[key].index[-1], self.beta - )[i]].x] - y += [self.eta_lut[key].loc[np.linspace( - 0, self.eta_lut[key].index[-1], self.beta - )[i]].y] - self.eta_beta[i] = pd.DataFrame({'x': x, 'y': y}, - index=sorted(self.raw_data)) - -# allowed keyword arguments - def keywordargs(self): - return ['raw_data', 'beta'] - -# default values - def default(self, key): - """ - source of map: - Marcin Plis, Henryk Rusinowski (2016): Mathematical modeling of an - axial compressor in a gas turbine cycle. Journal of Power - Technologies 96 (3), pp. 194-199. - """ - - # default map - default_map = {} - default_map[1.062] = [[1.045, 1.441, 0.879], [1.056, 0.805, 0.887], - [1.052, 1.176, 0.925]] - - default_map[1.029] = [[1.014, 1.340, 0.948], [1.026, 1.082, 0.967], - [1.036, 0.767, 0.868]] - - default_map[1.000] = [[0.948, 1.195, 0.956], [0.961, 1.176, 0.958], - [0.974, 1.151, 0.962], [0.987, 1.101, 0.981], - [0.994, 1.057, 0.992], [1.000, 1.000, 1.000], - [1.005, 0.893, 0.9909], [1.006, 0.748, 0.914]] - - default_map[0.971] = [[0.874, 1.050, 0.977], [0.909, 1.019, 0.977], - [0.922, 1.000, 0.987], [0.935, 0.969, 1.004], - [0.948, 0.918, 1.008], [0.961, 0.861, 1.004], - [0.964, 0.672, 0.920]] - - default_map[0.946] = [[0.767, 0.931, 0.891], [0.819, 0.912, 1.002], - [0.838, 0.893, 1.013], [0.858, 0.861, 1.017], - [0.871, 0.817, 1.017], [0.877, 0.767, 1.013], - [0.880, 0.616, 0.925]] - - default_map[0.870] = [[0.59, 0.65, 0.887], [0.618, 0.641, 0.929], - [0.657, 0.616, 0.962], [0.676, 0.597, 0.969], - [0.7, 0.553, 0.975], [0.702, 0.528, 0.967], - [0.709, 0.477, 0.948], [0.713, 0.37, 0.887]] - - default_map[0.810] = [[0.46, 0.502, 0.872], [0.534, 0.483, 0.918], - [0.573, 0.452, 0.948], [0.586, 0.424, 0.944], - [0.599, 0.38, 0.925], [0.605, 0.348, 0.906], - [0.612, 0.276, 0.879]] - - default_val = { - 'raw_data': default_map, - 'beta': 10 - } - - return default_val[key] - -# adding speedlines to the compressor map - def add_speedline(self, n): - n = round(n, 3) - if n not in self.pr.keys(): - if n > max(self.pr.keys()) or n < min(self.pr.keys()): - return - - x = [] - y = [] - for i in range(self.beta): - self.pr_beta[i].loc[n] = [np.nan, np.nan] - self.pr_beta[i] = self.pr_beta[i].sort_index() - self.pr_beta[i] = self.pr_beta[i].interpolate(method='index') - x += [self.pr_beta[i].loc[n].x] - y += [self.pr_beta[i].loc[n].y] - - self.pr[n] = interp1d(x, y, 'linear') - - x = [] - y = [] - for i in range(self.beta): - self.eta_beta[i].loc[n] = [np.nan, np.nan] - self.eta_beta[i] = self.eta_beta[i].sort_index() - self.eta_beta[i] = self.eta_beta[i].interpolate(method='index') - x += [self.eta_beta[i].loc[n].x] - y += [self.eta_beta[i].loc[n].y] - - self.eta[n] = interp1d(x, y, 'linear') - -# get speedline as interpolation object - def get_speedline(self, n, vigv): - """ - source of speedline adaption by igv: - GasTurb GmbH (2015): GasTurb 12. - """ - n = round(n, 3) - self.add_speedline(n) - pr = interp1d(self.pr[n].x * (1 - vigv / 100), - self.pr[n].y * (1 - vigv / 100), 'linear') - eta = interp1d(self.eta[n].x * (1 - vigv / 100), - self.eta[n].y * (1 - vigv ** 2 / 10000), 'linear') - return pr, eta - -# calculate feasible vigv angles for given speedline and non dimensional mass flow - def get_vigv_range(self, n, m): - n = round(n, 3) - if n not in self.pr.keys(): - self.add_speedline(n) - - vigv_min = 100 * (1 - m / self.eta_beta[0].loc[n].x) - vigv_max = 100 * (1 - m / self.eta_beta[self.beta - 1].loc[n].x) - - return vigv_min, vigv_max - -# get pressure ratio - def get_pr(self, n, m, vigv): - return self.get_speedline(n, vigv)[0](m) - -# get isentropic efficiency - def get_eta(self, n, m, vigv): - return self.get_speedline(n, vigv)[1](m) - -# calculate vigv angle for given speedline, non dimensional mass flow and pressure ratio - def get_vigv(self, n, m, p): - n = round(n, 3) - if n not in self.pr.keys(): - self.add_speedline(n) - - tolerance = 1e-12 - d = 1e-3 - res = 1 - deriv = 1 - - vigv_range = self.get_vigv_range(n, m) - vigv = (vigv_range[0] + vigv_range[1]) / 2 - vigv_hist = [vigv] - z_hist = [res / deriv] - - while abs(res) >= tolerance: - try: - res = p - self.get_pr(n, m, vigv) - deriv = ((self.get_pr(n, m, vigv + d) - - self.get_pr(n, m, vigv - d)) / (2 * d)) - vigv += res / deriv - z_hist += [res / deriv] - except: - if vigv < vigv_range[0]: - vigv = vigv_range[0] + 1e-2 - if vigv > vigv_range[1]: - vigv = vigv_range[1] - 1e-2 - - vigv_hist += [vigv] - - if ((len(vigv_hist) > 10 and - vigv_hist[(len(vigv_hist) - 10):] == 10 * [vigv_hist[-1]]) or - (len(z_hist) > 5 and - z_hist[(len(z_hist) - 5):] == 5 * [z_hist[-1]])): - raise ValueError('Given pressure ratio can not be archieved' - ' with given speedline.') - -# print(time.time() - tmp) - return vigv - -# plot the compressor map - def plot(self): - fig, ax1 = plt.subplots() - ax2 = ax1.twinx() - for i in range(self.beta): - ax1.plot(self.pr_beta[i].x, self.pr_beta[i].y, 'xk', ms=3) - - for i in range(self.beta): - ax2.plot(self.eta_beta[i].x, self.eta_beta[i].y, 'xr', ms=3) - - ax1.set_ylabel('$p$ / $p_\mathrm{ref}$') - ax2.set_ylabel('$\eta$ / $\eta_\mathrm{ref}$') - ax1.set_xlabel('$m$ / $m_\mathrm{ref}$') - ax2.set_ylim([0, 1.1]) - ax1.set_ylim([0, 2]) - plt.sca(ax1) - plt.show() - - class characteristics: r""" @@ -399,19 +108,16 @@ class turbine(characteristics): def default(self, key): r""" - default **characteristic lines** for turbines **are designed for the - following cases**: + default characteristic lines for turbines: - \frac{\eta_\mathrm{s,t}}{\eta_\mathrm{s,t,ref}}=f\left(X \right) + .. math:: - available lines characteristics: + \frac{\eta_\mathrm{s,t}}{\eta_\mathrm{s,t,ref}}=f\left(X \right) **GENERIC** .. math:: - \frac{\eta_\mathrm{s,t}}{\eta_\mathrm{s,t,ref}}=f\left(X \right) - \text{choose calculation method for X} X = \begin{cases} @@ -626,7 +332,8 @@ class pump(characteristics): .. math:: n_q = \frac{333 \cdot n \cdot \sqrt{\dot{V}_{ref}}} - {\left(g \cdot H_{ref}\right)^{0,75}} + {\left(g \cdot H_{ref}\right)^{0,75}}\\ + \text{assuming n=}\frac{50}{s} .. note:: @@ -661,3 +368,225 @@ def char(self, v): def f_x(self, x): return self.char(x) + + +class compressor(characteristics): + r""" + + generic characteristic map for axial compressors + + - links mass flow to pressure rise and isentropic efficiency + + the map can be plotted using :code:`map.plot()` + + **literature** + + compressor map: + + - Marcin Plis, Henryk Rusinowski (2016): Mathematical modeling of an + axial compressor in a gas turbine cycle. Journal of Power + Technologies 96 (3), pp. 194-199. + + vigv: + + - GasTurb GmbH (2015): GasTurb 12. + """ + + def __init__(self, **kwargs): + + for key in kwargs: + if key not in self.attr(): + msg = ('Invalid keyword ' + key + '. Available keywords for ' + 'kwargs are: ' + str(self.attr()) + '.') + raise KeyError(msg) + + # in case of various default characteristics + method = kwargs.get('method', 'default') + + self.x = kwargs.get('x', None) + self.y = kwargs.get('y', None) + self.z1 = kwargs.get('z1', None) + self.z2 = kwargs.get('z2', None) + + if self.x is None: + self.x = self.default(method)[0] + if self.y is None: + self.y = self.default(method)[1] + + if self.z1 is None: + self.z1 = self.default(method)[2] + if self.z2 is None: + self.z2 = self.default(method)[3] + + def default(self, key): + + r""" + + default characteristic map for compressor + + .. math:: + + X = \sqrt{\frac{T_\mathrm{1,ref}}{T_\mathrm{1}}} + + Y = \frac{\dot{m}_\mathrm{1} \cdot p_\mathrm{1,ref}} + {\dot{m}_\mathrm{1,ref} \cdot p_\mathrm{1} \cdot X} + + Z1 = \frac{p_2 \cdot p_\mathrm{1,ref}}{p_1 \cdot p_\mathrm{2,ref}}= + f\left(X, Y \right) + + Z2 = \frac{\eta_\mathrm{s,c}}{\eta_\mathrm{s,c,ref}}= + f\left(X, Y \right) + + .. image:: _images/GENERIC.svg + :scale: 100 % + :alt: alternative text + :align: center + """ + + if key == 'default': + return np.array([0, 1, 2]), np.array([1, 1, 1]) + + x = {} + y = {} + z1 = {} + z2 = {} + + x['GENERIC'] = np.array([0.810, 0.870, 0.946, 0.971, 1, 1.029, 1.062]) + y['GENERIC'] = np.array([[0.460, 0.481, 0.502, 0.523, 0.543, + 0.562, 0.583, 0.598, 0.606, 0.612], + [0.590, 0.605, 0.620, 0.640, 0.660, + 0.685, 0.703, 0.710, 0.711, 0.713], + [0.767, 0.805, 0.838, 0.859, 0.87, + 0.876, 0.878, 0.878, 0.879, 0.88], + [0.874, 0.908, 0.93, 0.943, 0.953, + 0.961, 0.962, 0.963, 0.963, 0.964], + [0.948, 0.974, 0.987, 0.995, 1.0, + 1.002, 1.005, 1.005, 1.006, 1.006], + [1.014, 1.017, 1.02, 1.023, 1.026, + 1.028, 1.03, 1.032, 1.034, 1.036], + [1.045, 1.047, 1.049, 1.051, 1.052, + 1.053, 1.054, 1.054, 1.055, 1.056]]) + + z1['GENERIC'] = np.array([[0.502, 0.493, 0.485, 0.467, 0.442, + 0.411, 0.378, 0.344, 0.31, 0.276], + [0.65, 0.637, 0.617, 0.589, 0.556, + 0.519, 0.482, 0.445, 0.407, 0.37], + [0.931, 0.917, 0.893, 0.859, 0.82, + 0.779, 0.738, 0.698, 0.657, 0.616], + [1.05, 1.02, 0.982, 0.939, 0.895, + 0.851, 0.806, 0.762, 0.717, 0.672], + [1.195, 1.151, 1.102, 1.052, 1.0, + 0.951, 0.9, 0.85, 0.799, 0.748], + [1.34, 1.276, 1.213, 1.149, 1.085, + 1.022, 0.958, 0.894, 0.831, 0.767], + [1.441, 1.37, 1.3, 1.229, 1.158, + 1.088, 1.017, 0.946, 0.876, 0.805]]) + + z2['GENERIC'] = np.array([[0.872, 0.885, 0.898, 0.911, 0.925, + 0.94, 0.945, 0.926, 0.903, 0.879], + [0.887, 0.909, 0.93, 0.947, 0.963, + 0.971, 0.965, 0.939, 0.913, 0.887], + [0.891, 0.918, 0.946, 0.973, 1.001, + 1.014, 1.015, 0.986, 0.955, 0.925], + [0.977, 0.977, 0.981, 0.995, 1.007, + 1.002, 0.981, 0.961, 0.94, 0.92], + [0.956, 0.959, 0.969, 0.984, 1.0, + 0.985, 0.967, 0.95, 0.932, 0.914], + [0.948, 0.959, 0.962, 0.949, 0.935, + 0.922, 0.908, 0.895, 0.881, 0.868], + [0.879, 0.888, 0.898, 0.907, 0.916, + 0.924, 0.915, 0.906, 0.896, 0.887]]) + + return x[key], y[key], z1[key], z2[key] + + def get_pr_eta(self, x, y, igva): + """ + returns the pressure ratio and isentropic efficiency at given speedline + and correxted mass flow + + :param x: speedline + :type x: float + :param y: corrected mass flow + :type y: float + :returns: - pr (*float*) - pressure ratio + - eta (*float*) - isentropic efficiency + """ + xpos = np.searchsorted(self.x, x) + if xpos == len(self.x): + yarr = self.y[xpos - 1] + z1 = self.z1[xpos - 1] + z2 = self.z2[xpos - 1] + elif xpos == 0: + yarr = self.y[0] + z1 = self.z1[0] + z2 = self.z2[0] + else: + yfrac = (x - self.x[xpos - 1]) / (self.x[xpos] - self.x[xpos - 1]) + yarr = self.y[xpos - 1] + yfrac * (self.y[xpos] - self.y[xpos - 1]) + z1 = self.z1[xpos - 1] + yfrac * ( + self.z1[xpos] - self.z1[xpos - 1]) + z2 = self.z2[xpos - 1] + yfrac * ( + self.z2[xpos] - self.z2[xpos - 1]) + + yarr *= (1 - igva / 100) + z1 *= (1 - igva / 100) + z2 *= (1 - igva ** 2 / 10000) + + ypos = np.searchsorted(yarr, y) + if ypos == len(yarr): + return z1[ypos - 1], z2[ypos - 1] + elif ypos == 0: + return z1[0], z2[0] + else: + zfrac = (y - yarr[ypos - 1]) / (yarr[ypos] - yarr[ypos - 1]) + pr = z1[ypos - 1] + zfrac * (z1[ypos] - z1[ypos - 1]) + eta = z2[ypos - 1] + zfrac * (z2[ypos] - z2[ypos - 1]) + return pr, eta + + def get_bound_errors(self, x, y, igva): + """ + returns error messages for operation out of the maps bounds + + :param x: speedline + :type x: float + :param y: corrected mass flow + :type y: float + :returns: - msg (*float*) - errormessage + - ypos (*float*) - position of corrected mass flow + """ + xpos = np.searchsorted(self.x, x) + if xpos == len(self.x): + yarr = self.y[xpos - 1] + msg = ('##### WARNING #####\n' + 'Operating point above compressor map range: ' + 'X=' + str(round(x, 3)) + ' with maximum of ' + + str(self.x[-1])) + return msg + elif xpos == 0: + yarr = self.y[0] + msg = ('##### WARNING #####\n' + 'Operating point below compressor map range: ' + 'X=' + str(round(x, 3)) + ' with minimum of ' + + str(self.x[0])) + return msg + else: + yfrac = (x - self.x[xpos - 1]) / (self.x[xpos] - self.x[xpos - 1]) + yarr = self.y[xpos - 1] + yfrac * (self.y[xpos] - self.y[xpos - 1]) + + yarr *= (1 - igva / 100) + + ypos = np.searchsorted(yarr, y) + if ypos == len(yarr): + msg = ('##### WARNING #####\n' + 'Operating point above compressor map range: ' + 'Y=' + str(round(y, 3)) + ' with maximum of ' + + str(yarr[-1])) + return msg + elif ypos == 0: + msg = ('##### WARNING #####\n' + 'Operating point below compressor map range: ' + 'Y=' + str(round(y, 3)) + ' with minimum of ' + + str(yarr[0])) + return msg + else: + return None From 8e8a9581f684bb37eb2175d8548a7fd6c1a5a6ba Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 26 Sep 2018 15:34:05 +0200 Subject: [PATCH 23/80] added characteristics plots for compressor --- tespy/components/characteristics.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tespy/components/characteristics.py b/tespy/components/characteristics.py index 0a22a26f5..f0f348c24 100644 --- a/tespy/components/characteristics.py +++ b/tespy/components/characteristics.py @@ -437,7 +437,12 @@ def default(self, key): Z2 = \frac{\eta_\mathrm{s,c}}{\eta_\mathrm{s,c,ref}}= f\left(X, Y \right) - .. image:: _images/GENERIC.svg + .. image:: _images/CMAP_GENERIC_PR.svg + :scale: 100 % + :alt: alternative text + :align: center + + .. image:: _images/CMAP_GENERIC_ETA.svg :scale: 100 % :alt: alternative text :align: center From 675050b1bbcf10d682e37c8e3fa2aed2d69f4c5f Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 26 Sep 2018 15:34:50 +0200 Subject: [PATCH 24/80] adjusted compressor characteristic functions, added igv-angle as custom variable --- tespy/components/components.py | 214 ++++++++++++--------------------- 1 file changed, 75 insertions(+), 139 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index 0234719c9..e8c30b64d 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -1428,10 +1428,10 @@ def calc_parameters(self, nw, mode): if (mode == 'pre' and 'eta_s' in self.offdesign) or mode == 'post': self.eta_s.val = ((self.h_os('post') - self.inl[0].h.val_SI) / (self.outl[0].h.val_SI - self.inl[0].h.val_SI)) - if self.eta_s.val > 1 or self.eta_s.val <= 0 and nw.comperr: + if (self.eta_s.val > 1 or self.eta_s.val <= 0) and nw.comperr: msg = ('##### ERROR #####\n' 'Invalid value for isentropic efficiency: ' - 'eta_s =', self.eta_s.val) + 'eta_s =' + str(self.eta_s.val) + ' at ' + self.label) print(msg) nw.errors += [self] @@ -1456,7 +1456,7 @@ class compressor(turbomachine): - pr: outlet to inlet pressure ratio, :math:`[pr]=1` - char_map: characteristic map for compressors, map is generated in preprocessing of offdesign calculations - - vigv: variable inlet guide vane angle, :math:`[vigv]=^\circ` + - igva: inlet guide vane angle, :math:`[igva]=^\circ` **equations** @@ -1481,14 +1481,22 @@ class compressor(turbomachine): :align: center """ + def comp_init(self, nw): + + component.comp_init(self, nw) + + if self.char_map.func is None: + method = self.char_map.method + self.char_map.func = cmp_char.compressor(method=method) + def component(self): return 'compressor' def attr(self): - return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), 'vigv': dc_cp(), + return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), + 'igva': dc_cp(min_val=-10, max_val=30, d=1e-3), 'Sirr': dc_cp(), - 'char_map': dc_cc(func=cmp_char.compressor(), - x=[0, 1, 2], y=[0, 1, 2])} + 'char_map': dc_cc(method='GENERIC')} def default_offdesign(self): return ['char_map'] @@ -1572,35 +1580,33 @@ def char_func(self): r""" equation(s) for characteristics of compressor - - returns one value, if vigv is not set - - returns two values, if vigv is set - :returns: val (*numpy array*) - residual value(s) of equation(s): - - - :code:`np.array([val1, val2])` if vigv_set - - :code:`np.array([val2])` else + :returns: val (:code:`np.array([Z1, Z2])`) - residual values of + equations: .. math:: - n = \sqrt{\frac{T_{in,ref}}{T_{in}}}\\ - m = \frac{\dot{m}_{in} \cdot \sqrt{T_{in}} \cdot p_{in,ref}} - {\dot{m}_{in,ref} \cdot \sqrt{T_{in,ref}} \cdot p_{in}}\\ - val_1 = \frac{p_{out} \cdot p_{in,ref}}{p_{in} \cdot p_{out,ref}} - - pr_{c}(char(m))\\ - val_2 = \frac{\eta_{s,c}}{\eta_{s,c,ref}} - \eta_{s,c}(char(m)) + X = \sqrt{\frac{T_\mathrm{1,ref}}{T_\mathrm{1}}} + + Y = \frac{\dot{m}_\mathrm{1} \cdot p_\mathrm{1,ref}} + {\dot{m}_\mathrm{1,ref} \cdot p_\mathrm{1} \cdot X} + + Z1 = \frac{p_2 \cdot p_\mathrm{1,ref}}{p_1 \cdot p_\mathrm{2,ref}}- + pr_{c}(char(m)) + + Z2 = \frac{\eta_\mathrm{s,c}}{\eta_\mathrm{s,c,ref}} - + \eta_{s,c}(char(m)) **parameters** - - n: speedline index (rotational speed is constant) - - m: nondimensional mass flow - - val1: change ratio to reference case in mass flow and pressure - - val2: change of isentropic efficiency to reference case + - X: speedline index (rotational speed is constant) + - Y: nondimensional mass flow + - Z1: change ratio to reference case in mass flow and pressure + - Z2: change of isentropic efficiency to reference case **logic** - - calculate n - - calculate m - - calculate dn for convergence stability reasons (move speedline - inside of feasible range of compressor map) + - calculate X + - calculate Y **if vigv is set** @@ -1613,52 +1619,20 @@ def char_func(self): **else** - - set vigv (from compressor map with pressure ratio) - - calculate relative factor for isentropic efficiency + - calculate Z1 and Z2 """ i = self.inl[0].to_flow() o = self.outl[0].to_flow() - n = math.sqrt(T_mix_ph(self.i0)) / math.sqrt(T_mix_ph(i)) - m = (i[0] * math.sqrt(T_mix_ph(i)) * self.i0[1] / - (self.i0[0] * math.sqrt(T_mix_ph(self.i0)) * i[1])) - - dn = 0 + x = math.sqrt(T_mix_ph(self.i0)) / math.sqrt(T_mix_ph(i)) + y = (i[0] * self.i0[1]) / (self.i0[0] * i[1] * x) - if n < min(self.char_map.func.pr.keys()): - dn = min(self.char_map.func.pr.keys()) - n - if n > max(self.char_map.func.pr.keys()): - dn = max(self.char_map.func.pr.keys()) - n + pr, eta = self.char_map.func.get_pr_eta(x, y, self.igva.val) - if self.vigv.is_set: + z1 = o[1] * self.i0[1] / (i[1] * self.o0[1]) - pr + z2 = ((self.h_os('post') - i[2]) / (o[2] - i[2])) / ( + self.dh_s0 / (self.o0[2] - self.i0[2])) - eta - vigv_range = self.char_map.func.get_vigv_range(n + dn, m) - - dvigv = 0 - if self.vigv.val < vigv_range[0]: - dvigv = vigv_range[0] - self.vigv.val + 0.01 - if self.vigv.val > vigv_range[1]: - dvigv = vigv_range[1] - self.vigv.val - 0.01 - - speedline = self.char_map.func.get_speedline(n + dn, - self.vigv.val + dvigv) - - return np.array([ - o[1] * self.i0[1] / (i[1] * self.o0[1]) - speedline[0](m), - ((self.h_os('post') - i[2]) / (o[2] - i[2])) / - (self.dh_s0 / (self.o0[2] - self.i0[2])) - - speedline[1](m) - ]) - - else: - - self.vigv.val = self.char_map.func.get_vigv( - n + dn, m, o[1] / i[1] / (self.o0[1] / self.i0[1])) - - return np.array([ - ((self.h_os('post') - i[2]) / (o[2] - i[2])) / - (self.dh_s0 / (self.o0[2] - self.i0[2])) - - self.char_map.func.get_eta(n + dn, m, self.vigv.val) - ]) + return np.array([z1, z2]) def char_deriv(self): r""" @@ -1685,27 +1659,24 @@ def char_deriv(self): p21 = self.ddx_func(self.char_func, 'p', 1) h21 = self.ddx_func(self.char_func, 'h', 1) - if self.vigv.is_set: - deriv = np.zeros((2, 2, num_fl + 3)) - deriv[0, 0, 0] = m11[0] - deriv[0, 0, 1] = p11[0] - deriv[0, 0, 2] = h11[0] - deriv[0, 1, 1] = p21[0] - deriv[0, 1, 2] = h21[0] - deriv[1, 0, 0] = m11[1] - deriv[1, 0, 1] = p11[1] - deriv[1, 0, 2] = h11[1] - deriv[1, 1, 1] = p21[1] - deriv[1, 1, 2] = h21[1] - return deriv.tolist() - else: - deriv = np.zeros((1, 2, num_fl + 3)) - deriv[0, 0, 0] = m11[0] - deriv[0, 0, 1] = p11[0] - deriv[0, 0, 2] = h11[0] - deriv[0, 1, 1] = p21[0] - deriv[0, 1, 2] = h21[0] - return deriv.tolist() + if self.igva.is_var: + igva = self.ddx_func(self.char_func, 'igva', 1) + + deriv = np.zeros((2, 2 + self.num_c_vars, num_fl + 3)) + deriv[0, 0, 0] = m11[0] + deriv[0, 0, 1] = p11[0] + deriv[0, 0, 2] = h11[0] + deriv[0, 1, 1] = p21[0] + deriv[0, 1, 2] = h21[0] + deriv[1, 0, 0] = m11[1] + deriv[1, 0, 1] = p11[1] + deriv[1, 0, 2] = h11[1] + deriv[1, 1, 1] = p21[1] + deriv[1, 1, 2] = h21[1] + if self.igva.is_var: + deriv[0, 2 + self.igva.var_pos, 0] = igva[0] + deriv[1, 2 + self.igva.var_pos, 0] = igva[1] + return deriv.tolist() def convergence_check(self, nw): """ @@ -1792,51 +1763,31 @@ def calc_parameters(self, nw, mode): - generate characteristics for component """ + if (mode == 'post' and nw.mode == 'offdesign' and + self.char_map.is_set): + + if nw.compwarn: + + i = self.inl[0].to_flow() + x = math.sqrt(T_mix_ph(self.i0)) / math.sqrt(T_mix_ph(i)) + y = (i[0] * self.i0[1]) / (self.i0[0] * i[1] * x) + + msg = self.char_map.func.get_bound_errors(x, y, self.igva.val) + if msg is not None: + print(msg + ' at ' + self.label) + turbomachine.calc_parameters(self, nw, mode) if (mode == 'pre' and 'eta_s' in self.offdesign) or mode == 'post': self.eta_s.val = ((self.h_os('post') - self.inl[0].h.val_SI) / (self.outl[0].h.val_SI - self.inl[0].h.val_SI)) - if self.eta_s.val > 1 or self.eta_s.val <= 0 and nw.comperr: + if (self.eta_s.val > 1 or self.eta_s.val <= 0) and nw.comperr: msg = ('##### ERROR #####\n' 'Invalid value for isentropic efficiency: ' - 'eta_s =', self.eta_s.val) + 'eta_s =' + str(self.eta_s.val) + ' at ' + self.label) print(msg) nw.errors += [self] -# if (mode == 'pre' and 'char_map' in self.offdesign): -# print('Creating characteristics for component ', self) -# self.char_map.func = cmp_char.compressor() - -# def print_parameters(self, nw): -# -# turbomachine.print_parameters(self, nw) -# -# i1 = self.inl[0].to_flow() -# o1 = self.outl[0].to_flow() -# -# if self.char_map.is_set: -# n = math.sqrt(T_mix_ph(self.i0)) / math.sqrt(T_mix_ph(i1)) -# m = ( -# (i1[0] * math.sqrt(T_mix_ph(i1)) / i1[1]) / -# (self.i0[0] * math.sqrt(T_mix_ph(self.i0)) / self.i0[1]) -# ) -# vigv = self.char_map.func.get_vigv(n, m, (o1[1] * self.i0[1]) / -# (i1[1] * self.o0[1])) -# if abs(self.vigv.val - vigv) > err and nw.compwarn: -# msg = ('##### WARNING #####\n' -# 'Selected inlet guide vane angle is not feasible.') -# if self.vigv.val > vigv: -# msg += ('calculated maximum angle: ' + str(vigv) + -# ' selected: ' + str(self.vigv.val)) -# else: -# msg += ('calculated minimum angle: ' + str(vigv) + -# ' selected: ' + str(self.vigv.val)) -# print(msg) -# -# else: -# print('vigv =', self.vigv.val) - # %% @@ -2172,10 +2123,10 @@ def calc_parameters(self, nw, mode): if (mode == 'pre' and 'eta_s' in self.offdesign) or mode == 'post': self.eta_s.val = ((self.outl[0].h.val_SI - self.inl[0].h.val_SI) / (self.h_os('post') - self.inl[0].h.val_SI)) - if self.eta_s.val > 1 or self.eta_s.val <= 0 and nw.comperr: + if (self.eta_s.val > 1 or self.eta_s.val <= 0) and nw.comperr: msg = ('##### ERROR #####\n' 'Invalid value for isentropic efficiency: ' - 'eta_s =', self.eta_s.val) + 'eta_s =' + str(self.eta_s.val) + ' at ' + self.label) print(msg) nw.errors += [self] @@ -4825,21 +4776,6 @@ def initialise_target(self, c, key): else: return 0 - def convergence_check(self, nw): - r""" - prevent bad values for fluid properties in calculation - - :param nw: network using this component object - :type nw: tespy.networks.network - :returns: no return value - """ - - for var in self.vars.keys(): - if var.val < var.min_val: - var.val = var.min_val - if var.val > var.max_val: - var.val = var.max_val - def calc_parameters(self, nw, mode): if mode == 'post': From 9b61c9aee44519a82aa36d36f811dc6f59285290 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 26 Sep 2018 15:35:32 +0200 Subject: [PATCH 25/80] convergence improvements for custom vars --- tespy/networks.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/tespy/networks.py b/tespy/networks.py index 0ba176f70..aaa635f0f 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -1024,7 +1024,6 @@ def solve(self, mode, init_file=None, design_file=None, dec='.', self.vec_res = [] self.iter = 0 - self.relax = 1 self.num_restart = 0 # number of variables self.num_vars = len(self.fluids) + 3 @@ -1070,7 +1069,7 @@ def solve(self, mode, init_file=None, design_file=None, dec='.', str(round(self.iter / (end_time - start_time), 2))) print(msg) - if self.nwkwarn: + if self.nwkwarn and errmsg is not None: print(errmsg) if self.lin_dep: @@ -1172,9 +1171,11 @@ def solve_loop(self): msg += ' | nan' print(msg) - # stop calculation after rediculous amount of iterations - if self.iter > 3 and self.res[-1] < hlp.err ** (1 / 2): - return '' + msg = None + + if ((self.iter > 3 and self.res[-1] < hlp.err ** (1 / 2)) or + self.lin_dep): + return msg if self.iter > 15: if (all(self.res[(self.iter - 3):] >= self.res[-2]) and @@ -1185,9 +1186,6 @@ def solve_loop(self): '{:.2e}'.format(norm(self.vec_res))) return msg - if self.lin_dep: - return '' - if self.iter == self.max_iter - 1: msg = ('##### WARNING #####\n' 'Reached maximum iteration count, calculation ' @@ -1233,14 +1231,14 @@ def solve_control(self): i = 0 for c in self.conns.index: if not c.m.val_set: - c.m.val_SI += self.vec_z[i * (self.num_vars)] * self.relax + c.m.val_SI += self.vec_z[i * (self.num_vars)] if not c.p.val_set: # this prevents negative pressures relax = max(1, -self.vec_z[i * (self.num_vars) + 1] / (0.5 * c.p.val_SI)) c.p.val_SI += self.vec_z[i * (self.num_vars) + 1] / relax if not c.h.val_set: - c.h.val_SI += self.vec_z[i * (self.num_vars) + 2] * self.relax + c.h.val_SI += self.vec_z[i * (self.num_vars) + 2] j = 0 for fluid in self.fluids: @@ -1264,7 +1262,13 @@ def solve_control(self): pos = var.var_pos var.val += self.vec_z[ self.num_vars * len(self.conns) + - c_vars + pos] * self.relax + c_vars + pos] + + if var.val < var.min_val: + var.val = var.min_val + if var.val > var.max_val: + var.val = var.max_val + c_vars += cp.num_c_vars if cp.num_c_vars > 0: From 15adc4f44b6d853ff8d406b411492cb580f9555c Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 26 Sep 2018 15:35:57 +0200 Subject: [PATCH 26/80] added characteristics plots for compressor --- doc/api/_images/CMAP_GENERIC_ETA.svg | 967 +++++++++++++++++++++++++++ doc/api/_images/CMAP_GENERIC_PR.svg | 918 +++++++++++++++++++++++++ 2 files changed, 1885 insertions(+) create mode 100644 doc/api/_images/CMAP_GENERIC_ETA.svg create mode 100644 doc/api/_images/CMAP_GENERIC_PR.svg diff --git a/doc/api/_images/CMAP_GENERIC_ETA.svg b/doc/api/_images/CMAP_GENERIC_ETA.svg new file mode 100644 index 000000000..80c0610a7 --- /dev/null +++ b/doc/api/_images/CMAP_GENERIC_ETA.svg @@ -0,0 +1,967 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/api/_images/CMAP_GENERIC_PR.svg b/doc/api/_images/CMAP_GENERIC_PR.svg new file mode 100644 index 000000000..d8f5c4546 --- /dev/null +++ b/doc/api/_images/CMAP_GENERIC_PR.svg @@ -0,0 +1,918 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From cddd081c0279e3dfecc8087529853d87d0264d9b Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Fri, 28 Sep 2018 10:04:48 +0200 Subject: [PATCH 27/80] adjusted custom variable handling --- tespy/components/components.py | 10 ++++---- tespy/networks.py | 46 ++++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index e8c30b64d..07871cea5 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -1494,7 +1494,7 @@ def component(self): def attr(self): return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), - 'igva': dc_cp(min_val=-10, max_val=30, d=1e-3), + 'igva': dc_cp(min_val=-45, max_val=45, d=1e-2), 'Sirr': dc_cp(), 'char_map': dc_cc(method='GENERIC')} @@ -4366,10 +4366,10 @@ def comp_init(self, nw): def attr(self): return {'Q': dc_cp(), 'pr': dc_cp(), 'zeta': dc_cp(), - 'D': dc_cp(min_val=1e-2, max_val=2, d=1e-4), - 'L': dc_cp(min_val=1e-3, d=1e-3), - 'ks': dc_cp(min_val=1e-7, max_val=1e-4, d=1e-7), - 'kA': dc_cp(min_val=100, d=1), + 'D': dc_cp(min_val=1e-2, max_val=2, d=1e-3), + 'L': dc_cp(min_val=1e-1, d=1e-3), + 'ks': dc_cp(min_val=1e-7, max_val=1e-4, d=1e-8), + 'kA': dc_cp(min_val=1, d=1), 't_a': dc_cp(), 't_a_design': dc_cp(), 'kA_char': dc_cc(method='HE_HOT', param='m'), 'SQ1': dc_cp(), 'SQ2': dc_cp(), 'Sirr': dc_cp(), diff --git a/tespy/networks.py b/tespy/networks.py index aaa635f0f..b97a81d3b 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -1133,7 +1133,8 @@ def solve_loop(self): '----------+---------') print(msg) - +# self.relax = 1 +# self.reset_relax = 0 for self.iter in range(self.max_iter): self.solve_control() @@ -1256,23 +1257,35 @@ def solve_control(self): j += 1 i += 1 - c_vars = 0 - for cp in self.comps.index: - for var in cp.vars.keys(): - pos = var.var_pos - var.val += self.vec_z[ - self.num_vars * len(self.conns) + - c_vars + pos] - - if var.val < var.min_val: - var.val = var.min_val - if var.val > var.max_val: - var.val = var.max_val + if self.num_c_vars > 0: - c_vars += cp.num_c_vars +# self.var_hist[:, self.iter] = ( +# self.vec_z[self.num_vars * len(self.conns):]) +# a = self.var_hist +# self.var_hist = np.zeros((self.num_c_vars, self.iter + 2)) +# self.var_hist[:, :-1] = a - if cp.num_c_vars > 0: - cp.convergence_check(self) + c_vars = 0 + for cp in self.comps.index: + for var in cp.vars.keys(): + pos = var.var_pos +# if np.isin(self.var_hist[c_vars + pos, self.iter], +# self.var_hist[c_vars + pos, :-2]): +# self.relax = 0.2 +# self.reset_relax = self.iter +# elif self.reset_relax + 10 == self.iter: +# self.relax = 1 + + var.val += self.vec_z[ + self.num_vars * len(self.conns) + + c_vars + pos] * self.relax + + if var.val < var.min_val: + var.val = var.min_val + if var.val > var.max_val: + var.val = var.max_val + + c_vars += cp.num_c_vars # check properties for consistency if self.iter < 3 and self.init_file is None: @@ -1912,6 +1925,7 @@ def solve_determination(self): self.num_c_vars += cp.num_c_vars n += len(cp.equations()) +# self.var_hist = np.zeros((self.num_c_vars, 1)) for c in self.conns.index: n += [c.m.val_set, c.p.val_set, c.h.val_set, c.T.val_set, c.x.val_set, c.v.val_set].count(True) From 8e8a336735fb3a9929ebfededc23d534aba0cb11 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Fri, 28 Sep 2018 10:06:34 +0200 Subject: [PATCH 28/80] added missing attribute self.relax --- tespy/networks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tespy/networks.py b/tespy/networks.py index b97a81d3b..63e670b0a 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -1133,7 +1133,8 @@ def solve_loop(self): '----------+---------') print(msg) -# self.relax = 1 + + self.relax = 1 # self.reset_relax = 0 for self.iter in range(self.max_iter): From 71cae481a2dabfdd92334f23bf87433fb4a571f7 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Mon, 1 Oct 2018 11:27:40 +0200 Subject: [PATCH 29/80] fixed derivatives for compressor with custom vars --- tespy/components/characteristics.py | 8 ++++---- tespy/components/components.py | 13 ++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/tespy/components/characteristics.py b/tespy/components/characteristics.py index f0f348c24..528243455 100644 --- a/tespy/components/characteristics.py +++ b/tespy/components/characteristics.py @@ -560,14 +560,14 @@ def get_bound_errors(self, x, y, igva): - ypos (*float*) - position of corrected mass flow """ xpos = np.searchsorted(self.x, x) - if xpos == len(self.x): + if xpos == len(self.x) and x != self.x[-1]: yarr = self.y[xpos - 1] msg = ('##### WARNING #####\n' 'Operating point above compressor map range: ' 'X=' + str(round(x, 3)) + ' with maximum of ' + str(self.x[-1])) return msg - elif xpos == 0: + elif xpos == 0 and y != self.x[0]: yarr = self.y[0] msg = ('##### WARNING #####\n' 'Operating point below compressor map range: ' @@ -581,13 +581,13 @@ def get_bound_errors(self, x, y, igva): yarr *= (1 - igva / 100) ypos = np.searchsorted(yarr, y) - if ypos == len(yarr): + if ypos == len(yarr) and y != yarr[-1]: msg = ('##### WARNING #####\n' 'Operating point above compressor map range: ' 'Y=' + str(round(y, 3)) + ' with maximum of ' + str(yarr[-1])) return msg - elif ypos == 0: + elif ypos == 0 and y != yarr[0]: msg = ('##### WARNING #####\n' 'Operating point below compressor map range: ' 'Y=' + str(round(y, 3)) + ' with minimum of ' + diff --git a/tespy/components/components.py b/tespy/components/components.py index 07871cea5..45f10b120 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -168,7 +168,6 @@ def set_attr(self, **kwargs): self.get_attr(key).set_attr(is_var=False) elif kwargs[key] == 'var': - self.get_attr(key).set_attr(val=1) self.get_attr(key).set_attr(is_set=True) self.get_attr(key).set_attr(is_var=True) @@ -926,14 +925,14 @@ def derivatives(self, nw): mat_deriv += self.mass_flow_deriv() if self.P.is_set: - P_deriv = np.zeros((1, 2, num_fl + 3)) + P_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) P_deriv[0, 0, 0] = self.outl[0].h.val_SI - self.inl[0].h.val_SI P_deriv[0, 0, 2] = -self.inl[0].m.val_SI P_deriv[0, 1, 2] = self.inl[0].m.val_SI mat_deriv += P_deriv.tolist() if self.pr.is_set: - pr_deriv = np.zeros((1, 2, num_fl + 3)) + pr_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) pr_deriv[0, 0, 1] = self.pr.val pr_deriv[0, 1, 1] = -1 mat_deriv += pr_deriv.tolist() @@ -1212,7 +1211,7 @@ def eta_s_deriv(self): """ num_fl = len(self.inl[0].fluid.val) - mat_deriv = np.zeros((1, 2, num_fl + 3)) + mat_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) for i in range(2): mat_deriv[0, i, 1] = self.ddx_func(self.eta_s_func, 'p', i) @@ -1494,7 +1493,7 @@ def component(self): def attr(self): return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), - 'igva': dc_cp(min_val=-45, max_val=45, d=1e-2), + 'igva': dc_cp(min_val=-45, max_val=45, d=1e-2, val=0), 'Sirr': dc_cp(), 'char_map': dc_cc(method='GENERIC')} @@ -1565,7 +1564,7 @@ def eta_s_deriv(self): """ num_fl = len(self.inl[0].fluid.val) - mat_deriv = np.zeros((1, 2, num_fl + 3)) + mat_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) for i in range(2): mat_deriv[0, i, 1] = self.ddx_func(self.eta_s_func, 'p', i) @@ -1933,7 +1932,7 @@ def eta_s_deriv(self): """ num_fl = len(self.inl[0].fluid.val) - mat_deriv = np.zeros((1, 2, num_fl + 3)) + mat_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) if abs(self.eta_s_res) > err ** (2): From 75b19cefc752cfb62bb9aea0e75b10bbc6707f41 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Mon, 1 Oct 2018 11:30:08 +0200 Subject: [PATCH 30/80] changed standard value for component properties to 1 --- tespy/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tespy/helpers.py b/tespy/helpers.py index ebda52fc1..d3600ea45 100644 --- a/tespy/helpers.py +++ b/tespy/helpers.py @@ -148,7 +148,7 @@ class dc_cp(data_container): val will be used as starting value """ def attr(self): - return {'val': 0, 'val_SI': 0, 'is_set': False, 'printout': True, + return {'val': 1, 'val_SI': 0, 'is_set': False, 'printout': True, 'd': 1e-4, 'min_val': 0, 'max_val': 1e12, 'is_var': False} From 111943f7550146bdf213a2f61ba88bd92a2668b5 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Mon, 1 Oct 2018 11:31:06 +0200 Subject: [PATCH 31/80] increased number of iterations for convergence progress check --- tespy/networks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tespy/networks.py b/tespy/networks.py index 63e670b0a..909bf78c6 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -1179,7 +1179,7 @@ def solve_loop(self): self.lin_dep): return msg - if self.iter > 15: + if self.iter > 20: if (all(self.res[(self.iter - 3):] >= self.res[-2]) and self.res[-1] >= self.res[-2]): msg = ('##### WARNING #####\n' From 977a5be7f8441c1d67ff83feeabd13645cb51052 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 2 Oct 2018 09:05:11 +0200 Subject: [PATCH 32/80] custom variables available for turbomachinery, vessel, simple_heat_exchanger, pipe, solar_collector --- tespy/components/components.py | 295 +++++++++++++++++++++------------ 1 file changed, 185 insertions(+), 110 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index 45f10b120..f12219ae8 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -635,7 +635,7 @@ def ddx_func(self, func, dx, pos): :param dx: dx :type dx: str :param pos: position of inlet or outlet, logic: ['in1', 'in2', ..., - 'out1', ...] -> 0, 1, ..., n, n + 1, ... + 'out1', ...] -> 0, 1, ..., n, n + 1, ..., n + m :type pos: int :returns: deriv (list or float) - partial derivative of the function func to dx @@ -1263,7 +1263,7 @@ def char_deriv(self): """ num_fl = len(self.inl[0].fluid.val) - mat_deriv = np.zeros((1, 2, num_fl + 3)) + mat_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) mat_deriv[0, 0, 0] = ( self.ddx_func(self.char_func, 'm', 0)) @@ -1318,7 +1318,7 @@ def flow_char_deriv(self): """ num_fl = len(self.inl[0].fluid.val) - mat_deriv = np.zeros((1, 2, num_fl + 3)) + mat_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) mat_deriv[0, 0, 0] = self.ddx_func(self.flow_char_func, 'm', 0) mat_deriv[0, 0, 2] = self.ddx_func(self.flow_char_func, 'h', 0) @@ -1895,7 +1895,7 @@ def additional_derivatives(self, nw): mat_deriv += self.char_deriv() if self.cone.is_set: - cone_deriv = np.zeros((1, 2, num_fl + 3)) + cone_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) cone_deriv[0, 0, 0] = -1 cone_deriv[0, 0, 1] = self.ddx_func(self.cone_func, 'p', 0) cone_deriv[0, 0, 2] = self.ddx_func(self.cone_func, 'h', 0) @@ -2029,7 +2029,7 @@ def char_deriv(self): """ num_fl = len(self.inl[0].fluid.val) - mat_deriv = np.zeros((1, 2, num_fl + 3)) + mat_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) mat_deriv[0, 0, 0] = self.ddx_func(self.char_func, 'm', 0) for i in range(2): @@ -2201,20 +2201,17 @@ def equations(self): - :func:`tespy.components.components.component.mass_flow_res` .. math:: + 0 = p_{in} - p_{out,i} \; \forall i \in \mathrm{outlets} - **equations for splitter** + **splitter** - .. math:: - 0 = h_{in} - h_{out,i} \; - \forall i \in \mathrm{outlets}\\ + - :func:`tespy.components.components.splitter.additional_equations` - **equations for separator** + **separator** - .. math:: - 0 = T_{in} - T_{out,i} \; - \forall i \in \mathrm{outlets}\\ + - :func:`tespy.components.components.separator.additional_equations` **TODO** @@ -2230,20 +2227,7 @@ def equations(self): for o in self.outl: vec_res += [self.inl[0].p.val_SI - o.p.val_SI] - # different equations for splitter and separator - if isinstance(self, splitter): - for o in self.outl: - vec_res += [self.inl[0].h.val_SI - o.h.val_SI] - - # different equations for splitter and separator - if isinstance(self, separator): - if num_fluids(self.inl[0].fluid.val) <= 1: - for o in self.outl: - vec_res += [self.inl[0].h.val_SI - o.h.val_SI] - else: - for o in self.outl: - vec_res += [T_mix_ph(self.inl[0].to_flow()) - - T_mix_ph(o.to_flow())] + vec_res += self.additional_equations() return vec_res @@ -2272,46 +2256,35 @@ def derivatives(self, nw): k += 1 mat_deriv += p_deriv.tolist() - if isinstance(self, splitter): + mat_deriv += self.additional_derivatives(nw) - h_deriv = np.zeros((self.num_o, 1 + self.num_o, num_fl + 3)) - k = 0 - for o in self.outl: - h_deriv[k, 0, 2] = 1 - h_deriv[k, k + 1, 2] = -1 - k += 1 + return np.asarray(mat_deriv) - mat_deriv += h_deriv.tolist() + def additional_equations(self): + r""" + additional equations for component split - if isinstance(self, separator): - if num_fluids(self.inl[0].fluid.val) <= 1: + :param nw: network using this component object + :type nw: tespy.networks.network + :returns: vec_res (*list*) - residual value vector - h_deriv = np.zeros((self.num_o, 1 + self.num_o, num_fl + 3)) - k = 0 - for o in self.outl: - h_deriv[k, 0, 2] = 1 - h_deriv[k, k + 1, 2] = -1 - k += 1 + empty list is returned for this component + """ - mat_deriv += h_deriv.tolist() - else: + return [] - T_deriv = np.zeros((self.num_o, 1 + self.num_o, num_fl + 3)) - i = self.inl[0].to_flow() - k = 0 - for o in self.outl: - o = o.to_flow() - T_deriv[k, 0, 1] = dT_mix_dph(i) - T_deriv[k, 0, 2] = dT_mix_pdh(i) - T_deriv[k, 0, 3:] = dT_mix_ph_dfluid(i) - T_deriv[k, k + 1, 1] = -dT_mix_dph(o) - T_deriv[k, k + 1, 2] = -dT_mix_pdh(o) - T_deriv[k, k + 1, 3:] = -1 * dT_mix_ph_dfluid(o) - k += 1 + def additional_derivatives(self, nw): + r""" + derivatives for additional equations of component split - mat_deriv += T_deriv.tolist() + :param nw: network using this component object + :type nw: tespy.networks.network + :returns: mat_deriv (*list*) - matrix of partial derivatives - return np.asarray(mat_deriv) + empty matrix is returned for this component + """ + + return [] def initialise_source(self, c, key): r""" @@ -2357,6 +2330,7 @@ def initialise_target(self, c, key): # %% + class splitter(split): """ **available parameters** @@ -2381,6 +2355,49 @@ class splitter(split): def component(self): return 'splitter' + def additional_equations(self): + r""" + additional equations for component splitter + + :param nw: network using this component object + :type nw: tespy.networks.network + :returns: vec_res (*list*) - residual value vector + + **emandatory quations for splitter** + + .. math:: + 0 = h_{in} - h_{out,i} \; + \forall i \in \mathrm{outlets}\\ + """ + vec_res = [] + + for o in self.outl: + vec_res += [self.inl[0].h.val_SI - o.h.val_SI] + + return vec_res + + def additional_derivatives(self, nw): + r""" + derivatives for additional equations of component splitter + + :param nw: network using this component object + :type nw: tespy.networks.network + :returns: mat_deriv (*list*) - matrix of partial derivatives + """ + num_fl = len(nw.fluids) + mat_deriv = [] + + h_deriv = np.zeros((self.num_o, 1 + self.num_o, num_fl + 3)) + k = 0 + for o in self.outl: + h_deriv[k, 0, 2] = 1 + h_deriv[k, k + 1, 2] = -1 + k += 1 + + mat_deriv += h_deriv.tolist() + + return mat_deriv + # %% @@ -2408,6 +2425,64 @@ class separator(split): def component(self): return 'separator' + def additional_equations(self): + r""" + additional equations for component separator + + :param nw: network using this component object + :type nw: tespy.networks.network + :returns: vec_res (*list*) - residual value vector + + **mandatory equations for separator** + + .. math:: + + 0 = T_{in} - T_{out,i} \; + \forall i \in \mathrm{outlets} + """ + vec_res = [] + + for o in self.outl: + vec_res += [T_mix_ph(self.inl[0].to_flow()) - + T_mix_ph(o.to_flow())] + + return vec_res + + def additional_derivatives(self, nw): + r""" + derivatives for additional equations of component separator + + :param nw: network using this component object + :type nw: tespy.networks.network + :returns: mat_deriv (*list*) - matrix of partial derivatives + + **derivatives for separator** + + .. math:: + + 0 = T_{in} - T_{out,i} \; + \forall i \in \mathrm{outlets} + """ + num_fl = len(nw.fluids) + mat_deriv = [] + + deriv = np.zeros((self.num_o, 1 + self.num_o, num_fl + 3)) + i = self.inl[0].to_flow() + k = 0 + for o in self.outl: + o = o.to_flow() + deriv[k, 0, 1] = dT_mix_dph(i) + deriv[k, 0, 2] = dT_mix_pdh(i) + deriv[k, 0, 3:] = dT_mix_ph_dfluid(i) + deriv[k, k + 1, 1] = -dT_mix_dph(o) + deriv[k, k + 1, 2] = -dT_mix_pdh(o) + deriv[k, k + 1, 3:] = -1 * dT_mix_ph_dfluid(o) + k += 1 + + mat_deriv += deriv.tolist() + + return mat_deriv + # %% @@ -5577,57 +5652,57 @@ def kA_func(self): (T_o1 - T_i2 - T_i1 + T_o2) / math.log((T_o1 - T_i2) / (T_i1 - T_o2))) - def td_log_func(self): - r""" - equation for logarithmic temperature difference - - - calculate temperatures at inlets and outlets - - perform convergence correction, if temperature levels do not - match logic: - - * :math:`T_{1,in} > T_{2,out}`? - * :math:`T_{1,out} < T_{2,in}`? - - :returns: val (*float*) - residual value of equation - - .. math:: - - 0 = td_{log} \cdot - \frac{\ln{\frac{T_{1,out} - T_{2,in}}{T_{1,in} - T_{2,out}}}} - {T_{1,out} - T_{2,in} - T_{1,in} + T_{2,out}} - """ - - i1 = self.inl[0].to_flow() - i2 = self.inl[1].to_flow() - o1 = self.outl[0].to_flow() - o2 = self.outl[1].to_flow() - - T_i1 = T_mix_ph(i1) - T_i2 = T_mix_ph(i2) - T_o1 = T_mix_ph(o1) - T_o2 = T_mix_ph(o2) - - if T_i1 <= T_o2 and not self.inl[0].T.val_set: - T_i1 = T_o2 + 1 - if T_i1 <= T_o2 and not self.outl[1].T.val_set: - T_o2 = T_i1 - 1 - if T_i1 <= T_o2 and self.inl[0].T.val_set and self.outl[1].T.val_set: - msg = ('Infeasibility at ' + str(self.label) + ': Upper ' - 'temperature difference is negative!') - raise MyComponentError(msg) - - if T_o1 <= T_i2 and not self.outl[0].T.val_set: - T_o1 = T_i2 + 1 - if T_o1 <= T_i2 and not self.inl[1].T.val_set: - T_i2 = T_o1 - 1 - if T_o1 <= T_i2 and self.inl[1].T.val_set and self.outl[0].T.val_set: - msg = ('Infeasibility at ' + str(self.label) + ': Lower ' - 'temperature difference is negative!') - raise MyComponentError(msg) - - return (self.td_log.val * - math.log((T_o1 - T_i2) / (T_i1 - T_o2)) - - T_o1 + T_i2 + T_i1 - T_o2) +# def td_log_func(self): +# r""" +# equation for logarithmic temperature difference +# +# - calculate temperatures at inlets and outlets +# - perform convergence correction, if temperature levels do not +# match logic: +# +# * :math:`T_{1,in} > T_{2,out}`? +# * :math:`T_{1,out} < T_{2,in}`? +# +# :returns: val (*float*) - residual value of equation +# +# .. math:: +# +# 0 = td_{log} \cdot +# \frac{\ln{\frac{T_{1,out} - T_{2,in}}{T_{1,in} - T_{2,out}}}} +# {T_{1,out} - T_{2,in} - T_{1,in} + T_{2,out}} +# """ +# +# i1 = self.inl[0].to_flow() +# i2 = self.inl[1].to_flow() +# o1 = self.outl[0].to_flow() +# o2 = self.outl[1].to_flow() +# +# T_i1 = T_mix_ph(i1) +# T_i2 = T_mix_ph(i2) +# T_o1 = T_mix_ph(o1) +# T_o2 = T_mix_ph(o2) +# +# if T_i1 <= T_o2 and not self.inl[0].T.val_set: +# T_i1 = T_o2 + 1 +# if T_i1 <= T_o2 and not self.outl[1].T.val_set: +# T_o2 = T_i1 - 1 +# if T_i1 <= T_o2 and self.inl[0].T.val_set and self.outl[1].T.val_set: +# msg = ('Infeasibility at ' + str(self.label) + ': Upper ' +# 'temperature difference is negative!') +# raise MyComponentError(msg) +# +# if T_o1 <= T_i2 and not self.outl[0].T.val_set: +# T_o1 = T_i2 + 1 +# if T_o1 <= T_i2 and not self.inl[1].T.val_set: +# T_i2 = T_o1 - 1 +# if T_o1 <= T_i2 and self.inl[1].T.val_set and self.outl[0].T.val_set: +# msg = ('Infeasibility at ' + str(self.label) + ': Lower ' +# 'temperature difference is negative!') +# raise MyComponentError(msg) +# +# return (self.td_log.val * +# math.log((T_o1 - T_i2) / (T_i1 - T_o2)) - +# T_o1 + T_i2 + T_i1 - T_o2) def ttd_u_func(self): r""" From 0eb2feda993e12ce423dabfb940c1dc2c4c99819 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 9 Oct 2018 12:08:21 +0200 Subject: [PATCH 33/80] standard parameter specification adjusted for custom variable usage --- tespy/components/components.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index f12219ae8..b818d72ab 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -4154,7 +4154,9 @@ class vessel(component): """ def attr(self): - return {'pr': dc_cp(), 'zeta': dc_cp(), 'Sirr': dc_cp()} + return {'pr': dc_cp(min_val=1e-4), + 'zeta': dc_cp(min_val=1e-4), + 'Sirr': dc_cp()} def default_design(self): return ['pr'] @@ -4439,7 +4441,9 @@ def comp_init(self, nw): self.kA_group.set_attr(is_set=False) def attr(self): - return {'Q': dc_cp(), 'pr': dc_cp(), 'zeta': dc_cp(), + return {'Q': dc_cp(), + 'pr': dc_cp(min_val=1e-4), + 'zeta': dc_cp(min_val=1e-4), 'D': dc_cp(min_val=1e-2, max_val=2, d=1e-3), 'L': dc_cp(min_val=1e-1, d=1e-3), 'ks': dc_cp(min_val=1e-7, max_val=1e-4, d=1e-8), @@ -5120,10 +5124,14 @@ def comp_init(self, nw): self.energy_group.set_attr(is_set=False) def attr(self): - return {'Q': dc_cp(), 'pr': dc_cp(), 'zeta': dc_cp(), - 'D': dc_cp(), 'L': dc_cp(), 'ks': dc_cp(), - 'E': dc_cp(), 'lkf_lin': dc_cp(), 'lkf_quad': dc_cp(), - 'A': dc_cp(), 't_a': dc_cp(), + return {'Q': dc_cp(), + 'pr': dc_cp(min_val=1e-4), + 'zeta': dc_cp(min_val=1e-4), + 'D': dc_cp(min_val=1e-2, max_val=2, d=1e-3), + 'L': dc_cp(min_val=1e-1, d=1e-3), + 'ks': dc_cp(min_val=1e-7, max_val=1e-4, d=1e-8), + 'E': dc_cp(min_val=0), 'lkf_lin': dc_cp(), 'lkf_quad': dc_cp(), + 'A': dc_cp(min_val=0), 't_a': dc_cp(), 'SQ': dc_cp(), 'hydro_group': dc_gcp(), 'energy_group': dc_gcp()} From a01adfcaf87e434ce6ae5f2e058cc7211ca81487 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 10 Oct 2018 12:43:41 +0200 Subject: [PATCH 34/80] added zero vector for increment in case of linear dependency in jacobian --- tespy/networks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tespy/networks.py b/tespy/networks.py index 00fe85462..7f12ab171 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -1224,6 +1224,7 @@ def solve_control(self): self.lin_dep = False except: pass + self.vec_z = np.asarray(self.vec_res) * 0 # check for linear dependency if self.lin_dep: From 2c926bb5cb9b9c04d5f767a2c866bba00efcf5aa Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 11 Oct 2018 14:30:26 +0200 Subject: [PATCH 35/80] fixed bug in condenser with subcooler --- tespy/components/subsystems.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tespy/components/subsystems.py b/tespy/components/subsystems.py index 57c783ee1..544c5adf3 100644 --- a/tespy/components/subsystems.py +++ b/tespy/components/subsystems.py @@ -384,7 +384,7 @@ def attr(self): return ([n for n in subsystem.attr(self) if n != 'num_i' and n != 'num_o'] + ['ttd', 'pr1_desup', 'pr2_desup', - 'pr1_cond', 'pr2_cond', 'pr1_subc', 'pr2_subc']) + 'pr1_cond', 'pr2_cond', 'pr1_subc', 'pr2_subc', 'pr_v']) def create_comps(self): @@ -406,8 +406,9 @@ def set_comps(self): self.desup.set_attr(pr2=self.pr2_desup) self.condenser.set_attr(pr1=self.pr1_cond) self.condenser.set_attr(pr2=self.pr2_cond) - self.subcooler.set_attr(pr1=self.pr1_cond) - self.subcooler.set_attr(pr2=self.pr2_cond) + self.subcooler.set_attr(pr1=self.pr1_subc) + self.subcooler.set_attr(pr2=self.pr2_subc) + self.vessel.set_attr(pr=self.pr_v) def create_conns(self): From 4cbcfb948fbf527c1b3e5425f92538240daf314d Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Mon, 15 Oct 2018 17:01:14 +0200 Subject: [PATCH 36/80] adjusted printout for pump characteristics creation --- tespy/components/components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index b818d72ab..76f1839d7 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -1436,7 +1436,7 @@ def calc_parameters(self, nw, mode): if (mode == 'pre' and 'eta_s_char' in self.offdesign): if nw.compinfo: - print('Creating characteristics for component ', self) + print('Creating characteristics for component ' + self.label) v_opt = (self.i0[0] * (v_mix_ph(self.i0) + v_mix_ph(self.o0)) / 2) H_opt = ((self.o0[1] - self.i0[1]) / From d48738163c402c17456193a17d4bce60782258d0 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 17 Oct 2018 16:43:15 +0200 Subject: [PATCH 37/80] fixed v_mix_pT function, added CoolProp ranges for fluid properties (p, T) --- tespy/helpers.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tespy/helpers.py b/tespy/helpers.py index d3600ea45..be4024b95 100644 --- a/tespy/helpers.py +++ b/tespy/helpers.py @@ -489,6 +489,11 @@ def __init__(self, fluids): memorise.s_ph_f[fl] = [] memorise.count = 0 + for f in fluids: + pmin, pmax = CPPSI('PMIN', f), CPPSI('PMAX', f), + Tmin, Tmax = CPPSI('TMIN', f), CPPSI('TMAX', f) + memorise.vrange[f] = [pmin, pmax, Tmin, Tmax] + def del_memory(fluids): fl = tuple(fluids) @@ -529,6 +534,7 @@ def del_memory(fluids): memorise.visc_ph_f = {} memorise.s_ph = {} memorise.s_ph_f = {} +memorise.vrange = {} # %% @@ -1132,15 +1138,12 @@ def v_mix_pT(flow, T): \forall i \in \text{fluid components}\\ pp: \text{partial pressure} """ - n = molar_massflow(flow[3]) - - d = 0 + v = 0 for fluid, x in flow[3].items(): if x > err: - pp = flow[1] * x / (molar_masses[fluid] * n) - d += d_pT(pp, T, fluid) * x + v += x / d_pT(flow[1], T, fluid) - return 1 / d + return v def d_mix_pT(flow, T): From 187505ba73b4749df6451e4d5464fc909c4f63ed Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 17 Oct 2018 16:43:50 +0200 Subject: [PATCH 38/80] adjusted derivatives in heat_exchanger --- tespy/components/components.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index 76f1839d7..90b2d5cee 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -5795,10 +5795,9 @@ def bus_deriv(self): i = self.inl[0].to_flow() o = self.outl[0].to_flow() deriv = np.zeros((1, 4, len(self.inl[0].fluid.val) + 3)) - for i in range(2): - deriv[0, 0, 0] = o[2] - i[2] - deriv[0, 0, 2] = - i[0] - deriv[0, 2, 2] = i[0] + deriv[0, 0, 0] = o[2] - i[2] + deriv[0, 0, 2] = - i[0] + deriv[0, 2, 2] = i[0] return deriv def convergence_check(self, nw): From 8adc76c25ef976ed90ac6fba0d8c2f94e43ca7c5 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 17 Oct 2018 16:44:20 +0200 Subject: [PATCH 39/80] improved convergence stability and fluid initialization --- tespy/networks.py | 58 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/tespy/networks.py b/tespy/networks.py index 7f12ab171..7afbe22b6 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -629,11 +629,6 @@ def init_fluids(self): self.init_target(c, c.t) self.init_source(c, c.s) - if isinstance(c.s, cmp.merge): - c.s.initialise_fluids(self) - if isinstance(c.t, cmp.merge): - c.t.initialise_fluids(self) - for c in self.conns.index: c.s.initialise_fluids(self) c.t.initialise_fluids(self) @@ -719,6 +714,15 @@ def init_source(self, c, start): self.init_source(inconn, start) + if isinstance(c.s, cmp.merge): + print(c.t.label) + for inconn in self.comps.loc[c.s].i: + for fluid, x in c.fluid.val.items(): + if not inconn.fluid.val_set[fluid]: + inconn.fluid.val[fluid] = x + + self.init_source(inconn, start) + if isinstance(c.s, cmp.drum) and c.s != start: start = c.s for inconn in self.comps.loc[c.s].i: @@ -1257,6 +1261,8 @@ def solve_control(self): c.fluid.val[fluid] = 1 j += 1 + + self.solve_check_props(c) i += 1 if self.num_c_vars > 0: @@ -1289,16 +1295,46 @@ def solve_control(self): c_vars += cp.num_c_vars - # check properties for consistency + # check properties without given init_file if self.iter < 3 and self.init_file is None: - for c in self.conns.index: - self.solve_check_properties(c) - +# for c in self.conns.index: +# self.solve_check_properties(c) +# for cp in self.comps.index: cp.convergence_check(self) +# +# for c in self.conns.index: +# self.solve_check_properties(c) - for c in self.conns.index: - self.solve_check_properties(c) + def solve_check_props(self, c): + """ + checks for invalid fluid properties in solution progress and adjusts + values if necessary + + - check pressure + - check enthalpy + - check temperature + + :param c: connection object to check + :type c: tespy.connections.connection + :returns: no return value + """ + fl = hlp.single_fluid(c.fluid.val) + + if isinstance(fl, str): + # pressure + if c.p.val_SI < hlp.memorise.vrange[fl][0]: + c.p.val_SI = hlp.memorise.vrange[fl][0] * 1.01 + if c.p.val_SI > hlp.memorise.vrange[fl][1]: + c.p.val_SI = hlp.memorise.vrange[fl][1] * 0.99 + + # enthalpy + hmin = hlp.h_pT(c.p.val_SI, hlp.memorise.vrange[fl][2], fl) + hmax = hlp.h_pT(c.p.val_SI, hlp.memorise.vrange[fl][3], fl) + if c.h.val_SI < hmin: + c.h.val_SI = hmin * 2 + if c.h.val_SI > hmax: + c.h.val_SI = hmax * 0.99 def solve_check_properties(self, c): """ From d0079e522095b5a79b892b787fd05be1559ebc69 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 18 Oct 2018 10:58:39 +0200 Subject: [PATCH 40/80] added cogeneration unit basics --- doc/api/_images/cogeneration_unit.svg | 235 ++++++++ tespy/components/components.py | 780 ++++++++++++++++++++++++++ 2 files changed, 1015 insertions(+) create mode 100644 doc/api/_images/cogeneration_unit.svg diff --git a/doc/api/_images/cogeneration_unit.svg b/doc/api/_images/cogeneration_unit.svg new file mode 100644 index 000000000..237edf947 --- /dev/null +++ b/doc/api/_images/cogeneration_unit.svg @@ -0,0 +1,235 @@ + + + + + + + + + + image/svg+xml + + + + + + + in4 + out3 + in3 + + + + + + + + + + + + + + + out2 + out1 + in2 + in1 + + + + diff --git a/tespy/components/components.py b/tespy/components/components.py index 90b2d5cee..2a5c72085 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -4121,6 +4121,786 @@ def calc_parameters(self, nw, mode): # %% +class cogeneration_unit(component): + r""" + + .. note:: + For more information on the usage of the cogeneration unit see the + examples in the tespy_examples repository + + **available parameters** + + - fuel: fuel for combustion + - lamb: air to stoichiometric air ratio, :math:`[\lambda] = 1` + - ti: thermal input (:math:`{LHV \cdot \dot{m}_f}`), + :math:`[LHV \cdot \dot{m}_f] = \text{W}` + - P: power output, :math:`{[P]=\text{W}}` + - Q: total heat output, :math:`{[\dot Q]=\text{W}}` + + **equations** + + see :func:`tespy.components.components.cogeneration_unit.equations` + + **available fuels** + + - methane + - ethane + - propane + - butane + - hydrogen + + **inlets and outlets** + + - in1, in2 (cooling water), in3, in4 (air and fuel) + - out1, out2 (cooling water), out3 (flue gas) + + .. note:: + + + + .. image:: _images/cogeneration_unit.svg + :scale: 100 % + :alt: alternative text + :align: center + """ + + def inlets(self): + return ['in1', 'in2', 'in3', 'in4'] + + def outlets(self): + return ['out1', 'out2', 'out3'] + + def attr(self): + return {'fuel': dc_cp(printout=False), 'lamb': dc_cp(), 'ti': dc_cp(), + 'P': dc_cp(), 'Q': dc_cp(), 'S': dc_cp()} + + def fuels(self): + return ['methane', 'ethane', 'propane', 'butane', + 'hydrogen'] + + def component(self): + return 'cogeneration unit' + + def comp_init(self, nw): + + component.comp_init(self, nw) + + if not self.fuel.is_set: + msg = 'Must specify fuel for combustion chamber.' + raise MyComponentError(msg) + + if (len([x for x in nw.fluids if x in [a.replace(' ', '') for a in + CP.get_aliases(self.fuel.val)]]) == 0): + msg = ('The fuel you specified does not match the fuels available' + ' within the network.') + raise MyComponentError(msg) + + if (len([x for x in self.fuels() if x in [a.replace(' ', '') for a in + CP.get_aliases(self.fuel.val)]])) == 0: + msg = ('The fuel you specified is not available. Available fuels ' + 'are: ' + str(self.fuels()) + '.') + raise MyComponentError(msg) + + self.fuel.val = [x for x in nw.fluids if x in [ + a.replace(' ', '') for a in CP.get_aliases(self.fuel.val)]][0] + + self.o2 = [x for x in nw.fluids if x in + [a.replace(' ', '') for a in CP.get_aliases('O2')]][0] + self.co2 = [x for x in nw.fluids if x in + [a.replace(' ', '') for a in CP.get_aliases('CO2')]][0] + self.h2o = [x for x in nw.fluids if x in + [a.replace(' ', '') for a in CP.get_aliases('H2O')]][0] + self.n2 = [x for x in nw.fluids if x in + [a.replace(' ', '') for a in CP.get_aliases('N2')]][0] + + structure = fluid_structure(self.fuel.val) + + self.n = {} + for el in ['C', 'H', 'O']: + if el in structure.keys(): + self.n[el] = structure[el] + else: + self.n[el] = 0 + + self.lhv = self.calc_lhv() + + def calc_lhv(self): + r""" + calculates the lower heating value of the combustion chambers fuel + + :returns: val (*float*) - lhv of the specified fuel + + **equation** + + .. math:: + LHV = -\frac{\sum_i {\Delta H_f^0}_i - + \sum_j {\Delta H_f^0}_j } + {M_{fuel}}\\ + \forall i \in \text{reation products},\\ + \forall j \in \text{reation educts},\\ + \Delta H_f^0: \text{molar formation enthalpy} + + =============== ===================================== + substance :math:`\frac{\Delta H_f^0}{kJ/mol}` + =============== ===================================== + hydrogen 0 + methane -74.85 + ethane -84.68 + propane -103.8 + butane -124.51 + --------------- ------------------------------------- + oxygen 0 + carbondioxide -393.5 + water (g) -241.8 + =============== ===================================== + + """ + + hf = {} + hf['hydrogen'] = 0 + hf['methane'] = -74.85 + hf['ethane'] = -84.68 + hf['propane'] = -103.8 + hf['butane'] = -124.51 + hf[self.o2] = 0 + hf[self.co2] = -393.5 + # water (gaseous) + hf[self.h2o] = -241.8 + + key = set(list(hf.keys())).intersection( + set([a.replace(' ', '') + for a in CP.get_aliases(self.fuel.val)])) + + val = (-(self.n['H'] / 2 * hf[self.h2o] + self.n['C'] * hf[self.co2] - + ((self.n['C'] + self.n['H'] / 4) * hf[self.o2] + + hf[list(key)[0]])) / + molar_masses[self.fuel.val] * 1000) + + return val + + def equations(self): + r""" + returns vector vec_res with result of equations for this component + + :param nw: network using this component object + :type nw: tespy.networks.network + :returns: vec_res (*list*) - vector of residual values + + **mandatory equations** + + - :func:`tespy.components.components.cogeneration_unit.reaction_balance` + - :func:`tespy.components.components.component.mass_flow_res` + .. math:: + + 0 = p_{3,in} - p_{3,out}\\ + 0 = p_{4,in} - p_{3,out} + + - :func:`tespy.components.components.cogeneration_unit.energy_balance` + + **optional equations** + + - :func:`tespy.components.components.cogeneration_unit.lambda_func` + - :func:`tespy.components.components.cogeneration_unit.ti_func` + + .. math:: + + 0 = p_{1,in} \cdot pr1 - p_{1,out}\\ + 0 = p_{2,in} \cdot pr2 - p_{2,out} + + - :func:`tespy.components.components.component.zeta_func` + - :func:`tespy.components.components.component.zeta2_func` + + - :func:`tespy.components.components.cogeneration_unit.power_func` + - :func:`tespy.components.components.cogeneration_unit.heat_func` + + """ + + vec_res = [] + + for fluid in self.inl[0].fluid.val.keys(): + vec_res += [self.reaction_balance(fluid)] + + vec_res += self.mass_flow_res() + + vec_res += [self.inl[0].p.val_SI - self.outl[0].p.val_SI] + vec_res += [self.outl[0].p.val_SI - self.outl[0].p.val_SI] + + vec_res += [self.energy_balance()] + + if self.lamb.is_set: + vec_res += [self.lambda_func()] + + if self.ti.is_set: + vec_res += [self.ti_func()] + + if self.pr1.is_set: + vec_res += [self.ti_func()] + + if self.pr2.is_set: + vec_res += [self.ti_func()] + + if self.P.is_set: + vec_res += [self.power_func()] + + if self.Q.is_set: + vec_res += [self.heat_func()] + + return vec_res + + def derivatives(self, nw): + r""" + calculate matrix of partial derivatives towards mass flow, pressure, + enthalpy and fluid composition + + :param nw: network using this component object + :type nw: tespy.networks.network + :returns: mat_deriv (*numpy array*) - matrix of partial derivatives + """ + + num_fl = len(nw.fluids) + mat_deriv = [] + + # derivatives for reaction balance + j = 0 + fl_deriv = np.zeros((num_fl, 3, num_fl + 3)) + for fluid in nw.fluids: + for i in range(3): + fl_deriv[j, i, 0] = self.drb_dx('m', i, fluid) + fl_deriv[j, i, 3:] = ( + self.drb_dx('fluid', i, fluid)) + + j += 1 + mat_deriv += fl_deriv.tolist() + + # derivatives for mass balance + mat_deriv += self.mass_flow_deriv() + + # derivatives for pressure equations + p_deriv = np.zeros((2, 3, num_fl + 3)) + for k in range(2): + p_deriv[k][2][1] = 1 + p_deriv[k][k][1] = -1 + mat_deriv += p_deriv.tolist() + + # derivatives for energy balance + eb_deriv = np.zeros((1, 3, num_fl + 3)) + for i in range(3): + eb_deriv[0, i, 0] = ( + self.ddx_func(self.energy_balance, 'm', i)) + eb_deriv[0, i, 1] = ( + self.ddx_func(self.energy_balance, 'p', i)) + if i >= self.num_i: + eb_deriv[0, i, 2] = -(self.inl + self.outl)[i].m.val_SI + else: + eb_deriv[0, i, 2] = (self.inl + self.outl)[i].m.val_SI + mat_deriv += eb_deriv.tolist() + + if self.lamb.is_set: + # derivatives for specified lambda + lamb_deriv = np.zeros((1, 3, num_fl + 3)) + for i in range(2): + lamb_deriv[0, i, 0] = self.ddx_func(self.lambda_func, 'm', i) + lamb_deriv[0, i, 3:] = self.ddx_func(self.lambda_func, + 'fluid', i) + mat_deriv += lamb_deriv.tolist() + + if self.ti.is_set: + # derivatives for specified thermal input + ti_deriv = np.zeros((1, 3, num_fl + 3)) + for i in range(2): + ti_deriv[0, i, 0] = self.ddx_func(self.ti_func, 'm', i) + ti_deriv[0, i, 3:] = self.ddx_func(self.ti_func, 'fluid', i) + ti_deriv[0, 2, 0] = self.ddx_func(self.ti_func, 'm', 2) + ti_deriv[0, 2, 3:] = self.ddx_func(self.ti_func, 'fluid', 2) + mat_deriv += ti_deriv.tolist() + + return np.asarray(mat_deriv) + + def reaction_balance(self, fluid): + r""" + calculates the reactions mass balance for one fluid + + - determine molar mass flows of fuel and oxygen + - calculate excess fuel + - calculate residual value of the fluids balance + + :param fluid: fluid to calculate the reaction balance for + :type fluid: str + :returns: res (*float*) - residual value of mass balance + + **reaction balance equations** + + .. math:: + res = \sum_i \left(x_{fluid,i} \cdot \dot{m}_{i}\right) - + \sum_j \left(x_{fluid,j} \cdot \dot{m}_{j}\right) \; + \forall i \in inlets, \; \forall j \in outlets + + \dot{m}_{fluid,m} = \sum_i \frac{x_{fluid,i} \cdot \dot{m}_{i}} + {M_{fluid}} \; \forall i \in inlets\\ + + \lambda = \frac{\dot{m}_{f,m}}{\dot{m}_{O_2,m} \cdot + \left(n_{C,fuel} + 0.25 \cdot n_{H,fuel}\right)} + + *fuel* + + .. math:: + 0 = res - \left(\dot{m}_{f,m} - \dot{m}_{f,exc,m}\right) + \cdot M_{fuel}\\ + + \dot{m}_{f,exc,m} = \begin{cases} + 0 & \lambda \geq 1\\ + \dot{m}_{f,m} - \frac{\dot{m}_{O_2,m}} + {n_{C,fuel} + 0.25 \cdot n_{H,fuel}} & \lambda < 1 + \end{cases} + + *oxygen* + + .. math:: + 0 = res - \begin{cases} + -\frac{\dot{m}_{O_2,m} \cdot M_{O_2}}{\lambda} & \lambda \geq 1\\ + - \dot{m}_{O_2,m} \cdot M_{O_2} & \lambda < 1 + \end{cases} + + *water* + + .. math:: + 0 = res + \left( \dot{m}_{f,m} - \dot{m}_{f,exc,m} \right) + \cdot 0.5 \cdot n_{H,fuel} \cdot M_{H_2O} + + *carbondioxide* + + .. math:: + 0 = res + \left( \dot{m}_{f,m} - \dot{m}_{f,exc,m} \right) + \cdot n_{C,fuel} \cdot M_{CO_2} + + *other* + + .. math:: + 0 = res + + """ + + n_fuel = 0 + for i in self.inl: + n_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val] / + molar_masses[self.fuel.val]) + + n_oxygen = 0 + for i in self.inl: + n_oxygen += (i.m.val_SI * i.fluid.val[self.o2] / + molar_masses[self.o2]) + + if not self.lamb.is_set: + self.lamb.val = n_oxygen / ( + n_fuel * (self.n['C'] + self.n['H'] / 4)) + + n_fuel_exc = 0 + if self.lamb.val < 1: + n_fuel_exc = n_fuel - n_oxygen / (self.n['C'] + self.n['H'] / 4) + + if fluid == self.co2: + dm = ((n_fuel - n_fuel_exc) * + self.n['C'] * molar_masses[self.co2]) + elif fluid == self.h2o: + dm = ((n_fuel - n_fuel_exc) * + self.n['H'] / 2 * molar_masses[self.h2o]) + elif fluid == self.o2: + if self.lamb.val < 1: + dm = -n_oxygen * molar_masses[self.o2] + else: + dm = -n_oxygen / self.lamb.val * molar_masses[self.o2] + elif fluid == self.fuel.val: + dm = -(n_fuel - n_fuel_exc) * molar_masses[self.fuel.val] + else: + dm = 0 + + res = dm + + for i in self.inl: + res += i.fluid.val[fluid] * i.m.val_SI + for o in self.outl: + res -= o.fluid.val[fluid] * o.m.val_SI + return res + + def energy_balance(self): + r""" + calculates the energy balance of the adiabatic combustion chamber + + - reference temperature: 500 K + - reference pressure: 1 bar + + :returns: res (*float*) - residual value of energy balance + + .. math:: + 0 = \dot{m}_{in,i} \cdot \left( h_{in,i} - h_{in,i,ref} \right) - + \dot{m}_{out,j} \cdot \left( h_{out,j} - h_{out,j,ref} \right) + + H_{I,f} \cdot \left( \dot{m}_{in,i} \cdot x_{f,i} - + \dot{m}_{out,j} \cdot x_{f,j} \right) + + """ + T_ref = 500 + p_ref = 1e5 + + res = 0 + for i in self.inl: + res += i.m.val_SI * (i.h.val_SI - + h_mix_pT([i.m.val_SI, p_ref, i.h.val_SI, + i.fluid.val], T_ref)) + res += i.m.val_SI * i.fluid.val[self.fuel.val] * self.lhv + for o in self.outl: + res -= o.m.val_SI * (o.h.val_SI - + h_mix_pT([o.m.val_SI, p_ref, o.h.val_SI, + o.fluid.val], T_ref)) + res -= o.m.val_SI * o.fluid.val[self.fuel.val] * self.lhv + + return res + + def lambda_func(self): + r""" + calculates the residual for specified lambda + + :returns: res (*float*) - residual value of equation + + .. math:: + + \dot{m}_{fluid,m} = \sum_i \frac{x_{fluid,i} \cdot \dot{m}_{i}} + {M_{fluid}} \; \forall i \in inlets\\ + + 0 = \frac{\dot{m}_{f,m}}{\dot{m}_{O_2,m} \cdot + \left(n_{C,fuel} + 0.25 \cdot n_{H,fuel}\right)} - \lambda + """ + n_fuel = 0 + for i in self.inl: + n_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val] / + molar_masses[self.fuel.val]) + + n_oxygen = 0 + for i in self.inl: + n_oxygen += (i.m.val_SI * i.fluid.val[self.o2] / + molar_masses[self.o2]) + + return (n_oxygen / (n_fuel * (self.n['C'] + self.n['H'] / 4)) - + self.lamb.val) + + def ti_func(self): + r""" + calculates the residual for specified thermal input + + :returns: res (*float*) - residual value of equation + + .. math:: + + 0 = ti - \dot{m}_f \cdot LHV + """ + m_fuel = 0 + for i in self.inl: + m_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val]) + + for o in self.outl: + m_fuel -= (o.m.val_SI * o.fluid.val[self.fuel.val]) + + return (self.ti.val - m_fuel * self.lhv) + + def bus_func(self): + r""" + function for use on busses + + :returns: val (*float*) - residual value of equation + + .. math:: + + val = \dot{m}_{fuel} \cdot LHV + """ + + m_fuel = 0 + for i in self.inl: + m_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val]) + + for o in self.outl: + m_fuel -= (o.m.val_SI * o.fluid.val[self.fuel.val]) + + return m_fuel * self.lhv + + def bus_deriv(self): + r""" + calculate matrix of partial derivatives towards mass flow and fluid + composition for bus + function + + :returns: mat_deriv (*list*) - matrix of partial derivatives + """ + deriv = np.zeros((1, 3, len(self.inl[0].fluid.val) + 3)) + for i in range(2): + deriv[0, i, 0] = self.ddx_func(self.bus_func, 'm', i) + deriv[0, i, 3:] = self.ddx_func(self.bus_func, 'fluid', i) + + deriv[0, 2, 0] = self.ddx_func(self.bus_func, 'm', 2) + deriv[0, 2, 3:] = self.ddx_func(self.bus_func, 'fluid', 2) + return deriv + + def drb_dx(self, dx, pos, fluid): + r""" + calculates derivative of the reaction balance to dx at components inlet + or outlet in position pos for the fluid fluid + + :param dx: dx + :type dx: str + :param pos: position of inlet or outlet, logic: ['in1', 'in2', ..., + 'out1', ...] -> 0, 1, ..., n, n + 1, ... + :type pos: int + :param fluid: calculate reaction balance for this fluid + :type fluid: str + :returns: deriv (*list* or *float*) - partial derivative of the + function reaction balance to dx + + .. math:: + + \frac{\partial f}{\partial x} = \frac{f(x + d) + f(x - d)} + {2 \cdot d} + """ + + dm, dp, dh, df = 0, 0, 0, 0 + if dx == 'm': + dm = 1e-4 + elif dx == 'p': + dp = 1 + elif dx == 'h': + dh = 1 + else: + df = 1e-5 + + if dx == 'fluid': + deriv = [] + for f in self.inl[0].fluid.val.keys(): + val = (self.inl + self.outl)[pos].fluid.val[f] + exp = 0 + if (self.inl + self.outl)[pos].fluid.val[f] + df <= 1: + (self.inl + self.outl)[pos].fluid.val[f] += df + else: + (self.inl + self.outl)[pos].fluid.val[f] = 1 + exp += self.reaction_balance(fluid) + if (self.inl + self.outl)[pos].fluid.val[f] - 2 * df >= 0: + (self.inl + self.outl)[pos].fluid.val[f] -= 2 * df + else: + (self.inl + self.outl)[pos].fluid.val[f] = 0 + exp -= self.reaction_balance(fluid) + (self.inl + self.outl)[pos].fluid.val[f] = val + + deriv += [exp / (2 * (dm + dp + dh + df))] + + else: + exp = 0 + (self.inl + self.outl)[pos].m.val_SI += dm + (self.inl + self.outl)[pos].p.val_SI += dp + (self.inl + self.outl)[pos].h.val_SI += dh + exp += self.reaction_balance(fluid) + + (self.inl + self.outl)[pos].m.val_SI -= 2 * dm + (self.inl + self.outl)[pos].p.val_SI -= 2 * dp + (self.inl + self.outl)[pos].h.val_SI -= 2 * dh + exp -= self.reaction_balance(fluid) + deriv = exp / (2 * (dm + dp + dh + df)) + + (self.inl + self.outl)[pos].m.val_SI += dm + (self.inl + self.outl)[pos].p.val_SI += dp + (self.inl + self.outl)[pos].h.val_SI += dh + + return deriv + + def initialise_fluids(self, nw): + r""" + calculates reaction balance with given lambda for good generic + starting values + + - sets the fluid composition at the combustion chambers outlet + + for the reaction balance equations see + :func:`tespy.components.components.combustion_chamber.reaction_balance` + + :param nw: network using this component object + :type nw: tespy.networks.network + :returns: no return value + """ + N_2 = 0.7655 + O_2 = 0.2345 + + n_fuel = 1 + lamb = 3 + m_co2 = n_fuel * self.n['C'] * molar_masses[self.co2] + m_h2o = n_fuel * self.n['H'] / 2 * molar_masses[self.h2o] + + n_o2 = (m_co2 / molar_masses[self.co2] + + 0.5 * m_h2o / molar_masses[self.h2o]) * lamb + + m_air = n_o2 * molar_masses[self.o2] / O_2 + m_fuel = n_fuel * molar_masses[self.fuel.val] + m_fg = m_air + m_fuel + + m_o2 = n_o2 * molar_masses[self.o2] * (1 - 1 / lamb) + m_n2 = N_2 * m_air + + fg = { + self.n2: m_n2 / m_fg, + self.co2: m_co2 / m_fg, + self.o2: m_o2 / m_fg, + self.h2o: m_h2o / m_fg + } + + for o in self.outl: + for fluid, x in o.fluid.val.items(): + if not o.fluid.val_set[fluid] and fluid in fg.keys(): + o.fluid.val[fluid] = fg[fluid] + + def convergence_check(self, nw): + r""" + prevent impossible fluid properties in calculation + + - check if mass fractions of fluid components at combustion chambers + outlet are within typical range + - propagate the corrected fluid composition towards target + + :param nw: network using this component object + :type nw: tespy.networks.network + :returns: no return value + """ + m = 0 + for i in self.inl: + if i.m.val_SI < 0 and not i.m.val_set: + i.m.val_SI = 0.01 + m += i.m.val_SI + + for o in self.outl: + fluids = [f for f in o.fluid.val.keys() if not o.fluid.val_set[f]] + for f in fluids: + if f not in [self.o2, self.co2, self.h2o, self.fuel.val]: + m_f = 0 + for i in self.inl: + m_f += i.fluid.val[f] * i.m.val_SI + + if abs(o.fluid.val[f] - m_f / m) > 0.03: + o.fluid.val[f] = m_f / m + + elif f == self.o2: + if o.fluid.val[f] > 0.25: + o.fluid.val[f] = 0.2 + if o.fluid.val[f] < 0.05: + o.fluid.val[f] = 0.05 + + elif f == self.co2: + if o.fluid.val[f] > 0.075: + o.fluid.val[f] = 0.075 + if o.fluid.val[f] < 0.02: + o.fluid.val[f] = 0.02 + + elif f == self.h2o: + if o.fluid.val[f] > 0.075: + o.fluid.val[f] = 0.075 + if o.fluid.val[f] < 0.02: + o.fluid.val[f] = 0.02 + + elif f == self.fuel.val: + if o.fluid.val[f] > 0: + o.fluid.val[f] = 0 + + else: + continue + + for o in self.outl: + if o.m.val_SI < 0 and not o.m.val_set: + o.m.val_SI = 10 + init_target(nw, o, o.t) + + if o.h.val_SI < 7.5e5 and not o.h.val_set: + o.h.val_SI = 1e6 + + if self.lamb.val < 1 and not self.lamb.is_set: + self.lamb.val = 3 + + def initialise_source(self, c, key): + r""" + returns a starting value for fluid properties at components outlet + + :param c: connection to apply initialisation + :type c: tespy.connections.connection + :param key: property + :type key: str + :returns: - p (*float*) - starting value for pressure at components + outlet, :math:`val = 5 \cdot 10^5 \; \text{Pa}` + - h (*float*) - starting value for enthalpy at components + outlet, + :math:`val = 10 \cdot 10^5 \; \frac{\text{J}}{\text{kg}}` + """ + if key == 'p': + return 5e5 + elif key == 'h': + return 10e5 + else: + return 0 + + def initialise_target(self, c, key): + r""" + returns a starting value for fluid properties at components inlet + + :param c: connection to apply initialisation + :type c: tespy.connections.connection + :param key: property + :type key: str + :returns: - p (*float*) - starting value for pressure at components + inlet, :math:`val = 5 \cdot 10^5 \; \text{Pa}` + - h (*float*) - starting value for enthalpy at components + inlet, + :math:`val = 5 \cdot 10^5 \; \frac{\text{J}}{\text{kg}}` + """ + if key == 'p': + return 5e5 + elif key == 'h': + return 5e5 + else: + return 0 + + def calc_parameters(self, nw, mode): + + self.ti.val = 0 + for i in self.inl: + self.ti.val += i.m.val_SI * i.fluid.val[self.fuel.val] * self.lhv + + n_fuel = 0 + for i in self.inl: + n_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val] / + molar_masses[self.fuel.val]) + + n_oxygen = 0 + for i in self.inl: + n_oxygen += (i.m.val_SI * i.fluid.val[self.o2] / + molar_masses[self.o2]) + + if mode == 'post': + if not self.lamb.is_set: + self.lamb.val = n_oxygen / ( + n_fuel * (self.n['C'] + self.n['H'] / 4)) + + S = 0 + T_ref = 500 + p_ref = 1e5 + + for i in self.inl: + S -= i.m.val_SI * (s_mix_ph(i.to_flow()) - + s_mix_pT([0, p_ref, 0, i.fluid.val], T_ref)) + + for o in self.outl: + S += o.m.val_SI * (s_mix_ph(o.to_flow()) - + s_mix_pT([0, p_ref, 0, o.fluid.val], T_ref)) + + self.S.val = S + + if mode == 'pre': + if 'lamb' in self.offdesign: + self.lamb.val = n_oxygen / (n_fuel * + (self.n['C'] + self.n['H'] / 4)) + +# %% + + class vessel(component): r""" **available parameters** From b4b490d2c23431bc435a7e698d567e9635aabefd Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 18 Oct 2018 10:59:45 +0200 Subject: [PATCH 41/80] adjustemnts in solution control --- tespy/helpers.py | 6 +++++- tespy/networks.py | 16 ++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/tespy/helpers.py b/tespy/helpers.py index be4024b95..59250bcd5 100644 --- a/tespy/helpers.py +++ b/tespy/helpers.py @@ -490,7 +490,11 @@ def __init__(self, fluids): memorise.count = 0 for f in fluids: - pmin, pmax = CPPSI('PMIN', f), CPPSI('PMAX', f), + try: + pmin, pmax = CPPSI('PMIN', f), CPPSI('PMAX', f) + except ValueError: + pmin, pmax = 2000, 2000e5 + Tmin, Tmax = CPPSI('TMIN', f), CPPSI('TMAX', f) memorise.vrange[f] = [pmin, pmax, Tmin, Tmax] diff --git a/tespy/networks.py b/tespy/networks.py index 7afbe22b6..ee76983d5 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -1297,14 +1297,14 @@ def solve_control(self): # check properties without given init_file if self.iter < 3 and self.init_file is None: -# for c in self.conns.index: -# self.solve_check_properties(c) + for c in self.conns.index: + self.solve_check_properties(c) # for cp in self.comps.index: cp.convergence_check(self) # -# for c in self.conns.index: -# self.solve_check_properties(c) + for c in self.conns.index: + self.solve_check_properties(c) def solve_check_props(self, c): """ @@ -1324,17 +1324,17 @@ def solve_check_props(self, c): if isinstance(fl, str): # pressure if c.p.val_SI < hlp.memorise.vrange[fl][0]: - c.p.val_SI = hlp.memorise.vrange[fl][0] * 1.01 + c.p.val_SI = hlp.memorise.vrange[fl][0] * 1.1 if c.p.val_SI > hlp.memorise.vrange[fl][1]: - c.p.val_SI = hlp.memorise.vrange[fl][1] * 0.99 + c.p.val_SI = hlp.memorise.vrange[fl][1] * 0.9 # enthalpy hmin = hlp.h_pT(c.p.val_SI, hlp.memorise.vrange[fl][2], fl) hmax = hlp.h_pT(c.p.val_SI, hlp.memorise.vrange[fl][3], fl) if c.h.val_SI < hmin: - c.h.val_SI = hmin * 2 + c.h.val_SI = hmin * 3 if c.h.val_SI > hmax: - c.h.val_SI = hmax * 0.99 + c.h.val_SI = hmax * 0.9 def solve_check_properties(self, c): """ From 2096b17255ba6f531854e47148ab4f39c57064a4 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 18 Oct 2018 11:00:06 +0200 Subject: [PATCH 42/80] fixed typo in combustion chamber image --- doc/api/_images/combustion_chamber.svg | 39 ++++++++++++-------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/doc/api/_images/combustion_chamber.svg b/doc/api/_images/combustion_chamber.svg index 11c8bb5ad..a63c5261a 100644 --- a/doc/api/_images/combustion_chamber.svg +++ b/doc/api/_images/combustion_chamber.svg @@ -12,7 +12,7 @@ viewBox="0 0 174.11069 131.01425" height="131.01425" width="174.11067" - inkscape:version="0.91 r13725" + inkscape:version="0.92.3 (2405546, 2018-03-11)" sodipodi:docname="combustion_chamber.svg"> in2 out2 + style="font-size:65.00000954%;baseline-shift:sub" + id="tspan9757">1 in1 Date: Thu, 18 Oct 2018 13:09:20 +0200 Subject: [PATCH 43/80] adjusted fluid property initialization at simple heat exchanger --- tespy/components/components.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index 2a5c72085..96c31cfdd 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -5592,9 +5592,9 @@ def initialise_source(self, c, key): if key == 'p': return 1e5 elif key == 'h': - if self.Q.val < 0: + if self.Q.val < 0 and self.Q.is_set: return 1e5 - elif self.Q.val > 0: + elif self.Q.val > 0 and self.Q.is_set: return 5e5 else: return 3e5 @@ -5625,9 +5625,9 @@ def initialise_target(self, c, key): if key == 'p': return 1e5 elif key == 'h': - if self.Q.val < 0: + if self.Q.val < 0 and self.Q.is_set: return 5e5 - elif self.Q.val > 0: + elif self.Q.val > 0 and self.Q.is_set: return 1e5 else: return 3e5 From 818e37b52b21848cfec30ee921a2033a8639dd11 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 18 Oct 2018 13:10:20 +0200 Subject: [PATCH 44/80] implemented CoolProp fluid property boundaries, overall improvement on fluid property handling in solve_control --- tespy/helpers.py | 7 +- tespy/networks.py | 163 ++++++++++++++++++---------------------------- 2 files changed, 70 insertions(+), 100 deletions(-) diff --git a/tespy/helpers.py b/tespy/helpers.py index 59250bcd5..153586ad7 100644 --- a/tespy/helpers.py +++ b/tespy/helpers.py @@ -495,7 +495,12 @@ def __init__(self, fluids): except ValueError: pmin, pmax = 2000, 2000e5 - Tmin, Tmax = CPPSI('TMIN', f), CPPSI('TMAX', f) + for f in fluids: + try: + Tmin, Tmax = CPPSI('TMIN', f), CPPSI('TMAX', f) + except ValueError: + Tmin, Tmax = 2000, 2000e5 + memorise.vrange[f] = [pmin, pmax, Tmin, Tmax] def del_memory(fluids): diff --git a/tespy/networks.py b/tespy/networks.py index ee76983d5..b2cf0421d 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -139,13 +139,9 @@ def __init__(self, fluids, **kwargs): self.v_unit = self.SI_units['v'] # standard value range - self.p_range = [2e3, 300e5] - self.h_range = [1e3, 7e6] - self.T_range = [273.16, 1773.15] - - self.p_range_SI = self.p_range[:] - self.h_range_SI = self.h_range[:] - self.T_range_SI = self.T_range[:] + self.p_range_SI = np.array([2e2, 300e5]) + self.h_range_SI = np.array([1e3, 7e6]) + self.T_range_SI = np.array([273.16, 1773.15]) # add attributes from kwargs for key in kwargs: @@ -200,25 +196,46 @@ def set_attr(self, **kwargs): raise hlp.MyNetworkError(msg) # value ranges - if not isinstance(self.p_range, list): - msg = ('Specify the value range as list: [p_min, p_max]') - raise TypeError(msg) + if 'p_range' in kwargs.keys(): + if not isinstance(self.p_range, list): + msg = ('Specify the value range as list: [p_min, p_max]') + raise TypeError(msg) + else: + self.p_range_SI = np.array(self.p_range) * self.p[self.p_unit] else: - self.p_range_SI = np.array(self.p_range) * self.p[self.p_unit] + self.p_range = self.p_range_SI / self.p[self.p_unit] - if not isinstance(self.h_range, list): - msg = ('Specify the value range as list: [h_min, h_max]') - raise TypeError(msg) + if 'h_range' in kwargs.keys(): + if not isinstance(self.h_range, list): + msg = ('Specify the value range as list: [h_min, h_max]') + raise TypeError(msg) + else: + self.h_range_SI = np.array(self.h_range) * self.h[self.h_unit] else: - self.h_range_SI = np.array(self.h_range) * self.h[self.h_unit] + self.h_range = self.h_range_SI / self.h[self.h_unit] - if not isinstance(self.T_range, list): - msg = ('Specify the value range as list: [T_min, T_max]') - raise TypeError(msg) + if 'T_range' in kwargs.keys(): + if not isinstance(self.T_range, list): + msg = ('Specify the value range as list: [T_min, T_max]') + raise TypeError(msg) + else: + self.T_range_SI = ((np.array(self.T_range) + + self.T[self.T_unit][0]) * + self.T[self.T_unit][1]) else: - self.T_range_SI = ((np.array(self.T_range) + - self.T[self.T_unit][0]) * - self.T[self.T_unit][1]) + self.T_range = (self.T_range_SI / self.T[self.T_unit][1] - + self.T[self.T_unit][0]) + + for f in self.fluids: + if 'TESPy::' in f: + hlp.memorise.vrange[f][0] = self.p_range_SI[0] + hlp.memorise.vrange[f][1] = self.p_range_SI[1] + hlp.memorise.vrange[f][2] = self.T_range_SI[0] + hlp.memorise.vrange[f][3] = self.T_range_SI[1] + + if 'INCOMP::' in f: + hlp.memorise.vrange[f][0] = self.p_range_SI[0] + hlp.memorise.vrange[f][1] = self.p_range_SI[1] def get_attr(self, key): if key in self.__dict__: @@ -1297,14 +1314,9 @@ def solve_control(self): # check properties without given init_file if self.iter < 3 and self.init_file is None: - for c in self.conns.index: - self.solve_check_properties(c) # for cp in self.comps.index: cp.convergence_check(self) -# - for c in self.conns.index: - self.solve_check_properties(c) def solve_check_props(self, c): """ @@ -1323,56 +1335,37 @@ def solve_check_props(self, c): if isinstance(fl, str): # pressure - if c.p.val_SI < hlp.memorise.vrange[fl][0]: + if c.p.val_SI < hlp.memorise.vrange[fl][0] and not c.p.val_set: c.p.val_SI = hlp.memorise.vrange[fl][0] * 1.1 - if c.p.val_SI > hlp.memorise.vrange[fl][1]: + if c.p.val_SI > hlp.memorise.vrange[fl][1] and not c.p.val_set: c.p.val_SI = hlp.memorise.vrange[fl][1] * 0.9 # enthalpy hmin = hlp.h_pT(c.p.val_SI, hlp.memorise.vrange[fl][2], fl) hmax = hlp.h_pT(c.p.val_SI, hlp.memorise.vrange[fl][3], fl) - if c.h.val_SI < hmin: + if c.h.val_SI < hmin and not c.h.val_set: c.h.val_SI = hmin * 3 - if c.h.val_SI > hmax: + if c.h.val_SI > hmax and not c.h.val_set: c.h.val_SI = hmax * 0.9 - def solve_check_properties(self, c): - """ - checks for invalid fluid properties in solution progress and adjusts - values if necessary + elif self.iter < 3 and self.init_file is None: + # pressure + if c.p.val_SI <= self.p_range_SI[0] and not c.p.val_set: + c.p.val_SI = self.p_range_SI[0] + if c.p.val_SI >= self.p_range_SI[1] and not c.p.val_set: + c.p.val_SI = self.p_range_SI[1] - - check pressure - - check enthalpy - - check temperature + # enthalpy + if c.h.val_SI < self.h_range_SI[0] and not c.h.val_set: + c.h.val_SI = self.h_range_SI[0] + if c.h.val_SI > self.h_range_SI[1] and not c.h.val_set: + c.h.val_SI = self.h_range_SI[1] - :param c: connection object to check - :type c: tespy.connections.connection - :returns: no return value - """ - # pressure - if c.p.val_SI <= self.p_range_SI[0] and not c.p.val_set: - c.p.val_SI = self.p_range_SI[0] - if c.p.val_SI >= self.p_range_SI[1] and not c.p.val_set: - c.p.val_SI = self.p_range_SI[1] - - # enthalpy - if c.h.val_SI < self.h_range_SI[0] and not c.h.val_set: - c.h.val_SI = self.h_range_SI[0] - if c.h.val_SI > self.h_range_SI[1] and not c.h.val_set: - c.h.val_SI = self.h_range_SI[1] - - # make sure, that at given temperatures values stay within feasible - # range: - # for mixtures: calculate maximum enthalpy and compare with - # acutal value - # for pure fluids: - # obtain maximum temperature from fluid properties directly - ###### this part seems to cause bad convergence in some cases ###### - if c.T.val_set and not c.h.val_set and not c.p.val_set: - self.solve_check_temperature(c, 'min') - self.solve_check_temperature(c, 'max') - - def solve_check_temperature(self, c, pos): + # temperature + if c.T.val_set and not c.h.val_set and not c.p.val_set: + self.solve_check_temperature(c) + + def solve_check_temperature(self, c): """ checks for invalid fluid temperatures in solution progress and adjusts values if necessary @@ -1382,45 +1375,17 @@ def solve_check_temperature(self, c, pos): :param c: connection object to check :type c: tespy.connections.connection - :param pos: check at upper or lower boundary - :type pos: str :returns: no return value """ - val = 'T' + pos - if pos == 'min': - fac = 1.1 - idx = 0 - else: - fac = 0.9 - idx = 1 - - try: - T = CPPSI(val, 'T', 0, 'P', 0, hlp.single_fluid(c.fluid.val)) * fac - except: - T = self.T_range_SI[idx] + hmin = hlp.h_mix_pT(c.to_flow(), self.T_range_SI[0]) + hmax = hlp.h_mix_pT(c.to_flow(), self.T_range_SI[1]) - if pos == 'min': - if T < self.T_range_SI[idx]: - T = self.T_range_SI[idx] - else: - if T > self.T_range_SI[idx]: - T = self.T_range_SI[idx] - - p_temp = c.p.val_SI - if 'INCOMP::' in hlp.single_fluid(c.fluid.val): - c.p.val_SI = CPPSI('P', 'T', T, 'Q', 0, - hlp.single_fluid(c.fluid.val)) + if c.h.val_SI < hmin: + c.h.val_SI = hmin * 1.05 - h = hlp.h_mix_pT(c.to_flow(), T) - c.p.val_SI = p_temp - - if pos == 'min': - if c.h.val_SI < h: - c.h.val_SI = h * fac - else: - if c.h.val_SI > h: - c.h.val_SI = h * fac + if c.h.val_SI > hmax: + c.h.val_SI = hmax * 0.95 def solve_components(self): """ From bff80c8e1b3d3a2f685235f4cc666ef4eb0086c2 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 18 Oct 2018 18:50:00 +0200 Subject: [PATCH 45/80] modified combustion chamber reference states and added documentation --- tespy/components/components.py | 135 +++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 48 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index 96c31cfdd..fb180744d 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -3067,31 +3067,51 @@ def energy_balance(self): r""" calculates the energy balance of the adiabatic combustion chamber - - reference temperature: 500 K - - reference pressure: 1 bar + .. note:: + The temperature for the reference state is set to 20 °C, thus + the water may be liquid. In order to make sure, the state is + referring to the lower heating value, the necessary enthalpy + difference for evaporation is added. The stoichiometric combustion + chamber uses a different reference, you will find it in the + :func:`tespy.components.components.combustion_chamber_stoich.energy_balance` + documentation. + + - reference temperature: 293.15 K + - reference pressure: 1 bar :returns: res (*float*) - residual value of energy balance .. math:: - 0 = \dot{m}_{in,i} \cdot \left( h_{in,i} - h_{in,i,ref} \right) - - \dot{m}_{out,j} \cdot \left( h_{out,j} - h_{out,j,ref} \right) + - H_{I,f} \cdot \left( \dot{m}_{in,i} \cdot x_{f,i} - - \dot{m}_{out,j} \cdot x_{f,j} \right) + 0 = \sum_i \dot{m}_{in,i} \cdot \left( h_{in,i} - h_{in,i,ref} + \right) - \sum_j \dot{m}_{out,j} \cdot + \left( h_{out,j} - h_{out,j,ref} \right) + + H_{I,f} \cdot \left(\sum_i \dot{m}_{in,i} \cdot x_{f,i} - + \sum_j \dot{m}_{out,j} \cdot x_{f,j} \right) + \; \forall i \in \text{inlets}\; \forall j \in \text{outlets} """ - T_ref = 500 + T_ref = 293.15 p_ref = 1e5 res = 0 for i in self.inl: - res += i.m.val_SI * (i.h.val_SI - - h_mix_pT([i.m.val_SI, p_ref, i.h.val_SI, - i.fluid.val], T_ref)) + res += i.m.val_SI * ( + i.h.val_SI - h_mix_pT([0, p_ref, 0, i.fluid.val], T_ref)) res += i.m.val_SI * i.fluid.val[self.fuel.val] * self.lhv + for o in self.outl: - res -= o.m.val_SI * (o.h.val_SI - - h_mix_pT([o.m.val_SI, p_ref, o.h.val_SI, - o.fluid.val], T_ref)) + dh = 0 + n_h2o = o.fluid.val[self.h2o] / molar_masses[self.h2o] + if n_h2o > 0: + p = p_ref * n_h2o / molar_massflow(o.fluid.val) + h = CP.PropsSI('H', 'P', p, 'T', T_ref, self.h2o) + h_steam = CP.PropsSI('H', 'P', p, 'Q', 1, self.h2o) + if h < h_steam: + dh = (h_steam - h) * o.fluid.val[self.h2o] + + res -= o.m.val_SI * ( + o.h.val_SI - h_mix_pT([0, p_ref, 0, o.fluid.val], T_ref) - + dh) res -= o.m.val_SI * o.fluid.val[self.fuel.val] * self.lhv return res @@ -3420,24 +3440,34 @@ def calc_parameters(self, nw, mode): self.lamb.val = n_oxygen / ( n_fuel * (self.n['C'] + self.n['H'] / 4)) - S = 0 - T_ref = 500 + val = 0 + T_ref = 293.15 p_ref = 1e5 for i in self.inl: - S -= i.m.val_SI * (s_mix_ph(i.to_flow()) - - s_mix_pT([0, p_ref, 0, i.fluid.val], T_ref)) + val -= i.m.val_SI * ( + s_mix_ph(i.to_flow()) - + s_mix_pT([0, p_ref, 0, i.fluid.val], T_ref)) for o in self.outl: - S += o.m.val_SI * (s_mix_ph(o.to_flow()) - - s_mix_pT([0, p_ref, 0, o.fluid.val], T_ref)) - - self.S.val = S + dS = 0 + n_h2o = o.fluid.val[self.h2o] / molar_masses[self.h2o] + if n_h2o > 0: + p = p_ref * n_h2o / molar_massflow(o.fluid.val) + S = CP.PropsSI('S', 'P', p, 'T', T_ref, self.h2o) + S_steam = CP.PropsSI('H', 'P', p, 'Q', 1, self.h2o) + if S < S_steam: + dS = (S_steam - S) * o.fluid.val[self.h2o] + val += o.m.val_SI * ( + s_mix_ph(o.to_flow()) - + s_mix_pT([0, p_ref, 0, o.fluid.val], T_ref) - dS) + + self.S.val = val if mode == 'pre': if 'lamb' in self.offdesign: - self.lamb.val = n_oxygen / (n_fuel * - (self.n['C'] + self.n['H'] / 4)) + self.lamb.val = n_oxygen / (n_fuel * ( + self.n['C'] + self.n['H'] / 4)) # %% @@ -3732,7 +3762,9 @@ def stoich_flue_gas(self, nw): molar_masses[self.co2] = CP.PropsSI('M', self.co2) molar_masses[self.o2] = CP.PropsSI('M', self.o2) - fg = {} + self.fg = {} + self.fg[self.co2] = 0 + self.fg[self.h2o] = 0 for f, x in self.fuel.val.items(): fl = set(list(self.fuels())).intersection( @@ -3740,8 +3772,10 @@ def stoich_flue_gas(self, nw): for a in CP.get_aliases(f)])) if len(fl) == 0: - fg[f] = x * m_fuel - continue + if f in self.fg.keys(): + self.fg[f] += x * m_fuel + else: + self.fg[f] = x * m_fuel else: n_fluid = x * m_fuel / molar_masses[f] m_fuel_fg -= n_fluid * molar_masses[f] @@ -3756,8 +3790,8 @@ def stoich_flue_gas(self, nw): m_co2 += n_fluid * n['C'] * molar_masses[self.co2] m_h2o += n_fluid * n['H'] / 2 * molar_masses[self.h2o] - fg[self.co2] = m_co2 - fg[self.h2o] = m_h2o + self.fg[self.co2] += m_co2 + self.fg[self.h2o] += m_h2o n_o2 = (m_co2 / molar_masses[self.co2] + 0.5 * m_h2o / molar_masses[self.h2o]) * lamb @@ -3767,20 +3801,20 @@ def stoich_flue_gas(self, nw): for f, x in self.air.val.items(): if f != self.o2: - if f in fg.keys(): - fg[f] += m_air * x + if f in self.fg.keys(): + self.fg[f] += m_air * x else: - fg[f] = m_air * x + self.fg[f] = m_air * x m_fg = m_fuel + m_air - for f in fg.keys(): - fg[f] /= m_fg + for f in self.fg.keys(): + self.fg[f] /= m_fg tespy_fluid(self.fuel_alias.val, self.fuel.val, [1000, nw.p_range_SI[1]], nw.T_range_SI, path=self.path) - tespy_fluid(self.fuel_alias.val + '_fg', fg, + tespy_fluid(self.fuel_alias.val + '_fg', self.fg, [1000, nw.p_range_SI[1]], nw.T_range_SI, path=self.path) if self.air_alias.val not in ['Air', 'air']: @@ -3899,33 +3933,38 @@ def energy_balance(self): r""" calculates the energy balance of the adiabatic combustion chamber - - reference temperature: 500 K - - reference pressure: 1 bar + .. note:: + The temperature for the reference state is set to 100 °C, as the + custom fluid properties are inacurate at the dew-point of water in + the flue gas! + + - reference temperature: 373.15 K + - reference pressure: 1 bar :returns: res (*float*) - residual value of energy balance .. math:: - 0 = \dot{m}_{in,i} \cdot \left( h_{in,i} - h_{in,i,ref} \right) - - \dot{m}_{out,j} \cdot \left( h_{out,j} - h_{out,j,ref} \right) + - H_{I,f} \cdot \left( \dot{m}_{in,i} \cdot x_{f,i} - - \dot{m}_{out,j} \cdot x_{f,j} \right) + 0 = \sum_i \dot{m}_{in,i} \cdot \left( h_{in,i} - h_{in,i,ref} + \right) - \sum_j \dot{m}_{out,j} \cdot + \left( h_{out,j} - h_{out,j,ref} \right) + + H_{I,f} \cdot \left(\sum_i \dot{m}_{in,i} \cdot x_{f,i} - + \sum_j \dot{m}_{out,j} \cdot x_{f,j} \right) + \; \forall i \in \text{inlets}\; \forall j \in \text{outlets} """ fuel = 'TESPy::' + self.fuel_alias.val - T_ref = 500 + T_ref = 373.15 p_ref = 1e5 res = 0 for i in self.inl: - res += i.m.val_SI * (i.h.val_SI - - h_mix_pT([i.m.val_SI, p_ref, i.h.val_SI, - i.fluid.val], T_ref)) + res += i.m.val_SI * ( + i.h.val_SI - h_mix_pT([0, p_ref, 0, i.fluid.val], T_ref)) res += i.m.val_SI * i.fluid.val[fuel] * self.lhv for o in self.outl: - res -= o.m.val_SI * (o.h.val_SI - - h_mix_pT([o.m.val_SI, p_ref, o.h.val_SI, - o.fluid.val], T_ref)) + res -= o.m.val_SI * ( + o.h.val_SI - h_mix_pT([0, p_ref, 0, o.fluid.val], T_ref)) res -= o.m.val_SI * o.fluid.val[fuel] * self.lhv return res @@ -4097,7 +4136,7 @@ def calc_parameters(self, nw, mode): self.lamb.val = (m_air / m_fuel) / self.air_min S = 0 - T_ref = 500 + T_ref = 373.15 p_ref = 1e5 for i in self.inl: From 4e8712e2b55ea5dc5429ea5311ad88f474ca7945 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 18 Oct 2018 18:51:32 +0200 Subject: [PATCH 46/80] adjusted feasible temperature ranges for convergence check of CoolProp fluids --- tespy/networks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tespy/networks.py b/tespy/networks.py index b2cf0421d..90f56e205 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -1341,8 +1341,8 @@ def solve_check_props(self, c): c.p.val_SI = hlp.memorise.vrange[fl][1] * 0.9 # enthalpy - hmin = hlp.h_pT(c.p.val_SI, hlp.memorise.vrange[fl][2], fl) - hmax = hlp.h_pT(c.p.val_SI, hlp.memorise.vrange[fl][3], fl) + hmin = hlp.h_pT(c.p.val_SI, hlp.memorise.vrange[fl][2] * 1.01, fl) + hmax = hlp.h_pT(c.p.val_SI, hlp.memorise.vrange[fl][3] * 0.99, fl) if c.h.val_SI < hmin and not c.h.val_set: c.h.val_SI = hmin * 3 if c.h.val_SI > hmax and not c.h.val_set: From 09a9fc3d2f51db0d7fa9a414ee1f39e33c71db22 Mon Sep 17 00:00:00 2001 From: Paul-Jonas Date: Thu, 18 Oct 2018 19:55:24 +0200 Subject: [PATCH 47/80] characteristics vessel and compressor --- tespy/components/components.py | 133 ++++++++++++++++++++++++++++++++- tespy/components/subsystems.py | 58 +++++++++++++- 2 files changed, 188 insertions(+), 3 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index b818d72ab..a664e8df6 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -1488,6 +1488,13 @@ def comp_init(self, nw): method = self.char_map.method self.char_map.func = cmp_char.compressor(method=method) + if self.flow_char.func is None: + method = self.flow_char.method + x = self.flow_char.x + y = self.flow_char.y + self.flow_char.func = cmp_char.characteristics(method=method, + x=x, y=y) + def component(self): return 'compressor' @@ -1495,7 +1502,8 @@ def attr(self): return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), 'igva': dc_cp(min_val=-45, max_val=45, d=1e-2, val=0), 'Sirr': dc_cp(), - 'char_map': dc_cc(method='GENERIC')} + 'char_map': dc_cc(method='GENERIC'), + 'flow_char': dc_cc(x=[0, 1, 2, 3], y=[1, 1, 1, 1])} def default_offdesign(self): return ['char_map'] @@ -1519,6 +1527,9 @@ def additional_equations(self): if self.char_map.is_set: vec_res += self.char_func().tolist() + if self.flow_char.is_set: + vec_res += self.flow_char_func().tolist() + return vec_res def additional_derivatives(self, nw): @@ -1535,6 +1546,9 @@ def additional_derivatives(self, nw): if self.char_map.is_set: mat_deriv += self.char_deriv() + if self.flow_char.is_set: + mat_deriv += self.flow_char_deriv() + return mat_deriv def eta_s_func(self): @@ -1677,6 +1691,58 @@ def char_deriv(self): deriv[1, 2 + self.igva.var_pos, 0] = igva[1] return deriv.tolist() + def flow_char_func(self): + r""" + equation for characteristics of a compressor + """ + i = self.inl[0].to_flow() + o = self.outl[0].to_flow() + + expr = (o[1] / i[1]) + + if expr > self.flow_char.func.x[-1]: + expr = self.flow_char.func.x[-1] + elif expr < self.flow_char.func.x[0]: + expr = self.flow_char.func.x[0] + +# print(self.flow_char.func.f_x(expr)) + return np.array([(self.h_os('post') - i[2]) - + (o[2] - i[2]) * + self.flow_char.func.f_x(expr)]) + + def flow_char_deriv(self): + r""" + calculates the derivatives for the characteristics + + :returns: mat_deriv (*list*) - matrix of derivatives + + **example** + + one fluid in fluid vector + + .. math:: + + \left( + \begin{array}{cccc} + \frac{\partial char}{\partial \dot{m}_{in}} & + \frac{\partial char}{\partial p_{in}} & + \frac{\partial char}{\partial h_{in}} & 0\\ + 0 & \frac{\partial char}{\partial p_{out}} & + \frac{\partial char}{\partial h_{out}} & 0\\ + \end{array} + \right) + + """ + num_fl = len(self.inl[0].fluid.val) + mat_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) + + mat_deriv[0, 0, 1] = self.ddx_func(self.flow_char_func, 'p', 0) + mat_deriv[0, 1, 1] = self.ddx_func(self.flow_char_func, 'p', 1) + mat_deriv[0, 0, 2] = self.ddx_func(self.flow_char_func, 'h', 0) + mat_deriv[0, 1, 2] = self.ddx_func(self.flow_char_func, 'h', 1) + + return mat_deriv.tolist() + def convergence_check(self, nw): """ performs a convergence check @@ -1707,6 +1773,16 @@ def convergence_check(self, nw): if not i[0].h.val_set and o[0].h.val_SI < i[0].h.val_SI: i[0].h.val_SI = o[0].h.val_SI * 0.9 +# if self.flow_char.is_set: +# expr = i[0].m.val_SI * v_mix_ph(i[0].to_flow()) +# +# if expr > self.flow_char.func.x[-1] and not i[0].m.val_set: +# i[0].m.val_SI = self.flow_char.func.x[-1] / v_mix_ph( +# i[0].to_flow()) +# elif expr < self.flow_char.func.x[1] and not i[0].m.val_set: +# i[0].m.val_SI = self.flow_char.func.x[0] / v_mix_ph( +# i[0].to_flow()) + def initialise_source(self, c, key): r""" returns a starting value for fluid properties at components outlet @@ -4152,11 +4228,24 @@ class vessel(component): :alt: alternative text :align: center """ + def comp_init(self, nw): + + component.comp_init(self, nw) + + if self.pr_char.func is None: + method = self.pr_char.method + w = self.pr_char.x + v = self.pr_char.y + + print(w, v) + self.pr_char.func = cmp_char.characteristics(method=method, + x=w, y=v) def attr(self): return {'pr': dc_cp(min_val=1e-4), 'zeta': dc_cp(min_val=1e-4), - 'Sirr': dc_cp()} + 'Sirr': dc_cp(), + 'pr_char': dc_cc(x=[0, 1, 2, 3], y=[1, 1, 1, 1])} def default_design(self): return ['pr'] @@ -4213,6 +4302,9 @@ def equations(self): if self.zeta.is_set: vec_res += [self.zeta_func()] + if self.pr_char.is_set: + vec_res += self.pr_char_func().tolist() + return vec_res def derivatives(self, nw): @@ -4255,6 +4347,9 @@ def derivatives(self, nw): self.ddx_func(self.zeta_func, 'zeta', i)) mat_deriv += zeta_deriv.tolist() + if self.pr_char.is_set: + mat_deriv += self.pr_char_deriv() + return np.asarray(mat_deriv) def initialise_source(self, c, key): @@ -4299,6 +4394,40 @@ def initialise_target(self, c, key): else: return 0 + def pr_char_func(self): + r""" + equation for characteristics of a vessel + """ + i = self.inl[0].to_flow() + o = self.outl[0].to_flow() + + expr = i[1] + + if expr > self.pr_char.func.x[-1]: + expr = self.pr_char.func.x[-1] + elif expr < self.pr_char.func.x[0]: + expr = self.pr_char.func.x[0] + +# print(i[1], o[1]) +# print(self.pr_char.func.f_x(expr)) + + return np.array([(i[1] / o[1]) - self.pr_char.func.f_x(expr)]) +# return np.array([-i[1] + o[1] * self.pr_char.func.f_x(expr)]) + + def pr_char_deriv(self): + r""" + calculates the derivatives for the characteristics + + :returns: mat_deriv (*list*) - matrix of derivatives + """ + num_fl = len(self.inl[0].fluid.val) + mat_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) + + mat_deriv[0, 0, 1] = self.ddx_func(self.pr_char_func, 'p', 0) + mat_deriv[0, 1, 1] = self.ddx_func(self.pr_char_func, 'p', 1) + + return mat_deriv.tolist() + def calc_parameters(self, nw, mode): if mode == 'post' or (mode == 'pre' and 'pr' in self.offdesign): diff --git a/tespy/components/subsystems.py b/tespy/components/subsystems.py index 57c783ee1..747ef5307 100644 --- a/tespy/components/subsystems.py +++ b/tespy/components/subsystems.py @@ -384,7 +384,7 @@ def attr(self): return ([n for n in subsystem.attr(self) if n != 'num_i' and n != 'num_o'] + ['ttd', 'pr1_desup', 'pr2_desup', - 'pr1_cond', 'pr2_cond', 'pr1_subc', 'pr2_subc']) + 'pr1_cond', 'pr2_cond', 'pr1_subc', 'pr2_subc', 'pr_v']) def create_comps(self): @@ -408,6 +408,7 @@ def set_comps(self): self.condenser.set_attr(pr2=self.pr2_cond) self.subcooler.set_attr(pr1=self.pr1_cond) self.subcooler.set_attr(pr2=self.pr2_cond) + self.vessel.set_attr(pr=self.pr_v) def create_conns(self): @@ -498,3 +499,58 @@ def create_conns(self): self.condenser, 'in2')] self.conns += [connection(self.condenser, 'out2', self.desup, 'in2')] self.conns += [connection(self.desup, 'out2', self.outlet, 'in2')] + +# %% + + +class eva_sup(subsystem): + """ + Evaporator with superheater + + **available parameters** + + - pr1_eva: pressure drop at hot side of evaporator + - pr2_eva: pressure drop at cold side of evaporator + - pr1_sup: pressure drop at hot side of superheater + - pr2_sup: pressure drop at cold side of superheater + + **inlets and outlets** + + - in1, in2 + - out1, out2 + + """ + + def attr(self): + return ([n for n in subsystem.attr(self) if + n != 'num_i' and n != 'num_o'] + + ['pr1_eva', 'pr2_eva', 'pr1_sup', 'pr2_sup']) + + def create_comps(self): + + self.num_i = 2 + self.num_o = 2 + self.inlet = cmp.subsys_interface(label=self.label + '_inlet', + num_inter=self.num_i) + self.outlet = cmp.subsys_interface(label=self.label + '_outlet', + num_inter=self.num_o) + self.eva = cmp.heat_exchanger(label=self.label + '_eva') + self.sup = cmp.heat_exchanger(label=self.label + '_sup') + + def set_comps(self): + + self.eva.set_attr(pr1=self.pr1_eva) + self.eva.set_attr(pr2=self.pr2_eva) + self.sup.set_attr(pr1=self.pr1_sup) + self.sup.set_attr(pr2=self.pr2_sup) + + def create_conns(self): + + self.conns = [] + + self.conns += [connection(self.inlet, 'out1', self.sup, 'in1')] + self.conns += [connection(self.sup, 'out1', self.eva, 'in1')] + self.conns += [connection(self.eva, 'out1', self.outlet, 'in1')] + self.conns += [connection(self.inlet, 'out2', self.eva, 'in2')] + self.conns += [connection(self.eva, 'out2', self.sup, 'in2', x=1)] + self.conns += [connection(self.sup, 'out2', self.outlet, 'in2')] From 4cee151b5abf721245978c5edf155f7e606cd79f Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Fri, 19 Oct 2018 11:37:06 +0200 Subject: [PATCH 48/80] added first equations for cogeneration unit --- tespy/components/components.py | 413 +++++++++++++++------------------ 1 file changed, 188 insertions(+), 225 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index fb180744d..1b6a233e4 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -326,30 +326,35 @@ def fluid_res(self): .. math:: 0 = fluid_{i,in} - fluid_{i,out} \; \forall i \in \mathrm{fluid} - **component heat exchanger or subsystem interface** + **heat exchanger, subsystem interface** .. math:: 0 = fluid_{i,in_{j}} - fluid_{i,out_{j}} \; \forall i \in \mathrm{fluid}, \; \forall j \in inlets/outlets - **component splitter** + **cogeneration unit** + + .. math:: 0 = fluid_{i,in_{j}} - fluid_{i,out_{j}} \; + \forall i \in \mathrm{fluid}, \; \forall j \in [1, 2] + + **splitter** .. math:: 0 = fluid_{i,in} - fluid_{i,out_{j}} \; \forall i \in \mathrm{fluid}, \; \forall j \in outlets - **component merge** + **merge** .. math:: 0 = \dot{m}_{in_{j}} \cdot fluid_{i,in_{j}} - \dot {m}_{out} \cdot fluid_{i,out} \\ \forall i \in \mathrm{fluid}, \; \forall j \in inlets - **component drum** + **drum** .. math:: 0 = fluid_{i,in_1} - fluid_{i,out_{j}} \; \forall i \in \mathrm{fluid}, \; \forall j \in inlets - **component separator** + **separator** .. math:: 0 = \dot{m}_{in} \cdot fluid_{i,in} - @@ -371,6 +376,12 @@ def fluid_res(self): vec_res += [x - self.outl[i].fluid.val[fluid]] return vec_res + if isinstance(self, cogeneration_unit): + for i in range(2): + for fluid, x in self.inl[i].fluid.val.items(): + vec_res += [x - self.outl[i].fluid.val[fluid]] + return vec_res + if isinstance(self, splitter): for o in self.outl: for fluid, x in self.inl[0].fluid.val.items(): @@ -409,32 +420,6 @@ def fluid_deriv(self): returns derivatives for fluid equations :returns: mat_deriv (*list*) - a list containing the derivatives - - **example:** - - component with one inlet and one outlet and 3 fluids in fluid vector - - .. math:: - \left( - \begin{array}{cccccc} - 0 & 0 & 0 & 1 & 0 & 0\\ - 0 & 0 & 0 & -1 & 0 & 0\\ - \end{array} - \right) - - \left( - \begin{array}{cccccc} - 0 & 0 & 0 & 0 & 1 & 0\\ - 0 & 0 & 0 & 0 & -1 & 0\\ - \end{array} - \right) - - \left( - \begin{array}{cccccc} - 0 & 0 & 0 & 0 & 0 & 1\\ - 0 & 0 & 0 & 0 & 0 & -1\\ - \end{array} - \right) """ num_fl = len(self.inl[0].fluid.val) @@ -461,6 +446,20 @@ def fluid_deriv(self): j += 1 return mat_deriv.tolist() + if isinstance(self, cogeneration_unit): + mat_deriv = np.zeros((num_fl * 2, 7 + self.num_c_vars, 3 + num_fl)) + i = 0 + for fluid in self.inl[0].fluid.val.keys(): + mat_deriv[i, 0, i + 3] = 1 + mat_deriv[i, 4, i + 3] = -1 + i += 1 + j = 0 + for fluid in self.inl[1].fluid.val.keys(): + mat_deriv[i + j, 1, j + 3] = 1 + mat_deriv[i + j, 5, j + 3] = -1 + j += 1 + return mat_deriv.tolist() + if isinstance(self, splitter): mat_deriv = np.zeros((num_fl * self.num_o, 1 + self.num_o, 3 + num_fl)) @@ -536,24 +535,31 @@ def mass_flow_res(self): :returns: vec_res (*list*) - a list containing the residual values - **all components but heat exchanger and subsystem interface** + **heat exchanger and subsystem interface (same number of inlets and + outlets)** - .. math:: 0 = \sum \dot{m}_{in,i} - \sum \dot{m}_{out,j} \; - \forall i \in inlets, \forall j \in outlets + .. math:: 0 = \dot{m}_{in,i} - \dot{m}_{out,i} \; + \forall i \in inlets/outlets - heat exchanger and subsystem interface (same number of inlets and - outlets + **cogeneration unit** .. math:: 0 = \dot{m}_{in,i} - \dot{m}_{out,i} \; - \forall i \in inlets/outlets + \forall i \in [1, 2]\\ + 0 = \dot{m}_{in,3} + \dot{m}_{in,4} - \dot{m}_{out,3} + + **other components** + + .. math:: 0 = \sum \dot{m}_{in,i} - \sum \dot{m}_{out,j} \; + \forall i \in inlets, \forall j \in outlets """ - if (isinstance(self, split) or + if ((isinstance(self, split) or isinstance(self, merge) or isinstance(self, combustion_chamber) or isinstance(self, combustion_chamber_stoich) or isinstance(self, drum) or - (self.num_i == 1 and self.num_o == 1)): + (self.num_i == 1 and self.num_o == 1)) and + not isinstance(self, cogeneration_unit)): res = 0 for i in self.inl: res += i.m.val_SI @@ -568,6 +574,14 @@ def mass_flow_res(self): vec_res += [self.inl[i].m.val_SI - self.outl[i].m.val_SI] return vec_res + if isinstance(self, cogeneration_unit): + vec_res = [] + for i in range(2): + vec_res += [self.inl[i].m.val_SI - self.outl[i].m.val_SI] + vec_res += [self.inl[2].m.val_SI + self.inl[3].m.val_SI - + self.outl[2].m.val_SI] + return vec_res + if isinstance(self, source) or isinstance(self, sink): return None @@ -576,20 +590,6 @@ def mass_flow_deriv(self): returns derivatives for mass flow equations :returns: mat_deriv (*list*) - a list containing the derivatives - - **example** - - merge with three inlets and one outlet (one fluid in fluid vector) - - .. math:: - \left( - \begin{array}{cccc} - 1 & 0 & 0 & 0\\ - 1 & 0 & 0 & 0\\ - 1 & 0 & 0 & 0\\ - -1 & 0 & 0 & 0\\ - \end{array} - \right) """ num_fl = len(self.inl[0].fluid.val) @@ -621,6 +621,18 @@ def mass_flow_deriv(self): mat_deriv[j, j + i + 1, 0] = -1 return mat_deriv.tolist() + if isinstance(self, cogeneration_unit): + mat_deriv = np.zeros((3, self.num_i + self.num_o + + self.num_c_vars, num_fl + 3)) + for i in range(2): + mat_deriv[i, i, 0] = 1 + for j in range(2): + mat_deriv[j, self.num_i + j, 0] = -1 + mat_deriv[2, 2, 0] = 1 + mat_deriv[2, 3, 0] = 1 + mat_deriv[2, 6, 0] = -1 + return mat_deriv.tolist() + if isinstance(self, source) or isinstance(self, sink): return None # %% @@ -735,6 +747,29 @@ def zeta_func(self): return (val - (i[1] - o[1]) * math.pi ** 2 / (8 * i[0] ** 2 * (v_mix_ph(i) + v_mix_ph(o)) / 2)) + def zeta2_func(self): + r""" + calculates pressure drop from zeta2 + + :returns: residual value for the pressure drop + + .. math:: + + \zeta_2 = \frac{\Delta p_2 \cdot v_2 \cdot 2}{c_2^2}\\ + c_2 = \frac{\dot{m}_2 \cdot v_2}{A_2} + + As the cross sectional area A will not change from design to offdesign + calculation, it is possible to handle this the following way: + + .. math:: + 0 = \zeta_2 - \frac{(p_{2,in} - p_{2,out}) \cdot \pi^2}{8 \cdot + \dot{m}_{2,in}^2 \cdot \frac{v_{2,in} + v_{2,out}}{2}} + """ + i = self.inl[1].to_flow() + o = self.outl[1].to_flow() + return (self.zeta2.val - (i[1] - o[1]) * math.pi ** 2 / + (8 * i[0] ** 2 * (v_mix_ph(i) + v_mix_ph(o)) / 2)) + # %% @@ -2907,8 +2942,7 @@ def derivatives(self, nw): for fluid in nw.fluids: for i in range(3): fl_deriv[j, i, 0] = self.drb_dx('m', i, fluid) - fl_deriv[j, i, 3:] = ( - self.drb_dx('fluid', i, fluid)) + fl_deriv[j, i, 3:] = self.drb_dx('fluid', i, fluid) j += 1 mat_deriv += fl_deriv.tolist() @@ -2972,12 +3006,15 @@ def reaction_balance(self, fluid): **reaction balance equations** .. math:: + \text{combustion chamber: } i \in [1,2], o \in [1]\\ + \text{cogeneration unit: } i \in [3,4], o \in [3]\\ + res = \sum_i \left(x_{fluid,i} \cdot \dot{m}_{i}\right) - \sum_j \left(x_{fluid,j} \cdot \dot{m}_{j}\right) \; - \forall i \in inlets, \; \forall j \in outlets + \forall i, \; \forall j \dot{m}_{fluid,m} = \sum_i \frac{x_{fluid,i} \cdot \dot{m}_{i}} - {M_{fluid}} \; \forall i \in inlets\\ + {M_{fluid}} \; \forall i \lambda = \frac{\dot{m}_{f,m}}{\dot{m}_{O_2,m} \cdot \left(n_{C,fuel} + 0.25 \cdot n_{H,fuel}\right)} @@ -3021,13 +3058,20 @@ def reaction_balance(self, fluid): """ + if isinstance(self, cogeneration_unit): + inl = self.inl[2:] + outl = self.outl[2:] + else: + inl = self.inl + outl = self.inl + n_fuel = 0 - for i in self.inl: + for i in inl: n_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val] / molar_masses[self.fuel.val]) n_oxygen = 0 - for i in self.inl: + for i in inl: n_oxygen += (i.m.val_SI * i.fluid.val[self.o2] / molar_masses[self.o2]) @@ -3057,9 +3101,9 @@ def reaction_balance(self, fluid): res = dm - for i in self.inl: + for i in inl: res += i.fluid.val[fluid] * i.m.val_SI - for o in self.outl: + for o in outl: res -= o.fluid.val[fluid] * o.m.val_SI return res @@ -4160,7 +4204,7 @@ def calc_parameters(self, nw, mode): # %% -class cogeneration_unit(component): +class cogeneration_unit(combustion_chamber): r""" .. note:: @@ -4175,6 +4219,14 @@ class cogeneration_unit(component): :math:`[LHV \cdot \dot{m}_f] = \text{W}` - P: power output, :math:`{[P]=\text{W}}` - Q: total heat output, :math:`{[\dot Q]=\text{W}}` + - pr1: outlet to inlet pressure ratio at hot side, :math:`[pr1]=1` + - pr2: outlet to inlet pressure ratio at cold side, :math:`[pr2]=1` + - zeta1: geometry independent friction coefficient heat extraction 1 + :math:`[\zeta1]=\frac{\text{Pa}}{\text{m}^4}`, also see + :func:`tespy.components.components.component.zeta_func` + - zeta2: geometry independent friction coefficient heat extraction 2 + :math:`[\zeta2]=\frac{\text{Pa}}{\text{m}^4}`, also see + :func:`tespy.components.components.heat_exchanger.zeta2_func` **equations** @@ -4211,7 +4263,10 @@ def outlets(self): def attr(self): return {'fuel': dc_cp(printout=False), 'lamb': dc_cp(), 'ti': dc_cp(), - 'P': dc_cp(), 'Q': dc_cp(), 'S': dc_cp()} + 'P': dc_cp(), 'Q': dc_cp(), + 'pr1': dc_cp(), 'pr2': dc_cp(), + 'zeta1': dc_cp(), 'zeta2': dc_cp(), + 'S': dc_cp()} def fuels(self): return ['methane', 'ethane', 'propane', 'butane', @@ -4328,6 +4383,8 @@ def equations(self): **mandatory equations** - :func:`tespy.components.components.cogeneration_unit.reaction_balance` + - :func:`tespy.components.components.component.fluid_res` + (for cooling water) - :func:`tespy.components.components.component.mass_flow_res` .. math:: @@ -4359,10 +4416,11 @@ def equations(self): for fluid in self.inl[0].fluid.val.keys(): vec_res += [self.reaction_balance(fluid)] + vec_res += self.fluid_res() vec_res += self.mass_flow_res() - vec_res += [self.inl[0].p.val_SI - self.outl[0].p.val_SI] - vec_res += [self.outl[0].p.val_SI - self.outl[0].p.val_SI] + vec_res += [self.inl[2].p.val_SI - self.outl[2].p.val_SI] + vec_res += [self.inl[2].p.val_SI - self.inl[3].p.val_SI] vec_res += [self.energy_balance()] @@ -4372,18 +4430,26 @@ def equations(self): if self.ti.is_set: vec_res += [self.ti_func()] - if self.pr1.is_set: - vec_res += [self.ti_func()] - - if self.pr2.is_set: - vec_res += [self.ti_func()] - if self.P.is_set: vec_res += [self.power_func()] if self.Q.is_set: vec_res += [self.heat_func()] + if self.pr1.is_set: + vec_res += [self.pr1.val * self.inl[0].p.val_SI - + self.outl[0].p.val_SI] + + if self.pr2.is_set: + vec_res += [self.pr2.val * self.inl[1].p.val_SI - + self.outl[1].p.val_SI] + + if self.zeta1.is_set: + vec_res += [self.zeta_func()] + + if self.zeta2.is_set: + vec_res += [self.zeta2_func()] + return vec_res def derivatives(self, nw): @@ -4401,28 +4467,31 @@ def derivatives(self, nw): # derivatives for reaction balance j = 0 - fl_deriv = np.zeros((num_fl, 3, num_fl + 3)) + fl_deriv = np.zeros((num_fl, 7, num_fl + 3)) for fluid in nw.fluids: for i in range(3): fl_deriv[j, i, 0] = self.drb_dx('m', i, fluid) - fl_deriv[j, i, 3:] = ( - self.drb_dx('fluid', i, fluid)) + fl_deriv[j, i, 3:] = self.drb_dx('fluid', i, fluid) j += 1 mat_deriv += fl_deriv.tolist() + # derivatives for fluid balances (except combustion) + mat_deriv += self.fluid_deriv() + # derivatives for mass balance mat_deriv += self.mass_flow_deriv() # derivatives for pressure equations - p_deriv = np.zeros((2, 3, num_fl + 3)) + p_deriv = np.zeros((2, 7, num_fl + 3)) for k in range(2): p_deriv[k][2][1] = 1 - p_deriv[k][k][1] = -1 + p_deriv[0][6][1] = -1 + p_deriv[1][3][1] = -1 mat_deriv += p_deriv.tolist() # derivatives for energy balance - eb_deriv = np.zeros((1, 3, num_fl + 3)) + eb_deriv = np.zeros((1, 7, num_fl + 3)) for i in range(3): eb_deriv[0, i, 0] = ( self.ddx_func(self.energy_balance, 'm', i)) @@ -4436,7 +4505,7 @@ def derivatives(self, nw): if self.lamb.is_set: # derivatives for specified lambda - lamb_deriv = np.zeros((1, 3, num_fl + 3)) + lamb_deriv = np.zeros((1, 7, num_fl + 3)) for i in range(2): lamb_deriv[0, i, 0] = self.ddx_func(self.lambda_func, 'm', i) lamb_deriv[0, i, 3:] = self.ddx_func(self.lambda_func, @@ -4445,7 +4514,7 @@ def derivatives(self, nw): if self.ti.is_set: # derivatives for specified thermal input - ti_deriv = np.zeros((1, 3, num_fl + 3)) + ti_deriv = np.zeros((1, 7, num_fl + 3)) for i in range(2): ti_deriv[0, i, 0] = self.ddx_func(self.ti_func, 'm', i) ti_deriv[0, i, 3:] = self.ddx_func(self.ti_func, 'fluid', i) @@ -4453,113 +4522,39 @@ def derivatives(self, nw): ti_deriv[0, 2, 3:] = self.ddx_func(self.ti_func, 'fluid', 2) mat_deriv += ti_deriv.tolist() - return np.asarray(mat_deriv) + ########################## - def reaction_balance(self, fluid): - r""" - calculates the reactions mass balance for one fluid - - - determine molar mass flows of fuel and oxygen - - calculate excess fuel - - calculate residual value of the fluids balance - - :param fluid: fluid to calculate the reaction balance for - :type fluid: str - :returns: res (*float*) - residual value of mass balance - - **reaction balance equations** - - .. math:: - res = \sum_i \left(x_{fluid,i} \cdot \dot{m}_{i}\right) - - \sum_j \left(x_{fluid,j} \cdot \dot{m}_{j}\right) \; - \forall i \in inlets, \; \forall j \in outlets - - \dot{m}_{fluid,m} = \sum_i \frac{x_{fluid,i} \cdot \dot{m}_{i}} - {M_{fluid}} \; \forall i \in inlets\\ - - \lambda = \frac{\dot{m}_{f,m}}{\dot{m}_{O_2,m} \cdot - \left(n_{C,fuel} + 0.25 \cdot n_{H,fuel}\right)} - - *fuel* - - .. math:: - 0 = res - \left(\dot{m}_{f,m} - \dot{m}_{f,exc,m}\right) - \cdot M_{fuel}\\ - - \dot{m}_{f,exc,m} = \begin{cases} - 0 & \lambda \geq 1\\ - \dot{m}_{f,m} - \frac{\dot{m}_{O_2,m}} - {n_{C,fuel} + 0.25 \cdot n_{H,fuel}} & \lambda < 1 - \end{cases} - - *oxygen* - - .. math:: - 0 = res - \begin{cases} - -\frac{\dot{m}_{O_2,m} \cdot M_{O_2}}{\lambda} & \lambda \geq 1\\ - - \dot{m}_{O_2,m} \cdot M_{O_2} & \lambda < 1 - \end{cases} - - *water* - - .. math:: - 0 = res + \left( \dot{m}_{f,m} - \dot{m}_{f,exc,m} \right) - \cdot 0.5 \cdot n_{H,fuel} \cdot M_{H_2O} - - *carbondioxide* - - .. math:: - 0 = res + \left( \dot{m}_{f,m} - \dot{m}_{f,exc,m} \right) - \cdot n_{C,fuel} \cdot M_{CO_2} - - *other* - - .. math:: - 0 = res - - """ - - n_fuel = 0 - for i in self.inl: - n_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val] / - molar_masses[self.fuel.val]) - - n_oxygen = 0 - for i in self.inl: - n_oxygen += (i.m.val_SI * i.fluid.val[self.o2] / - molar_masses[self.o2]) - - if not self.lamb.is_set: - self.lamb.val = n_oxygen / ( - n_fuel * (self.n['C'] + self.n['H'] / 4)) + if self.pr1.is_set: + pr1_deriv = np.zeros((1, 7, num_fl + 3)) + pr1_deriv[0, 0, 1] = self.pr1.val + pr1_deriv[0, 2, 1] = -1 + mat_deriv += pr1_deriv.tolist() - n_fuel_exc = 0 - if self.lamb.val < 1: - n_fuel_exc = n_fuel - n_oxygen / (self.n['C'] + self.n['H'] / 4) + if self.pr2.is_set: + pr2_deriv = np.zeros((1, 7, num_fl + 3)) + pr2_deriv[0, 1, 1] = self.pr2.val + pr2_deriv[0, 3, 1] = -1 + mat_deriv += pr2_deriv.tolist() - if fluid == self.co2: - dm = ((n_fuel - n_fuel_exc) * - self.n['C'] * molar_masses[self.co2]) - elif fluid == self.h2o: - dm = ((n_fuel - n_fuel_exc) * - self.n['H'] / 2 * molar_masses[self.h2o]) - elif fluid == self.o2: - if self.lamb.val < 1: - dm = -n_oxygen * molar_masses[self.o2] - else: - dm = -n_oxygen / self.lamb.val * molar_masses[self.o2] - elif fluid == self.fuel.val: - dm = -(n_fuel - n_fuel_exc) * molar_masses[self.fuel.val] - else: - dm = 0 + if self.zeta1.is_set: + zeta1_deriv = np.zeros((1, 7, num_fl + 3)) + zeta1_deriv[0, 0, 0] = self.ddx_func(self.zeta_func, 'm', 0) + zeta1_deriv[0, 0, 1] = self.ddx_func(self.zeta_func, 'p', 0) + zeta1_deriv[0, 0, 2] = self.ddx_func(self.zeta_func, 'h', 0) + zeta1_deriv[0, 4, 1] = self.ddx_func(self.zeta_func, 'p', 4) + zeta1_deriv[0, 4, 2] = self.ddx_func(self.zeta_func, 'h', 4) + mat_deriv += zeta1_deriv.tolist() - res = dm + if self.zeta2.is_set: + zeta2_deriv = np.zeros((1, 7, num_fl + 3)) + zeta2_deriv[0, 1, 0] = self.ddx_func(self.zeta2_func, 'm', 1) + zeta2_deriv[0, 1, 1] = self.ddx_func(self.zeta2_func, 'p', 1) + zeta2_deriv[0, 1, 2] = self.ddx_func(self.zeta2_func, 'h', 1) + zeta2_deriv[0, 5, 1] = self.ddx_func(self.zeta2_func, 'p', 5) + zeta2_deriv[0, 5, 2] = self.ddx_func(self.zeta2_func, 'h', 5) + mat_deriv += zeta2_deriv.tolist() - for i in self.inl: - res += i.fluid.val[fluid] * i.m.val_SI - for o in self.outl: - res -= o.fluid.val[fluid] * o.m.val_SI - return res + return np.asarray(mat_deriv) def energy_balance(self): r""" @@ -6277,7 +6272,6 @@ def derivatives(self, nw): q_deriv = np.zeros((1, 4, num_fl + 3)) for k in range(2): q_deriv[0, k, 0] = self.outl[k].h.val_SI - self.inl[k].h.val_SI - q_deriv[0, k, 2] = -self.inl[k].m.val_SI q_deriv[0, 2, 2] = self.inl[0].m.val_SI q_deriv[0, 3, 2] = self.inl[1].m.val_SI @@ -6295,10 +6289,8 @@ def derivatives(self, nw): kA_deriv[0, 0, 0] = self.ddx_func(self.kA_func, 'm', 0) kA_deriv[0, 1, 0] = self.ddx_func(self.kA_func, 'm', 1) for i in range(4): - kA_deriv[0, i, 1] = ( - self.ddx_func(self.kA_func, 'p', i)) - kA_deriv[0, i, 2] = ( - self.ddx_func(self.kA_func, 'h', i)) + kA_deriv[0, i, 1] = self.ddx_func(self.kA_func, 'p', i) + kA_deriv[0, i, 2] = self.ddx_func(self.kA_func, 'h', i) mat_deriv += kA_deriv.tolist() # derivatives for logarithmic temperature difference not implemented @@ -6336,26 +6328,20 @@ def derivatives(self, nw): if self.zeta1.is_set: zeta1_deriv = np.zeros((1, 4, num_fl + 3)) - for i in range(2): - if i == 0: - zeta1_deriv[0, i * 2, 0] = ( - self.ddx_func(self.zeta_func, 'm', i * 2)) - zeta1_deriv[0, i * 2, 1] = ( - self.ddx_func(self.zeta_func, 'p', i * 2)) - zeta1_deriv[0, i * 2, 2] = ( - self.ddx_func(self.zeta_func, 'h', i * 2)) + zeta1_deriv[0, 0, 0] = self.ddx_func(self.zeta_func, 'm', 0) + zeta1_deriv[0, 0, 1] = self.ddx_func(self.zeta_func, 'p', 0) + zeta1_deriv[0, 0, 2] = self.ddx_func(self.zeta_func, 'h', 0) + zeta1_deriv[0, 2, 1] = self.ddx_func(self.zeta_func, 'p', 2) + zeta1_deriv[0, 2, 2] = self.ddx_func(self.zeta_func, 'h', 2) mat_deriv += zeta1_deriv.tolist() if self.zeta2.is_set: zeta2_deriv = np.zeros((1, 4, num_fl + 3)) - for i in range(2): - if i == 0: - zeta2_deriv[0, i * 2 + 1, 0] = ( - self.ddx_func(self.zeta2_func, 'm', i * 2 + 1)) - zeta2_deriv[0, i * 2 + 1, 1] = ( - self.ddx_func(self.zeta2_func, 'p', i * 2 + 1)) - zeta2_deriv[0, i * 2 + 1, 2] = ( - self.ddx_func(self.zeta2_func, 'h', i * 2 + 1)) + zeta2_deriv[0, 1, 0] = self.ddx_func(self.zeta2_func, 'm', 1) + zeta2_deriv[0, 1, 1] = self.ddx_func(self.zeta2_func, 'p', 1) + zeta2_deriv[0, 1, 2] = self.ddx_func(self.zeta2_func, 'h', 1) + zeta2_deriv[0, 3, 1] = self.ddx_func(self.zeta2_func, 'p', 3) + zeta2_deriv[0, 3, 2] = self.ddx_func(self.zeta2_func, 'h', 3) mat_deriv += zeta2_deriv.tolist() mat_deriv += self.additional_derivatives(nw) @@ -6373,29 +6359,6 @@ def additional_derivatives(self, nw): """ return [] - def zeta2_func(self): - r""" - calculates pressure drop from zeta2 - - :returns: residual value for the pressure drop - - .. math:: - - \zeta_2 = \frac{\Delta p_2 \cdot v_2 \cdot 2}{c_2^2}\\ - c_2 = \frac{\dot{m}_2 \cdot v_2}{A_2} - - As the cross sectional area A will not change from design to offdesign - calculation, it is possible to handle this the following way: - - .. math:: - 0 = \zeta_2 - \frac{(p_{2,in} - p_{2,out}) \cdot \pi^2}{8 \cdot - \dot{m}_{2,in}^2 \cdot \frac{v_{2,in} + v_{2,out}}{2}} - """ - i = self.inl[1].to_flow() - o = self.outl[1].to_flow() - return (self.zeta2.val - (i[1] - o[1]) * math.pi ** 2 / - (8 * i[0] ** 2 * (v_mix_ph(i) + v_mix_ph(o)) / 2)) - def kA_func(self): r""" equation for heat flux from conditions on both sides of heat exchanger From cccd89c6b1648be3e1819deccecc0df69ca92ba4 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Fri, 19 Oct 2018 11:37:28 +0200 Subject: [PATCH 49/80] fixed some error messages and network check --- tespy/networks.py | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/tespy/networks.py b/tespy/networks.py index 90f56e205..04ed74a26 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -459,19 +459,22 @@ def check_network(self): comps = pd.unique(self.conns[['s', 't']].values.ravel()) self.init_components(comps) # build the dataframe for components for comp in self.comps.index: - freq = 0 - freq += (self.conns[['s', 't']] == comp).sum().s - freq += (self.conns[['s', 't']] == comp).sum().t - - freq -= comp.num_i - freq -= comp.num_o - if freq != 0: - msg = (str(comp) + ' (' + str(comp.label) + ') is missing ' + - str(-freq) + ' connections. Make sure all ' - 'inlets and outlets are connected and ' - 'all connections have been added to the ' + num_o = (self.conns[['s', 't']] == comp).sum().s + num_i = (self.conns[['s', 't']] == comp).sum().t + if num_o != comp.num_o: + msg = (comp.label + ' is missing ' + str(comp.num_o - num_o) + + ' outgoing connections. Make sure all outlets are ' + ' connected and all connections have been added to the ' 'network.') - raise hlp.MyNetworkError(msg) + elif num_i != comp.num_i: + msg = (comp.label + ' is missing ' + str(comp.num_i - num_i) + + ' incoming connections. Make sure all inlets are ' + ' connected and all connections have been added to the ' + 'network.') + else: + continue + + raise hlp.MyNetworkError(msg) self.checked = True if self.nwkinfo: @@ -550,8 +553,8 @@ def init_components(self, comps): self.comps.loc[comp] = [t, s] comp.inl = t.tolist() comp.outl = s.tolist() - comp.num_i = len(comp.inl) - comp.num_o = len(comp.outl) + comp.num_i = len(comp.inlets()) + comp.num_o = len(comp.outlets()) labels += [comp.label] if len(labels) != len(list(set(labels))): @@ -1941,14 +1944,16 @@ def solve_determination(self): n += [b.P_set].count(True) if n > self.num_vars * len(self.conns.index) + self.num_c_vars: - msg = ('You have provided too many parameters:', - self.num_vars * len(self.conns.index) + self.num_c_vars, - 'required, ', n, 'given.') + msg = ('You have provided too many parameters: ' + + str(self.num_vars * len(self.conns.index) + + self.num_c_vars) + ' required, ' + str(n) + + ' supplied.') raise hlp.MyNetworkError(msg) elif n < self.num_vars * len(self.conns.index) + self.num_c_vars: - msg = ('You have not provided enough parameters:', - self.num_vars * len(self.conns.index) + self.num_c_vars, - 'required, ', n, 'given.') + msg = ('You have not provided enough parameters: ' + + str(self.num_vars * len(self.conns.index) + + self.num_c_vars) + ' required, ' + str(n) + + ' supplied.') raise hlp.MyNetworkError(msg) else: return From 3e0f9bdad1d9faf00360804d2622f57f67acd885 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Fri, 19 Oct 2018 16:05:48 +0200 Subject: [PATCH 50/80] minor adjustments in subsystems --- tespy/components/subsystems.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tespy/components/subsystems.py b/tespy/components/subsystems.py index 544c5adf3..66ede58cf 100644 --- a/tespy/components/subsystems.py +++ b/tespy/components/subsystems.py @@ -59,7 +59,6 @@ def __init__(self, label, **kwargs): # set default values for key in self.attr(): self.__dict__.update({key: np.nan}) - self.__dict__.update({key + '_set': False}) self.subsys_init() self.set_attr(**kwargs) @@ -77,10 +76,8 @@ def set_attr(self, **kwargs): isinstance(kwargs[key], int)): if np.isnan(kwargs[key]): self.__dict__.update({key: np.nan}) - self.__dict__.update({key + '_set': False}) else: self.__dict__.update({key: kwargs[key]}) - self.__dict__.update({key + '_set': True}) elif isinstance(kwargs[key], str): self.__dict__.update({key: kwargs[key]}) @@ -99,8 +96,7 @@ def get_attr(self, key): if key in self.__dict__: return self.__dict__[key] else: - print(self, ' \"', self.label, '\" has no attribute \"', - key, '\"') + print('Subsystem ' + self.label + ' has no attribute ' + key) return None def display_information(self): From 8517bd8f616411eb5e892909ac9648cf99f8b27c Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Fri, 19 Oct 2018 17:04:26 +0200 Subject: [PATCH 51/80] added characteristic lines for cogeneration unit --- tespy/components/characteristics.py | 87 +++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/tespy/components/characteristics.py b/tespy/components/characteristics.py index 528243455..31e5bae16 100644 --- a/tespy/components/characteristics.py +++ b/tespy/components/characteristics.py @@ -161,6 +161,93 @@ def default(self, key): return x[key], y[key] +class turbine(characteristics): + r""" + + generic characteristics for turbine isentropic efficiency + + - links isentropic efficiencay :math:`\eta_\mathrm{s,t}` to keyfigure + - three default characteristic lines available (see literature and method + turbine.default()) + + **literature** + + - Walter Traupel (2001): Thermische Turbomaschinen Band 2. Berlin: Spinger. + -> TRAUPEL + """ + + def default(self, key): + r""" + + default characteristic lines for cogeneration units: + + .. math:: + + \frac{X}{P}=f\left(\frac{P}{P_{ref}} \right) + + **heat production** (Q1) + + .. math:: + X = \dot{Q}_1 = \dot{m}_1 \cdot \left( h_{out,1} - h_{in,1} \right) + + .. image:: _images/chp_Q1.svg + :scale: 100 % + :alt: alternative text + :align: center + + **heat production** (Q2) + + .. math:: + X = \dot{Q}_2 = \dot{m}_2 \cdot \left( h_{out,2} - h_{in,2} \right) + + .. image:: _images/chp_Q2.svg + :scale: 100 % + :alt: alternative text + :align: center + + **heat loss** (QLOSS) + + .. math:: + X = \dot{Q}_{loss} + + .. image:: _images/chp_Q_loss.svg + :scale: 100 % + :alt: alternative text + :align: center + + **thermal input** (TI) + + .. math:: + X = TI = \dot{m}_f \cdot LHV + + .. image:: _images/chp_TI.svg + :scale: 100 % + :alt: alternative text + :align: center + + """ + + if key == 'default': + return np.array([0, 1, 2]), np.array([1, 1, 1]) + + x = {} + y = {} + + x['Q1'] = np.array([0.660, 0.770, 0.880, 0.990, 1.100]) + y['Q1'] = np.array([0.244, 0.233, 0.222, 0.211, 0.200]) + + x['Q2'] = np.array([0.660, 0.770, 0.880, 0.990, 1.100]) + y['Q2'] = np.array([0.246, 0.235, 0.224, 0.213, 0.202]) + + x['QLOSS'] = np.array([0.50, 0.75, 0.90, 1.00]) + y['QLOSS'] = np.array([0.06, 0.08, 0.09, 0.10]) + + x['TI'] = np.array([0.50, 0.71, 0.90, 1.00]) + y['TI'] = np.array([2.20, 2.15, 2.10, 2.05]) + + return x[key], y[key] + + class heat_ex(characteristics): r""" From cd79da6bf76391b57de3cb7a4c07571fb05e1d13 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Fri, 19 Oct 2018 17:06:38 +0200 Subject: [PATCH 52/80] removed deprecated methods for cogeneration unit --- tespy/components/components.py | 389 ++++++++++----------------------- 1 file changed, 118 insertions(+), 271 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index 1b6a233e4..5d24e0f70 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -2770,19 +2770,21 @@ def comp_init(self, nw): component.comp_init(self, nw) if not self.fuel.is_set: - msg = 'Must specify fuel for combustion chamber.' + msg = ('Must specify fuel for component ' + self.label + + '. Available fuels are: ' + str(self.fuels()) + '.') raise MyComponentError(msg) if (len([x for x in nw.fluids if x in [a.replace(' ', '') for a in CP.get_aliases(self.fuel.val)]]) == 0): - msg = ('The fuel you specified does not match the fuels available' - ' within the network.') + msg = ('The fuel you specified for component ' + self.label + + ' does not match the fuels available within the network.') raise MyComponentError(msg) if (len([x for x in self.fuels() if x in [a.replace(' ', '') for a in CP.get_aliases(self.fuel.val)]])) == 0: - msg = ('The fuel you specified is not available. Available fuels ' - 'are: ' + str(self.fuels()) + '.') + msg = ('The fuel you specified is not available for component ' + + self.label + '. Available fuels are: ' + str(self.fuels()) + + '.') raise MyComponentError(msg) self.fuel.val = [x for x in nw.fluids if x in [ @@ -3063,7 +3065,7 @@ def reaction_balance(self, fluid): outl = self.outl[2:] else: inl = self.inl - outl = self.inl + outl = self.outl n_fuel = 0 for i in inl: @@ -3174,13 +3176,19 @@ def lambda_func(self): 0 = \frac{\dot{m}_{f,m}}{\dot{m}_{O_2,m} \cdot \left(n_{C,fuel} + 0.25 \cdot n_{H,fuel}\right)} - \lambda """ + + if isinstance(self, cogeneration_unit): + inl = self.inl[2:] + else: + inl = self.inl + n_fuel = 0 - for i in self.inl: + for i in inl: n_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val] / molar_masses[self.fuel.val]) n_oxygen = 0 - for i in self.inl: + for i in inl: n_oxygen += (i.m.val_SI * i.fluid.val[self.o2] / molar_masses[self.o2]) @@ -3197,11 +3205,19 @@ def ti_func(self): 0 = ti - \dot{m}_f \cdot LHV """ + + if isinstance(self, cogeneration_unit): + inl = self.inl[2:] + outl = self.outl[2:] + else: + inl = self.inl + outl = self.outl + m_fuel = 0 - for i in self.inl: + for i in inl: m_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val]) - for o in self.outl: + for o in outl: m_fuel -= (o.m.val_SI * o.fluid.val[self.fuel.val]) return (self.ti.val - m_fuel * self.lhv) @@ -3368,6 +3384,14 @@ def convergence_check(self, nw): :type nw: tespy.networks.network :returns: no return value """ + + if isinstance(self, cogeneration_unit): + inl = self.inl[2:] + outl = self.outl[2:] + else: + inl = self.inl + outl = self.outl + m = 0 for i in self.inl: if i.m.val_SI < 0 and not i.m.val_set: @@ -4218,7 +4242,9 @@ class cogeneration_unit(combustion_chamber): - ti: thermal input (:math:`{LHV \cdot \dot{m}_f}`), :math:`[LHV \cdot \dot{m}_f] = \text{W}` - P: power output, :math:`{[P]=\text{W}}` - - Q: total heat output, :math:`{[\dot Q]=\text{W}}` + - Q1: total heat output, :math:`{[\dot Q]=\text{W}}` + - Q2: total heat output, :math:`{[\dot Q]=\text{W}}` + - Qloss: heat loss, :math:`{[\dot Q_{loss}]=\text{W}}` - pr1: outlet to inlet pressure ratio at hot side, :math:`[pr1]=1` - pr2: outlet to inlet pressure ratio at cold side, :math:`[pr2]=1` - zeta1: geometry independent friction coefficient heat extraction 1 @@ -4245,10 +4271,6 @@ class cogeneration_unit(combustion_chamber): - in1, in2 (cooling water), in3, in4 (air and fuel) - out1, out2 (cooling water), out3 (flue gas) - .. note:: - - - .. image:: _images/cogeneration_unit.svg :scale: 100 % :alt: alternative text @@ -4268,110 +4290,9 @@ def attr(self): 'zeta1': dc_cp(), 'zeta2': dc_cp(), 'S': dc_cp()} - def fuels(self): - return ['methane', 'ethane', 'propane', 'butane', - 'hydrogen'] - def component(self): return 'cogeneration unit' - def comp_init(self, nw): - - component.comp_init(self, nw) - - if not self.fuel.is_set: - msg = 'Must specify fuel for combustion chamber.' - raise MyComponentError(msg) - - if (len([x for x in nw.fluids if x in [a.replace(' ', '') for a in - CP.get_aliases(self.fuel.val)]]) == 0): - msg = ('The fuel you specified does not match the fuels available' - ' within the network.') - raise MyComponentError(msg) - - if (len([x for x in self.fuels() if x in [a.replace(' ', '') for a in - CP.get_aliases(self.fuel.val)]])) == 0: - msg = ('The fuel you specified is not available. Available fuels ' - 'are: ' + str(self.fuels()) + '.') - raise MyComponentError(msg) - - self.fuel.val = [x for x in nw.fluids if x in [ - a.replace(' ', '') for a in CP.get_aliases(self.fuel.val)]][0] - - self.o2 = [x for x in nw.fluids if x in - [a.replace(' ', '') for a in CP.get_aliases('O2')]][0] - self.co2 = [x for x in nw.fluids if x in - [a.replace(' ', '') for a in CP.get_aliases('CO2')]][0] - self.h2o = [x for x in nw.fluids if x in - [a.replace(' ', '') for a in CP.get_aliases('H2O')]][0] - self.n2 = [x for x in nw.fluids if x in - [a.replace(' ', '') for a in CP.get_aliases('N2')]][0] - - structure = fluid_structure(self.fuel.val) - - self.n = {} - for el in ['C', 'H', 'O']: - if el in structure.keys(): - self.n[el] = structure[el] - else: - self.n[el] = 0 - - self.lhv = self.calc_lhv() - - def calc_lhv(self): - r""" - calculates the lower heating value of the combustion chambers fuel - - :returns: val (*float*) - lhv of the specified fuel - - **equation** - - .. math:: - LHV = -\frac{\sum_i {\Delta H_f^0}_i - - \sum_j {\Delta H_f^0}_j } - {M_{fuel}}\\ - \forall i \in \text{reation products},\\ - \forall j \in \text{reation educts},\\ - \Delta H_f^0: \text{molar formation enthalpy} - - =============== ===================================== - substance :math:`\frac{\Delta H_f^0}{kJ/mol}` - =============== ===================================== - hydrogen 0 - methane -74.85 - ethane -84.68 - propane -103.8 - butane -124.51 - --------------- ------------------------------------- - oxygen 0 - carbondioxide -393.5 - water (g) -241.8 - =============== ===================================== - - """ - - hf = {} - hf['hydrogen'] = 0 - hf['methane'] = -74.85 - hf['ethane'] = -84.68 - hf['propane'] = -103.8 - hf['butane'] = -124.51 - hf[self.o2] = 0 - hf[self.co2] = -393.5 - # water (gaseous) - hf[self.h2o] = -241.8 - - key = set(list(hf.keys())).intersection( - set([a.replace(' ', '') - for a in CP.get_aliases(self.fuel.val)])) - - val = (-(self.n['H'] / 2 * hf[self.h2o] + self.n['C'] * hf[self.co2] - - ((self.n['C'] + self.n['H'] / 4) * hf[self.o2] + - hf[list(key)[0]])) / - molar_masses[self.fuel.val] * 1000) - - return val - def equations(self): r""" returns vector vec_res with result of equations for this component @@ -4469,10 +4390,13 @@ def derivatives(self, nw): j = 0 fl_deriv = np.zeros((num_fl, 7, num_fl + 3)) for fluid in nw.fluids: - for i in range(3): - fl_deriv[j, i, 0] = self.drb_dx('m', i, fluid) - fl_deriv[j, i, 3:] = self.drb_dx('fluid', i, fluid) - + # fresh air and fuel inlets + for i in range(2): + fl_deriv[j, i + 2, 0] = self.drb_dx('m', i + 2, fluid) + fl_deriv[j, i + 2, 3:] = self.drb_dx('fluid', i + 2, fluid) + # combustion outlet + fl_deriv[j, 6, 0] = self.drb_dx('m', 6, fluid) + fl_deriv[j, 6, 3:] = self.drb_dx('fluid', 6, fluid) j += 1 mat_deriv += fl_deriv.tolist() @@ -4522,8 +4446,6 @@ def derivatives(self, nw): ti_deriv[0, 2, 3:] = self.ddx_func(self.ti_func, 'fluid', 2) mat_deriv += ti_deriv.tolist() - ########################## - if self.pr1.is_set: pr1_deriv = np.zeros((1, 7, num_fl + 3)) pr1_deriv[0, 0, 1] = self.pr1.val @@ -4560,117 +4482,91 @@ def energy_balance(self): r""" calculates the energy balance of the adiabatic combustion chamber - - reference temperature: 500 K - - reference pressure: 1 bar + .. note:: + The temperature for the reference state is set to 20 °C, thus + the water may be liquid. In order to make sure, the state is + referring to the lower heating value, the necessary enthalpy + difference for evaporation is added. The stoichiometric combustion + chamber uses a different reference, you will find it in the + :func:`tespy.components.components.combustion_chamber_stoich.energy_balance` + documentation. + + - reference temperature: 293.15 K + - reference pressure: 1 bar :returns: res (*float*) - residual value of energy balance .. math:: - 0 = \dot{m}_{in,i} \cdot \left( h_{in,i} - h_{in,i,ref} \right) - - \dot{m}_{out,j} \cdot \left( h_{out,j} - h_{out,j,ref} \right) + - H_{I,f} \cdot \left( \dot{m}_{in,i} \cdot x_{f,i} - - \dot{m}_{out,j} \cdot x_{f,j} \right) + 0 = \sum_i \dot{m}_{in,i} \cdot \left( h_{in,i} - h_{in,i,ref} + \right) - \sum_j \dot{m}_{out,j} \cdot + \left( h_{out,j} - h_{out,j,ref} \right) + + H_{I,f} \cdot \left(\sum_i \dot{m}_{in,i} \cdot x_{f,i} - + \sum_j \dot{m}_{out,j} \cdot x_{f,j} \right) + \; \forall i \in \text{inlets}\; \forall j \in \text{outlets} """ - T_ref = 500 + T_ref = 293.15 p_ref = 1e5 res = 0 - for i in self.inl: - res += i.m.val_SI * (i.h.val_SI - - h_mix_pT([i.m.val_SI, p_ref, i.h.val_SI, - i.fluid.val], T_ref)) + for i in self.inl[2:]: + res += i.m.val_SI * ( + i.h.val_SI - h_mix_pT([0, p_ref, 0, i.fluid.val], T_ref)) res += i.m.val_SI * i.fluid.val[self.fuel.val] * self.lhv - for o in self.outl: - res -= o.m.val_SI * (o.h.val_SI - - h_mix_pT([o.m.val_SI, p_ref, o.h.val_SI, - o.fluid.val], T_ref)) - res -= o.m.val_SI * o.fluid.val[self.fuel.val] * self.lhv - - return res - def lambda_func(self): - r""" - calculates the residual for specified lambda - - :returns: res (*float*) - residual value of equation - - .. math:: - - \dot{m}_{fluid,m} = \sum_i \frac{x_{fluid,i} \cdot \dot{m}_{i}} - {M_{fluid}} \; \forall i \in inlets\\ - - 0 = \frac{\dot{m}_{f,m}}{\dot{m}_{O_2,m} \cdot - \left(n_{C,fuel} + 0.25 \cdot n_{H,fuel}\right)} - \lambda - """ - n_fuel = 0 - for i in self.inl: - n_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val] / - molar_masses[self.fuel.val]) - - n_oxygen = 0 - for i in self.inl: - n_oxygen += (i.m.val_SI * i.fluid.val[self.o2] / - molar_masses[self.o2]) - - return (n_oxygen / (n_fuel * (self.n['C'] + self.n['H'] / 4)) - - self.lamb.val) - - def ti_func(self): - r""" - calculates the residual for specified thermal input - - :returns: res (*float*) - residual value of equation - - .. math:: - - 0 = ti - \dot{m}_f \cdot LHV - """ - m_fuel = 0 - for i in self.inl: - m_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val]) - - for o in self.outl: - m_fuel -= (o.m.val_SI * o.fluid.val[self.fuel.val]) - - return (self.ti.val - m_fuel * self.lhv) - - def bus_func(self): - r""" - function for use on busses - - :returns: val (*float*) - residual value of equation - - .. math:: - - val = \dot{m}_{fuel} \cdot LHV - """ - - m_fuel = 0 - for i in self.inl: - m_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val]) - - for o in self.outl: - m_fuel -= (o.m.val_SI * o.fluid.val[self.fuel.val]) - - return m_fuel * self.lhv + for o in self.outl[2:]: + dh = 0 + n_h2o = o.fluid.val[self.h2o] / molar_masses[self.h2o] + if n_h2o > 0: + p = p_ref * n_h2o / molar_massflow(o.fluid.val) + h = CP.PropsSI('H', 'P', p, 'T', T_ref, self.h2o) + h_steam = CP.PropsSI('H', 'P', p, 'Q', 1, self.h2o) + if h < h_steam: + dh = (h_steam - h) * o.fluid.val[self.h2o] - def bus_deriv(self): - r""" - calculate matrix of partial derivatives towards mass flow and fluid - composition for bus - function + res -= o.m.val_SI * ( + o.h.val_SI - h_mix_pT([0, p_ref, 0, o.fluid.val], T_ref) - + dh) + res -= o.m.val_SI * o.fluid.val[self.fuel.val] * self.lhv - :returns: mat_deriv (*list*) - matrix of partial derivatives - """ - deriv = np.zeros((1, 3, len(self.inl[0].fluid.val) + 3)) - for i in range(2): - deriv[0, i, 0] = self.ddx_func(self.bus_func, 'm', i) - deriv[0, i, 3:] = self.ddx_func(self.bus_func, 'fluid', i) + return res - deriv[0, 2, 0] = self.ddx_func(self.bus_func, 'm', 2) - deriv[0, 2, 3:] = self.ddx_func(self.bus_func, 'fluid', 2) - return deriv +# def bus_func(self): +# r""" +# function for use on busses +# +# :returns: val (*float*) - residual value of equation +# +# .. math:: +# +# val = \dot{m}_{fuel} \cdot LHV +# """ +# +# m_fuel = 0 +# for i in self.inl: +# m_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val]) +# +# for o in self.outl: +# m_fuel -= (o.m.val_SI * o.fluid.val[self.fuel.val]) +# +# return m_fuel * self.lhv +# +# def bus_deriv(self): +# r""" +# calculate matrix of partial derivatives towards mass flow and fluid +# composition for bus +# function +# +# :returns: mat_deriv (*list*) - matrix of partial derivatives +# """ +# deriv = np.zeros((1, 3, len(self.inl[0].fluid.val) + 3)) +# for i in range(2): +# deriv[0, i, 0] = self.ddx_func(self.bus_func, 'm', i) +# deriv[0, i, 3:] = self.ddx_func(self.bus_func, 'fluid', i) +# +# deriv[0, 2, 0] = self.ddx_func(self.bus_func, 'm', 2) +# deriv[0, 2, 3:] = self.ddx_func(self.bus_func, 'fluid', 2) +# return deriv def drb_dx(self, dx, pos, fluid): r""" @@ -4780,7 +4676,7 @@ def initialise_fluids(self, nw): self.h2o: m_h2o / m_fg } - for o in self.outl: + for o in self.outl[2:]: for fluid, x in o.fluid.val.items(): if not o.fluid.val_set[fluid] and fluid in fg.keys(): o.fluid.val[fluid] = fg[fluid] @@ -4797,58 +4693,9 @@ def convergence_check(self, nw): :type nw: tespy.networks.network :returns: no return value """ - m = 0 - for i in self.inl: - if i.m.val_SI < 0 and not i.m.val_set: - i.m.val_SI = 0.01 - m += i.m.val_SI + combustion_chamber.convergence_check(self, nw) - for o in self.outl: - fluids = [f for f in o.fluid.val.keys() if not o.fluid.val_set[f]] - for f in fluids: - if f not in [self.o2, self.co2, self.h2o, self.fuel.val]: - m_f = 0 - for i in self.inl: - m_f += i.fluid.val[f] * i.m.val_SI - - if abs(o.fluid.val[f] - m_f / m) > 0.03: - o.fluid.val[f] = m_f / m - - elif f == self.o2: - if o.fluid.val[f] > 0.25: - o.fluid.val[f] = 0.2 - if o.fluid.val[f] < 0.05: - o.fluid.val[f] = 0.05 - - elif f == self.co2: - if o.fluid.val[f] > 0.075: - o.fluid.val[f] = 0.075 - if o.fluid.val[f] < 0.02: - o.fluid.val[f] = 0.02 - - elif f == self.h2o: - if o.fluid.val[f] > 0.075: - o.fluid.val[f] = 0.075 - if o.fluid.val[f] < 0.02: - o.fluid.val[f] = 0.02 - - elif f == self.fuel.val: - if o.fluid.val[f] > 0: - o.fluid.val[f] = 0 - - else: - continue - - for o in self.outl: - if o.m.val_SI < 0 and not o.m.val_set: - o.m.val_SI = 10 - init_target(nw, o, o.t) - - if o.h.val_SI < 7.5e5 and not o.h.val_set: - o.h.val_SI = 1e6 - - if self.lamb.val < 1 and not self.lamb.is_set: - self.lamb.val = 3 + # additional stuff here? def initialise_source(self, c, key): r""" From 0341a1dfc2e32bd007ebdd7a7346e1ee6514dc8e Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Fri, 19 Oct 2018 17:25:28 +0200 Subject: [PATCH 53/80] modifications for better readability --- tespy/components/components.py | 305 +++++++++++++++++---------------- 1 file changed, 158 insertions(+), 147 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index 5d24e0f70..7c61adc4b 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -836,6 +836,9 @@ class turbomachine(component): - out1 """ + def component(self): + return 'turbomachine' + def attr(self): return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), 'eta_s_char': dc_cc(), 'Sirr': dc_cp()} @@ -852,9 +855,6 @@ def inlets(self): def outlets(self): return ['out1'] - def component(self): - return 'turbomachine' - def equations(self): r""" returns vector vec_res with result of equations for this component @@ -1150,6 +1150,14 @@ class pump(turbomachine): :align: center """ + def component(self): + return 'pump' + + def attr(self): + return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), 'Sirr': dc_cp(), + 'eta_s_char': dc_cc(x=[0, 1, 2, 3], y=[1, 1, 1, 1]), + 'flow_char': dc_cc(x=[0, 1, 2, 3], y=[1, 1, 1, 1])} + def comp_init(self, nw): component.comp_init(self, nw) @@ -1168,14 +1176,6 @@ def comp_init(self, nw): self.eta_s_char.func = cmp_char.characteristics(method=method, x=x, y=y) - def component(self): - return 'pump' - - def attr(self): - return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), 'Sirr': dc_cp(), - 'eta_s_char': dc_cc(x=[0, 1, 2, 3], y=[1, 1, 1, 1]), - 'flow_char': dc_cc(x=[0, 1, 2, 3], y=[1, 1, 1, 1])} - def additional_equations(self): r""" additional equations for pumps @@ -1515,14 +1515,6 @@ class compressor(turbomachine): :align: center """ - def comp_init(self, nw): - - component.comp_init(self, nw) - - if self.char_map.func is None: - method = self.char_map.method - self.char_map.func = cmp_char.compressor(method=method) - def component(self): return 'compressor' @@ -1535,6 +1527,14 @@ def attr(self): def default_offdesign(self): return ['char_map'] + def comp_init(self, nw): + + component.comp_init(self, nw) + + if self.char_map.func is None: + method = self.char_map.method + self.char_map.func = cmp_char.compressor(method=method) + def additional_equations(self): r""" additional equations for compressor @@ -1860,6 +1860,18 @@ class turbine(turbomachine): :align: center """ + def component(self): + return 'turbine' + + def attr(self): + return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), + 'Sirr': dc_cp(), + 'eta_s_char': dc_cc(method='GENERIC', param='m'), + 'cone': dc_cc(method='default')} + + def default_offdesign(self): + return turbomachine.default_offdesign(self) + ['cone'] + def comp_init(self, nw): component.comp_init(self, nw) @@ -1876,18 +1888,6 @@ def comp_init(self, nw): y = self.cone.y self.cone.func = cmp_char.characteristics(method=method, x=x, y=y) - def component(self): - return 'turbine' - - def attr(self): - return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), - 'Sirr': dc_cp(), - 'eta_s_char': dc_cc(method='GENERIC', param='m'), - 'cone': dc_cc(method='default')} - - def default_offdesign(self): - return turbomachine.default_offdesign(self) + ['cone'] - def additional_equations(self): r""" additional equations for turbines @@ -2204,6 +2204,9 @@ class split(component): :align: center """ + def component(self): + return 'split' + def attr(self): return {'num_out': dc_cp(printout=False)} @@ -2217,9 +2220,6 @@ def outlets(self): self.set_attr(num_out=2) return self.outlets() - def component(self): - return 'split' - def equations(self): r""" returns vector vec_res with result of equations for this component @@ -2542,6 +2542,9 @@ class merge(component): :align: center """ + def component(self): + return 'merge' + def attr(self): return {'num_in': dc_cp(printout=False)} @@ -2555,9 +2558,6 @@ def inlets(self): def outlets(self): return ['out1'] - def component(self): - return 'merge' - def equations(self): r""" returns vector vec_res with result of equations for this component @@ -2748,23 +2748,23 @@ class combustion_chamber(component): :align: center """ + def component(self): + return 'combustion chamber' + + def attr(self): + return {'fuel': dc_cp(printout=False), 'lamb': dc_cp(), 'ti': dc_cp(), + 'S': dc_cp()} + def inlets(self): return ['in1', 'in2'] def outlets(self): return ['out1'] - def attr(self): - return {'fuel': dc_cp(printout=False), 'lamb': dc_cp(), 'ti': dc_cp(), - 'S': dc_cp()} - def fuels(self): return ['methane', 'ethane', 'propane', 'butane', 'hydrogen'] - def component(self): - return 'combustion chamber' - def comp_init(self, nw): component.comp_init(self, nw) @@ -3599,11 +3599,8 @@ class combustion_chamber_stoich(combustion_chamber): :align: center """ - def inlets(self): - return ['in1', 'in2'] - - def outlets(self): - return ['out1'] + def component(self): + return 'combustion chamber stoichiometric flue gas' def attr(self): return {'fuel': dc_cp(printout=False), @@ -3613,13 +3610,16 @@ def attr(self): 'path': dc_cp(printout=False), 'lamb': dc_cp(), 'ti': dc_cp(), 'S': dc_cp()} + def inlets(self): + return ['in1', 'in2'] + + def outlets(self): + return ['out1'] + def fuels(self): return ['methane', 'ethane', 'propane', 'butane', 'hydrogen'] - def component(self): - return 'combustion chamber stoichiometric flue gas' - def comp_init(self, nw): component.comp_init(self, nw) @@ -4242,8 +4242,8 @@ class cogeneration_unit(combustion_chamber): - ti: thermal input (:math:`{LHV \cdot \dot{m}_f}`), :math:`[LHV \cdot \dot{m}_f] = \text{W}` - P: power output, :math:`{[P]=\text{W}}` - - Q1: total heat output, :math:`{[\dot Q]=\text{W}}` - - Q2: total heat output, :math:`{[\dot Q]=\text{W}}` + - Q1: heat output 1, :math:`{[\dot Q]=\text{W}}` + - Q2: heat output 2, :math:`{[\dot Q]=\text{W}}` - Qloss: heat loss, :math:`{[\dot Q_{loss}]=\text{W}}` - pr1: outlet to inlet pressure ratio at hot side, :math:`[pr1]=1` - pr2: outlet to inlet pressure ratio at cold side, :math:`[pr2]=1` @@ -4254,6 +4254,13 @@ class cogeneration_unit(combustion_chamber): :math:`[\zeta2]=\frac{\text{Pa}}{\text{m}^4}`, also see :func:`tespy.components.components.heat_exchanger.zeta2_func` + **characteristic lines** + + - ti_char: characteristic line for fuel input + - Q1_char: characteristic line for heat output 1 + - Q2_char: characteristic line for heat output 2 + - Qloss_char: characteristic line for heat loss + **equations** see :func:`tespy.components.components.cogeneration_unit.equations` @@ -4277,21 +4284,25 @@ class cogeneration_unit(combustion_chamber): :align: center """ - def inlets(self): - return ['in1', 'in2', 'in3', 'in4'] - - def outlets(self): - return ['out1', 'out2', 'out3'] + def component(self): + return 'cogeneration unit' def attr(self): return {'fuel': dc_cp(printout=False), 'lamb': dc_cp(), 'ti': dc_cp(), - 'P': dc_cp(), 'Q': dc_cp(), + 'P': dc_cp(), 'Q1': dc_cp(), 'Q2': dc_cp(), 'Qloss': dc_cp(), 'pr1': dc_cp(), 'pr2': dc_cp(), 'zeta1': dc_cp(), 'zeta2': dc_cp(), + 'ti_char': dc_cc(method='TI'), + 'Q1_char': dc_cc(method='Q1'), + 'Q2_char': dc_cc(method='Q2'), + 'Qloss_char': dc_cc(method='QLOSS'), 'S': dc_cp()} - def component(self): - return 'cogeneration unit' + def inlets(self): + return ['in1', 'in2', 'in3', 'in4'] + + def outlets(self): + return ['out1', 'out2', 'out3'] def equations(self): r""" @@ -4814,6 +4825,9 @@ class vessel(component): :align: center """ + def component(self): + return 'vessel' + def attr(self): return {'pr': dc_cp(min_val=1e-4), 'zeta': dc_cp(min_val=1e-4), @@ -4831,9 +4845,6 @@ def inlets(self): def outlets(self): return ['out1'] - def component(self): - return 'vessel' - def equations(self): r""" returns vector vec_res with result of equations for this component @@ -5042,6 +5053,34 @@ class heat_exchanger_simple(component): the appropriate equation """ + def component(self): + return 'simplified heat exchanger' + + def attr(self): + return {'Q': dc_cp(), + 'pr': dc_cp(min_val=1e-4), + 'zeta': dc_cp(min_val=1e-4), + 'D': dc_cp(min_val=1e-2, max_val=2, d=1e-3), + 'L': dc_cp(min_val=1e-1, d=1e-3), + 'ks': dc_cp(min_val=1e-7, max_val=1e-4, d=1e-8), + 'kA': dc_cp(min_val=1, d=1), + 't_a': dc_cp(), 't_a_design': dc_cp(), + 'kA_char': dc_cc(method='HE_HOT', param='m'), + 'SQ1': dc_cp(), 'SQ2': dc_cp(), 'Sirr': dc_cp(), + 'hydro_group': dc_gcp(), 'kA_group': dc_gcp()} + + def default_design(self): + return ['pr'] + + def default_offdesign(self): + return ['kA'] + + def inlets(self): + return ['in1'] + + def outlets(self): + return ['out1'] + def comp_init(self, nw): component.comp_init(self, nw) @@ -5101,34 +5140,6 @@ def comp_init(self, nw): else: self.kA_group.set_attr(is_set=False) - def attr(self): - return {'Q': dc_cp(), - 'pr': dc_cp(min_val=1e-4), - 'zeta': dc_cp(min_val=1e-4), - 'D': dc_cp(min_val=1e-2, max_val=2, d=1e-3), - 'L': dc_cp(min_val=1e-1, d=1e-3), - 'ks': dc_cp(min_val=1e-7, max_val=1e-4, d=1e-8), - 'kA': dc_cp(min_val=1, d=1), - 't_a': dc_cp(), 't_a_design': dc_cp(), - 'kA_char': dc_cc(method='HE_HOT', param='m'), - 'SQ1': dc_cp(), 'SQ2': dc_cp(), 'Sirr': dc_cp(), - 'hydro_group': dc_gcp(), 'kA_group': dc_gcp()} - - def default_design(self): - return ['pr'] - - def default_offdesign(self): - return ['kA'] - - def inlets(self): - return ['in1'] - - def outlets(self): - return ['out1'] - - def component(self): - return 'simplified heat exchanger' - def equations(self): r""" returns vector vec_res with result of equations for this component @@ -5734,6 +5745,33 @@ class solar collector :align: center """ + def component(self): + return 'solar collector' + + def attr(self): + return {'Q': dc_cp(), + 'pr': dc_cp(min_val=1e-4), + 'zeta': dc_cp(min_val=1e-4), + 'D': dc_cp(min_val=1e-2, max_val=2, d=1e-3), + 'L': dc_cp(min_val=1e-1, d=1e-3), + 'ks': dc_cp(min_val=1e-7, max_val=1e-4, d=1e-8), + 'E': dc_cp(min_val=0), 'lkf_lin': dc_cp(), 'lkf_quad': dc_cp(), + 'A': dc_cp(min_val=0), 't_a': dc_cp(), + 'SQ': dc_cp(), + 'hydro_group': dc_gcp(), 'energy_group': dc_gcp()} + + def inlets(self): + return ['in1'] + + def outlets(self): + return ['out1'] + + def default_design(self): + return ['pr'] + + def default_offdesign(self): + return ['zeta'] + def comp_init(self, nw): component.comp_init(self, nw) @@ -5784,33 +5822,6 @@ def comp_init(self, nw): else: self.energy_group.set_attr(is_set=False) - def attr(self): - return {'Q': dc_cp(), - 'pr': dc_cp(min_val=1e-4), - 'zeta': dc_cp(min_val=1e-4), - 'D': dc_cp(min_val=1e-2, max_val=2, d=1e-3), - 'L': dc_cp(min_val=1e-1, d=1e-3), - 'ks': dc_cp(min_val=1e-7, max_val=1e-4, d=1e-8), - 'E': dc_cp(min_val=0), 'lkf_lin': dc_cp(), 'lkf_quad': dc_cp(), - 'A': dc_cp(min_val=0), 't_a': dc_cp(), - 'SQ': dc_cp(), - 'hydro_group': dc_gcp(), 'energy_group': dc_gcp()} - - def inlets(self): - return ['in1'] - - def outlets(self): - return ['out1'] - - def default_design(self): - return ['pr'] - - def default_offdesign(self): - return ['zeta'] - - def component(self): - return 'solar collector' - def additional_equations(self): r""" additional equations for solar collectors @@ -5959,21 +5970,8 @@ class heat_exchanger(component): - add direct current heat exchangers """ - def comp_init(self, nw): - - component.comp_init(self, nw) - - if self.kA_char1.func is None: - method = self.kA_char1.method - x = self.kA_char1.x - y = self.kA_char1.y - self.kA_char1.func = cmp_char.heat_ex(method=method, x=x, y=y) - - if self.kA_char2.func is None: - method = self.kA_char2.method - x = self.kA_char2.x - y = self.kA_char2.y - self.kA_char2.func = cmp_char.heat_ex(method=method, x=x, y=y) + def component(self): + return 'heat_exchanger' def attr(self): # derivatives for logarithmic temperature difference not implemented @@ -5985,20 +5983,33 @@ def attr(self): 'zeta1': dc_cp(), 'zeta2': dc_cp(), 'SQ1': dc_cp(), 'SQ2': dc_cp(), 'Sirr': dc_cp()} + def default_design(self): + return ['ttd_u', 'ttd_l', 'pr1', 'pr2'] + + def default_offdesign(self): + return ['kA', 'zeta1', 'zeta2'] + def inlets(self): return ['in1', 'in2'] def outlets(self): return ['out1', 'out2'] - def default_design(self): - return ['ttd_u', 'ttd_l', 'pr1', 'pr2'] + def comp_init(self, nw): - def default_offdesign(self): - return ['kA', 'zeta1', 'zeta2'] + component.comp_init(self, nw) - def component(self): - return 'heat_exchanger' + if self.kA_char1.func is None: + method = self.kA_char1.method + x = self.kA_char1.x + y = self.kA_char1.y + self.kA_char1.func = cmp_char.heat_ex(method=method, x=x, y=y) + + if self.kA_char2.func is None: + method = self.kA_char2.method + x = self.kA_char2.x + y = self.kA_char2.y + self.kA_char2.func = cmp_char.heat_ex(method=method, x=x, y=y) def equations(self): r""" @@ -7019,15 +7030,15 @@ class drum(component): :align: center """ + def component(self): + return 'drum' + def inlets(self): return ['in1', 'in2'] def outlets(self): return ['out1', 'out2'] - def component(self): - return 'drum' - def equations(self): r""" returns vector vec_res with result of equations for this component @@ -7209,6 +7220,9 @@ class subsys_interface(component): :align: center """ + def component(self): + return 'subsystem interface' + def attr(self): return {'num_inter': dc_cp(printout=False)} @@ -7224,9 +7238,6 @@ def outlets(self): else: return ['out1'] - def component(self): - return 'subsystem interface' - def equations(self): r""" returns vector vec_res with result of equations for this component From 55cac247c1500210edd5fb68e3a249d4aac20fdd Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Sat, 20 Oct 2018 11:57:43 +0200 Subject: [PATCH 54/80] rearrangements in cogeneration unit lines --- tespy/components/characteristics.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tespy/components/characteristics.py b/tespy/components/characteristics.py index 31e5bae16..7d9440399 100644 --- a/tespy/components/characteristics.py +++ b/tespy/components/characteristics.py @@ -185,6 +185,16 @@ def default(self, key): \frac{X}{P}=f\left(\frac{P}{P_{ref}} \right) + **thermal input** (TI) + + .. math:: + X = TI = \dot{m}_f \cdot LHV + + .. image:: _images/chp_TI.svg + :scale: 100 % + :alt: alternative text + :align: center + **heat production** (Q1) .. math:: @@ -214,17 +224,6 @@ def default(self, key): :scale: 100 % :alt: alternative text :align: center - - **thermal input** (TI) - - .. math:: - X = TI = \dot{m}_f \cdot LHV - - .. image:: _images/chp_TI.svg - :scale: 100 % - :alt: alternative text - :align: center - """ if key == 'default': @@ -233,6 +232,9 @@ def default(self, key): x = {} y = {} + x['TI'] = np.array([0.50, 0.71, 0.90, 1.00]) + y['TI'] = np.array([2.20, 2.15, 2.10, 2.05]) + x['Q1'] = np.array([0.660, 0.770, 0.880, 0.990, 1.100]) y['Q1'] = np.array([0.244, 0.233, 0.222, 0.211, 0.200]) @@ -242,9 +244,6 @@ def default(self, key): x['QLOSS'] = np.array([0.50, 0.75, 0.90, 1.00]) y['QLOSS'] = np.array([0.06, 0.08, 0.09, 0.10]) - x['TI'] = np.array([0.50, 0.71, 0.90, 1.00]) - y['TI'] = np.array([2.20, 2.15, 2.10, 2.05]) - return x[key], y[key] From 0f466ac6078c16927a5f87f46dbf5dd7b5b08a96 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Sat, 20 Oct 2018 14:37:43 +0200 Subject: [PATCH 55/80] restructured characteristics classes --- tespy/components/characteristics.py | 283 ++++++++++++---------------- 1 file changed, 122 insertions(+), 161 deletions(-) diff --git a/tespy/components/characteristics.py b/tespy/components/characteristics.py index 7d9440399..9c492e30d 100644 --- a/tespy/components/characteristics.py +++ b/tespy/components/characteristics.py @@ -50,6 +50,7 @@ def __init__(self, **kwargs): self.x = kwargs.get('x', None) self.y = kwargs.get('y', None) + self.comp = kwargs.get('comp', None) if self.x is None: self.x = self.default(method)[0] @@ -59,56 +60,19 @@ def __init__(self, **kwargs): self.char = interp1d(self.x, self.y, kind='cubic', bounds_error=True) def default(self, key): - - x = {} - y = {} - - x['default'] = np.array([0, 1, 2, 3]) - y['default'] = np.array([1, 1, 1, 1]) - - return x[key], y[key] - - def attr(self): - return ['x', 'y', 'method'] - - def f_x(self, x): - return self.char(x) - - def get_attr(self, key): - """ - get the value of a characteristics attribute - - :param key: attribute to return its value - :type key: str - :returns: - - :code:`self.__dict__[key]` if object has attribute key - - :code:`None` if object has no attribute key - """ - if key in self.__dict__: - return self.__dict__[key] - else: - return None - - -class turbine(characteristics): r""" - generic characteristics for turbine isentropic efficiency - - - links isentropic efficiencay :math:`\eta_\mathrm{s,t}` to keyfigure - - three default characteristic lines available (see literature and method - turbine.default()) - - **literature** + generic characteristics for heat exchanger heat transfer coefficient - - Walter Traupel (2001): Thermische Turbomaschinen Band 2. Berlin: Spinger. - -> TRAUPEL + - links heat transfer coefficient :math:`kA` to keyfigures + - different default characteristic lines available for different types of + heat exchangers (see method heat_exchanger.default()) """ def default(self, key): r""" - default characteristic lines for turbines: + **default characteristic lines for turbines** .. math:: @@ -143,43 +107,15 @@ def default(self, key): :scale: 100 % :alt: alternative text :align: center - """ - - if key == 'default': - return np.array([0, 1, 2]), np.array([1, 1, 1]) - x = {} - y = {} + **literature** - x['GENERIC'] = np.array([0, 0.5, 0.8, 0.95, 1, 1.05, 1.2]) - y['GENERIC'] = np.array([0.975, 0.985, 0.994, 0.999, 1, 0.999, 0.99]) - x['TRAUPEL'] = np.array([0.0, 0.1905, 0.3810, 0.5714, 0.7619, 0.9524, - 1.0, 1.1429, 1.3333, 1.5238, 1.7143, 1.9048]) - y['TRAUPEL'] = np.array([0.0, 0.3975, 0.6772, 0.8581, 0.9593, 0.9985, - 1.0, 0.9875, 0.9357, 0.8464, 0.7219, 0.5643]) - - return x[key], y[key] - - -class turbine(characteristics): - r""" + - Walter Traupel (2001): Thermische Turbomaschinen Band 2. Berlin: + Spinger. + -> TRAUPEL - generic characteristics for turbine isentropic efficiency - - links isentropic efficiencay :math:`\eta_\mathrm{s,t}` to keyfigure - - three default characteristic lines available (see literature and method - turbine.default()) - - **literature** - - - Walter Traupel (2001): Thermische Turbomaschinen Band 2. Berlin: Spinger. - -> TRAUPEL - """ - - def default(self, key): - r""" - - default characteristic lines for cogeneration units: + **default characteristic lines for cogeneration units** .. math:: @@ -224,48 +160,12 @@ def default(self, key): :scale: 100 % :alt: alternative text :align: center - """ - - if key == 'default': - return np.array([0, 1, 2]), np.array([1, 1, 1]) - - x = {} - y = {} - - x['TI'] = np.array([0.50, 0.71, 0.90, 1.00]) - y['TI'] = np.array([2.20, 2.15, 2.10, 2.05]) - - x['Q1'] = np.array([0.660, 0.770, 0.880, 0.990, 1.100]) - y['Q1'] = np.array([0.244, 0.233, 0.222, 0.211, 0.200]) - - x['Q2'] = np.array([0.660, 0.770, 0.880, 0.990, 1.100]) - y['Q2'] = np.array([0.246, 0.235, 0.224, 0.213, 0.202]) - - x['QLOSS'] = np.array([0.50, 0.75, 0.90, 1.00]) - y['QLOSS'] = np.array([0.06, 0.08, 0.09, 0.10]) - return x[key], y[key] - - -class heat_ex(characteristics): - r""" - - generic characteristics for heat exchanger heat transfer coefficient - - - links heat transfer coefficient :math:`kA` to keyfigures - - different default characteristic lines available for different types of - heat exchangers (see method heat_exchanger.default()) - """ - - def default(self, key): - r""" - - default **characteristic lines** for heat exchangers **are designed for - the following cases**: + **default characteristic lines for heat exchangers** .. math:: - \frac{kA_\mathrm{s,t}}{kA_\mathrm{s,t,ref}}=f_1\left(x_1 \right) + \frac{kA}{kA_\mathrm{ref}}=f_1\left(x_1 \right) \cdot f_2\left(x_2 \right) available lines characteristics: @@ -310,61 +210,122 @@ def default(self, key): """ if key == 'default': - return np.array([0, 1, 2]), np.array([1, 1, 1]) + return np.array([0, 1, 2, 3]), np.array([1, 1, 1, 1]) x = {} y = {} - x['COND_HOT'] = np.array([0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, - 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, - 0.8, 0.85, 0.9, 0.95, 1, 1.5, 2]) - y['COND_HOT'] = np.array( - [0.030, 0.158, 0.344, 0.567, 0.838, 0.888, 0.906, 0.921, 0.933, - 0.943, 0.952, 0.959, 0.966, 0.972, 0.977, 0.982, 0.986, 0.990, - 0.994, 0.997, 1.000, 1.021, 1.033]) - - x['COND_COLD'] = np.array([0.01, 0.04, 0.07, 0.11, 0.15, 0.2, 0.25, - 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, - 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1, 1.5, 2]) - y['COND_COLD'] = np.array( - [0.019, 0.075, 0.134, 0.192, 0.243, 0.303, 0.359, 0.412, 0.463, - 0.512, 0.559, 0.604, 0.648, 0.691, 0.733, 0.774, 0.813, 0.852, - 0.890, 0.928, 0.964, 1.000, 1.327, 1.612]) - - x['EVA_HOT'] = np.array([0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, - 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, - 0.8, 0.85, 0.9, 0.95, 1, 1.5, 2]) - y['EVA_HOT'] = np.array( - [0.030, 0.158, 0.245, 0.313, 0.373, 0.427, 0.477, 0.524, 0.569, - 0.611, 0.652, 0.692, 0.730, 0.767, 0.803, 0.838, 0.872, 0.905, - 0.937, 0.969, 1.000, 1.281, 1.523]) - - x['EVA_COLD'] = np.array([0.01, 0.04, 0.07, 0.11, 0.15, 0.2, 0.25, - 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, - 0.7, 0.8, 1, 2]) - y['EVA_COLD'] = np.array( - [0.018, 0.075, 0.134, 0.215, 0.300, 0.412, 0.531, 0.658, 0.794, - 0.934, 0.988, 0.991, 0.994, 0.995, 0.997, 0.998, 0.999, 1.000, - 1.001]) - - x['HE_HOT'] = np.array([0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, - 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, - 0.8, 0.85, 0.9, 0.95, 1, 1.5, 2]) - y['HE_HOT'] = np.array( - [0.030, 0.158, 0.344, 0.469, 0.535, 0.590, 0.638, 0.680, 0.718, - 0.752, 0.783, 0.812, 0.839, 0.864, 0.887, 0.909, 0.929, 0.948, - 0.966, 0.984, 1.000, 1.128, 1.216]) - - x['HE_COLD'] = np.array([0.01, 0.04, 0.07, 0.11, 0.15, 0.2, 0.25, - 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, - 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1, 1.5, 2]) - y['HE_COLD'] = np.array( - [0.018, 0.075, 0.134, 0.215, 0.300, 0.412, 0.507, 0.564, 0.614, - 0.660, 0.701, 0.739, 0.774, 0.806, 0.836, 0.864, 0.890, 0.915, - 0.938, 0.960, 0.981, 1.000, 1.151, 1.253]) + if self.comp == 'turbine': + + x['GENERIC'] = np.array( + [0.000, 0.500, 0.800, 0.950, 1.000, 1.050, 1.200]) + y['GENERIC'] = np.array( + [0.975, 0.985, 0.994, 0.999, 1.000, 0.999, 0.990]) + + x['TRAUPEL'] = np.array( + [0.0000, 0.1905, 0.3810, 0.5714, 0.7619, 0.9524, 1.0000, + 1.1429, 1.3333, 1.5238, 1.7143, 1.9048]) + y['TRAUPEL'] = np.array( + [0.0000, 0.3975, 0.6772, 0.8581, 0.9593, 0.9985, 1.0000, + 0.9875, 0.9357, 0.8464, 0.7219, 0.5643]) + + elif self.comp == 'cogeneration unit': + + x['TI'] = np.array([0.50, 0.71, 0.90, 1.00]) + y['TI'] = np.array([2.20, 2.15, 2.10, 2.05]) + + x['Q1'] = np.array([0.660, 0.770, 0.880, 0.990, 1.100]) + y['Q1'] = np.array([0.244, 0.233, 0.222, 0.211, 0.200]) + + x['Q2'] = np.array([0.660, 0.770, 0.880, 0.990, 1.100]) + y['Q2'] = np.array([0.246, 0.235, 0.224, 0.213, 0.202]) + + x['QLOSS'] = np.array([0.50, 0.75, 0.90, 1.00]) + y['QLOSS'] = np.array([0.06, 0.08, 0.09, 0.10]) + + elif self.comp == 'heat exchanger' or self.comp == 'desuperheater': + + x['EVA_HOT'] = np.array( + [0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, + 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1, + 1.5, 2]) + y['EVA_HOT'] = np.array( + [0.030, 0.158, 0.245, 0.313, 0.373, 0.427, 0.477, 0.524, + 0.569, 0.611, 0.652, 0.692, 0.730, 0.767, 0.803, 0.838, + 0.872, 0.905, 0.937, 0.969, 1.000, 1.281, 1.523]) + + x['EVA_COLD'] = np.array( + [0.01, 0.04, 0.07, 0.11, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, + 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.8, 1, 2]) + y['EVA_COLD'] = np.array( + [0.018, 0.075, 0.134, 0.215, 0.300, 0.412, 0.531, 0.658, + 0.794, 0.934, 0.988, 0.991, 0.994, 0.995, 0.997, 0.998, + 0.999, 1.000, 1.001]) + + x['HE_HOT'] = np.array([ + 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, + 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1, + 1.5, 2]) + y['HE_HOT'] = np.array( + [0.030, 0.158, 0.344, 0.469, 0.535, 0.590, 0.638, 0.680, + 0.718, 0.752, 0.783, 0.812, 0.839, 0.864, 0.887, 0.909, + 0.929, 0.948, 0.966, 0.984, 1.000, 1.128, 1.216]) + + x['HE_COLD'] = np.array([ + 0.01, 0.04, 0.07, 0.11, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, + 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, + 0.95, 1, 1.5, 2]) + y['HE_COLD'] = np.array( + [0.018, 0.075, 0.134, 0.215, 0.300, 0.412, 0.507, 0.564, + 0.614, 0.660, 0.701, 0.739, 0.774, 0.806, 0.836, 0.864, + 0.890, 0.915, 0.938, 0.960, 0.981, 1.000, 1.151, 1.253]) + + elif self.comp == 'condenser': + + x['COND_HOT'] = np.array( + [0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, + 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1, + 1.5, 2]) + y['COND_HOT'] = np.array( + [0.030, 0.158, 0.344, 0.567, 0.838, 0.888, 0.906, 0.921, + 0.933, 0.943, 0.952, 0.959, 0.966, 0.972, 0.977, 0.982, + 0.986, 0.990, 0.994, 0.997, 1.000, 1.021, 1.033]) + + x['COND_COLD'] = np.array([ + 0.01, 0.04, 0.07, 0.11, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, + 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, + 0.95, 1, 1.5, 2]) + y['COND_COLD'] = np.array( + [0.019, 0.075, 0.134, 0.192, 0.243, 0.303, 0.359, 0.412, + 0.463, 0.512, 0.559, 0.604, 0.648, 0.691, 0.733, 0.774, + 0.813, 0.852, 0.890, 0.928, 0.964, 1.000, 1.327, 1.612]) + + else: + return np.array([0, 1, 2, 3]), np.array([1, 1, 1, 1]) return x[key], y[key] + def attr(self): + return ['x', 'y', 'method', 'comp'] + + def f_x(self, x): + return self.char(x) + + def get_attr(self, key): + """ + get the value of a characteristics attribute + + :param key: attribute to return its value + :type key: str + :returns: + - :code:`self.__dict__[key]` if object has attribute key + - :code:`None` if object has no attribute key + """ + if key in self.__dict__: + return self.__dict__[key] + else: + return None + class pump(characteristics): r""" From 91881371c67b48b2dc624d11caebe227d7a90491 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Sat, 20 Oct 2018 14:44:45 +0200 Subject: [PATCH 56/80] moved boundary check into characteristics class --- tespy/components/subsystems.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tespy/components/subsystems.py b/tespy/components/subsystems.py index 66ede58cf..e3653f255 100644 --- a/tespy/components/subsystems.py +++ b/tespy/components/subsystems.py @@ -59,6 +59,7 @@ def __init__(self, label, **kwargs): # set default values for key in self.attr(): self.__dict__.update({key: np.nan}) + self.__dict__.update({key + '_set': False}) self.subsys_init() self.set_attr(**kwargs) @@ -76,8 +77,10 @@ def set_attr(self, **kwargs): isinstance(kwargs[key], int)): if np.isnan(kwargs[key]): self.__dict__.update({key: np.nan}) + self.__dict__.update({key + '_set': False}) else: self.__dict__.update({key: kwargs[key]}) + self.__dict__.update({key + '_set': True}) elif isinstance(kwargs[key], str): self.__dict__.update({key: kwargs[key]}) From 3cc4998c83c1a6ed4eea5484e5424c1ec44e4e0f Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Sat, 20 Oct 2018 14:50:37 +0200 Subject: [PATCH 57/80] adjusted fluid initialisation for usage of cogeneration unit --- tespy/networks.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tespy/networks.py b/tespy/networks.py index 04ed74a26..21cc97c87 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -689,6 +689,14 @@ def init_target(self, c, start): self.init_target(outconn, start) + if isinstance(c.t, cmp.cogeneration_unit): + for outconn in self.comps.loc[c.t].o[:2]: + for fluid, x in c.fluid.val.items(): + if not outconn.fluid.val_set[fluid]: + outconn.fluid.val[fluid] = x + + self.init_target(outconn, start) + if isinstance(c.t, cmp.drum) and c.t != start: start = c.t for outconn in self.comps.loc[c.t].o: @@ -743,6 +751,14 @@ def init_source(self, c, start): self.init_source(inconn, start) + if isinstance(c.s, cmp.cogeneration_unit): + for inconn in self.comps.loc[c.s].i[:2]: + for fluid, x in c.fluid.val.items(): + if not inconn.fluid.val_set[fluid]: + inconn.fluid.val[fluid] = x + + self.init_source(inconn, start) + if isinstance(c.s, cmp.drum) and c.s != start: start = c.s for inconn in self.comps.loc[c.s].i: From 0d9fa919910046787cb9badb6719708539d956c2 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Sat, 20 Oct 2018 14:50:54 +0200 Subject: [PATCH 58/80] moved boundary check into characteristics class --- tespy/components/characteristics.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/tespy/components/characteristics.py b/tespy/components/characteristics.py index 9c492e30d..ac2032615 100644 --- a/tespy/components/characteristics.py +++ b/tespy/components/characteristics.py @@ -59,16 +59,6 @@ def __init__(self, **kwargs): self.char = interp1d(self.x, self.y, kind='cubic', bounds_error=True) - def default(self, key): - r""" - - generic characteristics for heat exchanger heat transfer coefficient - - - links heat transfer coefficient :math:`kA` to keyfigures - - different default characteristic lines available for different types of - heat exchangers (see method heat_exchanger.default()) - """ - def default(self, key): r""" @@ -309,7 +299,12 @@ def attr(self): return ['x', 'y', 'method', 'comp'] def f_x(self, x): - return self.char(x) + if x > self.x[-1]: + return self.char(self.x[-1]) + elif x < self.x[0]: + return self.char(self.x[0]) + else: + return self.char(x) def get_attr(self, key): """ From a7792b219bc549f258f588e0a939e9415b4d18d9 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Sat, 20 Oct 2018 14:51:44 +0200 Subject: [PATCH 59/80] generic characteristics creation, moved boundary checks to characteristics class, more functionalities for cogeneration unit --- tespy/components/components.py | 360 ++++++++++++++++++++------------- 1 file changed, 217 insertions(+), 143 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index 7c61adc4b..9a9615f1a 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -242,6 +242,16 @@ def get_attr(self, key): return None def comp_init(self, nw): + r""" + generic component initialisation + + - counts/searches custom variables + - creates characteristics for components + + :param nw: network using this component object + :type nw: tespy.networks.network + :returns: no return value + """ self.vars = {} self.num_c_vars = 0 for var in self.attr().keys(): @@ -251,6 +261,16 @@ def comp_init(self, nw): self.num_c_vars += 1 self.vars[self.get_attr(var)] = var + # characteristics creation + for key, val in self.attr().items(): + if isinstance(val, dc_cc): + if self.get_attr(key).func is None: + self.get_attr(key).func = cmp_char.characteristics( + method=val.method, x=self.get_attr(key).x, + y=self.get_attr(key).y, comp=self.component()) + self.get_attr(key).x = self.get_attr(key).func.x + self.get_attr(key).y = self.get_attr(key).func.y + def attr(self): return {} @@ -855,6 +875,10 @@ def inlets(self): def outlets(self): return ['out1'] + def comp_init(self, nw): + + component.comp_init(self, nw) + def equations(self): r""" returns vector vec_res with result of equations for this component @@ -929,29 +953,6 @@ def derivatives(self, nw): :param nw: network using this component object :type nw: tespy.networks.network :returns: mat_deriv (*numpy array*) - matrix of partial derivatives - - **example** - - matrix of partial derivatives for a turbine with one fluid in fluid - vector and specified value for power P - - .. math:: - \left( - \begin{array}{cccc} - 0 & 0 & 0 & 1\\ - 0 & 0 & 0 & -1\\ - 1 & 0 & 0 & 0\\ - -1 & 0 & 0 & 0\\ - h_{out} - h_{in} & 0 & -\dot{m}_{in} & 0\\ - 0 & 0 & \dot{m}_{in} & 0 - \end{array} - \right) - - .. note:: - in this example you can see, that there is no equation regarding - pressure change, thus the pressure at the inlet and the outlet must - be defined externally through other components or by connection - parametrisation """ num_fl = len(nw.fluids) mat_deriv = [] @@ -1155,26 +1156,8 @@ def component(self): def attr(self): return {'P': dc_cp(), 'eta_s': dc_cp(), 'pr': dc_cp(), 'Sirr': dc_cp(), - 'eta_s_char': dc_cc(x=[0, 1, 2, 3], y=[1, 1, 1, 1]), - 'flow_char': dc_cc(x=[0, 1, 2, 3], y=[1, 1, 1, 1])} - - def comp_init(self, nw): - - component.comp_init(self, nw) - - if self.flow_char.func is None: - method = self.flow_char.method - x = self.flow_char.x - y = self.flow_char.y - self.flow_char.func = cmp_char.characteristics(method=method, - x=x, y=y) - - if self.eta_s_char.func is None: - method = self.eta_s_char.method - x = self.eta_s_char.x - y = self.eta_s_char.y - self.eta_s_char.func = cmp_char.characteristics(method=method, - x=x, y=y) + 'eta_s_char': dc_cc(), + 'flow_char': dc_cc()} def additional_equations(self): r""" @@ -1322,11 +1305,6 @@ def flow_char_func(self): expr = i[0] * v_mix_ph(i) - if expr > self.flow_char.func.x[-1]: - expr = self.flow_char.func.x[-1] - elif expr < self.flow_char.func.x[0]: - expr = self.flow_char.func.x[0] - return np.array([o[1] - i[1] - self.flow_char.func.f_x(expr)]) def flow_char_deriv(self): @@ -1531,7 +1509,8 @@ def comp_init(self, nw): component.comp_init(self, nw) - if self.char_map.func is None: + if (self.char_map.func is None or + not isinstance(self.char_map.func, cmp_char.compressor)): method = self.char_map.method self.char_map.func = cmp_char.compressor(method=method) @@ -1872,22 +1851,6 @@ def attr(self): def default_offdesign(self): return turbomachine.default_offdesign(self) + ['cone'] - def comp_init(self, nw): - - component.comp_init(self, nw) - - if self.eta_s_char.func is None: - method = self.eta_s_char.method - x = self.eta_s_char.x - y = self.eta_s_char.y - self.eta_s_char.func = cmp_char.turbine(method=method, x=x, y=y) - - if self.cone.func is None: - method = self.cone.method - x = self.cone.x - y = self.cone.y - self.cone.func = cmp_char.characteristics(method=method, x=x, y=y) - def additional_equations(self): r""" additional equations for turbines @@ -2047,11 +2010,6 @@ def char_func(self): 'isentropic efficiency to.') raise MyComponentError(msg) - if expr > self.eta_s_char.func.x[-1]: - expr = self.eta_s_char.func.x[-1] - if expr < self.eta_s_char.func.x[0]: - expr = self.eta_s_char.func.x[0] - return np.array([(-(o[2] - i[2]) + (self.o0[2] - self.i0[2]) / self.dh_s0 * self.eta_s_char.func.f_x(expr) * (self.h_os('post') - i[2]))]) @@ -2970,6 +2928,11 @@ def derivatives(self, nw): eb_deriv[0, i, 2] = -(self.inl + self.outl)[i].m.val_SI else: eb_deriv[0, i, 2] = (self.inl + self.outl)[i].m.val_SI + # fluid composition +# pos = 3 + nw.fluids.index(self.fuel.val) +# eb_deriv[0, 0, pos] = self.inl[0].m.val_SI * self.lhv +# eb_deriv[0, 1, pos] = self.inl[1].m.val_SI * self.lhv +# eb_deriv[0, 2, pos] = -self.outl[0].m.val_SI * self.lhv mat_deriv += eb_deriv.tolist() if self.lamb.is_set: @@ -3077,6 +3040,9 @@ def reaction_balance(self, fluid): n_oxygen += (i.m.val_SI * i.fluid.val[self.o2] / molar_masses[self.o2]) + if n_fuel == 0: + n_fuel = 1 + if not self.lamb.is_set: self.lamb.val = n_oxygen / ( n_fuel * (self.n['C'] + self.n['H'] / 4)) @@ -3513,7 +3479,7 @@ def calc_parameters(self, nw, mode): p_ref = 1e5 for i in self.inl: - val -= i.m.val_SI * ( + val += i.m.val_SI * ( s_mix_ph(i.to_flow()) - s_mix_pT([0, p_ref, 0, i.fluid.val], T_ref)) @@ -3526,7 +3492,7 @@ def calc_parameters(self, nw, mode): S_steam = CP.PropsSI('H', 'P', p, 'Q', 1, self.h2o) if S < S_steam: dS = (S_steam - S) * o.fluid.val[self.h2o] - val += o.m.val_SI * ( + val -= o.m.val_SI * ( s_mix_ph(o.to_flow()) - s_mix_pT([0, p_ref, 0, o.fluid.val], T_ref) - dS) @@ -4208,11 +4174,11 @@ def calc_parameters(self, nw, mode): p_ref = 1e5 for i in self.inl: - S -= i.m.val_SI * (s_mix_ph(i.to_flow()) - + S += i.m.val_SI * (s_mix_ph(i.to_flow()) - s_mix_pT([0, p_ref, 0, i.fluid.val], T_ref)) for o in self.outl: - S += o.m.val_SI * (s_mix_ph(o.to_flow()) - + S -= o.m.val_SI * (s_mix_ph(o.to_flow()) - s_mix_pT([0, p_ref, 0, o.fluid.val], T_ref)) self.S.val = S @@ -4289,10 +4255,11 @@ def component(self): def attr(self): return {'fuel': dc_cp(printout=False), 'lamb': dc_cp(), 'ti': dc_cp(), - 'P': dc_cp(), 'Q1': dc_cp(), 'Q2': dc_cp(), 'Qloss': dc_cp(), + 'P': dc_cp(), 'P_ref': dc_cp(), + 'Q1': dc_cp(), 'Q2': dc_cp(), 'Qloss': dc_cp(), 'pr1': dc_cp(), 'pr2': dc_cp(), 'zeta1': dc_cp(), 'zeta2': dc_cp(), - 'ti_char': dc_cc(method='TI'), + 'tiP_char': dc_cc(method='TI'), 'Q1_char': dc_cc(method='Q1'), 'Q2_char': dc_cc(method='Q2'), 'Qloss_char': dc_cc(method='QLOSS'), @@ -4304,6 +4271,26 @@ def inlets(self): def outlets(self): return ['out1', 'out2', 'out3'] + def comp_init(self, nw): + + combustion_chamber.comp_init(self, nw) + + if not self.P.is_set: + self.set_attr(P='var') + if nw.compinfo: + msg = ('The power output of cogeneration units must be set! ' + 'We are adding the power output of component ' + + self.label + ' as custom variable of the system.') + print(msg) + + if not self.Qloss.is_set: + self.set_attr(Qloss='var') + if nw.compinfo: + msg = ('The heat loss of cogeneration units must be set! ' + 'We are adding the heat loss of component ' + + self.label + ' as custom variable of the system.') + print(msg) + def equations(self): r""" returns vector vec_res with result of equations for this component @@ -4355,6 +4342,10 @@ def equations(self): vec_res += [self.inl[2].p.val_SI - self.inl[3].p.val_SI] vec_res += [self.energy_balance()] + vec_res += [self.tiP_char_func()] + vec_res += [self.Q1_char_func()] + vec_res += [self.Q2_char_func()] + vec_res += [self.QLOSS_char_func()] if self.lamb.is_set: vec_res += [self.lambda_func()] @@ -4362,11 +4353,11 @@ def equations(self): if self.ti.is_set: vec_res += [self.ti_func()] - if self.P.is_set: - vec_res += [self.power_func()] + if self.Q1.is_set: + vec_res += [self.Q1_func()] - if self.Q.is_set: - vec_res += [self.heat_func()] + if self.Q2.is_set: + vec_res += [self.Q2_func()] if self.pr1.is_set: vec_res += [self.pr1.val * self.inl[0].p.val_SI - @@ -4427,34 +4418,45 @@ def derivatives(self, nw): # derivatives for energy balance eb_deriv = np.zeros((1, 7, num_fl + 3)) + # mass flow cooling water + for i in [0, 1]: + eb_deriv[0, i, 0] = -(self.outl[i].h.val_SI - self.inl[i].h.val_SI) + # mass flow and pressure for combustion reaction + for i in [2, 3, 6]: + eb_deriv[0, i, 0] = self.ddx_func(self.energy_balance, 'm', i) + eb_deriv[0, i, 1] = self.ddx_func(self.energy_balance, 'p', i) + # enthalpy + for i in range(4): + eb_deriv[0, i, 2] = self.inl[i].m.val_SI for i in range(3): - eb_deriv[0, i, 0] = ( - self.ddx_func(self.energy_balance, 'm', i)) - eb_deriv[0, i, 1] = ( - self.ddx_func(self.energy_balance, 'p', i)) - if i >= self.num_i: - eb_deriv[0, i, 2] = -(self.inl + self.outl)[i].m.val_SI - else: - eb_deriv[0, i, 2] = (self.inl + self.outl)[i].m.val_SI + eb_deriv[0, i + 4, 2] = -self.outl[i].m.val_SI + # fluid composition + pos = 3 + nw.fluids.index(self.fuel.val) + eb_deriv[0, 2, pos] = self.inl[2].m.val_SI * self.lhv + eb_deriv[0, 3, pos] = self.inl[3].m.val_SI * self.lhv + eb_deriv[0, 6, pos] = -self.outl[2].m.val_SI * self.lhv + mat_deriv += eb_deriv.tolist() if self.lamb.is_set: # derivatives for specified lambda lamb_deriv = np.zeros((1, 7, num_fl + 3)) for i in range(2): - lamb_deriv[0, i, 0] = self.ddx_func(self.lambda_func, 'm', i) - lamb_deriv[0, i, 3:] = self.ddx_func(self.lambda_func, - 'fluid', i) + lamb_deriv[0, i + 2, 0] = ( + self.ddx_func(self.lambda_func, 'm', i + 2)) + lamb_deriv[0, i + 2, 3:] = ( + self.ddx_func(self.lambda_func, 'fluid', i + 2)) mat_deriv += lamb_deriv.tolist() if self.ti.is_set: # derivatives for specified thermal input ti_deriv = np.zeros((1, 7, num_fl + 3)) for i in range(2): - ti_deriv[0, i, 0] = self.ddx_func(self.ti_func, 'm', i) - ti_deriv[0, i, 3:] = self.ddx_func(self.ti_func, 'fluid', i) - ti_deriv[0, 2, 0] = self.ddx_func(self.ti_func, 'm', 2) - ti_deriv[0, 2, 3:] = self.ddx_func(self.ti_func, 'fluid', 2) + ti_deriv[0, i + 2, 0] = self.ddx_func(self.ti_func, 'm', i + 2) + ti_deriv[0, i + 2, 3:] = ( + self.ddx_func(self.ti_func, 'fluid', i + 2)) + ti_deriv[0, 6, 0] = self.ddx_func(self.ti_func, 'm', 6) + ti_deriv[0, 6, 3:] = self.ddx_func(self.ti_func, 'fluid', 6) mat_deriv += ti_deriv.tolist() if self.pr1.is_set: @@ -4491,7 +4493,7 @@ def derivatives(self, nw): def energy_balance(self): r""" - calculates the energy balance of the adiabatic combustion chamber + calculates the energy balance of the cogeneration unit .. note:: The temperature for the reference state is set to 20 °C, thus @@ -4520,10 +4522,11 @@ def energy_balance(self): p_ref = 1e5 res = 0 + ti = 0 for i in self.inl[2:]: res += i.m.val_SI * ( i.h.val_SI - h_mix_pT([0, p_ref, 0, i.fluid.val], T_ref)) - res += i.m.val_SI * i.fluid.val[self.fuel.val] * self.lhv + ti += i.m.val_SI * i.fluid.val[self.fuel.val] * self.lhv for o in self.outl[2:]: dh = 0 @@ -4538,7 +4541,19 @@ def energy_balance(self): res -= o.m.val_SI * ( o.h.val_SI - h_mix_pT([0, p_ref, 0, o.fluid.val], T_ref) - dh) - res -= o.m.val_SI * o.fluid.val[self.fuel.val] * self.lhv + ti -= o.m.val_SI * o.fluid.val[self.fuel.val] * self.lhv + + res += ti + if not self.ti.is_set: + self.ti.val = ti + + # cooling water + for i in range(2): + res -= self.inl[i].m.val_SI * ( + self.outl[i].h.val_SI - self.inl[i].h.val_SI) + + # power output and heat loss + res -= self.P.val + self.Qloss.val return res @@ -4648,6 +4663,110 @@ def drb_dx(self, dx, pos, fluid): return deriv + def tiP_char_func(self): + r""" + calculates the relation of output power and thermal input from + specified characteristic line + + :returns: res (*float*) - residual value + + .. math:: + 0 = P \cdot f_{TI}\left(\frac{P}{P_{ref}}\right)- LHV \cdot + \left[\sum_i \left(\dot{m}_{in,i} \cdot + x_{f,i}\right) - \dot{m}_{out,3} \cdot x_{f,3} \right] + \; \forall i \in [1,2] + + """ + + if self.P_ref.is_set is True: + expr = self.P.val / self.Pref.val + else: + expr = 1 + + return self.ti.val - self.tiP_char.func.f_x(expr) * self.P + + def Q1_char_func(self): + r""" + calculates the relation of heat output 1 and thermal input from + specified characteristic lines + + :returns: res (*float*) - residual value + + .. math:: + 0 = \dot{m}_1 \cdot \left(h_{out,1} - h_{in,1} \right) \cdot + f_{TI}\left(\frac{P}{P_{ref}}\right)- LHV \cdot \left[\sum_i + \left(\dot{m}_{in,i} \cdot x_{f,i}\right) - + \dot{m}_{out,3} \cdot x_{f,3} \right] \cdot + f_{Q1}\left(\frac{P}{P_{ref}}\right) + \; \forall i \in [1,2] + + """ + + i = self.inl[0] + o = self.outl[0] + + if self.P_ref.is_set is True: + expr = self.P.val / self.Pref.val + else: + expr = 1 + + return (self.ti.val * self.Q1_char.func.f_x(expr) - + self.ti_char.func.f_x(expr) * i.m.val_SI * ( + o.h.val_SI - i.h.val_SI)) + + def Q2_char_func(self): + r""" + calculates the relation of heat output 2 and thermal input from + specified characteristic lines + + :returns: res (*float*) - residual value + + .. math:: + 0 = \dot{m}_2 \cdot \left(h_{out,2} - h_{in,2} \right) \cdot + f_{TI}\left(\frac{P}{P_{ref}}\right)- LHV \cdot \left[\sum_i + \left(\dot{m}_{in,i} \cdot x_{f,i}\right) - + \dot{m}_{out,3} \cdot x_{f,3} \right] \cdot + f_{Q2}\left(\frac{P}{P_{ref}}\right) + \; \forall i \in [1,2] + + """ + + i = self.inl[1] + o = self.outl[1] + + if self.P_ref.is_set is True: + expr = self.P.val / self.Pref.val + else: + expr = 1 + + return (self.ti.val * self.Q2_char.func.f_x(expr) - + self.ti_char.func.f_x(expr) * i.m.val_SI * ( + o.h.val_SI - i.h.val_SI)) + + def Qloss_char_func(self): + r""" + calculates the relation of heat loss and thermal input from + specified characteristic lines + + :returns: res (*float*) - residual value + + .. math:: + 0 = \dot{Q}_{loss} \cdot + f_{TI}\left(\frac{P}{P_{ref}}\right)- LHV \cdot \left[\sum_i + \left(\dot{m}_{in,i} \cdot x_{f,i}\right) - + \dot{m}_{out,3} \cdot x_{f,3} \right] \cdot + f_{QLOSS}\left(\frac{P}{P_{ref}}\right) + \; \forall i \in [1,2] + + """ + + if self.P_ref.is_set is True: + expr = self.P.val / self.Pref.val + else: + expr = 1 + + return self.ti.val - self.ti_char.func.f_x(expr) * self.Qloss.val + def initialise_fluids(self, nw): r""" calculates reaction balance with given lambda for good generic @@ -5085,14 +5204,6 @@ def comp_init(self, nw): component.comp_init(self, nw) - if self.kA_char.func is None: - method = self.kA_char.method - x = self.kA_char.x - y = self.kA_char.y - self.kA_char.func = cmp_char.heat_ex(method=method, x=x, y=y) - self.kA_char.x = self.kA_char.func.x - self.kA_char.y = self.kA_char.func.y - self.t_a.val_SI = ((self.t_a.val + nw.T[nw.T_unit][0]) * nw.T[nw.T_unit][1]) self.t_a_design.val_SI = ((self.t_a_design.val + nw.T[nw.T_unit][0]) * @@ -5421,11 +5532,6 @@ def kA_func(self): else: expr = 1 - if expr > self.kA_char.func.x[-1]: - expr = self.kA_char.func.x[-1] - if expr < self.kA_char.func.x[0]: - expr = self.kA_char.func.x[0] - fkA = self.kA_char.func.f_x(expr) return (i[0].m.val_SI * (o[0].h.val_SI - i[0].h.val_SI) + self.kA.val * @@ -5999,18 +6105,6 @@ def comp_init(self, nw): component.comp_init(self, nw) - if self.kA_char1.func is None: - method = self.kA_char1.method - x = self.kA_char1.x - y = self.kA_char1.y - self.kA_char1.func = cmp_char.heat_ex(method=method, x=x, y=y) - - if self.kA_char2.func is None: - method = self.kA_char2.method - x = self.kA_char2.x - y = self.kA_char2.y - self.kA_char2.func = cmp_char.heat_ex(method=method, x=x, y=y) - def equations(self): r""" returns vector vec_res with result of equations for this component @@ -6277,11 +6371,6 @@ def kA_func(self): else: expr = 1 - if expr > self.kA_char1.func.x[-1]: - expr = self.kA_char1.func.x[-1] - if expr < self.kA_char1.func.x[0]: - expr = self.kA_char1.func.x[0] - fkA1 = self.kA_char1.func.f_x(expr) if self.kA_char2.param == 'm': @@ -6289,11 +6378,6 @@ def kA_func(self): else: expr = 1 - if expr > self.kA_char2.func.x[-1]: - expr = self.kA_char2.func.x[-1] - if expr < self.kA_char2.func.x[0]: - expr = self.kA_char2.func.x[0] - fkA2 = self.kA_char2.func.f_x(expr) return (i1[0] * (o1[2] - i1[2]) + self.kA.val * fkA1 * fkA2 * @@ -6827,11 +6911,6 @@ def kA_func(self): else: expr = 1 - if expr > self.kA_char1.func.x[-1]: - expr = self.kA_char1.func.x[-1] - if expr < self.kA_char1.func.x[0]: - expr = self.kA_char1.func.x[0] - fkA1 = self.kA_char1.func.f_x(expr) if self.kA_char2.param == 'm': @@ -6839,11 +6918,6 @@ def kA_func(self): else: expr = 1 - if expr > self.kA_char2.func.x[-1]: - expr = self.kA_char2.func.x[-1] - if expr < self.kA_char2.func.x[0]: - expr = self.kA_char2.func.x[0] - fkA2 = self.kA_char2.func.f_x(expr) return (i1[0] * (o1[2] - i1[2]) + self.kA.val * fkA1 * fkA2 * From 74b504d8475c32fa7b159d0440a809e0f4ba160b Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Sun, 21 Oct 2018 19:16:09 +0200 Subject: [PATCH 60/80] first working version of cogeneration unit implemented --- tespy/components/components.py | 314 +++++++++++++++++++++------------ 1 file changed, 205 insertions(+), 109 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index 9a9615f1a..b2c0771e0 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -8,6 +8,8 @@ import numpy as np import math +import time + import CoolProp.CoolProp as CP from tespy.helpers import ( @@ -478,6 +480,7 @@ def fluid_deriv(self): mat_deriv[i + j, 1, j + 3] = 1 mat_deriv[i + j, 5, j + 3] = -1 j += 1 + return mat_deriv.tolist() if isinstance(self, splitter): @@ -613,14 +616,16 @@ def mass_flow_deriv(self): """ num_fl = len(self.inl[0].fluid.val) - if (isinstance(self, split) or + if ((isinstance(self, split) or isinstance(self, merge) or isinstance(self, combustion_chamber) or isinstance(self, combustion_chamber_stoich) or isinstance(self, drum) or - (self.num_i == 1 and self.num_o == 1)): + (self.num_i == 1 and self.num_o == 1)) and + not isinstance(self, cogeneration_unit)): mat_deriv = np.zeros((1, self.num_i + self.num_o + self.num_c_vars, num_fl + 3)) + j = 0 for i in self.inl: mat_deriv[0, j, 0] = 1 @@ -2946,13 +2951,14 @@ def derivatives(self, nw): if self.ti.is_set: # derivatives for specified thermal input + pos = nw.fluids.index(self.fuel.val) + 3 ti_deriv = np.zeros((1, 3, num_fl + 3)) for i in range(2): - ti_deriv[0, i, 0] = self.ddx_func(self.ti_func, 'm', i) - ti_deriv[0, i, 3:] = self.ddx_func(self.ti_func, 'fluid', i) - ti_deriv[0, 2, 0] = self.ddx_func(self.ti_func, 'm', 2) - ti_deriv[0, 2, 3:] = self.ddx_func(self.ti_func, 'fluid', 2) - mat_deriv += ti_deriv.tolist() + ti_deriv[0, i, 0] = -self.inl[i].fluid.val[self.fuel.val] + ti_deriv[0, i, pos] = -self.inl[i].m.val_SI + ti_deriv[0, 2, 0] = self.outl[0].fluid.val[self.fuel.val] + ti_deriv[0, 2, pos] = self.outl[0].m.val_SI + mat_deriv += (ti_deriv * self.lhv).tolist() return np.asarray(mat_deriv) @@ -3109,7 +3115,6 @@ def energy_balance(self): for i in self.inl: res += i.m.val_SI * ( i.h.val_SI - h_mix_pT([0, p_ref, 0, i.fluid.val], T_ref)) - res += i.m.val_SI * i.fluid.val[self.fuel.val] * self.lhv for o in self.outl: dh = 0 @@ -3124,7 +3129,8 @@ def energy_balance(self): res -= o.m.val_SI * ( o.h.val_SI - h_mix_pT([0, p_ref, 0, o.fluid.val], T_ref) - dh) - res -= o.m.val_SI * o.fluid.val[self.fuel.val] * self.lhv + + res += self.calc_ti() return res @@ -3172,41 +3178,21 @@ def ti_func(self): 0 = ti - \dot{m}_f \cdot LHV """ - if isinstance(self, cogeneration_unit): - inl = self.inl[2:] - outl = self.outl[2:] - else: - inl = self.inl - outl = self.outl - - m_fuel = 0 - for i in inl: - m_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val]) - - for o in outl: - m_fuel -= (o.m.val_SI * o.fluid.val[self.fuel.val]) - - return (self.ti.val - m_fuel * self.lhv) + return self.ti.val - self.calc_ti() def bus_func(self): r""" function for use on busses - :returns: val (*float*) - residual value of equation + :returns: val (*float*) - residual value of equation (equals thermal + input) .. math:: - val = \dot{m}_{fuel} \cdot LHV + val = ti """ - m_fuel = 0 - for i in self.inl: - m_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val]) - - for o in self.outl: - m_fuel -= (o.m.val_SI * o.fluid.val[self.fuel.val]) - - return m_fuel * self.lhv + return self.calc_ti() def bus_deriv(self): r""" @@ -3294,6 +3280,28 @@ def drb_dx(self, dx, pos, fluid): return deriv + def calc_ti(self): + r""" + calculates the thermal input of the combustion chamber + + :returns: ti (*float*) - thermal input + + .. math:: + ti = LHV \cdot \left[\sum_i \left(\dot{m}_{in,i} \cdot x_{f,i} + \right) - \dot{m}_{out,1} \cdot x_{f,1} \right] + \; \forall i \in [1,2] + + """ + + m = 0 + for i in self.inl: + m += i.m.val_SI * i.fluid.val[self.fuel.val] + + for o in self.outl: + m -= o.m.val_SI * o.fluid.val[self.fuel.val] + + return m * self.lhv + def initialise_fluids(self, nw): r""" calculates reaction balance with given lambda for good generic @@ -3359,17 +3367,17 @@ def convergence_check(self, nw): outl = self.outl m = 0 - for i in self.inl: + for i in inl: if i.m.val_SI < 0 and not i.m.val_set: i.m.val_SI = 0.01 m += i.m.val_SI - for o in self.outl: + for o in outl: fluids = [f for f in o.fluid.val.keys() if not o.fluid.val_set[f]] for f in fluids: if f not in [self.o2, self.co2, self.h2o, self.fuel.val]: m_f = 0 - for i in self.inl: + for i in inl: m_f += i.fluid.val[f] * i.m.val_SI if abs(o.fluid.val[f] - m_f / m) > 0.03: @@ -3400,7 +3408,7 @@ def convergence_check(self, nw): else: continue - for o in self.outl: + for o in outl: if o.m.val_SI < 0 and not o.m.val_set: o.m.val_SI = 10 init_target(nw, o, o.t) @@ -3408,8 +3416,10 @@ def convergence_check(self, nw): if o.h.val_SI < 7.5e5 and not o.h.val_set: o.h.val_SI = 1e6 - if self.lamb.val < 1 and not self.lamb.is_set: - self.lamb.val = 3 + if self.lamb.val < 2: + for i in inl: + if i.fluid.val[self.fuel.val] > 0.75 and not i.m.val_set: + i.m.val_SI = 0.01 def initialise_source(self, c, key): r""" @@ -3455,9 +3465,7 @@ def initialise_target(self, c, key): def calc_parameters(self, nw, mode): - self.ti.val = 0 - for i in self.inl: - self.ti.val += i.m.val_SI * i.fluid.val[self.fuel.val] * self.lhv + self.ti.val = self.calc_ti() n_fuel = 0 for i in self.inl: @@ -4255,8 +4263,9 @@ def component(self): def attr(self): return {'fuel': dc_cp(printout=False), 'lamb': dc_cp(), 'ti': dc_cp(), - 'P': dc_cp(), 'P_ref': dc_cp(), - 'Q1': dc_cp(), 'Q2': dc_cp(), 'Qloss': dc_cp(), + 'P': dc_cp(val=1e6, d=1, val_min=1), 'P_ref': dc_cp(), + 'Q1': dc_cp(), 'Q2': dc_cp(), + 'Qloss': dc_cp(val=1e4, d=1, val_min=1), 'pr1': dc_cp(), 'pr2': dc_cp(), 'zeta1': dc_cp(), 'zeta2': dc_cp(), 'tiP_char': dc_cc(method='TI'), @@ -4273,8 +4282,6 @@ def outlets(self): def comp_init(self, nw): - combustion_chamber.comp_init(self, nw) - if not self.P.is_set: self.set_attr(P='var') if nw.compinfo: @@ -4291,6 +4298,8 @@ def comp_init(self, nw): self.label + ' as custom variable of the system.') print(msg) + combustion_chamber.comp_init(self, nw) + def equations(self): r""" returns vector vec_res with result of equations for this component @@ -4345,7 +4354,7 @@ def equations(self): vec_res += [self.tiP_char_func()] vec_res += [self.Q1_char_func()] vec_res += [self.Q2_char_func()] - vec_res += [self.QLOSS_char_func()] + vec_res += [self.Qloss_char_func()] if self.lamb.is_set: vec_res += [self.lambda_func()] @@ -4386,11 +4395,12 @@ def derivatives(self, nw): """ num_fl = len(nw.fluids) + num_vars = 7 + self.num_c_vars mat_deriv = [] # derivatives for reaction balance j = 0 - fl_deriv = np.zeros((num_fl, 7, num_fl + 3)) + fl_deriv = np.zeros((num_fl, num_vars, num_fl + 3)) for fluid in nw.fluids: # fresh air and fuel inlets for i in range(2): @@ -4404,12 +4414,11 @@ def derivatives(self, nw): # derivatives for fluid balances (except combustion) mat_deriv += self.fluid_deriv() - # derivatives for mass balance mat_deriv += self.mass_flow_deriv() # derivatives for pressure equations - p_deriv = np.zeros((2, 7, num_fl + 3)) + p_deriv = np.zeros((2, num_vars, num_fl + 3)) for k in range(2): p_deriv[k][2][1] = 1 p_deriv[0][6][1] = -1 @@ -4417,7 +4426,7 @@ def derivatives(self, nw): mat_deriv += p_deriv.tolist() # derivatives for energy balance - eb_deriv = np.zeros((1, 7, num_fl + 3)) + eb_deriv = np.zeros((1, num_vars, num_fl + 3)) # mass flow cooling water for i in [0, 1]: eb_deriv[0, i, 0] = -(self.outl[i].h.val_SI - self.inl[i].h.val_SI) @@ -4436,11 +4445,88 @@ def derivatives(self, nw): eb_deriv[0, 3, pos] = self.inl[3].m.val_SI * self.lhv eb_deriv[0, 6, pos] = -self.outl[2].m.val_SI * self.lhv + if self.P.is_var: + eb_deriv[0, 7 + self.P.var_pos, 0] = ( + self.ddx_func(self.energy_balance, 'P', 7)) + + if self.Qloss.is_var: + eb_deriv[0, 7 + self.Qloss.var_pos, 0] = ( + self.ddx_func(self.energy_balance, 'Qloss', 7)) + mat_deriv += eb_deriv.tolist() +################ + + ti_deriv = np.zeros((1, num_vars, num_fl + 3)) + for i in range(2): + ti_deriv[0, i + 2, 0] = ( + self.ddx_func(self.tiP_char_func, 'm', i + 2)) + ti_deriv[0, i + 2, 3:] = ( + self.ddx_func(self.tiP_char_func, 'fluid', i + 2)) + ti_deriv[0, 6, 0] = self.ddx_func(self.tiP_char_func, 'm', 6) + ti_deriv[0, 6, 3:] = self.ddx_func(self.tiP_char_func, 'fluid', 6) + + if self.P.is_var: + ti_deriv[0, 7 + self.P.var_pos, 0] = ( + self.ddx_func(self.tiP_char_func, 'P', 7)) + mat_deriv += ti_deriv.tolist() + + q1_deriv = np.zeros((1, num_vars, num_fl + 3)) + q1_deriv[0, 0, 0] = self.ddx_func(self.Q1_char_func, 'm', 0) + q1_deriv[0, 0, 2] = self.ddx_func(self.Q1_char_func, 'h', 0) + q1_deriv[0, 4, 2] = self.ddx_func(self.Q1_char_func, 'h', 4) + for i in range(2): + q1_deriv[0, i + 2, 0] = ( + self.ddx_func(self.Q1_char_func, 'm', i + 2)) + q1_deriv[0, i + 2, 3:] = ( + self.ddx_func(self.Q1_char_func, 'fluid', i + 2)) + q1_deriv[0, 6, 0] = self.ddx_func(self.Q1_char_func, 'm', 6) + q1_deriv[0, 6, 3:] = self.ddx_func(self.Q1_char_func, 'fluid', 6) + + if self.P.is_var: + q1_deriv[0, 7 + self.P.var_pos, 0] = ( + self.ddx_func(self.Q1_char_func, 'P', 7)) + mat_deriv += q1_deriv.tolist() + + q2_deriv = np.zeros((1, num_vars, num_fl + 3)) + q2_deriv[0, 1, 0] = self.ddx_func(self.Q2_char_func, 'm', 1) + q2_deriv[0, 1, 2] = self.ddx_func(self.Q2_char_func, 'h', 1) + q2_deriv[0, 5, 2] = self.ddx_func(self.Q2_char_func, 'h', 5) + for i in range(2): + q2_deriv[0, i + 2, 0] = ( + self.ddx_func(self.Q2_char_func, 'm', i + 2)) + q2_deriv[0, i + 2, 3:] = ( + self.ddx_func(self.Q2_char_func, 'fluid', i + 2)) + q2_deriv[0, 6, 0] = self.ddx_func(self.Q2_char_func, 'm', 6) + q2_deriv[0, 6, 3:] = self.ddx_func(self.Q2_char_func, 'fluid', 6) + + if self.P.is_var: + q2_deriv[0, 7 + self.P.var_pos, 0] = ( + self.ddx_func(self.Q2_char_func, 'P', 7)) + mat_deriv += q2_deriv.tolist() + + ql_deriv = np.zeros((1, num_vars, num_fl + 3)) + for i in range(2): + ql_deriv[0, i + 2, 0] = ( + self.ddx_func(self.Qloss_char_func, 'm', i + 2)) + ql_deriv[0, i + 2, 3:] = ( + self.ddx_func(self.Qloss_char_func, 'fluid', i + 2)) + ql_deriv[0, 6, 0] = self.ddx_func(self.Qloss_char_func, 'm', 6) + ql_deriv[0, 6, 3:] = self.ddx_func(self.Qloss_char_func, 'fluid', 6) + + if self.P.is_var: + ql_deriv[0, 7 + self.P.var_pos, 0] = ( + self.ddx_func(self.Qloss_char_func, 'P', 7)) + + if self.Qloss.is_var: + ql_deriv[0, 7 + self.Qloss.var_pos, 0] = ( + self.ddx_func(self.Qloss_char_func, 'Qloss', 7)) + + mat_deriv += ql_deriv.tolist() + if self.lamb.is_set: # derivatives for specified lambda - lamb_deriv = np.zeros((1, 7, num_fl + 3)) + lamb_deriv = np.zeros((1, num_vars, num_fl + 3)) for i in range(2): lamb_deriv[0, i + 2, 0] = ( self.ddx_func(self.lambda_func, 'm', i + 2)) @@ -4448,9 +4534,12 @@ def derivatives(self, nw): self.ddx_func(self.lambda_func, 'fluid', i + 2)) mat_deriv += lamb_deriv.tolist() +# np.set_printoptions(threshold=np.nan) +# print(np.asarray(mat_deriv)) + if self.ti.is_set: # derivatives for specified thermal input - ti_deriv = np.zeros((1, 7, num_fl + 3)) + ti_deriv = np.zeros((1, num_vars, num_fl + 3)) for i in range(2): ti_deriv[0, i + 2, 0] = self.ddx_func(self.ti_func, 'm', i + 2) ti_deriv[0, i + 2, 3:] = ( @@ -4460,19 +4549,19 @@ def derivatives(self, nw): mat_deriv += ti_deriv.tolist() if self.pr1.is_set: - pr1_deriv = np.zeros((1, 7, num_fl + 3)) + pr1_deriv = np.zeros((1, num_vars, num_fl + 3)) pr1_deriv[0, 0, 1] = self.pr1.val - pr1_deriv[0, 2, 1] = -1 + pr1_deriv[0, 4, 1] = -1 mat_deriv += pr1_deriv.tolist() if self.pr2.is_set: - pr2_deriv = np.zeros((1, 7, num_fl + 3)) + pr2_deriv = np.zeros((1, num_vars, num_fl + 3)) pr2_deriv[0, 1, 1] = self.pr2.val - pr2_deriv[0, 3, 1] = -1 + pr2_deriv[0, 5, 1] = -1 mat_deriv += pr2_deriv.tolist() if self.zeta1.is_set: - zeta1_deriv = np.zeros((1, 7, num_fl + 3)) + zeta1_deriv = np.zeros((1, num_vars, num_fl + 3)) zeta1_deriv[0, 0, 0] = self.ddx_func(self.zeta_func, 'm', 0) zeta1_deriv[0, 0, 1] = self.ddx_func(self.zeta_func, 'p', 0) zeta1_deriv[0, 0, 2] = self.ddx_func(self.zeta_func, 'h', 0) @@ -4481,7 +4570,7 @@ def derivatives(self, nw): mat_deriv += zeta1_deriv.tolist() if self.zeta2.is_set: - zeta2_deriv = np.zeros((1, 7, num_fl + 3)) + zeta2_deriv = np.zeros((1, num_vars, num_fl + 3)) zeta2_deriv[0, 1, 0] = self.ddx_func(self.zeta2_func, 'm', 1) zeta2_deriv[0, 1, 1] = self.ddx_func(self.zeta2_func, 'p', 1) zeta2_deriv[0, 1, 2] = self.ddx_func(self.zeta2_func, 'h', 1) @@ -4522,11 +4611,9 @@ def energy_balance(self): p_ref = 1e5 res = 0 - ti = 0 for i in self.inl[2:]: res += i.m.val_SI * ( i.h.val_SI - h_mix_pT([0, p_ref, 0, i.fluid.val], T_ref)) - ti += i.m.val_SI * i.fluid.val[self.fuel.val] * self.lhv for o in self.outl[2:]: dh = 0 @@ -4541,11 +4628,8 @@ def energy_balance(self): res -= o.m.val_SI * ( o.h.val_SI - h_mix_pT([0, p_ref, 0, o.fluid.val], T_ref) - dh) - ti -= o.m.val_SI * o.fluid.val[self.fuel.val] * self.lhv - res += ti - if not self.ti.is_set: - self.ti.val = ti + res += self.calc_ti() # cooling water for i in range(2): @@ -4678,12 +4762,12 @@ def tiP_char_func(self): """ - if self.P_ref.is_set is True: - expr = self.P.val / self.Pref.val + if self.P_ref.is_set: + expr = self.P.val / self.P_ref.val else: expr = 1 - return self.ti.val - self.tiP_char.func.f_x(expr) * self.P + return self.calc_ti() - self.tiP_char.func.f_x(expr) * self.P.val def Q1_char_func(self): r""" @@ -4705,13 +4789,13 @@ def Q1_char_func(self): i = self.inl[0] o = self.outl[0] - if self.P_ref.is_set is True: - expr = self.P.val / self.Pref.val + if self.P_ref.is_set: + expr = self.P.val / self.P_ref.val else: expr = 1 - return (self.ti.val * self.Q1_char.func.f_x(expr) - - self.ti_char.func.f_x(expr) * i.m.val_SI * ( + return (self.calc_ti() * self.Q1_char.func.f_x(expr) - + self.tiP_char.func.f_x(expr) * i.m.val_SI * ( o.h.val_SI - i.h.val_SI)) def Q2_char_func(self): @@ -4734,13 +4818,13 @@ def Q2_char_func(self): i = self.inl[1] o = self.outl[1] - if self.P_ref.is_set is True: - expr = self.P.val / self.Pref.val + if self.P_ref.is_set: + expr = self.P.val / self.P_ref.val else: expr = 1 - return (self.ti.val * self.Q2_char.func.f_x(expr) - - self.ti_char.func.f_x(expr) * i.m.val_SI * ( + return (self.calc_ti() * self.Q2_char.func.f_x(expr) - + self.tiP_char.func.f_x(expr) * i.m.val_SI * ( o.h.val_SI - i.h.val_SI)) def Qloss_char_func(self): @@ -4760,12 +4844,35 @@ def Qloss_char_func(self): """ - if self.P_ref.is_set is True: - expr = self.P.val / self.Pref.val + if self.P_ref.is_set: + expr = self.P.val / self.P_ref.val else: expr = 1 - return self.ti.val - self.ti_char.func.f_x(expr) * self.Qloss.val + return (self.calc_ti() * self.Qloss_char.func.f_x(expr) - + self.tiP_char.func.f_x(expr) * self.Qloss.val) + + def calc_ti(self): + r""" + calculates the thermal input of the cogeneration unit + + :returns: ti (*float*) - thermal input + + .. math:: + ti = LHV \cdot \left[\sum_i \left(\dot{m}_{in,i} \cdot x_{f,i} + \right) - \dot{m}_{out,3} \cdot x_{f,3} \right] + \; \forall i \in [3,4] + + """ + + m = 0 + for i in self.inl[2:]: + m += i.m.val_SI * i.fluid.val[self.fuel.val] + + for o in self.outl[2:]: + m -= o.m.val_SI * o.fluid.val[self.fuel.val] + + return m * self.lhv def initialise_fluids(self, nw): r""" @@ -4871,43 +4978,32 @@ def initialise_target(self, c, key): def calc_parameters(self, nw, mode): - self.ti.val = 0 - for i in self.inl: - self.ti.val += i.m.val_SI * i.fluid.val[self.fuel.val] * self.lhv + combustion_chamber.calc_parameters(self, nw, mode) - n_fuel = 0 - for i in self.inl: - n_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val] / - molar_masses[self.fuel.val]) + if mode == 'post' or (mode == 'pre' and 'Q1' in self.offdesign): + self.Q1.val = self.inl[0].m.val_SI * ( + self.outl[0].h.val_SI - self.inl[0].h.val_SI) - n_oxygen = 0 - for i in self.inl: - n_oxygen += (i.m.val_SI * i.fluid.val[self.o2] / - molar_masses[self.o2]) + if mode == 'post' or (mode == 'pre' and 'Q2' in self.offdesign): + self.Q2.val = self.inl[1].m.val_SI * ( + self.outl[1].h.val_SI - self.inl[1].h.val_SI) - if mode == 'post': - if not self.lamb.is_set: - self.lamb.val = n_oxygen / ( - n_fuel * (self.n['C'] + self.n['H'] / 4)) + if mode == 'post' and not self.P_ref.is_set: + self.P_ref.is_set = self.P.val - S = 0 - T_ref = 500 - p_ref = 1e5 + if self.P_ref.is_set: + expr = self.P.val / self.P_ref.val + else: + expr = 1 - for i in self.inl: - S -= i.m.val_SI * (s_mix_ph(i.to_flow()) - - s_mix_pT([0, p_ref, 0, i.fluid.val], T_ref)) + if mode == 'post' or (mode == 'pre' and 'Qloss' in self.offdesign): + self.Qloss.val = self.ti.val * (self.Qloss_char.func.f_x(expr) / + self.tiP_char.func.f_x(expr)) - for o in self.outl: - S += o.m.val_SI * (s_mix_ph(o.to_flow()) - - s_mix_pT([0, p_ref, 0, o.fluid.val], T_ref)) + if mode == 'post' or (mode == 'pre' and 'P' in self.offdesign): + self.P.val = self.ti.val / self.tiP_char.func.f_x(expr) - self.S.val = S - if mode == 'pre': - if 'lamb' in self.offdesign: - self.lamb.val = n_oxygen / (n_fuel * - (self.n['C'] + self.n['H'] / 4)) # %% From a126bbcd205ad4acb7a93bcf8814363a2c504761 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Sun, 21 Oct 2018 19:17:11 +0200 Subject: [PATCH 61/80] fixed starting value handling for fluids --- tespy/connections.py | 1 - tespy/networks.py | 11 +++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/tespy/connections.py b/tespy/connections.py index 6e4d84a2d..30ac3efdd 100644 --- a/tespy/connections.py +++ b/tespy/connections.py @@ -164,7 +164,6 @@ def set_attr(self, **kwargs): # specified parameters else: self.get_attr(key).set_attr(val=kwargs[key].copy()) - self.get_attr(key).set_attr(val0=kwargs[key].copy()) for f in kwargs[key]: kwargs[key][f] = True self.get_attr(key).set_attr(val_set=kwargs[key]) diff --git a/tespy/networks.py b/tespy/networks.py index 21cc97c87..a3c06e79d 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -605,19 +605,14 @@ def init_fluids(self): for fluid in self.fluids: - if fluid in tmp.keys(): + if fluid in tmp.keys() and fluid in tmp_set.keys(): # if fluid in keys and is_set c.fluid.val[fluid] = tmp[fluid] c.fluid.val0[fluid] = tmp[fluid] - - if fluid in tmp_set.keys(): - c.fluid.val_set[fluid] = tmp_set[fluid] - # if fluid in keys and not is_set - else: - c.fluid.val_set[fluid] = False + c.fluid.val_set[fluid] = tmp_set[fluid] # if there is a starting value - if fluid in tmp0.keys(): + elif fluid in tmp0.keys(): if fluid in tmp_set.keys(): if not tmp_set[fluid]: c.fluid.val[fluid] = tmp0[fluid] From 9ef8ee17ed50fedf45ec7a314499226c9706b98f Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Mon, 22 Oct 2018 11:44:53 +0200 Subject: [PATCH 62/80] modified characteristic lines for cogeneration unit, renamed plots --- tespy/components/characteristics.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tespy/components/characteristics.py b/tespy/components/characteristics.py index ac2032615..49ebf9dfe 100644 --- a/tespy/components/characteristics.py +++ b/tespy/components/characteristics.py @@ -116,7 +116,7 @@ def default(self, key): .. math:: X = TI = \dot{m}_f \cdot LHV - .. image:: _images/chp_TI.svg + .. image:: _images/TI.svg :scale: 100 % :alt: alternative text :align: center @@ -126,7 +126,7 @@ def default(self, key): .. math:: X = \dot{Q}_1 = \dot{m}_1 \cdot \left( h_{out,1} - h_{in,1} \right) - .. image:: _images/chp_Q1.svg + .. image:: _images/Q1.svg :scale: 100 % :alt: alternative text :align: center @@ -136,7 +136,7 @@ def default(self, key): .. math:: X = \dot{Q}_2 = \dot{m}_2 \cdot \left( h_{out,2} - h_{in,2} \right) - .. image:: _images/chp_Q2.svg + .. image:: _images/Q2.svg :scale: 100 % :alt: alternative text :align: center @@ -146,7 +146,7 @@ def default(self, key): .. math:: X = \dot{Q}_{loss} - .. image:: _images/chp_Q_loss.svg + .. image:: _images/QLOSS.svg :scale: 100 % :alt: alternative text :align: center @@ -221,17 +221,17 @@ def default(self, key): elif self.comp == 'cogeneration unit': - x['TI'] = np.array([0.50, 0.71, 0.90, 1.00]) - y['TI'] = np.array([2.20, 2.15, 2.10, 2.05]) + x['TI'] = np.array([0.50, 0.75, 0.90, 1.00]) + y['TI'] = np.array([2.50, 2.33, 2.27, 2.25]) x['Q1'] = np.array([0.660, 0.770, 0.880, 0.990, 1.100]) - y['Q1'] = np.array([0.244, 0.233, 0.222, 0.211, 0.200]) + y['Q1'] = np.array([0.215, 0.197, 0.185, 0.175, 0.168]) x['Q2'] = np.array([0.660, 0.770, 0.880, 0.990, 1.100]) - y['Q2'] = np.array([0.246, 0.235, 0.224, 0.213, 0.202]) + y['Q2'] = np.array([0.215, 0.197, 0.185, 0.175, 0.168]) - x['QLOSS'] = np.array([0.50, 0.75, 0.90, 1.00]) - y['QLOSS'] = np.array([0.06, 0.08, 0.09, 0.10]) + x['QLOSS'] = np.array([0.50, 0.7500, 0.90, 1.000]) + y['QLOSS'] = np.array([0.32, 0.3067, 0.30, 0.295]) elif self.comp == 'heat exchanger' or self.comp == 'desuperheater': From d0a295046be113b87b0bf235970885f3beb1d308 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Mon, 22 Oct 2018 11:45:19 +0200 Subject: [PATCH 63/80] added plots for characteristic lines of cogeneration unit --- doc/api/_images/Q1.svg | 896 ++++++++++++++++++++++++++++++ doc/api/_images/Q2.svg | 896 ++++++++++++++++++++++++++++++ doc/api/_images/QLOSS.svg | 1097 +++++++++++++++++++++++++++++++++++++ doc/api/_images/TI.svg | 1056 +++++++++++++++++++++++++++++++++++ 4 files changed, 3945 insertions(+) create mode 100644 doc/api/_images/Q1.svg create mode 100644 doc/api/_images/Q2.svg create mode 100644 doc/api/_images/QLOSS.svg create mode 100644 doc/api/_images/TI.svg diff --git a/doc/api/_images/Q1.svg b/doc/api/_images/Q1.svg new file mode 100644 index 000000000..0c3ffaa56 --- /dev/null +++ b/doc/api/_images/Q1.svg @@ -0,0 +1,896 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/api/_images/Q2.svg b/doc/api/_images/Q2.svg new file mode 100644 index 000000000..5b2e880dd --- /dev/null +++ b/doc/api/_images/Q2.svg @@ -0,0 +1,896 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/api/_images/QLOSS.svg b/doc/api/_images/QLOSS.svg new file mode 100644 index 000000000..452c1b9c0 --- /dev/null +++ b/doc/api/_images/QLOSS.svg @@ -0,0 +1,1097 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/api/_images/TI.svg b/doc/api/_images/TI.svg new file mode 100644 index 000000000..8de8f4f21 --- /dev/null +++ b/doc/api/_images/TI.svg @@ -0,0 +1,1056 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7850fe9f33788f90809f1a955cd82b3e16b6083f Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 23 Oct 2018 15:34:47 +0200 Subject: [PATCH 64/80] modifications to handle bus characteristics --- tespy/network_reader.py | 44 ++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/tespy/network_reader.py b/tespy/network_reader.py index 535298280..efced70e3 100644 --- a/tespy/network_reader.py +++ b/tespy/network_reader.py @@ -6,7 +6,7 @@ """ import pandas as pd -from tespy import cmp, con, nwk, hlp +from tespy import cmp, con, nwk, hlp, cmp_char import os import ast @@ -41,6 +41,7 @@ def load_nwk(path): chars = pd.read_csv(path + '/comps/char.csv', sep=';', decimal='.', converters={'x': ast.literal_eval, 'y': ast.literal_eval}) + chars['char'] = chars.apply(construct_chars, axis=1) # load components comps = pd.DataFrame() @@ -52,12 +53,15 @@ def load_nwk(path): converters={'design': ast.literal_eval, 'offdesign': ast.literal_eval, 'busses': ast.literal_eval, - 'bus_factors': ast.literal_eval}) + 'bus_param': ast.literal_eval, + 'bus_P_ref': ast.literal_eval, + 'bus_char': ast.literal_eval}) # create components df['instance'] = df.apply(construct_comps, axis=1, args=(chars,)) comps = pd.concat((comps, df[['instance', 'label', 'busses', - 'bus_factors']]), + 'bus_param', 'bus_P_ref', + 'bus_char']]), axis=0) comps = comps.set_index('label') @@ -91,7 +95,7 @@ def load_nwk(path): busses['instance'] = busses.apply(construct_busses, axis=1) # add components to busses - comps.apply(busses_add_comps, axis=1, args=(busses,)) + comps.apply(busses_add_comps, axis=1, args=(busses, chars,)) # add busses to network for b in busses['instance']: @@ -128,7 +132,7 @@ def construct_comps(c, *args): for key in ['mode', 'design', 'offdesign']: kwargs[key] = c[key] - for key, value in instance.attr_prop().items(): + for key, value in instance.attr().items(): if key in c: # component parameters if isinstance(value, hlp.dc_cp): @@ -184,6 +188,22 @@ def construct_network(path): return nw +# %% create network object + + +def construct_chars(c): + """ + creates TESPy characteristic functions + + :param c: connection information + :type c: pandas.core.series.Series + :returns: instance (*tespy.components.characteristics*) - TESPy + characteristics object + """ + + char = cmp_char.characteristics(x=c.x, y=c.y) + return char + # %% create connections @@ -284,7 +304,7 @@ def construct_busses(c, *args): # set up bus with label and specify value for power b = con.bus(c.label, P=c.P) - b.P_set = c.P_set + b.P.val_set = c.P_set return b # %% add components to busses @@ -306,8 +326,14 @@ def busses_add_comps(c, *args): i = 0 for b in c.busses: - f = c.bus_factors[i] - # add component with corresponding factor to bus + p, P_ref, char = c.bus_param[i], c.bus_P_ref[i], c.bus_char[i] + + values = char == args[1]['id'] + char = args[1]['char'][values[values == True].index[0]] + + # add component with corresponding details to bus args[0].instance[b == args[0]['id'] - ].values[0].add_comps([c.instance, f]) + ].values[0].add_comps({'c': c.instance, + 'p': p, 'P_ref': P_ref, + 'char': char}) i += 1 From a096e0f07294832d1619764ec51211f00c8e0fa9 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 23 Oct 2018 15:36:36 +0200 Subject: [PATCH 65/80] adjusting bus_functions, renaming for better comprehensibility, finished cogeneration unit --- tespy/components/components.py | 771 +++++++++++++++++++-------------- 1 file changed, 455 insertions(+), 316 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index b2c0771e0..b9baf4015 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -294,10 +294,10 @@ def equations(self): def derivatives(self, nw): return [] - def bus_func(self): + def bus_func(self, bus): return 0 - def bus_deriv(self): + def bus_deriv(self, bus): return def initialise_source(self, c, key): @@ -662,7 +662,7 @@ def mass_flow_deriv(self): return None # %% - def ddx_func(self, func, dx, pos): + def ddx_func(self, func, dx, pos, **kwargs): r""" calculates derivative of the function func to dx at components inlet or outlet in position pos @@ -702,12 +702,12 @@ def ddx_func(self, func, dx, pos): (self.inl + self.outl)[pos].fluid.val[f] += df else: (self.inl + self.outl)[pos].fluid.val[f] = 1 - exp += func() + exp += func(**kwargs) if (self.inl + self.outl)[pos].fluid.val[f] - 2 * df >= 0: (self.inl + self.outl)[pos].fluid.val[f] -= 2 * df else: (self.inl + self.outl)[pos].fluid.val[f] = 0 - exp -= func() + exp -= func(**kwargs) (self.inl + self.outl)[pos].fluid.val[f] = val deriv += [exp / (2 * (dm + dp + dh + df))] @@ -717,12 +717,12 @@ def ddx_func(self, func, dx, pos): (self.inl + self.outl)[pos].m.val_SI += dm (self.inl + self.outl)[pos].p.val_SI += dp (self.inl + self.outl)[pos].h.val_SI += dh - exp += func() + exp += func(**kwargs) (self.inl + self.outl)[pos].m.val_SI -= 2 * dm (self.inl + self.outl)[pos].p.val_SI -= 2 * dp (self.inl + self.outl)[pos].h.val_SI -= 2 * dh - exp -= func() + exp -= func(**kwargs) deriv = exp / (2 * (dm + dp + dh + df)) (self.inl + self.outl)[pos].m.val_SI += dm @@ -1022,8 +1022,8 @@ def h_os(self, mode): :returns: h (*float*) - enthalpy after isentropic state change """ if mode == 'pre': - i = self.i0 - o = self.o0 + i = self.i_ref + o = self.o_ref else: i = self.inl[0].to_flow() o = self.outl[0].to_flow() @@ -1043,7 +1043,7 @@ def char_func(self): def char_deriv(self): raise MyComponentError('Function not available.') - def bus_func(self): + def bus_func(self, bus): r""" function for use on busses @@ -1056,21 +1056,24 @@ def bus_func(self): """ i = self.inl[0].to_flow() o = self.outl[0].to_flow() - return i[0] * (o[2] - i[2]) + val = i[0] * (o[2] - i[2]) + if np.isnan(bus.P_ref): + expr = 1 + else: + expr = val / bus.P_ref + return val * bus.char.f_x(expr) - def bus_deriv(self): + def bus_deriv(self, bus): r""" calculate matrix of partial derivatives towards mass flow and enthalpy for bus function :returns: mat_deriv (*list*) - matrix of partial derivatives """ - i = self.inl[0].to_flow() - o = self.outl[0].to_flow() deriv = np.zeros((1, 2, len(self.inl[0].fluid.val) + 3)) - deriv[0, 0, 0] = o[2] - i[2] - deriv[0, 0, 2] = - i[0] - deriv[0, 1, 2] = i[0] + deriv[0, 0, 0] = self.ddx_func(self.bus_func, 'm', 0, bus=bus) + deriv[0, 0, 2] = self.ddx_func(self.bus_func, 'h', 0, bus=bus) + deriv[0, 1, 2] = self.ddx_func(self.bus_func, 'h', 1, bus=bus) return deriv def calc_parameters(self, nw, mode): @@ -1084,26 +1087,27 @@ def calc_parameters(self, nw, mode): **preprocessing** - - set references for inlet :code:`self.i0` and outlet - :code:`self.o0` flows + - set references for inlet :code:`self.i_ref` and outlet + :code:`self.o_ref` flows - set attribute for isentropic enthalpy difference - :code:`self.dh_s0` at reference + :code:`self.dh_s_ref` at reference """ + i, o = self.inl[0].to_flow(), self.outl[0].to_flow() + if (mode == 'pre' and 'P' in self.offdesign) or mode == 'post': - self.P.val = self.inl[0].m.val_SI * ( - self.outl[0].h.val_SI - self.inl[0].h.val_SI) + self.P.val = i[0] * (o[2] - i[2]) if (mode == 'pre' and 'pr' in self.offdesign) or mode == 'post': - self.pr.val = self.outl[0].p.val_SI / self.inl[0].p.val_SI + self.pr.val = o[1] / i[1] if mode == 'pre': - self.i0 = self.inl[0].to_flow() - self.o0 = self.outl[0].to_flow() - self.i0[3] = self.i0[3].copy() - self.o0[3] = self.i0[3].copy() - self.dh_s0 = (self.h_os(mode) - self.i0[2]) + self.i_ref = i + self.o_ref = o + self.i_ref[3] = i[3].copy() + self.o_ref[3] = o[3].copy() + self.dh_s_ref = (self.h_os(mode) - self.i_ref[2]) if mode == 'post': self.Sirr.val = self.inl[0].m.val_SI * ( @@ -1257,8 +1261,8 @@ def char_func(self): """ i = self.inl[0].to_flow() o = self.outl[0].to_flow() - return np.array([((o[2] - i[2]) * self.dh_s0 / - (self.o0[2] - self.i0[2]) * + return np.array([((o[2] - i[2]) * self.dh_s_ref / + (self.o_ref[2] - self.i_ref[2]) * self.eta_s_char.func.f_x(i[0] * v_mix_ph(i)) - (self.h_os('post') - i[2]))]) @@ -1455,12 +1459,17 @@ def calc_parameters(self, nw, mode): if (mode == 'pre' and 'eta_s_char' in self.offdesign): if nw.compinfo: print('Creating characteristics for component ' + self.label) - v_opt = (self.i0[0] * - (v_mix_ph(self.i0) + v_mix_ph(self.o0)) / 2) - H_opt = ((self.o0[1] - self.i0[1]) / - (9.81 * 2 / (v_mix_ph(self.i0) + v_mix_ph(self.o0)))) + v_opt = (self.i_ref[0] * ( + v_mix_ph(self.i_ref) + v_mix_ph(self.o_ref)) / 2) + H_opt = ((self.o_ref[1] - self.i_ref[1]) / (9.81 * 2 / ( + v_mix_ph(self.i_ref) + v_mix_ph(self.o_ref)))) self.eta_s_char.func = cmp_char.pump(v_opt, H_opt) + if mode == 'post' and nw.mode == 'offdesign': + del self.i_ref + del self.o_ref + del self.dh_s_ref + # %% @@ -1641,14 +1650,14 @@ def char_func(self): """ i = self.inl[0].to_flow() o = self.outl[0].to_flow() - x = math.sqrt(T_mix_ph(self.i0)) / math.sqrt(T_mix_ph(i)) - y = (i[0] * self.i0[1]) / (self.i0[0] * i[1] * x) + x = math.sqrt(T_mix_ph(self.i_ref)) / math.sqrt(T_mix_ph(i)) + y = (i[0] * self.i_ref[1]) / (self.i_ref[0] * i[1] * x) pr, eta = self.char_map.func.get_pr_eta(x, y, self.igva.val) - z1 = o[1] * self.i0[1] / (i[1] * self.o0[1]) - pr + z1 = o[1] * self.i_ref[1] / (i[1] * self.o_ref[1]) - pr z2 = ((self.h_os('post') - i[2]) / (o[2] - i[2])) / ( - self.dh_s0 / (self.o0[2] - self.i0[2])) - eta + self.dh_s_ref / (self.o_ref[2] - self.i_ref[2])) - eta return np.array([z1, z2]) @@ -1787,8 +1796,8 @@ def calc_parameters(self, nw, mode): if nw.compwarn: i = self.inl[0].to_flow() - x = math.sqrt(T_mix_ph(self.i0)) / math.sqrt(T_mix_ph(i)) - y = (i[0] * self.i0[1]) / (self.i0[0] * i[1] * x) + x = math.sqrt(T_mix_ph(self.i_ref)) / math.sqrt(T_mix_ph(i)) + y = (i[0] * self.i_ref[1]) / (self.i_ref[0] * i[1] * x) msg = self.char_map.func.get_bound_errors(x, y, self.igva.val) if msg is not None: @@ -1806,6 +1815,11 @@ def calc_parameters(self, nw, mode): print(msg) nw.errors += [self] + if mode == 'post' and nw.mode == 'offdesign': + del self.i_ref + del self.o_ref + del self.dh_s_ref + # %% @@ -1960,28 +1974,22 @@ def cone_func(self): :returns: val (*float*) - residual value of equation .. math:: - 0 = \frac{\dot{m}_{in,0} \cdot p_{in}}{p_{in,0}} \cdot - \sqrt{\frac{p_{in,0} \cdot v_{in}}{p_{in} \cdot v_{in,0}}} \cdot - \sqrt{\frac{1 - \left(\frac{p_{out}}{p_{in}} \right)^{2}} - {1 - \left(\frac{p_{out,0}}{p_{in,0}} \right)^{2}}} - \dot{m}_{in} + 0 = \frac{\dot{m}_{in,ref} \cdot p_{in}}{p_{in,ref}} \cdot + \sqrt{\frac{p_{in,ref} \cdot v_{in}}{p_{in} \cdot v_{in,ref}}} + \cdot \sqrt{\frac{1 - \left(\frac{p_{out}}{p_{in}} \right)^{2}} + {1 - \left(\frac{p_{out,ref}}{p_{in,ref}} \right)^{2}}} - + \dot{m}_{in} """ i = self.inl[0].to_flow() o = self.outl[0].to_flow() n = 1 - return (self.i0[0] * i[1] / self.i0[1] * math.sqrt( - self.i0[1] * v_mix_ph(self.i0) / (i[1] * v_mix_ph(i))) * - math.sqrt(abs((1 - (o[1] / i[1]) ** ((n + 1) / n)) / - (1 - (self.o0[1] / self.i0[1]) ** ((n + 1) / n)))) - + return (self.i_ref[0] * i[1] / self.i_ref[1] * math.sqrt( + self.i_ref[1] * v_mix_ph(self.i_ref) / + (i[1] * v_mix_ph(i))) * math.sqrt( + abs((1 - (o[1] / i[1]) ** ((n + 1) / n)) / (1 - + (self.o_ref[1] / self.i_ref[1]) ** ((n + 1) / n)))) - i[0]) - # Formulation with T0 / T -# n = 1 -# return ( -# self.i1_0[0] * i1[1] / self.i1_0[1] * math.sqrt( -# T_mix_ph(self.i1_0) / T_mix_ph(i1)) * -# math.sqrt((1 - (o1[1] / i1[1]) ** ((n + 1) / n)) / -# (1 - (self.o1_0[1] / self.i1_0[1]) ** ((n + 1) / n))) - i1[0]) - def char_func(self): r""" equation for turbine characteristics @@ -2003,21 +2011,22 @@ def char_func(self): o = self.outl[0].to_flow() if self.eta_s_char.param == 'dh_s': - expr = math.sqrt(self.dh_s0 / (self.h_os('post') - i[2])) + expr = math.sqrt(self.dh_s_ref / (self.h_os('post') - i[2])) elif self.eta_s_char.param == 'm': - expr = i[0] / self.i0[0] + expr = i[0] / self.i_ref[0] elif self.eta_s_char.param == 'v': - expr = i[0] * v_mix_ph(i) / (self.i0[0] * v_mix_ph(self.i0)) + expr = i[0] * v_mix_ph(i) / ( + self.i_ref[0] * v_mix_ph(self.i_ref)) elif self.eta_s_char.param == 'pr': - expr = (o[1] * self.i0[1]) / (i[1] * self.o0[1]) + expr = (o[1] * self.i_ref[1]) / (i[1] * self.o_ref[1]) else: msg = ('Please choose the parameter, you want to link the ' 'isentropic efficiency to.') raise MyComponentError(msg) - return np.array([(-(o[2] - i[2]) + (self.o0[2] - self.i0[2]) / - self.dh_s0 * self.eta_s_char.func.f_x(expr) * - (self.h_os('post') - i[2]))]) + return np.array([-(o[2] - i[2]) + (self.o_ref[2] - self.i_ref[2]) / + self.dh_s_ref * self.eta_s_char.func.f_x(expr) * + (self.h_os('post') - i[2])]) def char_deriv(self): r""" @@ -2127,6 +2136,11 @@ def calc_parameters(self, nw, mode): print(msg) nw.errors += [self] + if mode == 'post' and nw.mode == 'offdesign': + del self.i_ref + del self.o_ref + del self.dh_s_ref + # %% @@ -3180,7 +3194,7 @@ def ti_func(self): return self.ti.val - self.calc_ti() - def bus_func(self): + def bus_func(self, bus): r""" function for use on busses @@ -3191,10 +3205,14 @@ def bus_func(self): val = ti """ + val = self.calc_ti() + if np.isnan(bus.P_ref): + expr = 1 + else: + expr = val / bus.P_ref + return val * bus.char.f_x(expr) - return self.calc_ti() - - def bus_deriv(self): + def bus_deriv(self, bus): r""" calculate matrix of partial derivatives towards mass flow and fluid composition for bus @@ -3204,11 +3222,11 @@ def bus_deriv(self): """ deriv = np.zeros((1, 3, len(self.inl[0].fluid.val) + 3)) for i in range(2): - deriv[0, i, 0] = self.ddx_func(self.bus_func, 'm', i) - deriv[0, i, 3:] = self.ddx_func(self.bus_func, 'fluid', i) + deriv[0, i, 0] = self.ddx_func(self.bus_func, 'm', i, bus=bus) + deriv[0, i, 3:] = self.ddx_func(self.bus_func, 'fluid', i, bus=bus) - deriv[0, 2, 0] = self.ddx_func(self.bus_func, 'm', 2) - deriv[0, 2, 3:] = self.ddx_func(self.bus_func, 'fluid', 2) + deriv[0, 2, 0] = self.ddx_func(self.bus_func, 'm', 2, bus=bus) + deriv[0, 2, 3:] = self.ddx_func(self.bus_func, 'fluid', 2, bus=bus) return deriv def drb_dx(self, dx, pos, fluid): @@ -3416,7 +3434,7 @@ def convergence_check(self, nw): if o.h.val_SI < 7.5e5 and not o.h.val_set: o.h.val_SI = 1e6 - if self.lamb.val < 2: + if self.lamb.val < 2 and not self.lamb.is_set: for i in inl: if i.fluid.val[self.fuel.val] > 0.75 and not i.m.val_set: i.m.val_SI = 0.01 @@ -4003,13 +4021,11 @@ def energy_balance(self): for i in self.inl: res += i.m.val_SI * ( i.h.val_SI - h_mix_pT([0, p_ref, 0, i.fluid.val], T_ref)) - res += i.m.val_SI * i.fluid.val[fuel] * self.lhv for o in self.outl: res -= o.m.val_SI * ( o.h.val_SI - h_mix_pT([0, p_ref, 0, o.fluid.val], T_ref)) - res -= o.m.val_SI * o.fluid.val[fuel] * self.lhv - return res + return res + self.calc_ti() def lambda_func(self): r""" @@ -4046,38 +4062,32 @@ def ti_func(self): 0 = ti - \dot{m}_f \cdot LHV """ - fuel = 'TESPy::' + self.fuel_alias.val - - m_fuel = 0 - for i in self.inl: - m_fuel += (i.m.val_SI * i.fluid.val[fuel]) - - for o in self.outl: - m_fuel -= (o.m.val_SI * o.fluid.val[fuel]) - return (self.ti.val - m_fuel * self.lhv) + return self.ti.val - self.calc_ti() - def bus_func(self): + def calc_ti(self): r""" - function for use on busses + calculates the thermal input of the combustion chamber - :returns: val (*float*) - residual value of equation + :returns: ti (*float*) - thermal input .. math:: + ti = LHV \cdot \left[\sum_i \left(\dot{m}_{in,i} \cdot x_{f,i} + \right) - \dot{m}_{out,1} \cdot x_{f,1} \right] + \; \forall i \in [1,2] - val = \dot{m}_{fuel} \cdot LHV """ fuel = 'TESPy::' + self.fuel_alias.val - m_fuel = 0 + m = 0 for i in self.inl: - m_fuel += (i.m.val_SI * i.fluid.val[fuel]) + m += (i.m.val_SI * i.fluid.val[fuel]) for o in self.outl: - m_fuel -= (o.m.val_SI * o.fluid.val[fuel]) + m -= (o.m.val_SI * o.fluid.val[fuel]) - return m_fuel * self.lhv + return m * self.lhv def initialise_fluids(self, nw): r""" @@ -4230,15 +4240,23 @@ class cogeneration_unit(combustion_chamber): **characteristic lines** - - ti_char: characteristic line for fuel input - - Q1_char: characteristic line for heat output 1 - - Q2_char: characteristic line for heat output 2 - - Qloss_char: characteristic line for heat loss + - tiP_char: characteristic line linking fuel input to power output + - Q1_char: characteristic line linking heat output 1 to power output + - Q2_char: characteristic line linking heat output 2 to power output + - Qloss_char: characteristic line linking heat loss to power output **equations** see :func:`tespy.components.components.cogeneration_unit.equations` + **default design parameters** + + - pr1, pr2 + + **default offdesign parameters** + + - zeta1, zeta2, P_ref + **available fuels** - methane @@ -4265,7 +4283,7 @@ def attr(self): return {'fuel': dc_cp(printout=False), 'lamb': dc_cp(), 'ti': dc_cp(), 'P': dc_cp(val=1e6, d=1, val_min=1), 'P_ref': dc_cp(), 'Q1': dc_cp(), 'Q2': dc_cp(), - 'Qloss': dc_cp(val=1e4, d=1, val_min=1), + 'Qloss': dc_cp(val=1e5, d=1, val_min=1), 'pr1': dc_cp(), 'pr2': dc_cp(), 'zeta1': dc_cp(), 'zeta2': dc_cp(), 'tiP_char': dc_cc(method='TI'), @@ -4274,6 +4292,12 @@ def attr(self): 'Qloss_char': dc_cc(method='QLOSS'), 'S': dc_cp()} + def default_design(self): + return ['pr1', 'pr2'] + + def default_offdesign(self): + return ['zeta1', 'zeta2', 'P_ref'] + def inlets(self): return ['in1', 'in2', 'in3', 'in4'] @@ -4326,6 +4350,9 @@ def equations(self): - :func:`tespy.components.components.cogeneration_unit.lambda_func` - :func:`tespy.components.components.cogeneration_unit.ti_func` + - :func:`tespy.components.components.cogeneration_unit.Q1_func` + - :func:`tespy.components.components.cogeneration_unit.Q2_func` + .. math:: 0 = p_{1,in} \cdot pr1 - p_{1,out}\\ @@ -4334,9 +4361,6 @@ def equations(self): - :func:`tespy.components.components.component.zeta_func` - :func:`tespy.components.components.component.zeta2_func` - - :func:`tespy.components.components.cogeneration_unit.power_func` - - :func:`tespy.components.components.cogeneration_unit.heat_func` - """ vec_res = [] @@ -4398,25 +4422,32 @@ def derivatives(self, nw): num_vars = 7 + self.num_c_vars mat_deriv = [] + ###################################################################### + # derivatives for reaction balance - j = 0 fl_deriv = np.zeros((num_fl, num_vars, num_fl + 3)) + j = 0 for fluid in nw.fluids: + # fresh air and fuel inlets for i in range(2): fl_deriv[j, i + 2, 0] = self.drb_dx('m', i + 2, fluid) fl_deriv[j, i + 2, 3:] = self.drb_dx('fluid', i + 2, fluid) + # combustion outlet fl_deriv[j, 6, 0] = self.drb_dx('m', 6, fluid) fl_deriv[j, 6, 3:] = self.drb_dx('fluid', 6, fluid) j += 1 mat_deriv += fl_deriv.tolist() - # derivatives for fluid balances (except combustion) + ###################################################################### + + # derivatives for cooling water fluid composition and mass flow mat_deriv += self.fluid_deriv() - # derivatives for mass balance mat_deriv += self.mass_flow_deriv() + ###################################################################### + # derivatives for pressure equations p_deriv = np.zeros((2, num_vars, num_fl + 3)) for k in range(2): @@ -4425,104 +4456,120 @@ def derivatives(self, nw): p_deriv[1][3][1] = -1 mat_deriv += p_deriv.tolist() + ###################################################################### + # derivatives for energy balance eb_deriv = np.zeros((1, num_vars, num_fl + 3)) + # mass flow cooling water for i in [0, 1]: eb_deriv[0, i, 0] = -(self.outl[i].h.val_SI - self.inl[i].h.val_SI) + # mass flow and pressure for combustion reaction for i in [2, 3, 6]: eb_deriv[0, i, 0] = self.ddx_func(self.energy_balance, 'm', i) eb_deriv[0, i, 1] = self.ddx_func(self.energy_balance, 'p', i) + # enthalpy for i in range(4): eb_deriv[0, i, 2] = self.inl[i].m.val_SI for i in range(3): eb_deriv[0, i + 4, 2] = -self.outl[i].m.val_SI + # fluid composition pos = 3 + nw.fluids.index(self.fuel.val) eb_deriv[0, 2, pos] = self.inl[2].m.val_SI * self.lhv eb_deriv[0, 3, pos] = self.inl[3].m.val_SI * self.lhv eb_deriv[0, 6, pos] = -self.outl[2].m.val_SI * self.lhv + # power and heat loss if self.P.is_var: eb_deriv[0, 7 + self.P.var_pos, 0] = ( self.ddx_func(self.energy_balance, 'P', 7)) - if self.Qloss.is_var: eb_deriv[0, 7 + self.Qloss.var_pos, 0] = ( self.ddx_func(self.energy_balance, 'Qloss', 7)) - mat_deriv += eb_deriv.tolist() -################ + ###################################################################### - ti_deriv = np.zeros((1, num_vars, num_fl + 3)) + # derivatives for thermal input to power charactersitics + tiP_deriv = np.zeros((1, num_vars, num_fl + 3)) for i in range(2): - ti_deriv[0, i + 2, 0] = ( + tiP_deriv[0, i + 2, 0] = ( self.ddx_func(self.tiP_char_func, 'm', i + 2)) - ti_deriv[0, i + 2, 3:] = ( + tiP_deriv[0, i + 2, 3:] = ( self.ddx_func(self.tiP_char_func, 'fluid', i + 2)) - ti_deriv[0, 6, 0] = self.ddx_func(self.tiP_char_func, 'm', 6) - ti_deriv[0, 6, 3:] = self.ddx_func(self.tiP_char_func, 'fluid', 6) + + tiP_deriv[0, 6, 0] = self.ddx_func(self.tiP_char_func, 'm', 6) + tiP_deriv[0, 6, 3:] = self.ddx_func(self.tiP_char_func, 'fluid', 6) if self.P.is_var: - ti_deriv[0, 7 + self.P.var_pos, 0] = ( + tiP_deriv[0, 7 + self.P.var_pos, 0] = ( self.ddx_func(self.tiP_char_func, 'P', 7)) - mat_deriv += ti_deriv.tolist() + mat_deriv += tiP_deriv.tolist() - q1_deriv = np.zeros((1, num_vars, num_fl + 3)) - q1_deriv[0, 0, 0] = self.ddx_func(self.Q1_char_func, 'm', 0) - q1_deriv[0, 0, 2] = self.ddx_func(self.Q1_char_func, 'h', 0) - q1_deriv[0, 4, 2] = self.ddx_func(self.Q1_char_func, 'h', 4) + ###################################################################### + + # derivatives for heat output 1 to power charactersitics + Q1_deriv = np.zeros((1, num_vars, num_fl + 3)) + Q1_deriv[0, 0, 0] = self.ddx_func(self.Q1_char_func, 'm', 0) + Q1_deriv[0, 0, 2] = self.ddx_func(self.Q1_char_func, 'h', 0) + Q1_deriv[0, 4, 2] = self.ddx_func(self.Q1_char_func, 'h', 4) for i in range(2): - q1_deriv[0, i + 2, 0] = ( + Q1_deriv[0, i + 2, 0] = ( self.ddx_func(self.Q1_char_func, 'm', i + 2)) - q1_deriv[0, i + 2, 3:] = ( + Q1_deriv[0, i + 2, 3:] = ( self.ddx_func(self.Q1_char_func, 'fluid', i + 2)) - q1_deriv[0, 6, 0] = self.ddx_func(self.Q1_char_func, 'm', 6) - q1_deriv[0, 6, 3:] = self.ddx_func(self.Q1_char_func, 'fluid', 6) + Q1_deriv[0, 6, 0] = self.ddx_func(self.Q1_char_func, 'm', 6) + Q1_deriv[0, 6, 3:] = self.ddx_func(self.Q1_char_func, 'fluid', 6) if self.P.is_var: - q1_deriv[0, 7 + self.P.var_pos, 0] = ( + Q1_deriv[0, 7 + self.P.var_pos, 0] = ( self.ddx_func(self.Q1_char_func, 'P', 7)) - mat_deriv += q1_deriv.tolist() + mat_deriv += Q1_deriv.tolist() + + ###################################################################### - q2_deriv = np.zeros((1, num_vars, num_fl + 3)) - q2_deriv[0, 1, 0] = self.ddx_func(self.Q2_char_func, 'm', 1) - q2_deriv[0, 1, 2] = self.ddx_func(self.Q2_char_func, 'h', 1) - q2_deriv[0, 5, 2] = self.ddx_func(self.Q2_char_func, 'h', 5) + # derivatives for heat output 2 to power charactersitics + Q2_deriv = np.zeros((1, num_vars, num_fl + 3)) + Q2_deriv[0, 1, 0] = self.ddx_func(self.Q2_char_func, 'm', 1) + Q2_deriv[0, 1, 2] = self.ddx_func(self.Q2_char_func, 'h', 1) + Q2_deriv[0, 5, 2] = self.ddx_func(self.Q2_char_func, 'h', 5) for i in range(2): - q2_deriv[0, i + 2, 0] = ( + Q2_deriv[0, i + 2, 0] = ( self.ddx_func(self.Q2_char_func, 'm', i + 2)) - q2_deriv[0, i + 2, 3:] = ( + Q2_deriv[0, i + 2, 3:] = ( self.ddx_func(self.Q2_char_func, 'fluid', i + 2)) - q2_deriv[0, 6, 0] = self.ddx_func(self.Q2_char_func, 'm', 6) - q2_deriv[0, 6, 3:] = self.ddx_func(self.Q2_char_func, 'fluid', 6) + Q2_deriv[0, 6, 0] = self.ddx_func(self.Q2_char_func, 'm', 6) + Q2_deriv[0, 6, 3:] = self.ddx_func(self.Q2_char_func, 'fluid', 6) if self.P.is_var: - q2_deriv[0, 7 + self.P.var_pos, 0] = ( + Q2_deriv[0, 7 + self.P.var_pos, 0] = ( self.ddx_func(self.Q2_char_func, 'P', 7)) - mat_deriv += q2_deriv.tolist() + mat_deriv += Q2_deriv.tolist() + + ###################################################################### - ql_deriv = np.zeros((1, num_vars, num_fl + 3)) + # derivatives for heat loss to power charactersitics + Ql_deriv = np.zeros((1, num_vars, num_fl + 3)) for i in range(2): - ql_deriv[0, i + 2, 0] = ( + Ql_deriv[0, i + 2, 0] = ( self.ddx_func(self.Qloss_char_func, 'm', i + 2)) - ql_deriv[0, i + 2, 3:] = ( + Ql_deriv[0, i + 2, 3:] = ( self.ddx_func(self.Qloss_char_func, 'fluid', i + 2)) - ql_deriv[0, 6, 0] = self.ddx_func(self.Qloss_char_func, 'm', 6) - ql_deriv[0, 6, 3:] = self.ddx_func(self.Qloss_char_func, 'fluid', 6) + Ql_deriv[0, 6, 0] = self.ddx_func(self.Qloss_char_func, 'm', 6) + Ql_deriv[0, 6, 3:] = self.ddx_func(self.Qloss_char_func, 'fluid', 6) if self.P.is_var: - ql_deriv[0, 7 + self.P.var_pos, 0] = ( + Ql_deriv[0, 7 + self.P.var_pos, 0] = ( self.ddx_func(self.Qloss_char_func, 'P', 7)) - if self.Qloss.is_var: - ql_deriv[0, 7 + self.Qloss.var_pos, 0] = ( + Ql_deriv[0, 7 + self.Qloss.var_pos, 0] = ( self.ddx_func(self.Qloss_char_func, 'Qloss', 7)) + mat_deriv += Ql_deriv.tolist() - mat_deriv += ql_deriv.tolist() + ###################################################################### if self.lamb.is_set: # derivatives for specified lambda @@ -4534,9 +4581,6 @@ def derivatives(self, nw): self.ddx_func(self.lambda_func, 'fluid', i + 2)) mat_deriv += lamb_deriv.tolist() -# np.set_printoptions(threshold=np.nan) -# print(np.asarray(mat_deriv)) - if self.ti.is_set: # derivatives for specified thermal input ti_deriv = np.zeros((1, num_vars, num_fl + 3)) @@ -4548,19 +4592,38 @@ def derivatives(self, nw): ti_deriv[0, 6, 3:] = self.ddx_func(self.ti_func, 'fluid', 6) mat_deriv += ti_deriv.tolist() + if self.Q1.is_set: + # derivatives for specified heat output 1 + Q_deriv = np.zeros((1, num_vars, num_fl + 3)) + Q_deriv[0, 0, 0] = - (self.outl[0].h.val_SI - self.inl[0].h.val_SI) + Q_deriv[0, 0, 2] = self.inl[0].m.val_SI + Q_deriv[0, 4, 2] = -self.inl[0].m.val_SI + mat_deriv += Q_deriv.tolist() + + if self.Q2.is_set: + # derivatives for specified heat output 2 + Q_deriv = np.zeros((1, num_vars, num_fl + 3)) + Q_deriv[0, 1, 0] = - (self.outl[1].h.val_SI - self.inl[1].h.val_SI) + Q_deriv[0, 1, 2] = self.inl[1].m.val_SI + Q_deriv[0, 5, 2] = -self.inl[1].m.val_SI + mat_deriv += Q_deriv.tolist() + if self.pr1.is_set: + # derivatives for specified pressure ratio 1 pr1_deriv = np.zeros((1, num_vars, num_fl + 3)) pr1_deriv[0, 0, 1] = self.pr1.val pr1_deriv[0, 4, 1] = -1 mat_deriv += pr1_deriv.tolist() if self.pr2.is_set: + # derivatives for specified pressure ratio 2 pr2_deriv = np.zeros((1, num_vars, num_fl + 3)) pr2_deriv[0, 1, 1] = self.pr2.val pr2_deriv[0, 5, 1] = -1 mat_deriv += pr2_deriv.tolist() if self.zeta1.is_set: + # derivatives for specified zeta 1 zeta1_deriv = np.zeros((1, num_vars, num_fl + 3)) zeta1_deriv[0, 0, 0] = self.ddx_func(self.zeta_func, 'm', 0) zeta1_deriv[0, 0, 1] = self.ddx_func(self.zeta_func, 'p', 0) @@ -4570,6 +4633,7 @@ def derivatives(self, nw): mat_deriv += zeta1_deriv.tolist() if self.zeta2.is_set: + # derivatives for specified zeta 2 zeta2_deriv = np.zeros((1, num_vars, num_fl + 3)) zeta2_deriv[0, 1, 0] = self.ddx_func(self.zeta2_func, 'm', 1) zeta2_deriv[0, 1, 1] = self.ddx_func(self.zeta2_func, 'p', 1) @@ -4599,12 +4663,17 @@ def energy_balance(self): :returns: res (*float*) - residual value of energy balance .. math:: - 0 = \sum_i \dot{m}_{in,i} \cdot \left( h_{in,i} - h_{in,i,ref} - \right) - \sum_j \dot{m}_{out,j} \cdot - \left( h_{out,j} - h_{out,j,ref} \right) + - H_{I,f} \cdot \left(\sum_i \dot{m}_{in,i} \cdot x_{f,i} - - \sum_j \dot{m}_{out,j} \cdot x_{f,j} \right) - \; \forall i \in \text{inlets}\; \forall j \in \text{outlets} + + \begin{split} + 0 = & \sum_i \dot{m}_{in,i} \cdot \left( h_{in,i} - h_{in,i,ref} + \right)\\ + & - \sum_j \dot{m}_{out,3} \cdot \left( h_{out,3} - h_{out,3,ref} + \right)\\ + & + H_{I,f} \cdot \left(\sum_i \left(\dot{m}_{in,i} \cdot x_{f,i} + \right)- \dot{m}_{out,3} \cdot x_{f,3} \right)\\ + & - \dot{Q}_1 - \dot{Q}_2 - P - \dot{Q}_{loss}\\ + \end{split}\\ + \forall i \in [3,4] """ T_ref = 293.15 @@ -4641,7 +4710,7 @@ def energy_balance(self): return res -# def bus_func(self): +# def bus_func(self, bus): # r""" # function for use on busses # @@ -4652,16 +4721,22 @@ def energy_balance(self): # val = \dot{m}_{fuel} \cdot LHV # """ # -# m_fuel = 0 -# for i in self.inl: -# m_fuel += (i.m.val_SI * i.fluid.val[self.fuel.val]) +# if par == 'TI': +# return self.calc_ti() +# +# elif par == 'P': +# +# elif par == 'Q': +# +# elif par == 'Q1': # -# for o in self.outl: -# m_fuel -= (o.m.val_SI * o.fluid.val[self.fuel.val]) +# elif par == 'Q2': # -# return m_fuel * self.lhv +# elif par == 'Qloss': # -# def bus_deriv(self): +# else: +# +# def bus_deriv(self, bus): # r""" # calculate matrix of partial derivatives towards mass flow and fluid # composition for bus @@ -4747,6 +4822,42 @@ def drb_dx(self, dx, pos, fluid): return deriv + def Q1_func(self): + r""" + calculates the relation of heat output 1 and thermal input from + specified characteristic lines + + :returns: res (*float*) - residual value + + .. math:: + + 0 = \dot{m}_1 \cdot \left(h_{out,1} - h_{in,1} \right) - \dot{Q}_1 + + """ + + i = self.inl[0] + o = self.outl[0] + + return self.Q1.val - i.m.val_SI * (o.h.val_SI - i.h.val_SI) + + def Q2_func(self): + r""" + calculates the relation of heat output 2 and thermal input from + specified characteristic lines + + :returns: res (*float*) - residual value + + .. math:: + + 0 = \dot{m}_2 \cdot \left(h_{out,2} - h_{in,2} \right) - \dot{Q}_2 + + """ + + i = self.inl[1] + o = self.outl[1] + + return self.Q2.val - i.m.val_SI * (o.h.val_SI - i.h.val_SI) + def tiP_char_func(self): r""" calculates the relation of output power and thermal input from @@ -4777,12 +4888,16 @@ def Q1_char_func(self): :returns: res (*float*) - residual value .. math:: - 0 = \dot{m}_1 \cdot \left(h_{out,1} - h_{in,1} \right) \cdot - f_{TI}\left(\frac{P}{P_{ref}}\right)- LHV \cdot \left[\sum_i + + \begin{split} + 0 = & \dot{m}_1 \cdot \left(h_{out,1} - h_{in,1} \right) \cdot + f_{TI}\left(\frac{P}{P_{ref}}\right) \\ + & - LHV \cdot \left[\sum_i \left(\dot{m}_{in,i} \cdot x_{f,i}\right) - \dot{m}_{out,3} \cdot x_{f,3} \right] \cdot - f_{Q1}\left(\frac{P}{P_{ref}}\right) - \; \forall i \in [1,2] + f_{Q1}\left(\frac{P}{P_{ref}}\right)\\ + \end{split}\\ + \forall i \in [3,4] """ @@ -4806,12 +4921,16 @@ def Q2_char_func(self): :returns: res (*float*) - residual value .. math:: - 0 = \dot{m}_2 \cdot \left(h_{out,2} - h_{in,2} \right) \cdot - f_{TI}\left(\frac{P}{P_{ref}}\right)- LHV \cdot \left[\sum_i + + \begin{split} + 0 = & \dot{m}_2 \cdot \left(h_{out,2} - h_{in,2} \right) \cdot + f_{TI}\left(\frac{P}{P_{ref}}\right) \\ + & - LHV \cdot \left[\sum_i \left(\dot{m}_{in,i} \cdot x_{f,i}\right) - \dot{m}_{out,3} \cdot x_{f,3} \right] \cdot - f_{Q2}\left(\frac{P}{P_{ref}}\right) - \; \forall i \in [1,2] + f_{Q2}\left(\frac{P}{P_{ref}}\right)\\ + \end{split}\\ + \forall i \in [3,4] """ @@ -4835,12 +4954,16 @@ def Qloss_char_func(self): :returns: res (*float*) - residual value .. math:: - 0 = \dot{Q}_{loss} \cdot - f_{TI}\left(\frac{P}{P_{ref}}\right)- LHV \cdot \left[\sum_i + + \begin{split} + 0 = & \dot{Q}_{loss} \cdot + f_{TI}\left(\frac{P}{P_{ref}}\right) \\ + & - LHV \cdot \left[\sum_i \left(\dot{m}_{in,i} \cdot x_{f,i}\right) - \dot{m}_{out,3} \cdot x_{f,3} \right] \cdot - f_{QLOSS}\left(\frac{P}{P_{ref}}\right) - \; \forall i \in [1,2] + f_{QLOSS}\left(\frac{P}{P_{ref}}\right)\\ + \end{split}\\ + \forall i \in [3,4] """ @@ -4980,31 +5103,41 @@ def calc_parameters(self, nw, mode): combustion_chamber.calc_parameters(self, nw, mode) - if mode == 'post' or (mode == 'pre' and 'Q1' in self.offdesign): - self.Q1.val = self.inl[0].m.val_SI * ( - self.outl[0].h.val_SI - self.inl[0].h.val_SI) + i1 = self.inl[0].to_flow() + i2 = self.inl[1].to_flow() + o1 = self.outl[0].to_flow() + o2 = self.outl[1].to_flow() - if mode == 'post' or (mode == 'pre' and 'Q2' in self.offdesign): - self.Q2.val = self.inl[1].m.val_SI * ( - self.outl[1].h.val_SI - self.inl[1].h.val_SI) + if (mode == 'pre' and 'pr1' in self.offdesign) or mode == 'post': + self.pr1.val = o1[1] / i1[1] + if (mode == 'pre' and 'pr2' in self.offdesign) or mode == 'post': + self.pr2.val = o2[1] / i2[1] - if mode == 'post' and not self.P_ref.is_set: - self.P_ref.is_set = self.P.val + if (mode == 'pre' and 'zeta1' in self.offdesign) or mode == 'post': + self.zeta1.val = ((i1[1] - o1[1]) * math.pi ** 2 / ( + 8 * i1[0] ** 2 * (v_mix_ph(i1) + v_mix_ph(o1)) / 2)) + if (mode == 'pre' and 'zeta2' in self.offdesign) or mode == 'post': + self.zeta2.val = ((i2[1] - o2[1]) * math.pi ** 2 / ( + 8 * i2[0] ** 2 * (v_mix_ph(i2) + v_mix_ph(o2)) / 2)) - if self.P_ref.is_set: - expr = self.P.val / self.P_ref.val - else: + if mode == 'post' or (mode == 'pre' and 'Q1' in self.offdesign): + self.Q1.val = i1[0] * (o1[2] - i1[2]) + if mode == 'post' or (mode == 'pre' and 'Q2' in self.offdesign): + self.Q2.val = i2[0] * (o2[2] - i2[2]) + + if ((mode == 'post' and nw.mode == 'design') or + (mode == 'pre' and 'P_ref' in self.offdesign)): expr = 1 + self.P_ref.val = self.calc_ti() / self.tiP_char.func.f_x(expr) + else: + expr = self.P.val / self.P_ref.val - if mode == 'post' or (mode == 'pre' and 'Qloss' in self.offdesign): + if not self.Qloss.is_set: self.Qloss.val = self.ti.val * (self.Qloss_char.func.f_x(expr) / self.tiP_char.func.f_x(expr)) - - if mode == 'post' or (mode == 'pre' and 'P' in self.offdesign): + if not self.P.is_set: self.P.val = self.ti.val / self.tiP_char.func.f_x(expr) - - # %% @@ -5223,9 +5356,10 @@ class heat_exchanger_simple(component): , :math:`[ks]=\text{1}` for hazen-williams equation - kA: area independent heat transition coefficient, :math:`[kA]=\frac{\text{W}}{\text{K}}` - - t_a: ambient temperature, provide parameter in network's temperature unit - - t_a_design: ambient temperature design case, provide parameter in - network's temperature unit + - Tamb: ambient temperature, provide parameter in network's temperature + unit + - Tamb_ref: ambient temperature for reference in offdesign case, provide + parameter in network's temperature unit .. note:: for now, it is not possible to make these parameters part of the @@ -5246,8 +5380,8 @@ class heat_exchanger_simple(component): **default offdesign parameters** - - kA (method: HE_COLD, param: m): *be aware that you must provide t_a and - t_a_design, if you want the heat flow calculated by this method* + - kA (method: HE_COLD, param: m), Tamb_ref: *be aware that you must provide + Tamb (and Tamb_ref), if you want the heat flow calculated by this method* **inlets and outlets** @@ -5279,7 +5413,7 @@ def attr(self): 'L': dc_cp(min_val=1e-1, d=1e-3), 'ks': dc_cp(min_val=1e-7, max_val=1e-4, d=1e-8), 'kA': dc_cp(min_val=1, d=1), - 't_a': dc_cp(), 't_a_design': dc_cp(), + 'Tamb': dc_cp(), 'Tamb_ref': dc_cp(), 'kA_char': dc_cc(method='HE_HOT', param='m'), 'SQ1': dc_cp(), 'SQ2': dc_cp(), 'Sirr': dc_cp(), 'hydro_group': dc_gcp(), 'kA_group': dc_gcp()} @@ -5288,7 +5422,7 @@ def default_design(self): return ['pr'] def default_offdesign(self): - return ['kA'] + return ['kA', 'Tamb_ref'] def inlets(self): return ['in1'] @@ -5300,10 +5434,10 @@ def comp_init(self, nw): component.comp_init(self, nw) - self.t_a.val_SI = ((self.t_a.val + nw.T[nw.T_unit][0]) * - nw.T[nw.T_unit][1]) - self.t_a_design.val_SI = ((self.t_a_design.val + nw.T[nw.T_unit][0]) * - nw.T[nw.T_unit][1]) + self.Tamb.val_SI = ((self.Tamb.val + nw.T[nw.T_unit][0]) * + nw.T[nw.T_unit][1]) + self.Tamb_ref.val_SI = ((self.Tamb_ref.val + nw.T[nw.T_unit][0]) * + nw.T[nw.T_unit][1]) # parameters for hydro group self.hydro_group.set_attr(elements=[self.L, self.ks, self.D]) @@ -5327,7 +5461,7 @@ def comp_init(self, nw): self.hydro_group.set_attr(is_set=False) # parameters for kA group - self.kA_group.set_attr(elements=[self.kA, self.t_a]) + self.kA_group.set_attr(elements=[self.kA, self.Tamb]) is_set = True for e in self.kA_group.elements: @@ -5340,7 +5474,7 @@ def comp_init(self, nw): msg = ('##### WARNING #####\n' 'All parameters of the component group have to be ' 'specified! This component group uses the following ' - 'parameters: kA, t_a at ' + self.label + '. ' + 'parameters: kA, Tamb at ' + self.label + '. ' 'Group will be set to False') print(msg) self.kA_group.set_attr(is_set=False) @@ -5387,9 +5521,8 @@ def equations(self): vec_res += self.mass_flow_res() if self.Q.is_set: - vec_res += [self.inl[0].m.val_SI * - (self.outl[0].h.val_SI - self.inl[0].h.val_SI) - - self.Q.val] + vec_res += [self.inl[0].m.val_SI * ( + self.outl[0].h.val_SI - self.inl[0].h.val_SI) - self.Q.val] if self.pr.is_set: vec_res += [self.inl[0].p.val_SI * self.pr.val - @@ -5403,7 +5536,6 @@ def equations(self): func = self.hw_func else: func = self.darcy_func - vec_res += [func()] vec_res += self.additional_equations() @@ -5512,7 +5644,8 @@ def additional_derivatives(self, nw): for i in range(2): kA_deriv[0, i, 1] = self.ddx_func(self.kA_func, 'p', i) kA_deriv[0, i, 2] = self.ddx_func(self.kA_func, 'h', i) - # this does not work atm, as t_a.val_SI is used instead of t_a.val! + # this does not work atm, as Tamb.val_SI is used instead of + # Tamb.val! for var in self.kA_group.elements: if var.is_var: kA_deriv[0, 2 + var.var_pos, 0] = ( @@ -5591,13 +5724,13 @@ def kA_func(self): .. math:: ttd_u = \begin{cases} - t_a - T_{out} & t_a > T_{in}\\ - T_{in} - t_a & t_a \leq T_{in} + T_{amb} - T_{out} & T_{amb} > T_{in}\\ + T_{in} - T_{amb} & T_{amb} \leq T_{in} \end{cases} ttd_l = \begin{cases} - t_a - T_{in} & t_a > T_{in}\\ - T_{out} - t_a & t_a \leq T_{in} + T_{amb} - T_{in} & T_{amb} > T_{in}\\ + T_{out} - T_{amb} & T_{amb} \leq T_{in} \end{cases} 0 = \dot{m}_{in} \cdot \left( h_{out} - h_{in}\right) + @@ -5606,34 +5739,34 @@ def kA_func(self): f_{kA} = f_1\left(\frac{m_1}{m_{1,ref}}\right) - t_a: \text{ambient temperature} + T_{amb}: \text{ambient temperature} for f\ :subscript:`1` \ see class :func:`tespy.component.characteristics.heat_ex` """ - i, o = self.inl, self.outl - T_i = T_mix_ph(i[0].to_flow()) - T_o = T_mix_ph(o[0].to_flow()) + i, o = self.inl[0].to_flow(), self.outl[0].to_flow() + T_i = T_mix_ph(i) + T_o = T_mix_ph(o) - if self.t_a.val_SI > T_i: - ttd_u = self.t_a.val_SI - T_o - ttd_l = self.t_a.val_SI - T_i + if self.Tamb.val_SI > T_i: + ttd_u = self.Tamb.val_SI - T_o + ttd_l = self.Tamb.val_SI - T_i else: - ttd_u = T_i - self.t_a.val_SI - ttd_l = T_o - self.t_a.val_SI + ttd_u = T_i - self.Tamb.val_SI + ttd_l = T_o - self.Tamb.val_SI - if self.kA_char.param == 'm': - expr = i[0].m.val_SI / self.i0[0] - else: - expr = 1 + expr = 1 + if hasattr(self, 'i_ref'): + if self.kA_char.param == 'm': + expr = i[0] / self.i_ref[0] fkA = self.kA_char.func.f_x(expr) - return (i[0].m.val_SI * (o[0].h.val_SI - i[0].h.val_SI) + self.kA.val * - fkA * ((ttd_u - ttd_l) / math.log(ttd_u / ttd_l))) + return (i[0] * (o[2] - i[2]) + self.kA.val * fkA * ( + (ttd_u - ttd_l) / math.log(ttd_u / ttd_l))) - def bus_func(self): + def bus_func(self, bus): r""" function for use on busses @@ -5642,25 +5775,29 @@ def bus_func(self): .. math:: val = \dot{m}_{in} \cdot \left( h_{out} - h_{in} - \right) + \right) \cdot f\left( \frac{val}{val_ref}\right) """ i = self.inl[0].to_flow() o = self.outl[0].to_flow() - return i[0] * (o[2] - i[2]) - def bus_deriv(self): + val = i[0] * (o[2] - i[2]) + if np.isnan(bus.P_ref): + expr = 1 + else: + expr = val / bus.P_ref + return val * bus.char.f_x(expr) + + def bus_deriv(self, bus): r""" calculate matrix of partial derivatives towards mass flow and enthalpy for bus function :returns: mat_deriv (*list*) - matrix of partial derivatives """ - i = self.inl[0].to_flow() - o = self.outl[0].to_flow() deriv = np.zeros((1, 2, len(self.inl[0].fluid.val) + 3)) - deriv[0, 0, 0] = o[2] - i[2] - deriv[0, 0, 2] = - i[0] - deriv[0, 1, 2] = i[0] + deriv[0, 0, 0] = self.ddx_func(self.bus_func, 'm', 0, bus=bus) + deriv[0, 0, 2] = self.ddx_func(self.bus_func, 'h', 0, bus=bus) + deriv[0, 1, 2] = self.ddx_func(self.bus_func, 'h', 1, bus=bus) return deriv def initialise_source(self, c, key): @@ -5730,40 +5867,35 @@ def initialise_target(self, c, key): def calc_parameters(self, nw, mode): + i = self.inl[0].to_flow() + o = self.outl[0].to_flow() + if mode == 'post': - self.SQ1.val = self.inl[0].m.val_SI * ( - s_mix_ph(self.outl[0].to_flow()) - - s_mix_ph(self.inl[0].to_flow())) + self.SQ1.val = i[0] * (s_mix_ph(o) - s_mix_ph(i)) if mode == 'pre': - self.i0 = self.inl[0].to_flow() - self.o0 = self.outl[0].to_flow() - self.i0[3] = self.i0[3].copy() - self.o0[3] = self.o0[3].copy() + self.i_ref = i + self.o_ref = o + self.i_ref[3] = i[3].copy() + self.o_ref[3] = o[3].copy() - if nw.mode == 'design': - if self.t_a_design.is_set: - t_a = self.t_a_design.val_SI - else: - t_a = np.nan + if mode == 'post' and nw.mode == 'design': + self.Tamb_ref.val = self.Tamb.val + t_a = np.nan if nw.mode == 'offdesign': if mode == 'pre': - if self.t_a_design.is_set: - t_a = self.t_a_design.val_SI - else: - t_a = np.nan + if self.Tamb_ref.is_set: + t_a = self.Tamb_ref.val_SI else: - if self.t_a.is_set: - t_a = self.t_a.val_SI - else: - t_a = np.nan + if self.Tamb.is_set: + t_a = self.Tamb.val_SI if t_a != np.nan: - T_i = T_mix_ph(self.inl[0].to_flow()) - T_o = T_mix_ph(self.outl[0].to_flow()) + T_i = T_mix_ph(i) + T_o = T_mix_ph(o) if t_a > T_i: ttd_u = t_a - T_o @@ -5782,29 +5914,23 @@ def calc_parameters(self, nw, mode): nw.errors += [self] if mode == 'post': - self.SQ2.val = - self.inl[0].m.val_SI * ( - self.outl[0].h.val_SI - self.inl[0].h.val_SI) / t_a + self.SQ2.val = -i[0] * (o[2] - i[2]) / t_a self.Sirr.val = self.SQ1.val + self.SQ2.val - self.kA.val = self.inl[0].m.val_SI * ( - self.outl[0].h.val_SI - self.inl[0].h.val_SI) / ( - (ttd_u - ttd_l) / math.log(ttd_l / ttd_u)) + self.kA.val = i[0] * (o[2] - i[2]) / ( + (ttd_u - ttd_l) / math.log(ttd_l / ttd_u)) if (mode == 'pre' and 'Q' in self.offdesign) or mode == 'post': - self.Q.val = self.inl[0].m.val_SI * (self.outl[0].h.val_SI - - self.inl[0].h.val_SI) + self.Q.val = i[0] * (o[2] - i[2]) if (mode == 'pre' and 'pr' in self.offdesign) or mode == 'post': - self.pr.val = self.outl[0].p.val_SI / self.inl[0].p.val_SI + self.pr.val = o[1] / i[1] if (mode == 'pre' and 'zeta' in self.offdesign) or mode == 'post': - self.zeta.val = ((self.inl[0].p.val_SI - self.outl[0].p.val_SI) * - math.pi ** 2 / - (8 * self.inl[0].m.val_SI ** 2 * - (v_mix_ph(self.inl[0].to_flow()) + - v_mix_ph(self.outl[0].to_flow())) / 2)) + self.zeta.val = ((i[1] - o[1]) * math.pi ** 2 / ( + 8 * i[0] ** 2 * (v_mix_ph(i) + v_mix_ph(o)) / 2)) # improve this part (for heat exchangers only atm) if self.kA.is_set: - expr = self.inl[0].m.val_SI / self.i0[0] + expr = i[0] / self.i_ref[0] minval = self.kA_char.func.x[0] maxval = self.kA_char.func.x[-1] if expr > maxval or expr < minval: @@ -5817,6 +5943,10 @@ def calc_parameters(self, nw, mode): print(msg) nw.errors += [self] + if mode == 'post' and nw.mode == 'offdesign': + del self.i_ref + del self.o_ref + # %% @@ -5840,9 +5970,10 @@ class pipe is an alias of class heat_exchanger_simple , :math:`[ks]=\text{1}` for hazen-williams equation - kA: area independent heat transition coefficient, :math:`[kA]=\frac{\text{W}}{\text{K}}` - - t_a: ambient temperature, provide parameter in network's temperature unit - - t_a_design: ambient temperature design case, provide parameter in - network's temperature unit + - Tamb: ambient temperature, provide parameter in network's temperature + unit + - Tamb_ref: ambient temperature for reference in offdesign case, provide + parameter in network's temperature unit .. note:: for now, it is not possible to make these parameters part of the @@ -5863,8 +5994,8 @@ class pipe is an alias of class heat_exchanger_simple **default offdesign parameters** - - kA (method: HE_COLD, param: m): *be aware that you must provide t_a and - t_a_design, if you want the heat flow calculated by this method* + - kA (method: HE_COLD, param: m), Tamb_ref: *be aware that you must provide + Tamb (and Tamb_ref), if you want the heat flow calculated by this method* **inlets and outlets** @@ -5913,7 +6044,8 @@ class solar collector - lkf_quad: quadratic loss key figure, :math:`[\alpha_2]=\frac{\text{W}}{\text{K}^2 \cdot \text{m}^2}` - A: collector surface area :math:`[A]=\text{m}^2` - - t_a: ambient temperature, provide parameter in network's temperature unit + - Tamb: ambient temperature, provide parameter in network's temperature + unit .. note:: for now, it is not possible to make these parameters part of the @@ -5958,7 +6090,7 @@ def attr(self): 'L': dc_cp(min_val=1e-1, d=1e-3), 'ks': dc_cp(min_val=1e-7, max_val=1e-4, d=1e-8), 'E': dc_cp(min_val=0), 'lkf_lin': dc_cp(), 'lkf_quad': dc_cp(), - 'A': dc_cp(min_val=0), 't_a': dc_cp(), + 'A': dc_cp(min_val=0), 'Tamb': dc_cp(), 'SQ': dc_cp(), 'hydro_group': dc_gcp(), 'energy_group': dc_gcp()} @@ -5978,8 +6110,8 @@ def comp_init(self, nw): component.comp_init(self, nw) - self.t_a.val_SI = ((self.t_a.val + nw.T[nw.T_unit][0]) * - nw.T[nw.T_unit][1]) + self.Tamb.val_SI = ((self.Tamb.val + nw.T[nw.T_unit][0]) * + nw.T[nw.T_unit][1]) # parameters for hydro group self.hydro_group.set_attr(elements=[self.L, self.ks, self.D]) @@ -6004,7 +6136,7 @@ def comp_init(self, nw): # parameters for kA group self.energy_group.set_attr(elements=[ - self.E, self.lkf_lin, self.lkf_quad, self.A, self.t_a]) + self.E, self.lkf_lin, self.lkf_quad, self.A, self.Tamb]) is_set = True for e in self.energy_group.elements: @@ -6017,7 +6149,7 @@ def comp_init(self, nw): msg = ('##### WARNING #####\n' 'All parameters of the component group have to be ' 'specified! This component group uses the following ' - 'parameters: E, lkf_lin, lkf_quad, A, t_a at ' + self.label + 'parameters: E, lkf_lin, lkf_quad, A, Tamb at ' + self.label + '. Group will be set to False') print(msg) self.energy_group.set_attr(is_set=False) @@ -6096,28 +6228,25 @@ def energy_func(self): T_m = (T_mix_ph(i) + T_mix_ph(o)) / 2 return (i[0] * (o[2] - i[2]) - self.A.val * (self.E.val - - (T_m - self.t_a.val_SI) * + (T_m - self.Tamb.val_SI) * (self.lkf_lin.val + self.lkf_quad.val * self.A.val * - (T_m - self.t_a.val_SI)))) + (T_m - self.Tamb.val_SI)))) def calc_parameters(self, nw, mode): + i = self.inl[0].to_flow() + o = self.outl[0].to_flow() + if mode == 'post': - self.SQ.val = self.inl[0].m.val_SI * ( - s_mix_ph(self.outl[0].to_flow()) - - s_mix_ph(self.inl[0].to_flow())) + self.SQ.val = i[0] * (s_mix_ph(o) - s_mix_ph(i)) if (mode == 'pre' and 'Q' in self.offdesign) or mode == 'post': - self.Q.val = self.inl[0].m.val_SI * (self.outl[0].h.val_SI - - self.inl[0].h.val_SI) + self.Q.val = i[0] * (o[2] - i[2]) if (mode == 'pre' and 'pr' in self.offdesign) or mode == 'post': - self.pr.val = self.outl[0].p.val_SI / self.inl[0].p.val_SI + self.pr.val = o[1] / i[1] if (mode == 'pre' and 'zeta' in self.offdesign) or mode == 'post': - self.zeta.val = ((self.inl[0].p.val_SI - self.outl[0].p.val_SI) * - math.pi ** 2 / - (8 * self.inl[0].m.val_SI ** 2 * - (v_mix_ph(self.inl[0].to_flow()) + - v_mix_ph(self.outl[0].to_flow())) / 2)) + self.zeta.val = ((i[1] - o[1]) * math.pi ** 2 / ( + 8 * i[0] ** 2 * (v_mix_ph(i) + v_mix_ph(o)) / 2)) # %% @@ -6463,14 +6592,14 @@ def kA_func(self): raise MyComponentError(msg) if self.kA_char1.param == 'm': - expr = i1[0] / self.i10[0] + expr = i1[0] / self.i1_ref[0] else: expr = 1 fkA1 = self.kA_char1.func.f_x(expr) if self.kA_char2.param == 'm': - expr = i2[0] / self.i20[0] + expr = i2[0] / self.i2_ref[0] else: expr = 1 @@ -6590,7 +6719,7 @@ def ttd_l_deriv(self): self.ddx_func(self.ttd_l_func, 'h', i + 1)) return deriv.tolist() - def bus_func(self): + def bus_func(self, bus): r""" function for use on busses @@ -6599,25 +6728,29 @@ def bus_func(self): .. math:: val = \dot{m}_{1,in} \cdot \left( h_{1,out} - h_{1,in} - \right) + \right) \cdot f\left( \frac{val}{val_ref}\right) """ i = self.inl[0].to_flow() o = self.outl[0].to_flow() - return i[0] * (o[2] - i[2]) - def bus_deriv(self): + val = i[0] * (o[2] - i[2]) + if np.isnan(bus.P_ref): + expr = 1 + else: + expr = val / bus.P_ref + return val * bus.char.f_x(expr) + + def bus_deriv(self, bus): r""" calculate matrix of partial derivatives towards mass flow and enthalpy for bus function :returns: mat_deriv (*list*) - matrix of partial derivatives """ - i = self.inl[0].to_flow() - o = self.outl[0].to_flow() deriv = np.zeros((1, 4, len(self.inl[0].fluid.val) + 3)) - deriv[0, 0, 0] = o[2] - i[2] - deriv[0, 0, 2] = - i[0] - deriv[0, 2, 2] = i[0] + deriv[0, 0, 0] = self.ddx_func(self.bus_func, 'm', 0, bus=bus) + deriv[0, 0, 2] = self.ddx_func(self.bus_func, 'h', 0, bus=bus) + deriv[0, 2, 2] = self.ddx_func(self.bus_func, 'h', 2, bus=bus) return deriv def convergence_check(self, nw): @@ -6733,14 +6866,14 @@ def calc_parameters(self, nw, mode): if mode == 'pre': - self.i10 = i1 - self.i20 = i2 - self.o10 = o1 - self.o20 = o2 - self.i10[3] = self.i10[3].copy() - self.i20[3] = self.i20[3].copy() - self.o10[3] = self.o10[3].copy() - self.o20[3] = self.o20[3].copy() + self.i1_ref = i1 + self.i2_ref = i2 + self.o1_ref = o1 + self.o2_ref = o2 + self.i1_ref[3] = self.i1_ref[3].copy() + self.i2_ref[3] = self.i2_ref[3].copy() + self.o1_ref[3] = self.o1_ref[3].copy() + self.o2_ref[3] = self.o2_ref[3].copy() T_i2 = T_mix_ph(i2) T_o1 = T_mix_ph(o1) @@ -6802,7 +6935,7 @@ def calc_parameters(self, nw, mode): # improve this part (for heat exchangers only atm) if self.kA.is_set: - expr = self.inl[0].m.val_SI / self.i10[0] + expr = self.inl[0].m.val_SI / self.i1_ref[0] minval = self.kA_char1.func.x[0] maxval = self.kA_char1.func.x[-1] if expr > maxval or expr < minval: @@ -6815,7 +6948,7 @@ def calc_parameters(self, nw, mode): print(msg) nw.errors += [self] - expr = self.inl[1].m.val_SI / self.i20[0] + expr = self.inl[1].m.val_SI / self.i2_ref[0] minval = self.kA_char2.func.x[0] maxval = self.kA_char2.func.x[-1] if expr > maxval or expr < minval: @@ -6828,6 +6961,12 @@ def calc_parameters(self, nw, mode): print(msg) nw.errors += [self] + if mode == 'post' and nw.mode == 'offdesign': + del self.i1_ref + del self.o1_ref + del self.i2_ref + del self.o2_ref + # %% @@ -7003,14 +7142,14 @@ def kA_func(self): T_i2 = T_o1 - 1 if self.kA_char1.param == 'm': - expr = i1[0] / self.i10[0] + expr = i1[0] / self.i1_ref[0] else: expr = 1 fkA1 = self.kA_char1.func.f_x(expr) if self.kA_char2.param == 'm': - expr = i2[0] / self.i20[0] + expr = i2[0] / self.i2_ref[0] else: expr = 1 From e287a8ddfcaae160ce7d567023ad1224fb450a8c Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 23 Oct 2018 15:37:17 +0200 Subject: [PATCH 66/80] added characteristics support for busses --- tespy/connections.py | 131 +++++++++++++++++++++++++++++++++---------- 1 file changed, 102 insertions(+), 29 deletions(-) diff --git a/tespy/connections.py b/tespy/connections.py index 30ac3efdd..b6a487f08 100644 --- a/tespy/connections.py +++ b/tespy/connections.py @@ -8,8 +8,10 @@ import numpy as np import pandas as pd -from tespy.helpers import MyConnectionError, data_container, dc_prop, dc_flu +from tespy.helpers import (MyConnectionError, data_container, dc_prop, dc_flu, + dc_cp) from tespy.components import components as cmp +from tespy.components import characteristics as cmp_char class connection: @@ -238,22 +240,31 @@ def to_flow(self): class bus: - """ - establish power connection between turbines, pumps, heat exchanger + r""" + establishes a bus for + + - power in- or output + - heat in- or output + - thermal input for combustion chambers + + **available parameters** + + - P: total power/heat of the bus, :math:`{[P]=\text{W}}` - - specification of a label and power (optional) - - if you specify busses with the labels 'P_res' and 'Q_diss', TESPy will - calculate the process key figures (thermal efficiency, coefficient of - performance) + **characteristic lines** + + - c_char: characteristic line linking each component's factor to its power + output in reference case (for an example see the tespy_examples + repository) + + **adding components to a bus** + + - :func:`tespy.connections.bus.add_comps` :param label: label for the bus :type label: str :returns: no return value - **allowed keywords** in kwargs: - - - P (*numeric*) - power of the bus - **Improvements** - improve architecture (e. g. make it similar to connections) @@ -261,24 +272,24 @@ class bus: def __init__(self, label, **kwargs): - self.comps = pd.DataFrame(columns=['factor']) + self.comps = pd.DataFrame(columns=['param', 'P_ref', 'char']) self.label = label - self.P_set = False - self.P = kwargs.get('P', np.nan) + self.P = dc_cp(val=np.nan, val_set=False) + self.char = cmp_char.characteristics(x=np.array([0, 1, 2, 3]), + y=np.array([1, 1, 1, 1])) - if not np.isnan(self.P): - self.P_set = True + self.set_attr(**kwargs) def set_attr(self, **kwargs): self.label = kwargs.get('label', self.label) - self.P = kwargs.get('P', self.P) + self.P.val = kwargs.get('P', self.P.val) - if not np.isnan(self.P): - self.P_set = True + if np.isnan(self.P.val): + self.P.val_set = False else: - self.P_set = False + self.P.val_set = True def get_attr(self, key): """ @@ -293,26 +304,88 @@ def get_attr(self, key): if key in self.__dict__: return self.__dict__[key] else: - print(self.bus(), ' has no attribute \"', key, '\"') + print('Bus ' + self.label + ' has no attribute ' + key + '.') return None def add_comps(self, *args): """ adds components to a bus - :param args: component objects ci :code:`add_comps(c1, c2, c3, ...)` - :type args: tespy.components.components.component + ci are dicts containing a TESPy component object + as well as a parameter (P, Q, Q1, TI, ...) and a characteristic line + char (cmp_char): + + {'c': TESPy component object, 'p': parameter as String, + 'char': TESPy characteristics object} + + **The parameter and characteristic line are optional!!** + + **parameter specfication** + - You do not need to provide a parameter, if the component only has one + option for the bus (turbomachines, heat exchangers, combustion + chamber). + - For instance, you do neet do provide a parameter, if you want to add + a cogeneration unit ('Q1', 'Q2', 'TI', 'P'). + + **characteristic line specification** + - If you do not provide a characteristic line at all, TESPy assumes a + constant factor of 1. + - If you provide a numeric value instead of a characteristic line, + TESPy takes this numeric value as a constant factor. + - Provide a TESPy.characteristic (cmp_char), if you want the factor + to follow a characteristic line. + + :param args: lists ci containing necessary data for the bus + :type args: list :returns: no return value """ for c in args: - if isinstance(c, list): - if len(c) == 2: - self.comps.loc[c[0]] = [c[1]] + if isinstance(c, dict): + if 'c' in c.keys(): + if isinstance(c['c'], cmp.component): + self.comps.loc[c['c']] = [None, np.nan, self.char] + else: + msg = ('c must be a TESPy component.') + raise TypeError(msg) else: - msg = 'List must have two elements: [component, factor].' - raise MyConnectionError(msg) + msg = ('You must provide the component c.') + raise TypeError(msg) + + for k, v in c.items(): + if k == 'p': + if isinstance(v, str) or v is None: + self.comps.loc[c['c']]['param'] = v + else: + msg = ('Parameter p must be a String.') + raise TypeError(msg) + + elif k == 'char': + if isinstance(v, cmp_char.characteristics): + self.comps.loc[c['c']]['char'] = v + elif (isinstance(v, float) or + isinstance(v, np.float64) or + isinstance(v, int)): + x = np.array([0, 1, 2, 3]) + y = np.array([1, 1, 1, 1]) * v + self.comps.loc[c['c']]['char'] = ( + cmp_char.characteristics(x=x, y=y)) + else: + msg = ('Char must be a number or a TESPy ' + 'characteristics.') + raise TypeError(msg) + + elif k == 'P_ref': + if (v is None or isinstance(v, float) or + isinstance(v, np.float64) or + isinstance(v, int)): + self.comps.loc[c['c']]['P_ref'] = v + else: + msg = ('Char must be a number or a TESPy ' + 'characteristics.') + raise TypeError(msg) else: - msg = 'Provide argument as list: [component, factor].' + msg = ('Provide arguments as dicts. See the documentation of ' + 'bus.add_comps() for more information.') raise MyConnectionError(msg) From ea77e95ed27e6315da3877bcc76729cf0c8060ba Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 23 Oct 2018 15:38:12 +0200 Subject: [PATCH 67/80] modified networks module to handle new bus characteristics --- tespy/networks.py | 96 +++++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 33 deletions(-) diff --git a/tespy/networks.py b/tespy/networks.py index a3c06e79d..c1a8a9443 100644 --- a/tespy/networks.py +++ b/tespy/networks.py @@ -1257,9 +1257,9 @@ def solve_control(self): try: self.vec_z = inv(self.mat_deriv).dot(-np.asarray(self.vec_res)) self.lin_dep = False - except: - pass + except np.linalg.linalg.LinAlgError: self.vec_z = np.asarray(self.vec_res) * 0 + pass # check for linear dependency if self.lin_dep: @@ -1612,24 +1612,26 @@ def solve_busses(self): """ row = len(self.vec_res) for b in self.busses: - if b.P_set: + if b.P.val_set: P_res = 0 for cp in b.comps.index: i = self.comps.loc[cp].i.tolist() o = self.comps.loc[cp].o.tolist() - P_res += cp.bus_func() * b.comps.loc[cp].factor - deriv = -cp.bus_deriv() + bus = b.comps.loc[cp] + + P_res += cp.bus_func(bus) + deriv = -cp.bus_deriv(bus) j = 0 for c in i + o: loc = self.conns.index.get_loc(c) self.mat_deriv[row, loc * (self.num_vars): (loc + 1) * self.num_vars] = ( - deriv[:, j] * b.comps.loc[cp].factor) + deriv[:, j]) j += 1 - self.vec_res += [b.P - P_res] + self.vec_res += [b.P.val - P_res] row += 1 @@ -1952,7 +1954,7 @@ def solve_determination(self): n += [c.fluid.balance].count(True) for b in self.busses: - n += [b.P_set].count(True) + n += [b.P.val_set].count(True) if n > self.num_vars * len(self.conns.index) + self.num_c_vars: msg = ('You have provided too many parameters: ' + @@ -2009,9 +2011,14 @@ def process_busses(self): :returns: no return value """ for b in self.busses: - b.P = 0 + b.P.val = 0 for cp in b.comps.index: - b.P += cp.bus_func() * b.comps.loc[cp].factor + + bus = b.comps.loc[cp] + val = cp.bus_func(bus) + b.P.val += val + if self.mode == 'design': + bus.P_ref = val def process_components(cols, nw, mode): """ @@ -2036,16 +2043,17 @@ def print_results(self): adjust number of decimal places according to specified units """ - P_res = [x.P for x in self.busses if x.label == 'P_res'] - Q_diss = [x.P for x in self.busses if x.label == 'Q_diss'] - - if len(P_res) != 0 and len(Q_diss) != 0: - if self.nwkinfo: - print('process key figures') - print('eta_th = ' + str(1 - sum(Q_diss) / - (sum(P_res) + sum(Q_diss)))) - print('eps_hp = ' + str(abs(sum(Q_diss)) / sum(P_res))) - print('eps_cm = ' + str(abs(sum(Q_diss)) / sum(P_res) - 1)) +# not used very much, remove it for now +# P_res = [x.P for x in self.busses if x.label == 'P_res'] +# Q_diss = [x.P for x in self.busses if x.label == 'Q_diss'] +# +# if len(P_res) != 0 and len(Q_diss) != 0: +# if self.nwkinfo: +# print('process key figures') +# print('eta_th = ' + str(1 - sum(Q_diss) / +# (sum(P_res) + sum(Q_diss)))) +# print('eps_hp = ' + str(abs(sum(Q_diss)) / sum(P_res))) +# print('eps_cm = ' + str(abs(sum(Q_diss)) / sum(P_res) - 1)) msg = 'Do you want to print the components parammeters?' if hlp.query_yes_no(msg): @@ -2304,9 +2312,15 @@ def save_components(self, path): cp_sort['cp'] = cp_sort.apply(network.get_class_base, axis=1) cp_sort['busses'] = cp_sort.apply(network.get_busses, axis=1, args=(self.busses,)) - cp_sort['bus_factors'] = cp_sort.apply(network.get_bus_factors, - axis=1, - args=(self.busses,)) + cp_sort['bus_param'] = cp_sort.apply(network.get_bus_data, + axis=1, + args=(self.busses, 'param')) + cp_sort['bus_P_ref'] = cp_sort.apply(network.get_bus_data, + axis=1, + args=(self.busses, 'P_ref')) + cp_sort['bus_char'] = cp_sort.apply(network.get_bus_data, + axis=1, + args=(self.busses, 'char')) pd.options.mode.chained_assignment = None for c in cp_sort.cp.unique(): @@ -2358,10 +2372,11 @@ def save_busses(self, fn): df = pd.DataFrame({'id': self.busses}, index=self.busses) df['id'] = df.apply(network.get_id, axis=1) - cols = ['label', 'P', 'P_set'] - for col in cols: - df[col] = df.apply(network.get_props, axis=1, - args=(col,)) + df['label'] = df.apply(network.get_props, axis=1, args=('label',)) + + df['P'] = df.apply(network.get_props, axis=1, args=('P', 'val')) + df['P_set'] = df.apply(network.get_props, axis=1, + args=('P', 'val_set')) df.to_csv(fn, sep=';', decimal='.', index=False, na_rep='nan') @@ -2395,6 +2410,13 @@ def save_characteristics(self, fn): else: continue + df = pd.DataFrame({'id': self.busses}, index=self.busses) + for bus in df.index: + for c in bus.comps.index: + ch = bus.comps.loc[c].char + if ch not in chars: + chars += [ch] + df = pd.DataFrame({'id': chars}, index=chars) df['id'] = df.apply(network.get_id, axis=1) @@ -2444,9 +2466,17 @@ def get_busses(c, *args): busses += [str(bus)[str(bus).find(' at ') + 4:-1]] return busses - def get_bus_factors(c, *args): - factors = [] - for bus in args[0]: - if c.name in bus.comps.index: - factors += [bus.comps.loc[c.name].factor] - return factors + def get_bus_data(c, *args): + items = [] + if args[1] == 'char': + for bus in args[0]: + if c.name in bus.comps.index: + val = bus.comps.loc[c.name][args[1]] + items += [str(val)[str(val).find(' at ') + 4:-1]] + + else: + for bus in args[0]: + if c.name in bus.comps.index: + items += [bus.comps.loc[c.name][args[1]]] + + return items From 207426bf1bb486b94b8785e5897fd90699361e70 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 24 Oct 2018 10:40:19 +0200 Subject: [PATCH 68/80] added bus functionalities for cogeneration unit --- tespy/components/components.py | 235 +++++++++++++++++++++++---------- 1 file changed, 167 insertions(+), 68 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index b9baf4015..9940123a1 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -3436,8 +3436,16 @@ def convergence_check(self, nw): if self.lamb.val < 2 and not self.lamb.is_set: for i in inl: + fuel_set = True if i.fluid.val[self.fuel.val] > 0.75 and not i.m.val_set: - i.m.val_SI = 0.01 + fuel_set = False + if i.fluid.val[self.fuel.val] < 0.75: + air_tmp = i.m.val_SI + + if not fuel_set: + for i in inl: + if i.fluid.val[self.fuel.val] > 0.75: + i.m.val_SI = air_tmp / 25 def initialise_source(self, c, key): r""" @@ -4710,49 +4718,6 @@ def energy_balance(self): return res -# def bus_func(self, bus): -# r""" -# function for use on busses -# -# :returns: val (*float*) - residual value of equation -# -# .. math:: -# -# val = \dot{m}_{fuel} \cdot LHV -# """ -# -# if par == 'TI': -# return self.calc_ti() -# -# elif par == 'P': -# -# elif par == 'Q': -# -# elif par == 'Q1': -# -# elif par == 'Q2': -# -# elif par == 'Qloss': -# -# else: -# -# def bus_deriv(self, bus): -# r""" -# calculate matrix of partial derivatives towards mass flow and fluid -# composition for bus -# function -# -# :returns: mat_deriv (*list*) - matrix of partial derivatives -# """ -# deriv = np.zeros((1, 3, len(self.inl[0].fluid.val) + 3)) -# for i in range(2): -# deriv[0, i, 0] = self.ddx_func(self.bus_func, 'm', i) -# deriv[0, i, 3:] = self.ddx_func(self.bus_func, 'fluid', i) -# -# deriv[0, 2, 0] = self.ddx_func(self.bus_func, 'm', 2) -# deriv[0, 2, 3:] = self.ddx_func(self.bus_func, 'fluid', 2) -# return deriv - def drb_dx(self, dx, pos, fluid): r""" calculates derivative of the reaction balance to dx at components inlet @@ -4822,6 +4787,108 @@ def drb_dx(self, dx, pos, fluid): return deriv + def bus_func(self, bus): + r""" + functions for use on busses + + :returns: val (*float*) - residual value of equation + """ + + if bus.param == 'TI': + return self.calc_ti() + + if bus.param == 'P': + return self.calc_P() + + if bus.param == 'Q': + val = 0 + for j in range(2): + i = self.inl[j] + o = self.outl[j] + val += i.m.val_SI * (o.h.val_SI - i.h.val_SI) + + return val + + if bus.param == 'Q1': + i = self.inl[0] + o = self.outl[0] + + return i.m.val_SI * (o.h.val_SI - i.h.val_SI) + + if bus.param == 'Q2': + i = self.inl[1] + o = self.outl[1] + + return i.m.val_SI * (o.h.val_SI - i.h.val_SI) + + if bus.param == 'Qloss': + return self.Qloss.val + + def bus_deriv(self, bus): + r""" + calculate matrix of partial derivatives towards mass flow and fluid + composition for bus + function + + :returns: mat_deriv (*list*) - matrix of partial derivatives + """ + deriv = np.zeros((1, 7 + self.num_c_vars, + len(self.inl[0].fluid.val) + 3)) + + if bus.param == 'TI': + for i in range(2): + deriv[0, i + 2, 0] = self.ddx_func(self.calc_ti, 'm', i + 2) + deriv[0, i + 2, 3:] = ( + self.ddx_func(self.calc_ti, 'fluid', i + 2)) + deriv[0, 6, 0] = self.ddx_func(self.calc_ti, 'm', 6) + deriv[0, 6, 3:] = self.ddx_func(self.calc_ti, 'fluid', 6) + + if bus.param == 'P': + for i in range(2): + deriv[0, i + 2, 0] = self.ddx_func(self.calc_P, 'm', i + 2) + deriv[0, i + 2, 3:] = ( + self.ddx_func(self.calc_P, 'fluid', i + 2)) + + deriv[0, 6, 0] = self.ddx_func(self.calc_P, 'm', 6) + deriv[0, 6, 3:] = self.ddx_func(self.calc_P, 'fluid', 6) + + if self.P.is_var: + deriv[0, 7 + self.P.var_pos, 0] = ( + self.ddx_func(self.calc_P, 'P', 7)) + + if bus.param == 'Q': + deriv[0, 0, 0] = self.outl[0].h.val_SI - self.inl[0].h.val_SI + deriv[0, 0, 2] = -self.inl[0].m.val_SI + deriv[0, 4, 2] = self.inl[0].m.val_SI + deriv[0, 1, 0] = self.outl[1].h.val_SI - self.inl[1].h.val_SI + deriv[0, 1, 2] = -self.inl[0].m.val_SI + deriv[0, 5, 2] = self.inl[0].m.val_SI + + if bus.param == 'Q1': + deriv[0, 0, 0] = self.outl[0].h.val_SI - self.inl[0].h.val_SI + deriv[0, 0, 2] = -self.inl[0].m.val_SI + deriv[0, 4, 2] = self.inl[0].m.val_SI + + if bus.param == 'Q2': + deriv[0, 1, 0] = self.outl[1].h.val_SI - self.inl[1].h.val_SI + deriv[0, 1, 2] = -self.inl[0].m.val_SI + deriv[0, 5, 2] = self.inl[0].m.val_SI + + if bus.param == 'Qloss': + for i in range(2): + deriv[0, i + 2, 0] = self.ddx_func(self.calc_Qloss, 'm', i + 2) + deriv[0, i + 2, 3:] = ( + self.ddx_func(self.calc_Qloss, 'fluid', i + 2)) + + deriv[0, 6, 0] = self.ddx_func(self.calc_Qloss, 'm', 6) + deriv[0, 6, 3:] = self.ddx_func(self.calc_Qloss, 'fluid', 6) + + if self.P.is_var: + deriv[0, 7 + self.P.var_pos, 0] = ( + self.ddx_func(self.calc_Qloss, 'P', 7)) + + return deriv + def Q1_func(self): r""" calculates the relation of heat output 1 and thermal input from @@ -4997,6 +5064,48 @@ def calc_ti(self): return m * self.lhv + def calc_P(self): + r""" + calculates power from thermal input and + specified characteristic lines + + :returns: res (*float*) - residual value + + .. math:: + + P = \frac{LHV \cdot \dot{m}_{f}} + {f_{TI}\left(\frac{P}{P_{ref}}\right)} + + """ + if self.P_ref.is_set: + expr = self.P.val / self.P_ref.val + else: + expr = 1 + + return self.calc_ti() / self.tiP_char.func.f_x(expr) + + def calc_Qloss(self): + r""" + calculates heat loss from thermal input and + specified characteristic lines + + :returns: res (*float*) - residual value + + .. math:: + + \dot{Q}_{loss} = \frac{LHV \cdot \dot{m}_{f} \cdot + f_{QLOSS}\left(\frac{P}{P_{ref}}\right)} + {f_{TI}\left(\frac{P}{P_{ref}}\right)} + + """ + if self.P_ref.is_set: + expr = self.P.val / self.P_ref.val + else: + expr = 1 + + return (self.calc_ti() * self.Qloss_char.func.f_x(expr) / + self.tiP_char.func.f_x(expr)) + def initialise_fluids(self, nw): r""" calculates reaction balance with given lambda for good generic @@ -5756,12 +5865,10 @@ def kA_func(self): ttd_u = T_i - self.Tamb.val_SI ttd_l = T_o - self.Tamb.val_SI - expr = 1 + fkA = 1 if hasattr(self, 'i_ref'): if self.kA_char.param == 'm': - expr = i[0] / self.i_ref[0] - - fkA = self.kA_char.func.f_x(expr) + fkA = self.kA_char.func.f_x(i[0] / self.i_ref[0]) return (i[0] * (o[2] - i[2]) + self.kA.val * fkA * ( (ttd_u - ttd_l) / math.log(ttd_u / ttd_l))) @@ -6591,19 +6698,15 @@ def kA_func(self): 'temperature difference is negative!') raise MyComponentError(msg) + fkA1 = 1 if self.kA_char1.param == 'm': - expr = i1[0] / self.i1_ref[0] - else: - expr = 1 - - fkA1 = self.kA_char1.func.f_x(expr) + if hasattr(self, 'i1_ref'): + fkA1 = self.kA_char1.func.f_x(i1[0] / self.i1_ref[0]) + fkA2 = 1 if self.kA_char2.param == 'm': - expr = i2[0] / self.i2_ref[0] - else: - expr = 1 - - fkA2 = self.kA_char2.func.f_x(expr) + if hasattr(self, 'i2_ref'): + fkA2 = self.kA_char2.func.f_x(i2[0] / self.i2_ref[0]) return (i1[0] * (o1[2] - i1[2]) + self.kA.val * fkA1 * fkA2 * (T_o1 - T_i2 - T_i1 + T_o2) / @@ -7141,19 +7244,15 @@ def kA_func(self): if T_o1 <= T_i2 and not self.inl[1].T.val_set: T_i2 = T_o1 - 1 + fkA1 = 1 if self.kA_char1.param == 'm': - expr = i1[0] / self.i1_ref[0] - else: - expr = 1 - - fkA1 = self.kA_char1.func.f_x(expr) + if hasattr(self, 'i1_ref'): + fkA1 = self.kA_char1.func.f_x(i1[0] / self.i1_ref[0]) + fkA2 = 1 if self.kA_char2.param == 'm': - expr = i2[0] / self.i2_ref[0] - else: - expr = 1 - - fkA2 = self.kA_char2.func.f_x(expr) + if hasattr(self, 'i2_ref'): + fkA2 = self.kA_char2.func.f_x(i2[0] / self.i2_ref[0]) return (i1[0] * (o1[2] - i1[2]) + self.kA.val * fkA1 * fkA2 * (T_o1 - T_i2 - T_i1 + T_o2) / From 85d317dea8cce8b7f6d149cca90317e6595a2485 Mon Sep 17 00:00:00 2001 From: Paul-Jonas Date: Wed, 24 Oct 2018 13:47:28 +0200 Subject: [PATCH 69/80] chars for vessel and compressor; subsystems modified --- tespy/components/components.py | 55 +++++++++++++++++----------------- tespy/components/subsystems.py | 4 +-- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index a664e8df6..fe846bf7d 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -1488,11 +1488,11 @@ def comp_init(self, nw): method = self.char_map.method self.char_map.func = cmp_char.compressor(method=method) - if self.flow_char.func is None: - method = self.flow_char.method - x = self.flow_char.x - y = self.flow_char.y - self.flow_char.func = cmp_char.characteristics(method=method, + if self.preta_s_char.func is None: + method = self.preta_s_char.method + x = self.preta_s_char.x + y = self.preta_s_char.y + self.preta_s_char.func = cmp_char.characteristics(method=method, x=x, y=y) def component(self): @@ -1503,7 +1503,7 @@ def attr(self): 'igva': dc_cp(min_val=-45, max_val=45, d=1e-2, val=0), 'Sirr': dc_cp(), 'char_map': dc_cc(method='GENERIC'), - 'flow_char': dc_cc(x=[0, 1, 2, 3], y=[1, 1, 1, 1])} + 'preta_s_char': dc_cc(x=[0, 1, 2, 3], y=[1, 1, 1, 1])} def default_offdesign(self): return ['char_map'] @@ -1527,8 +1527,8 @@ def additional_equations(self): if self.char_map.is_set: vec_res += self.char_func().tolist() - if self.flow_char.is_set: - vec_res += self.flow_char_func().tolist() + if self.preta_s_char.is_set: + vec_res += self.preta_s_char_func().tolist() return vec_res @@ -1546,8 +1546,8 @@ def additional_derivatives(self, nw): if self.char_map.is_set: mat_deriv += self.char_deriv() - if self.flow_char.is_set: - mat_deriv += self.flow_char_deriv() + if self.preta_s_char.is_set: + mat_deriv += self.preta_s_char_deriv() return mat_deriv @@ -1691,7 +1691,7 @@ def char_deriv(self): deriv[1, 2 + self.igva.var_pos, 0] = igva[1] return deriv.tolist() - def flow_char_func(self): + def preta_s_char_func(self): r""" equation for characteristics of a compressor """ @@ -1700,17 +1700,17 @@ def flow_char_func(self): expr = (o[1] / i[1]) - if expr > self.flow_char.func.x[-1]: - expr = self.flow_char.func.x[-1] - elif expr < self.flow_char.func.x[0]: - expr = self.flow_char.func.x[0] + if expr > self.preta_s_char.func.x[-1]: + expr = self.preta_s_char.func.x[-1] + elif expr < self.preta_s_char.func.x[0]: + expr = self.preta_s_char.func.x[0] -# print(self.flow_char.func.f_x(expr)) +# print(self.preta_s_char.func.f_x(expr)) return np.array([(self.h_os('post') - i[2]) - (o[2] - i[2]) * - self.flow_char.func.f_x(expr)]) + self.preta_s_char.func.f_x(expr)]) - def flow_char_deriv(self): + def preta_s_char_deriv(self): r""" calculates the derivatives for the characteristics @@ -1736,10 +1736,10 @@ def flow_char_deriv(self): num_fl = len(self.inl[0].fluid.val) mat_deriv = np.zeros((1, 2 + self.num_c_vars, num_fl + 3)) - mat_deriv[0, 0, 1] = self.ddx_func(self.flow_char_func, 'p', 0) - mat_deriv[0, 1, 1] = self.ddx_func(self.flow_char_func, 'p', 1) - mat_deriv[0, 0, 2] = self.ddx_func(self.flow_char_func, 'h', 0) - mat_deriv[0, 1, 2] = self.ddx_func(self.flow_char_func, 'h', 1) + mat_deriv[0, 0, 1] = self.ddx_func(self.preta_s_char_func, 'p', 0) + mat_deriv[0, 1, 1] = self.ddx_func(self.preta_s_char_func, 'p', 1) + mat_deriv[0, 0, 2] = self.ddx_func(self.preta_s_char_func, 'h', 0) + mat_deriv[0, 1, 2] = self.ddx_func(self.preta_s_char_func, 'h', 1) return mat_deriv.tolist() @@ -1773,14 +1773,14 @@ def convergence_check(self, nw): if not i[0].h.val_set and o[0].h.val_SI < i[0].h.val_SI: i[0].h.val_SI = o[0].h.val_SI * 0.9 -# if self.flow_char.is_set: +# if self.preta_s_char.is_set: # expr = i[0].m.val_SI * v_mix_ph(i[0].to_flow()) # -# if expr > self.flow_char.func.x[-1] and not i[0].m.val_set: -# i[0].m.val_SI = self.flow_char.func.x[-1] / v_mix_ph( +# if expr > self.preta_s_char.func.x[-1] and not i[0].m.val_set: +# i[0].m.val_SI = self.preta_s_char.func.x[-1] / v_mix_ph( # i[0].to_flow()) -# elif expr < self.flow_char.func.x[1] and not i[0].m.val_set: -# i[0].m.val_SI = self.flow_char.func.x[0] / v_mix_ph( +# elif expr < self.preta_s_char.func.x[1] and not i[0].m.val_set: +# i[0].m.val_SI = self.preta_s_char.func.x[0] / v_mix_ph( # i[0].to_flow()) def initialise_source(self, c, key): @@ -4237,7 +4237,6 @@ def comp_init(self, nw): w = self.pr_char.x v = self.pr_char.y - print(w, v) self.pr_char.func = cmp_char.characteristics(method=method, x=w, y=v) diff --git a/tespy/components/subsystems.py b/tespy/components/subsystems.py index 747ef5307..099a44f85 100644 --- a/tespy/components/subsystems.py +++ b/tespy/components/subsystems.py @@ -534,8 +534,8 @@ def create_comps(self): num_inter=self.num_i) self.outlet = cmp.subsys_interface(label=self.label + '_outlet', num_inter=self.num_o) - self.eva = cmp.heat_exchanger(label=self.label + '_eva') - self.sup = cmp.heat_exchanger(label=self.label + '_sup') + self.eva = cmp.heat_exchanger(label=self.label + '_eva', offdesign=['zeta1', 'zeta2']) + self.sup = cmp.heat_exchanger(label=self.label + '_sup', offdesign=['zeta1', 'zeta2']) def set_comps(self): From a3f461253c3d9b52cc82d2c888a306f3543f91fc Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 24 Oct 2018 17:33:54 +0200 Subject: [PATCH 70/80] updated What's new --- doc/whats_new.rst | 1 + doc/whats_new/v0-0-5.rst | 58 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 doc/whats_new/v0-0-5.rst diff --git a/doc/whats_new.rst b/doc/whats_new.rst index 5b09a7a09..d117b6f39 100644 --- a/doc/whats_new.rst +++ b/doc/whats_new.rst @@ -8,6 +8,7 @@ Discover noteable new features and improvements in each release :local: :backlinks: top +.. include:: whats_new/v0-0-5.rst .. include:: whats_new/v0-0-4.rst .. include:: whats_new/v0-0-3.rst .. include:: whats_new/v0-0-2.rst diff --git a/doc/whats_new/v0-0-5.rst b/doc/whats_new/v0-0-5.rst new file mode 100644 index 000000000..3ba37ba60 --- /dev/null +++ b/doc/whats_new/v0-0-5.rst @@ -0,0 +1,58 @@ +v0.0.5 (October, 25, 2018) +++++++++++++++++++++++ + +New Features +############ +- added new component: motoric cogeneration unit (`79a1177 `_). An example is provided `here `_. +- improved fluid property checks (`8adc76c `_) +- added bus characteristics for modeling variable efficiencys (e. g. for generator, motor, boiler) (`79a1177 `_). +- isentropic efficiency characteristic for compressor linked to pressure ratio () +- added volumetric flow specification (`63db64d `_) + +Documentation +############# +- adapted documentation and (`example code `_) in regard of new features. +- fixed some typos in documentation + +Parameter renaming +################## + +**compressor** +- vigv -> igva (inlet guide vane angle) + +**simple heat exchanger** +- t_a -> Tamb (ambient temperature) + +**solar collector** +- t_a -> Tamb (ambient temperature) + +Testing +####### + +Bug fixes +######### +- fixed a bug in the function v_mix_ph (specific volume for gas mixtures) (`d487381 `_) +- fixed compressor derivatives for usage with custom variables (`71cae48 `_) +- adjusted error messages (`cccd89c `_) +- removed unecessary loop (`187505b `_ +- fixed attribute handling in subsystem: condenser with subcooler (`2c926bb `_) + +Other changes +############# +- remodeled the characteristic map for compressors: if not specified, igva is assumed to be 0° (`2425a77 `_) +- redesigned the printouts for component parameters (`9465be6 `_, + `b2c0897 `_, + `cbbc1a1 `_, + `1e55e36 `_, + `2e795c2 `_) +- custom variables are available for (`977a5be `_) + - turbomachines + - vessels + - simple heat exchangers (as well as pipes and solar collctors) + - cogeneration unit + +Contributors +############ + +- Francesco Witte +- Paul Hansen From 0aefb6f63c7929aa258b59a7e19d4fce117ed222 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 24 Oct 2018 17:50:09 +0200 Subject: [PATCH 71/80] removed more residuals --- tespy/components/subsystems.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tespy/components/subsystems.py b/tespy/components/subsystems.py index b7d7c34f7..1b0bbff6b 100644 --- a/tespy/components/subsystems.py +++ b/tespy/components/subsystems.py @@ -405,13 +405,8 @@ def set_comps(self): self.desup.set_attr(pr2=self.pr2_desup) self.condenser.set_attr(pr1=self.pr1_cond) self.condenser.set_attr(pr2=self.pr2_cond) -<<<<<<< HEAD self.subcooler.set_attr(pr1=self.pr1_subc) self.subcooler.set_attr(pr2=self.pr2_subc) -======= - self.subcooler.set_attr(pr1=self.pr1_cond) - self.subcooler.set_attr(pr2=self.pr2_cond) ->>>>>>> features/characteristics self.vessel.set_attr(pr=self.pr_v) def create_conns(self): From 74e02a20ae769f35135ff30e6044a55dd16a1bef Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 24 Oct 2018 18:12:35 +0200 Subject: [PATCH 72/80] fixed typo for eta_s_char.func of compressor --- tespy/components/components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tespy/components/components.py b/tespy/components/components.py index 408eaa037..d4cdafbb0 100644 --- a/tespy/components/components.py +++ b/tespy/components/components.py @@ -1723,7 +1723,7 @@ def eta_s_char_func(self): return np.array([(self.h_os('post') - i[2]) - (o[2] - i[2]) * - self.flow_char.func.f_x(o[1] / i[1])]) + self.eta_s_char.func.f_x(o[1] / i[1])]) def eta_s_char_deriv(self): r""" From d15b814f13192a86c0420bcea6f72f6092da6e67 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 25 Oct 2018 09:20:34 +0200 Subject: [PATCH 73/80] adjusted heat pump example --- doc/api/_images/heat_pump_example.svg | 641 ++++++++++++++++++++++++++ doc/getting_started/heat_pump.rst | 3 +- 2 files changed, 643 insertions(+), 1 deletion(-) create mode 100644 doc/api/_images/heat_pump_example.svg diff --git a/doc/api/_images/heat_pump_example.svg b/doc/api/_images/heat_pump_example.svg new file mode 100644 index 000000000..efe558cdc --- /dev/null +++ b/doc/api/_images/heat_pump_example.svg @@ -0,0 +1,641 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sink ambient + consumerback flow + consumerfeed flow + sink ambient + sourceambient + + + + + coolant in + coolant out + condenser + vessel + drum withevaporator + superheater + compressor train + + + + + + fan + + + diff --git a/doc/getting_started/heat_pump.rst b/doc/getting_started/heat_pump.rst index 3c6994190..73fe01a5a 100644 --- a/doc/getting_started/heat_pump.rst +++ b/doc/getting_started/heat_pump.rst @@ -5,9 +5,10 @@ COP of a heat pump This example is based on the :ref:`heat pump tutorial ` and shows how to calculate the COP of a heat pump at different ambient temperatures and different loads of the plant. The idea is very similar to the :ref:`CHP example `, thus you should have a look at the tutorial and the CHP example first. +Be aware, that there are slight changes regarding the topology of the system from the tutorial to this example. Also, the heat for evaporation is extracted from ambient air instead of water. You will find the source code `in this repository `_. -.. figure:: api/_images/heat_pump.svg +.. figure:: api/_images/heat_pump_example.svg :align: center Figure: Topology of the heat pump unit. From 8b5ab52d1d478dcf6a1a1646f110a011be5a178c Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 25 Oct 2018 09:44:50 +0200 Subject: [PATCH 74/80] added cogeneration unit example --- doc/getting_started.rst | 5 +-- doc/getting_started/cogeneration_unit.rst | 37 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 doc/getting_started/cogeneration_unit.rst diff --git a/doc/getting_started.rst b/doc/getting_started.rst index 98a220194..b9050db57 100644 --- a/doc/getting_started.rst +++ b/doc/getting_started.rst @@ -7,7 +7,7 @@ Getting started Examples ======== -In the example section we provide a variety of TESPy applications: +In the example section we provide a variety of TESPy applications, amongst others: * a very basic model of the clausius rankine process, * the calculation of backpressure lines of a chp at different loads and feed flow temperature levels, @@ -22,8 +22,9 @@ You can find all examples in the `tespy examples github repository `). + +.. figure:: api/_images/cogeneration_unit.svg + :align: center + + Figure: Topology of a cogeneration unit. + +The characteristics take the power ratio (:math:`\frac{P}{P_{ref}}` as argument. For a design case simulation the power ratio is always assumed to be equal to 1. +For offdesign calculation TESPy will automatically take the rated power from the design case and use it to determine the power ratio. Still it is possible to specify the rated power manually, if you like. + +In contrast to other components, the cogeneration unit has several busses, which are accessible by specifying the corresponding bus parameter: +- TI (thermal input), +- Q (total heat output), +- Q1 and Q2 (heat output 1 and 2), +- QLOSS (heat losses) and +- P (power output). + +If you want to add a bus to the example from the tespy_examples repository, your code could look like this: + +.. code-block:: python + + chp = cmp.cogeneration_unit('chp') + + bus = con.bus('some label') + # for thermal input + bus.add_comps({'c': chp, 'p': 'TI'}) + # for power output + bus.add_comps({'c': chp, 'p': 'P'}) + +Enjoy fiddling around with the source code of the `cogeneration unit `_ in the examples repository! From 75ace565a940502d34547c3f0a6cd94f4edc19cd Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 25 Oct 2018 09:45:17 +0200 Subject: [PATCH 75/80] completed What's New section --- doc/whats_new/v0-0-5.rst | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/whats_new/v0-0-5.rst b/doc/whats_new/v0-0-5.rst index 3ba37ba60..090db66db 100644 --- a/doc/whats_new/v0-0-5.rst +++ b/doc/whats_new/v0-0-5.rst @@ -4,15 +4,15 @@ v0.0.5 (October, 25, 2018) New Features ############ - added new component: motoric cogeneration unit (`79a1177 `_). An example is provided `here `_. -- improved fluid property checks (`8adc76c `_) +- improved fluid property checks (`8adc76c `_). - added bus characteristics for modeling variable efficiencys (e. g. for generator, motor, boiler) (`79a1177 `_). -- isentropic efficiency characteristic for compressor linked to pressure ratio () -- added volumetric flow specification (`63db64d `_) +- isentropic efficiency characteristic for compressor linked to pressure ratio (`85d317d `_). +- added volumetric flow specification (`63db64d `_). Documentation ############# - adapted documentation and (`example code `_) in regard of new features. -- fixed some typos in documentation +- fixed some typos in documentation. Parameter renaming ################## @@ -31,25 +31,25 @@ Testing Bug fixes ######### -- fixed a bug in the function v_mix_ph (specific volume for gas mixtures) (`d487381 `_) -- fixed compressor derivatives for usage with custom variables (`71cae48 `_) -- adjusted error messages (`cccd89c `_) -- removed unecessary loop (`187505b `_ -- fixed attribute handling in subsystem: condenser with subcooler (`2c926bb `_) +- fixed a bug in the function v_mix_ph (specific volume for gas mixtures) (`d487381 `_). +- fixed compressor derivatives for usage with custom variables (`71cae48 `_). +- adjusted error messages (`cccd89c `_). +- removed unecessary loop (`187505b `_). +- fixed attribute handling in subsystem: condenser with subcooler (`2c926bb `_). Other changes ############# -- remodeled the characteristic map for compressors: if not specified, igva is assumed to be 0° (`2425a77 `_) +- remodeled the characteristic map for compressors: if not specified, igva is assumed to be 0° (`2425a77 `_). - redesigned the printouts for component parameters (`9465be6 `_, `b2c0897 `_, `cbbc1a1 `_, `1e55e36 `_, `2e795c2 `_) -- custom variables are available for (`977a5be `_) - - turbomachines - - vessels - - simple heat exchangers (as well as pipes and solar collctors) - - cogeneration unit +- custom variables are available for (`977a5be `_): + - turbomachines, + - vessels, + - simple heat exchangers (as well as pipes and solar collctors) and + - cogeneration unit. Contributors ############ From db1f2dcb9c248732de8aca311e4cedc1836e4153 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 25 Oct 2018 11:35:50 +0200 Subject: [PATCH 76/80] adjusted version number --- doc/conf.py | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index c9d82f572..8f0fde65a 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -63,9 +63,9 @@ # built documents. # # The short X.Y version. -version = '0.0.4' +version = '0.0.5' # The full version, including alpha/beta/rc tags. -release = '0.0.4 dev' +release = '0.0.5 master' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 8128fb5cc..51cdecb16 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ def read(fname): setup(name='TESPy', - version='0.0.4 dev', + version='0.0.5', description='Thermal Engineering Systems in Python (TESPy)', url='http://github.com/oemof/tespy', author='Francesco Witte', From 08d2e5f379c9fcdde1f7da3ab09298ca6640597c Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 25 Oct 2018 12:53:40 +0200 Subject: [PATCH 77/80] updated using_tespy --- doc/using_tespy.rst | 168 ++++++++++++++++++++++++++------------------ 1 file changed, 100 insertions(+), 68 deletions(-) diff --git a/doc/using_tespy.rst b/doc/using_tespy.rst index cc32cb4d3..f4dd94c68 100644 --- a/doc/using_tespy.rst +++ b/doc/using_tespy.rst @@ -50,7 +50,7 @@ You need to specify a list of the fluids you need for the calculation in your pl my_plant = nwk.network(fluids=fluid_list) On top of that, it is possible to specify a unit system and value ranges for the networks variables. If you do not specify these, TESPy will use SI-units. -The specification of the value range is used to improve convergence stability. +The specification of the value range is used to improve convergence stability, in case you are dealing with fluid mixtures, e. g. using a combustion chamber. .. code-block:: python @@ -91,6 +91,7 @@ If two components are connected to each other the fluid properties at the source It is possible to set the properties on each connection in a similar way as parameters are set for components. You may specify: * mass flow* (m), + * volumetric flow (v), * pressure* (p), * enthalpy* (h), * temperature* (T), @@ -164,15 +165,15 @@ The TESPy network contains all data of your plant, which in terms of the calcula * the mass fractions of the network's fluids. The solver will solve for these variables. As stated in the introduction the list of fluids is passed to your network on creation. -You should **always make use of the value ranges** of the system variables, like in the code block below. This improves the stability of the algorithm. Try to fit the boundaries as tight as possible, -for instance, if you kwow that the maximum pressure in the system will be at 10 bar, use it as upper boundary. You should **always state ranges for pressure and enthalpy**, temperature is optional. -Value ranges for mass flow and fluid composition are not necessary, as these are handeled automatically. +If your system includes fluid mixtures, you should **always make use of the value ranges** for the system variables. This improves the stability of the algorithm. Try to fit the boundaries as tight as possible, +for instance, if you kwow that the maximum pressure in the system will be at 10 bar, use it as upper boundary. +Value ranges for pure fluids are not required as these are dealt with automatically. .. code-block:: python from tespy import nwk - fluid_list = ['air', 'water'] + fluid_list = ['CO2', 'H2O', 'N2', 'O2', 'Ar'] my_plant = nwk.network(fluids=fluid_list) my_plant.set_attr(p_unit='bar', h_unit='kJ / kg') my_plant.set_attr(p_range=[0.05, 10], h_range=[15, 2000]) @@ -217,7 +218,7 @@ This starts the initialisation of your network and proceeds to its calculation. There are two calculation modes available (:code:`'design'` and :code:`'offdesign'`), which are explained in the subsections below. If you choose :code:`offdesign` as calculation mode the specification of a design_file is mandatory. -The usage of an initialisation file is always optional but highly recommended, as the convergence of the solution process will be improved. +The usage of an initialisation file is always optional but highly recommended, as the convergence of the solution process will be improved, if you provide good starting values. If do not specify an :code:`init_file`, the initialisation from .csv-file will be skipped. Parallel computation can improve the calculation velocity of very large networks or networks with a large number of fluids (if used for mixtures). **Parallel code execution does not work on windows at the moment!** :code:`init_only=True` usually is used for debugging. You could use this feature to export a not solved network, if you want to do the parametrisation in .csv-files rather than your python script. @@ -263,31 +264,33 @@ if you want to replace the enthalpy with the temperature for your offdesign. **T The table below contains frequently used offdesign parameters of the available components. -======================= ====================== =================================================== +======================= ====================== ========================================================= component parameter affects -======================= ====================== =================================================== +======================= ====================== ========================================================= vessel zeta pressure drop ------------------------ ---------------------- --------------------------------------------------- +----------------------- ---------------------- --------------------------------------------------------- pipe | zeta | pressure drop | k_s, D, L | pressure drop (via dimensions and roughness) | kA, t_a | heat flux (using constant ambient temperature) ------------------------ ---------------------- --------------------------------------------------- +----------------------- ---------------------- --------------------------------------------------------- simple heat exchanger see pipe ------------------------ ---------------------- --------------------------------------------------- +----------------------- ---------------------- --------------------------------------------------------- heat exchanger | zeta1 | pressure drop hot side | zeta2 | pressure drop cold side | kA | heat flux ------------------------ ---------------------- --------------------------------------------------- +----------------------- ---------------------- --------------------------------------------------------- pump char isentropic efficiency ------------------------ ---------------------- --------------------------------------------------- +----------------------- ---------------------- --------------------------------------------------------- turbine | cone | pressure drop, volumetric flow | char | isentropic efficiency ------------------------ ---------------------- --------------------------------------------------- - compressor | char | mass flow, pressure rise, isentropic efficiency - | vigv :sup:`1` | see above, one arbitrary parameter less -======================= ====================== =================================================== +----------------------- ---------------------- --------------------------------------------------------- + compressor | char_map | mass flow, pressure rise, isentropic efficiency + | igva :sup:`1` | shifting mass flow at (nearly) constant pressure rise +======================= ====================== ========================================================= -1: When setting the vigv angle the characteristic map will be used for a specific vigv angle. The vigv angle is a result of the calculation, if you use the characteristic map only. +.. note:: + When setting the inlet guide vane angle (igva) the characteristic map will be used for this specific angle. If you do not specify the angle, a value of 0° is assumed. + However, the igva may also be a variable of the system, e. g. if you specify pressure rise and mass flow, resulting in the appropriate inlet guide vane angle. Solving ------- @@ -316,12 +319,12 @@ The initialisation is performed in the following steps. * fluid property initialisation, * initialisation from .csv (preprocessing with design_file for offdesign case and setting starting values with init_file). -The network check is used to find errors in the network topology, the calulation can not start without a successful check. The component initialisation is important for components using charactersitcs and the combustion chamber, +The network check is used to find errors in the network topology, the calulation can not start without a successful check. The component initialisation is important for components using characteristics and the combustion chamber, a preprocessing of some parameters is required. The preprocessing for the components is performed in the :code:`comp_init` method of the components. You will find the methods in the :py:class:`components module `. The design/offdesign switch is described in the network setup section. **The fluid propagation is a very important step in the initialisation:** Often, you will specify the fluid at one point of the network only, thus all other connections are missing an initial information on the fluid vector, -if you are not using an init_file. Also, you do not want to state a starting value for the fluid vector at every point of the network. The fluid propagation will push/pull the specified fluid through the network. +if you are not using an init_file. Also, you do not need to state a starting value for the fluid vector at every point of the network. The fluid propagation will push/pull the specified fluid through the network. If you are using combustion chambers these will be starting points and a generic flue gas composition will be calculated prior to the propagation. .. note:: @@ -403,19 +406,19 @@ One of the main downsides of the Newton–Raphson method is that the initial ste for example mass fractions smaller than 0 and larger than 1 or negative pressure. Also, the large stepwidth can adjust enthalpy or pressure to quantities that are not covered by the fluid property databases. This would cause an inability e. g. to calculate a temperature from pressure and enthalpy in the next iteration of the algorithm. In order to improve convergence stability, we have added a convergence check. -**The convergence check manipulates the system variables after the increment has been added** (if the system variable's value is not user specified). This manipulation has four steps, the first is always applied: +**The convergence check manipulates the system variables after the increment has been added** (if the system variable's value is not user specified). This manipulation has four steps, the first two are always applied: * cutting off mass fractions smaller than 0 and larger than 1: This way a mass fraction of a single fluid components never exceeds these boundaries. + * check, wheather the fluid properties of pure fluids are within the available ranges of CoolProp and readjust the values if not. -The next three steps are applied, if the user did not specify an init_file and the iteration count is lower than 3, thus in the first three iteration steps of the algorithm only. In other cases this convergence check is skipped. +The next two steps are applied, if the user did not specify an init_file and the iteration count is lower than 3, thus in the first three iteration steps of the algorithm only. In other cases this convergence check is skipped. - * Check, if the fluid properties (pressure, enthalpy and temperature) are within the user specified boundaries (:code:`p_range, h_range, T_range`) and if not, cut off higher/lower values. + * Fox mixtures: check, if the fluid properties (pressure, enthalpy and temperature) are within the user specified boundaries (:code:`p_range, h_range, T_range`) and if not, cut off higher/lower values. * Check the fluid properties of the connections based on the components they are connecting. E. g. check if the pressure at the outlet of a turbine is lower than the pressure at the inlet or if the flue gas composition at a combustion chamber's outlet is within the range of a "typical" flue gas composition. If there are any violations, the corresponding variables are manipulated. If you want to look up, what exactly the convergence check for a specific component does, look out for the :code:`convergence_check` methods in the tespy.components.components module. - * A second check of the fluid properties towards the specified boundaries to cut off bad values generated by the component convergence check. -In most cases the algorithm has found a near enough solution after the third iteration, further checks are usually not required. +In a lot of different tests the algorithm has found a near enough solution after the third iteration, further checks are usually not required. Troubleshooting +++++++++++++++ @@ -423,11 +426,11 @@ Troubleshooting In this section we show you how you can troubleshoot your calculation and list up common mistakes. First of all, make sure your network topology is set up correctly, TESPy will prompt an Error, if not. -Also, TESPy will prompt an error, if you did not provide enough or if you provide too many parameters for your calculation, but at the moment you will not be given an information which parameters are under- or overdetermined. +Also, TESPy will prompt an error, if you did not provide enough or if you provide too many parameters for your calculation, but you will not be given an information which specific parameters are under- or overdetermined. .. note:: Always keep in mind, that the system has to find a value for mass flow, pressure, enthalpy and the fluid mass fractions. Try to build up your network step by step and have in mind, what parameters will be determined - by adding an additional component without any parametrisation. This way, you can easily find out, which parameters are still to be determined. + by adding an additional component without any parametrisation. This way, you can easily determine, which parameters are still to be specified. When using multiple fluids in your network, e. g. water, air and methane and at some point you want to have water only, you still need to specify the mass fractions for both air and methane (although beeing zero) at that point. Also, setting :code:`fluid={water: 1}, fluid_balance=True` will still not be sufficent, as the fluid_balance parameter adds only one equation to your system. @@ -468,7 +471,7 @@ A postprocessing is performed automatically after the calculation finished. You * save the results in a .csv-file (:code:`nw.save('savename')`). You can print the components and its properties to the prompt and the connections and its properties as well. If you choose to save your results in a .csv-file, open the file and look up the **connection parameters in the results file**. -**If you want to export up the parameters of the components, too, you have to save the network structure.** In order to do this, add this line to your code: :code:`nw.save('savename', structure=True)`. +**If you want to export the parameters of the components, too, you have to save the network structure.** In order to do this, add this line to your code: :code:`nw.save('savename', structure=True)`. In both cases TESPy will create a new folder 'savename' in your working directory containing the results.csv file and subfolders with the component results. In order to perform calculations based on your results, you can access all components' and connections' parameters: @@ -487,10 +490,6 @@ Use this code for connection parameters: mass_flow = myconn.m.val # value in specified network unit mass_flow_SI = myconn.m.val_SI # value in SI unit mass_fraction_oxy = myconn.fluid.val['O2'] # for the mass fraction of oxygen - -Additionally TESPy can calculate cycle process performance figures for you, if you define busses with the labels 'P_res' (components with power input/output) and 'Q_diss' -(add components with heat input/output for total dissipated heat) in your network: Thermal efficiency for a right-handed process, COP for left-handed processes. - .. _using_tespy_components_label: @@ -513,8 +512,10 @@ More information on the components can be gathered from the code documentation. * :py:class:`Pump ` (:py:meth:`equations `) * :py:class:`Compressor ` (:py:meth:`equations `) * :py:class:`Turbine ` (:py:meth:`equations `) -- :py:class:`Combustion chamber ` (:py:meth:`equations `) -- :py:class:`Combustion chamber stoichiometric ` (:py:meth:`equations `) +- Components with combustion + * :py:class:`Combustion chamber ` (:py:meth:`equations `) + * :py:class:`Combustion chamber stoichiometric ` (:py:meth:`equations `) + * :py:class:`Cogeneration unit ` (:py:meth:`equations `) - Heat exchangers * :py:class:`Heat exchanger ` (:py:meth:`equations `) * :py:class:`Condenser ` (:py:meth:`equations `) @@ -553,7 +554,7 @@ Parameters he.set_attr(kA=np.nan) he.kA.set_attr(is_set=False) - # to come in TESPy v0.0.4 + # custom variables pipe = cmp.pipe('my pipe') # make diameter variable of system @@ -583,7 +584,7 @@ Characteristics # specify data container (custom interpolation points x and y) x = np.array([0, 0.5, 1, 2]) y = np.array([0, 0.8, 1, 1.2]) - he.set_attr(kA_char1=hlp.dc_cc(method='EVA_HOT', param='m', x=x, y=y)) + he.set_attr(kA_char1=hlp.dc_cc(param='m', x=x, y=y)) Component characteristics @@ -591,11 +592,25 @@ Component characteristics Characteristics are available for the following components and parameters: -- pump (isentropic efficiency, not customizable at the moment, pressure rise vs. volumetric flow characteristic, customizable) -- compressor (component map for isentropic efficiency and pressure rise, not customizable at the moment) -- turbine (isentropic efficiency, various predefined methods and specification parameters, customizable) -- heat exchangers (heat transfer coefficient, various predefined types, mass flows as specification parameters, customizable) -- simple heat exchangers (e. g. pipe, see heat exchangers) +- pump + * eta_s_char: isentropic efficiency vs. volumetric flow rate, not customizable at the moment + * flow_char: pressure rise vs. volumetric flow characteristic, customizable +- compressor + * char_map: component map for isentropic efficiency and pressure rise, not customizable at the moment + * eta_s_char: isentropic efficiency vs. pressure ratio, customizable +- turbine + * eta_s_char: isentropic efficiency vs. isentropic enthalpy difference/pressure ratio/volumetric flow/mass flow, customizable +- vessel + * pr_char: pressure ratio vs. inlet pressure, customizable +- heat exchangers: + * kA1_char, kA2_char: heat transfer coefficient, various predefined types, mass flows as specification parameters, customizable +- simple heat exchangers + * kA_char: e. g. pipe, see heat exchangers +- cogeneration unit + * tiP_char: thermal input vs. power ratio, customizable + * Q1_char: heat output 1 vs. power ratio, customizable + * Q2_char: heat output 2 vs. power ratio, customizable + * Qloss_char: heat loss vs. power ratio, customizable There are two ways for specifying the customizable characteristic line of a component. You can specify the method directly by stating the methods name or you define the whole data container for this parameter. @@ -605,22 +620,28 @@ You can specify the method directly by stating the methods name or you define th from tespy import cmp, hlp turb = cmp.turbine('turbine') - # method specification + # method specification (default characteristic line "TRAUPEL") turb.set_attr(eta_s_char='TRAUPEL') # data container specification turb.set_attr(eta_s_char=hlp.dc_cc(method='TRAUPEL', param='dh_s', x=None, y=None)) - # defining a custom line + # defining a custom line (this line overrides the default characteristic line, method does not need to be specified) x = np.array([0, 1, 2]) y = np.array([0.95, 1, 0.95]) - turb.set_attr(eta_s_char=hlp.dc_cc(method='TRAUPEL', param='dh_s', x=x, y=y) + turb.set_attr(eta_s_char=hlp.dc_cc(param='dh_s', x=x, y=y) # heat exchanger analogously he = cmp.heat_exchanger('evaporator') he.set_attr(kA_char1='EVA_HOT') he.set_attr(kA_char2='EVA_COLD') -Turbines, pumps (isentropic efficiency characteristic) and heat exchangers are supplied with default characteristic lines, which can be found in the :py:class:`documentation `. +Default characteristic lines are available for +- turbines, +- pumps, +- compressor, +- heat exchangers and +- cogeneration untis, +which can be found in the :py:class:`documentation `. Custom components ----------------- @@ -629,7 +650,6 @@ If required, you can add custom components. These components should inherit from In order to do that, create a python file in your working directory and import the tespy.components.components module. The most important methods are - :code:`attr(self)`, -- :code:`attr_prop(self)`, - :code:`inlets(self)`, - :code:`outlets(self)`, - :code:`equations(self)`, @@ -651,15 +671,11 @@ The starting lines of your file would look like this: Attributes ^^^^^^^^^^ -The attr method must return a list with strings in it. These are the attributes you can specify when you want to parametrize your component. -The attr_prop method returns a dictionary with the same keys as the elements in the attr method. The values for each key are the type of data_container this parameter should hold. +The attr method returns a dictionary with the attributes you are able to specify when you want to parametrize your component as keys. The values for each key are the type of data_container this parameter should hold. .. code:: python - - def attr(self): - return ['par1', 'par2'] - def attr_prop(self): + def attr(self): return {'par1': dc_cp(), 'par2': dc_cc()} @@ -706,7 +722,7 @@ The connections connected to your component are available as a list in :code:`se vec_res = [] vec_res += [self.inl[0].m.val_SI - self.outl[0].m.val_SI] - vec_res += [self.inl[0].p.val_SI - self.outl[0].p.val_SI - self.dp()] + vec_res += [self.inl[0].p.val_SI - self.outl[0].p.val_SI - self.dp.val] The equations are added to a list one after another, which will be returned at the end. @@ -899,6 +915,7 @@ Parametrisation As mentioned in the introduction, for each connection you can specify the following parameters: * mass flow* (m), + * volumetric flow (v), * pressure* (p), * enthalpy* (h), * temperature* (T), @@ -958,39 +975,54 @@ Busses ------ Busses can be used to add up the power of different turbomachinery or to add up heat flow of different heat exchangers within your network. -The handling is very similar to connections and components. You need to add components to your busses as a list containing the component object and a factor, the power of the component will be multiplied with. -Do not forget to add the busses to you network. +The handling is very similar to connections and components. You need to add components to your busses as a dictionary containing at least the instance of your component. +Additionally you may provide a characteristic line, linking the ratio of actual heat flow/power to referenced heat flow/power to a factor the actual heat flow/power of the component is multiplied with on the bus. +For instance, you can provide a characteristic line of an electrical generator or motor for a variable conversion efficiency. The referenced value (P_ref) is retrieved by the design point of your system. +Offdesign calculations use the referenced value from your system design point for the characteristic line. In design case, the heat flow/power ratio thus will be equal to 1. + +.. note:: + The available keywords for the dictionary are + - 'c' for the component instance, + - 'p' for the parameter (the cogeneration unit has different parameters, have a look at the :ref:`cogeneration unit example `), + - 'P_ref' for the reference heat flow/power value of the component and + - 'char' for the characteristic line. + + There are different specification possibilites: + - If you specify the component only, the parameter will be default (not working with cogeneration unit) and the conversion factor of the characteristic line will be 1 for every load. + - If you specify a numeric value for char, the conversion factor will be that value for every load. + - If you want to specify a characteristic line, you need to provide a :py:class:`Source ` object. This can be used for easy post processing, e. g. to calculate thermal efficiency or you can build up relations between components in your network. -If you want to use the busses for postprocessing only, you do not specify the sum of the power or heat flux on your bus. -If you set a value for P (equal parameter for heat flux or power), an additional equation will be added to your network. +If you want to use the busses for postprocessing only, you must not specify the sum of the power or heat flux on your bus. +If you set a value for P (equal parameter for heat flux or power), an additional equation will be added to your network. This way the total heat flow/power of the bus will equal to the specified value. This could be useful, e. g. for establishing relations between different components, for instance when using a steam turbine powered feed water pump. In the code example the power of the turbine and the feed water pump is added up and set to zero, as the turbines and feed water pumps power have to be equal in absolute value but have different sign. The sign can be manipulated, e. g. in order to design two turbines with equal power output. +Do not forget to add the busses to you network. .. code-block:: python - from tespy import nwk, con + from tespy import nwk, con, cmp_char ... fwp_bus = con.bus('feed water pump', P=0) # set a value for the total power on this bus. - fwp_bus.add_comps([turbine_fwp, 1], [fwp, 1]) + fwp_bus.add_comps({'c': turbine_fwp}, {'c': fwp}) turbine_bus = con.bus('turbines', P=0) # set a value for the total power on this bus - turbine_bus.add_comps([turbine_hp, 1], [turbine_lp, -1]) + turbine_bus.add_comps({'c': turbine_hp}, {'c': turbine_hp, 'char': -1}) # the values for the busses power can be altered by using .set_attr() - power = con.bus('power output') # bus for postprocessing, no power (or heat flux) specified - power.add_comps([turbine_hp, 1], [turbine_lp, 1]) + power = con.bus('power output') # bus for postprocessing, no power (or heat flux) specified but with variable conversion efficiency + x = np.array([0.2, 0.4, 0.6, 0.8, 1.0, 1.1]) + y = np.array([0.85, 0.93, 0.95, 0.96, 0.97, 0.96]) + gen = cmp_char.characteristics(x=x, y=y) + power.add_comps({'c': turbine_hp, 'char': gen}, {'c': turbine_lp, 'char': gen}) - my_network.add_busses(fwp_bus, turbine_bus, power) + chp = con.bus('chp power') # bus for cogeneration unit power + chp.add_comps({'c': cog_unit, 'p': 'P', 'char': gen}) -Two labels for busses have a predefined function in the postprocessing analysis: 'P_res' and 'Q_diss'. -If you specify these labels for your busses, 'P_res' will be interpreted as the total power of your process and 'Q_diss' as total amount of dissipated heat flow (from the process, not internally). -Given these key figures, thermal efficiency or COP will be calculated and an entropy analysis for your systems components will be performed.* - -*Planned feature, not implemented yet! + my_network.add_busses(fwp_bus, turbine_bus, power) How can TESPy contribute to your energy system calculations? From 1d67f31d1719d78f70de52e35061ddd11580c1df Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 25 Oct 2018 12:58:36 +0200 Subject: [PATCH 78/80] removed examples --- examples/custom_vars.py | 63 ----------------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 examples/custom_vars.py diff --git a/examples/custom_vars.py b/examples/custom_vars.py deleted file mode 100644 index c0b7f314c..000000000 --- a/examples/custom_vars.py +++ /dev/null @@ -1,63 +0,0 @@ -from tespy import nwk, con, cmp, hlp -import numpy as np -from matplotlib import pyplot as plt - -nw = nwk.network(['water'], p_unit='bar', T_unit='C', h_unit='kJ / kg', - p_range=[0.5, 2], T_range=[10, 100], - h_range=[5,500]) - -# %% components -pipe = cmp.pipe('pipe') -pipe2 = cmp.pipe('pipe2') -pipe3 = cmp.pipe('pipe3') -pipe4 = cmp.pipe('pipe4') -pipe5 = cmp.pipe('pipe5') -pipe6 = cmp.pipe('pipe6') -sink = cmp.sink('sink') -source = cmp.source('source') - -# %% connections - -a = con.connection(source, 'out1', pipe, 'in1') -b = con.connection(pipe, 'out1', pipe2, 'in1') -c = con.connection(pipe2, 'out1', pipe3, 'in1') -d = con.connection(pipe3, 'out1', pipe4, 'in1') -e = con.connection(pipe4, 'out1', pipe5, 'in1') -f = con.connection(pipe5, 'out1', pipe6, 'in1') -g = con.connection(pipe6, 'out1', sink, 'in1') - -nw.add_conns(a, b, c, d, e, f, g) - -# %% connection parameters - -a.set_attr(h=40, fluid={'water': 1}, p=1, m=10) -b.set_attr(h=40) -c.set_attr(h=40) -d.set_attr(h=40) -e.set_attr(h=40) -f.set_attr(h=40) -g.set_attr(h=40) - - -# %% component parameters - -pipe.set_attr(pr=0.99, ks=1e-4, L=20, D='var') -pipe2.set_attr(pr=0.95, ks=1e-4, L=20, D='var') -pipe3.set_attr(pr=0.95, ks=1e-4, L=20, D='var') - -pipe4.set_attr(pr=0.99, ks=1e-4, L=30, D='var') -pipe5.set_attr(pr=0.95, ks=1e-4, L=20, D='var') -pipe6.set_attr(pr=0.95, ks=1e-4, L=20, D='var') -pipe.D.val=10 -pipe2.D.val=10 -pipe3.D.val=10 - -pipe4.D.val=10 -pipe5.D.val=10 -pipe6.D.val=10 - -# %% solve - -nw.solve(mode='design') -nw.print_results() -nw.save('test') From 969e8d748c29259b7ef833978f6e1c728b3cf821 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 25 Oct 2018 13:03:00 +0200 Subject: [PATCH 79/80] adjusted version number --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index efcac6abd..b1a19e323 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -__version__ = "0.0.4 dev" +__version__ = "0.0.5" From 1256098e44a80048f553172206883653a8df9781 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Thu, 25 Oct 2018 13:03:28 +0200 Subject: [PATCH 80/80] fixed some typos --- doc/getting_started/cogeneration_unit.rst | 3 ++- doc/using_tespy.rst | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/getting_started/cogeneration_unit.rst b/doc/getting_started/cogeneration_unit.rst index b6f3bc36e..c5a510c5e 100644 --- a/doc/getting_started/cogeneration_unit.rst +++ b/doc/getting_started/cogeneration_unit.rst @@ -12,10 +12,11 @@ TESPy provides a set of predefined characteristics (documented in the :py:class: Figure: Topology of a cogeneration unit. -The characteristics take the power ratio (:math:`\frac{P}{P_{ref}}` as argument. For a design case simulation the power ratio is always assumed to be equal to 1. +The characteristics take the power ratio (:math:`\frac{P}{P_{ref}}`) as argument. For a design case simulation the power ratio is always assumed to be equal to 1. For offdesign calculation TESPy will automatically take the rated power from the design case and use it to determine the power ratio. Still it is possible to specify the rated power manually, if you like. In contrast to other components, the cogeneration unit has several busses, which are accessible by specifying the corresponding bus parameter: + - TI (thermal input), - Q (total heat output), - Q1 and Q2 (heat output 1 and 2), diff --git a/doc/using_tespy.rst b/doc/using_tespy.rst index f4dd94c68..94f27934e 100644 --- a/doc/using_tespy.rst +++ b/doc/using_tespy.rst @@ -982,15 +982,17 @@ Offdesign calculations use the referenced value from your system design point fo .. note:: The available keywords for the dictionary are + - 'c' for the component instance, - 'p' for the parameter (the cogeneration unit has different parameters, have a look at the :ref:`cogeneration unit example `), - 'P_ref' for the reference heat flow/power value of the component and - 'char' for the characteristic line. There are different specification possibilites: + - If you specify the component only, the parameter will be default (not working with cogeneration unit) and the conversion factor of the characteristic line will be 1 for every load. - If you specify a numeric value for char, the conversion factor will be that value for every load. - - If you want to specify a characteristic line, you need to provide a :py:class:`Source ` object. + - If you want to specify a characteristic line, you need to provide a :py:class:`TESPy characteristics ` object. This can be used for easy post processing, e. g. to calculate thermal efficiency or you can build up relations between components in your network. If you want to use the busses for postprocessing only, you must not specify the sum of the power or heat flux on your bus.