diff --git a/docs/api/_images/DropletSeparator.svg b/docs/api/_images/DropletSeparator.svg index b41a4eceb..6f80e1828 100644 --- a/docs/api/_images/DropletSeparator.svg +++ b/docs/api/_images/DropletSeparator.svg @@ -3,7 +3,7 @@ inkscape:export-ydpi="1012" inkscape:export-xdpi="1012" sodipodi:docname="DropletSeparator.svg" - inkscape:version="1.1.2 (1:1.1+202202050950+0a00cf5339)" + inkscape:version="1.3 (0e150ed6c4, 2023-07-21)" id="svg8" version="1.1" viewBox="0 0 69.223438 95.000002" @@ -160,7 +160,7 @@ y="83.53054" style="fill:#000000;fill-opacity:1;stroke-width:0.529166;stroke-linecap:round;stroke-linejoin:round">out1 + id="tspan9360">2 out2 + id="tspan7156">1 out1 + style="font-size:6.42057px;baseline-shift:sub;fill:#ffffff;fill-opacity:1;stroke-width:0.529166" + id="tspan9360">2 out2 + style="font-size:6.42057px;baseline-shift:sub;fill:#ffffff;fill-opacity:1;stroke-width:0.529166" + id="tspan7156">1 `__ might be interesting for you. In contrast to the pure fluids, the properties -cover liquid state only. TESPy supports using pure incompressibles as well as -the predefined mixtures. +cover liquid state only. Fluid mixtures ++++++++++++++ -CoolProp provides a back end for predefined mixtures, which is rather instable -using HEOS. If you want to use the mixture feature of CoolProp we recommend -using the REFPROP back end instead. +TESPy provides support for three types of mixtures: + +- ideal: Mixtures for gases only. +- ideal-cond: Mixture for gases with condensation calculation for water share. +- incompressible: Mixtures for incompressible fluids. + +Furthermore, CoolProp provides a back end for predefined mixtures, which is +rather instable using HEOS. Using the CoolProp mixture back-end is not tested, +reach out if you would like to support us in adopting the TESPy implementation. +In general, to use the mixture feature of CoolProp we recommend using the +REFPROP back end instead of HEOS. Using other engines ------------------- diff --git a/docs/modules/ude.rst b/docs/modules/ude.rst index ee1488f4f..716c938a0 100644 --- a/docs/modules/ude.rst +++ b/docs/modules/ude.rst @@ -118,7 +118,7 @@ derivatives to mass flow are not zero. ... c0 = self.conns[0] ... c1 = self.conns[1] ... if c0.m.is_var: - ... ude.jacobian[c0.m.J_col] = 1 + ... self.jacobian[c0.m.J_col] = 1 ... if c1.m.is_var: ... self.jacobian[c1.m.J_col] = -2 * self.conns[1].m.val_SI diff --git a/docs/whats_new.rst b/docs/whats_new.rst index f03b829f0..7f5718e07 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -3,6 +3,7 @@ What's New Discover noteable new features and improvements in each release +.. include:: whats_new/v0-7-2.rst .. include:: whats_new/v0-7-1.rst .. include:: whats_new/v0-7-0.rst .. include:: whats_new/v0-6-3.rst diff --git a/docs/whats_new/v0-7-2.rst b/docs/whats_new/v0-7-2.rst new file mode 100644 index 000000000..430690e81 --- /dev/null +++ b/docs/whats_new/v0-7-2.rst @@ -0,0 +1,18 @@ +v0.7.1 - Newton's Nature (January, 21, 2024) +++++++++++++++++++++++++++++++++++++++++++++ + +Bug Fixes +######### +- The `delta` value of the :py:class:`tespy.connections.connection.Ref` class + was oriented with the wrong sign. A positive delta lead to a negative value. + Fixed in (`PR #459 `__). +- In initial simulations the temperature value of mixtures is 0 by default. + For calculating temperatures of the mixtures during that initial simulation, + that value was used as starting value causing CoolProp to raise an error and + the calculation to crash. This is now prevented by checking if the starting + value is reasonable or not + (`PR #477 `__). + +Contributors +############ +- Francesco Witte (`@fwitte `__) diff --git a/pyproject.toml b/pyproject.toml index eb184e992..648caec25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ exclude = ["docs/_build"] [project] name = "tespy" -version = "0.7.1.post1" +version = "0.7.2" description = "Thermal Engineering Systems in Python (TESPy)" readme = "README.rst" authors = [ @@ -47,7 +47,7 @@ classifiers = [ ] requires-python = ">=3.9" dependencies = [ - "CoolProp>=6.4,<7", + "CoolProp>=6.6,<7", "jinja2", "matplotlib>=3.2.1,<4", "numpy>=1.13.3,<2", diff --git a/src/tespy/__init__.py b/src/tespy/__init__.py index fde368697..9a79a2196 100644 --- a/src/tespy/__init__.py +++ b/src/tespy/__init__.py @@ -3,7 +3,7 @@ import os __datapath__ = os.path.join(importlib.resources.files("tespy"), "data") -__version__ = '0.7.1.post1 - Newton\'s Nature' +__version__ = '0.7.2 - Newton\'s Nature' # tespy data and connections import from . import connections # noqa: F401 diff --git a/src/tespy/components/combustion/engine.py b/src/tespy/components/combustion/engine.py index 6f47613a3..aa4661371 100644 --- a/src/tespy/components/combustion/engine.py +++ b/src/tespy/components/combustion/engine.py @@ -1302,11 +1302,9 @@ def calc_parameters(self): self.outl[i].h.val_SI - self.inl[i].h.val_SI) self.get_attr('pr' + str(i + 1)).val = ( self.outl[i].p.val_SI / self.inl[i].p.val_SI) - self.get_attr('zeta' + str(i + 1)).val = ( - (self.inl[i].p.val_SI - self.outl[i].p.val_SI) * np.pi ** 2 / ( - 4 * self.inl[i].m.val_SI ** 2 * - (self.inl[i].vol.val_SI + self.outl[i].vol.val_SI) - )) + self.get_attr('zeta' + str(i + 1)).val = self.calc_zeta( + self.inl[i], self.outl[i] + ) self.P.val = self.calc_P() self.Qloss.val = self.calc_Qloss() diff --git a/src/tespy/components/component.py b/src/tespy/components/component.py index 8c8dec496..3d9e0f24c 100644 --- a/src/tespy/components/component.py +++ b/src/tespy/components/component.py @@ -12,8 +12,6 @@ SPDX-License-Identifier: MIT """ -from collections import OrderedDict - import numpy as np from tespy.tools import logger @@ -122,7 +120,7 @@ def __init__(self, label, **kwargs): self.fkt_group = self.label # add container for components attributes - self.parameters = OrderedDict(self.get_parameters().copy()) + self.parameters = self.get_parameters().copy() self.__dict__.update(self.parameters) self.set_attr(**kwargs) @@ -342,7 +340,7 @@ def preprocess(self, num_nw_vars): self.num_eq = 0 self.vars = {} self.num_vars = 0 - self.constraints = OrderedDict(self.get_mandatory_constraints().copy()) + self.constraints = self.get_mandatory_constraints().copy() self.prop_specifications = {} self.var_specifications = {} self.group_specifications = {} @@ -415,7 +413,7 @@ def preprocess(self, num_nw_vars): if data.is_set and data.func is not None: self.num_eq += data.num_eq - self.jacobian = OrderedDict() + self.jacobian = {} self.residual = np.zeros(self.num_eq) sum_eq = 0 @@ -813,18 +811,18 @@ def check_parameter_bounds(self): if isinstance(data, dc_cp): if data.val > data.max_val + ERR : msg = ( - 'Invalid value for ' + p + ': ' + p + ' = ' + - str(data.val) + ' above maximum value (' + - str(data.max_val) + ') at component ' + self.label + - '.') + f"Invalid value for {p}: {p} = {data.val} above " + f"maximum value ({data.max_val}) at component " + f"{self.label}." + ) logger.warning(msg) elif data.val < data.min_val - ERR : msg = ( - 'Invalid value for ' + p + ': ' + p + ' = ' + - str(data.val) + ' below minimum value (' + - str(data.min_val) + ') at component ' + self.label + - '.') + f"Invalid value for {p}: {p} = {data.val} below " + f"minimum value ({data.max_val}) at component " + f"{self.label}." + ) logger.warning(msg) elif isinstance(data, dc_cc) and data.is_set: @@ -968,7 +966,8 @@ def enthalpy_equality_func_doc(self, label): indices = str(indices[0]) latex = ( r'0=h_{\mathrm{in,}i}-h_{\mathrm{out,}i}' - r'\; \forall i \in [' + indices + r']') + r'\; \forall i \in [' + indices + r']' + ) return generate_latex_eq(self, latex, label) def enthalpy_equality_deriv(self, k): @@ -1021,8 +1020,7 @@ def pr_func(self, pr='', inconn=0, outconn=0): 0 = p_{in} \cdot pr - p_{out} """ pr = self.get_attr(pr) - return (self.inl[inconn].p.val_SI * pr.val - - self.outl[outconn].p.val_SI) + return self.inl[inconn].p.val_SI * pr.val - self.outl[outconn].p.val_SI def pr_func_doc(self, label, pr='', inconn=0, outconn=0): r""" @@ -1083,6 +1081,15 @@ def pr_deriv(self, increment_filter, k, pr='', inconn=0, outconn=0): if pr.is_var: self.jacobian[k, self.pr.J_col] = i.p.val_SI + def calc_zeta(self, i, o): + if abs(i.m.val_SI) <= 1e-4: + return 0 + else: + return ( + (i.p.val_SI - o.p.val_SI) * np.pi ** 2 + / (4 * i.m.val_SI ** 2 * (i.vol.val_SI + o.vol.val_SI)) + ) + def zeta_func(self, zeta='', inconn=0, outconn=0): r""" Calculate residual value of :math:`\zeta`-function. diff --git a/src/tespy/components/heat_exchangers/base.py b/src/tespy/components/heat_exchangers/base.py index 82830fac8..5ad36b254 100644 --- a/src/tespy/components/heat_exchangers/base.py +++ b/src/tespy/components/heat_exchangers/base.py @@ -381,10 +381,10 @@ def calculate_td_log(self): o2 = self.outl[1] # temperature value manipulation for convergence stability - T_i1 = i1.calc_T(T0=i1.T.val_SI) - T_i2 = i2.calc_T(T0=i2.T.val_SI) - T_o1 = o1.calc_T(T0=o1.T.val_SI) - T_o2 = o2.calc_T(T0=o2.T.val_SI) + T_i1 = i1.calc_T() + T_i2 = i2.calc_T() + T_o1 = o1.calc_T() + T_o2 = o2.calc_T() if T_i1 <= T_o2: T_i1 = T_o2 + 0.01 @@ -581,8 +581,8 @@ def ttd_u_func(self): """ i = self.inl[0] o = self.outl[1] - T_i1 = i.calc_T(T0=i.T.val_SI) - T_o2 = o.calc_T(T0=o.T.val_SI) + T_i1 = i.calc_T() + T_o2 = o.calc_T() return self.ttd_u.val - T_i1 + T_o2 def ttd_u_func_doc(self, label): @@ -636,8 +636,8 @@ def ttd_l_func(self): """ i = self.inl[1] o = self.outl[0] - T_i2 = i.calc_T(T0=i.T.val_SI) - T_o1 = o.calc_T(T0=o.T.val_SI) + T_i2 = i.calc_T() + T_o1 = o.calc_T() return self.ttd_l.val - T_o1 + T_i2 def ttd_l_func_doc(self, label): @@ -830,11 +830,9 @@ def calc_parameters(self): for i in range(2): self.get_attr('pr' + str(i + 1)).val = ( self.outl[i].p.val_SI / self.inl[i].p.val_SI) - self.get_attr('zeta' + str(i + 1)).val = ( - (self.inl[i].p.val_SI - self.outl[i].p.val_SI) * np.pi ** 2 / ( - 4 * self.inl[i].m.val_SI ** 2 * - (self.inl[i].vol.val_SI + self.outl[i].vol.val_SI) - )) + self.get_attr('zeta' + str(i + 1)).val = self.calc_zeta( + self.inl[i], self.outl[i] + ) # kA and logarithmic temperature difference if self.ttd_u.val < 0 or self.ttd_l.val < 0: @@ -842,8 +840,10 @@ def calc_parameters(self): elif self.ttd_l.val == self.ttd_u.val: self.td_log.val = self.ttd_l.val else: - self.td_log.val = ((self.ttd_l.val - self.ttd_u.val) / - np.log(self.ttd_l.val / self.ttd_u.val)) + self.td_log.val = ( + (self.ttd_l.val - self.ttd_u.val) + / np.log(self.ttd_l.val / self.ttd_u.val) + ) self.kA.val = -self.Q.val / self.td_log.val def entropy_balance(self): diff --git a/src/tespy/components/heat_exchangers/condenser.py b/src/tespy/components/heat_exchangers/condenser.py index 241a9c01b..377660332 100644 --- a/src/tespy/components/heat_exchangers/condenser.py +++ b/src/tespy/components/heat_exchangers/condenser.py @@ -19,8 +19,6 @@ from tespy.tools.data_containers import GroupedComponentCharacteristics as dc_gcc from tespy.tools.data_containers import SimpleDataContainer as dc_simple from tespy.tools.document_models import generate_latex_eq -from tespy.tools.fluid_properties import T_mix_ph -from tespy.tools.fluid_properties import T_sat_p from tespy.tools.fluid_properties import dh_mix_dpQ from tespy.tools.fluid_properties import h_mix_pQ @@ -329,9 +327,9 @@ def calculate_td_log(self): o2 = self.outl[1] T_i1 = i1.calc_T_sat() - T_i2 = i2.calc_T(T0=i2.T.val_SI) - T_o1 = o1.calc_T(T0=o1.T.val_SI) - T_o2 = o2.calc_T(T0=o2.T.val_SI) + T_i2 = i2.calc_T() + T_o1 = o1.calc_T() + T_o2 = o2.calc_T() if T_i1 <= T_o2 and not i1.T.is_set: T_i1 = T_o2 + 0.5 @@ -454,7 +452,7 @@ def ttd_u_func(self): i = self.inl[0] o = self.outl[1] T_i1 = i.calc_T_sat() - T_o2 = o.calc_T(T0=self.outl[1].T.val_SI) + T_o2 = o.calc_T() return self.ttd_u.val - T_i1 + T_o2 def ttd_u_func_doc(self, label): diff --git a/src/tespy/components/heat_exchangers/parabolic_trough.py b/src/tespy/components/heat_exchangers/parabolic_trough.py index 5a41e98d1..1b948ae7e 100644 --- a/src/tespy/components/heat_exchangers/parabolic_trough.py +++ b/src/tespy/components/heat_exchangers/parabolic_trough.py @@ -274,7 +274,7 @@ def energy_group_func(self): i = self.inl[0] o = self.outl[0] - T_m = 0.5 * (i.calc_T(T0=i.T.val_SI) + o.calc_T(T0=o.T.val_SI)) + T_m = 0.5 * (i.calc_T() + o.calc_T()) iam = ( 1 - self.iam_1.val * abs(self.aoi.val) @@ -361,10 +361,8 @@ def calc_parameters(self): self.Q.val = i.m.val_SI * (o.h.val_SI - i.h.val_SI) self.pr.val = o.p.val_SI / i.p.val_SI - self.zeta.val = ( - (i.p.val_SI - o.p.val_SI) * np.pi ** 2 - / (4 * i.m.val_SI ** 2 * (i.vol.val_SI + o.vol.val_SI)) - ) + self.zeta.val = self.calc_zeta(i, o) + if self.energy_group.is_set: self.Q_loss.val = - self.E.val * self.A.val + self.Q.val self.Q_loss.is_result = True diff --git a/src/tespy/components/heat_exchangers/simple.py b/src/tespy/components/heat_exchangers/simple.py index 29d141ea9..a9e555079 100644 --- a/src/tespy/components/heat_exchangers/simple.py +++ b/src/tespy/components/heat_exchangers/simple.py @@ -535,8 +535,8 @@ def kA_group_func(self): i = self.inl[0] o = self.outl[0] - ttd_1 = i.calc_T(T0=i.T.val_SI) - self.Tamb.val_SI - ttd_2 = o.calc_T(T0=o.T.val_SI) - self.Tamb.val_SI + ttd_1 = i.calc_T() - self.Tamb.val_SI + ttd_2 = o.calc_T() - self.Tamb.val_SI # For numerical stability: If temperature differences have # different sign use mean difference to avoid negative logarithm. @@ -651,8 +651,8 @@ def kA_char_group_func(self): # For numerical stability: If temperature differences have # different sign use mean difference to avoid negative logarithm. - ttd_1 = i.calc_T(T0=i.T.val_SI) - self.Tamb.val_SI - ttd_2 = o.calc_T(T0=o.T.val_SI) - self.Tamb.val_SI + ttd_1 = i.calc_T() - self.Tamb.val_SI + ttd_2 = o.calc_T() - self.Tamb.val_SI if (ttd_1 / ttd_2) < 0: td_log = (ttd_2 + ttd_1) / 2 @@ -884,10 +884,7 @@ def calc_parameters(self): self.Q.val = i.m.val_SI * (o.h.val_SI - i.h.val_SI) self.pr.val = o.p.val_SI / i.p.val_SI - self.zeta.val = ( - (i.p.val_SI - o.p.val_SI) * np.pi ** 2 - / (4 * i.m.val_SI ** 2 * (i.vol.val_SI + o.vol.val_SI)) - ) + self.zeta.val = self.calc_zeta(i, o) if self.Tamb.is_set: ttd_1 = i.T.val_SI - self.Tamb.val_SI diff --git a/src/tespy/components/heat_exchangers/solar_collector.py b/src/tespy/components/heat_exchangers/solar_collector.py index bce3198ce..7129bd5c6 100644 --- a/src/tespy/components/heat_exchangers/solar_collector.py +++ b/src/tespy/components/heat_exchangers/solar_collector.py @@ -231,7 +231,7 @@ def energy_group_func(self): i = self.inl[0] o = self.outl[0] - T_m = 0.5 * (i.calc_T(T0=i.T.val_SI) + o.calc_T(T0=o.T.val_SI)) + T_m = 0.5 * (i.calc_T() + o.calc_T()) return ( i.m.val_SI * (o.h.val_SI - i.h.val_SI) @@ -311,10 +311,8 @@ def calc_parameters(self): self.Q.val = i.m.val_SI * (o.h.val_SI - i.h.val_SI) self.pr.val = o.p.val_SI / i.p.val_SI - self.zeta.val = ( - (i.p.val_SI - o.p.val_SI) * np.pi ** 2 - / (4 * i.m.val_SI ** 2 * (i.vol.val_SI + o.vol.val_SI)) - ) + self.zeta.val = self.calc_zeta(i, o) + if self.energy_group.is_set: self.Q_loss.val = -(self.E.val * self.A.val - self.Q.val) self.Q_loss.is_result = True diff --git a/src/tespy/components/nodes/separator.py b/src/tespy/components/nodes/separator.py index c826f022c..bf8a7be85 100644 --- a/src/tespy/components/nodes/separator.py +++ b/src/tespy/components/nodes/separator.py @@ -299,9 +299,9 @@ def energy_balance_func(self): \forall j \in \text{outlets} """ residual = [] - T_in = self.inl[0].calc_T(T0=300) + T_in = self.inl[0].calc_T() for o in self.outl: - residual += [T_in - o.calc_T(T0=300)] + residual += [T_in - o.calc_T()] return residual def energy_balance_func_doc(self, label): diff --git a/src/tespy/components/piping/valve.py b/src/tespy/components/piping/valve.py index 3b23aadcb..723e1d829 100644 --- a/src/tespy/components/piping/valve.py +++ b/src/tespy/components/piping/valve.py @@ -316,10 +316,7 @@ def calc_parameters(self): i = self.inl[0] o = self.outl[0] self.pr.val = o.p.val_SI / i.p.val_SI - self.zeta.val = ( - (i.p.val_SI - o.p.val_SI) * np.pi ** 2 - / (4 * i.m.val_SI ** 2 * (i.vol.val_SI + o.vol.val_SI)) - ) + self.zeta.val = self.calc_zeta(i, o) def entropy_balance(self): r""" diff --git a/src/tespy/components/reactors/fuel_cell.py b/src/tespy/components/reactors/fuel_cell.py index 088d09757..fe0198bcc 100644 --- a/src/tespy/components/reactors/fuel_cell.py +++ b/src/tespy/components/reactors/fuel_cell.py @@ -828,15 +828,13 @@ def initialise_target(self, c, key): def calc_parameters(self): r"""Postprocessing parameter calculation.""" - self.Q.val = - self.inl[0].m.val_SI * ( - self.outl[0].h.val_SI - self.inl[0].h.val_SI) + self.Q.val = -self.inl[0].m.val_SI * ( + self.outl[0].h.val_SI - self.inl[0].h.val_SI + ) self.pr.val = self.outl[0].p.val_SI / self.inl[0].p.val_SI self.e.val = self.P.val / self.inl[2].m.val_SI self.eta.val = self.e.val / self.e0 i = self.inl[0] o = self.outl[0] - self.zeta.val = ( - (i.p.val_SI - o.p.val_SI) * np.pi ** 2 - / (4 * i.m.val_SI ** 2 * (i.vol.val_SI + o.vol.val_SI)) - ) + self.zeta.val = self.calc_zeta(i, o) diff --git a/src/tespy/components/reactors/water_electrolyzer.py b/src/tespy/components/reactors/water_electrolyzer.py index 9e8e959d8..e93d87a9a 100644 --- a/src/tespy/components/reactors/water_electrolyzer.py +++ b/src/tespy/components/reactors/water_electrolyzer.py @@ -1157,18 +1157,16 @@ def initialise_target(self, c, key): def calc_parameters(self): r"""Postprocessing parameter calculation.""" - self.Q.val = - self.inl[0].m.val_SI * ( - self.outl[0].h.val_SI - self.inl[0].h.val_SI) + self.Q.val = -self.inl[0].m.val_SI * ( + self.outl[0].h.val_SI - self.inl[0].h.val_SI + ) self.pr.val = self.outl[0].p.val_SI / self.inl[0].p.val_SI self.e.val = self.P.val / self.outl[2].m.val_SI self.eta.val = self.e0 / self.e.val i = self.inl[0] o = self.outl[0] - self.zeta.val = ( - (i.p.val_SI - o.p.val_SI) * np.pi ** 2 - / (4 * i.m.val_SI ** 2 * (i.vol.val_SI + o.vol.val_SI)) - ) + self.zeta.val = self.calc_zeta(i, o) def exergy_balance(self, T0): self.E_P = ( diff --git a/src/tespy/components/turbomachinery/compressor.py b/src/tespy/components/turbomachinery/compressor.py index a28d0d344..5c7668d92 100644 --- a/src/tespy/components/turbomachinery/compressor.py +++ b/src/tespy/components/turbomachinery/compressor.py @@ -374,7 +374,7 @@ def char_map_pr_func(self): i = self.inl[0] o = self.outl[0] - x = np.sqrt(i.T.design / i.calc_T(T0=i.T.val_SI)) + x = np.sqrt(i.T.design / i.calc_T()) y = (i.m.val_SI * i.p.design) / (i.m.design * i.p.val_SI * x) yarr, zarr = self.char_map_pr.char_func.evaluate_x(x) @@ -477,7 +477,7 @@ def char_map_eta_s_func(self): i = self.inl[0] o = self.outl[0] - x = np.sqrt(i.T.design / i.calc_T(T0=i.T.val_SI)) + x = np.sqrt(i.T.design / i.calc_T()) y = (i.m.val_SI * i.p.design) / (i.m.design * i.p.val_SI * x) yarr, zarr = self.char_map_eta_s.char_func.evaluate_x(x) diff --git a/src/tespy/connections/connection.py b/src/tespy/connections/connection.py index ceb64a66f..e37049f05 100644 --- a/src/tespy/connections/connection.py +++ b/src/tespy/connections/connection.py @@ -9,8 +9,6 @@ SPDX-License-Identifier: MIT """ -from collections import OrderedDict - import numpy as np from tespy.components.component import Component @@ -38,6 +36,7 @@ from tespy.tools.fluid_properties import viscosity_mix_ph from tespy.tools.fluid_properties.functions import dT_mix_ph_dfluid from tespy.tools.fluid_properties.functions import p_sat_T +from tespy.tools.fluid_properties.helpers import get_mixture_temperature_range from tespy.tools.fluid_properties.helpers import get_number_of_fluids from tespy.tools.global_vars import ERR from tespy.tools.global_vars import fluid_property_data as fpd @@ -303,7 +302,7 @@ def _check_connector_id(self, component, connector_id, connecter_locations): if connector_id not in connecter_locations: msg = ( "Error creating connection. Specified connector for " - f"{component.label} ({connector_id} is not available. Choose " + f"{component.label} ({connector_id}) is not available. Choose " f"from " + ", ".join(connecter_locations) + "." ) logger.error(msg) @@ -523,8 +522,9 @@ def _parameter_specification(self, key, value): if value is None: self.get_attr(key).set_attr(is_set=False) + if f"{key}_ref" in self.property_data: - self.get_attr(key).set_attr(is_set=False) + self.get_attr(f"{key}_ref").set_attr(is_set=False) if key in ["m", "p", "h"]: self.get_attr(key).is_var = True @@ -626,7 +626,7 @@ def preprocess(self): container._solved = False self.residual = np.zeros(self.num_eq) - self.jacobian = OrderedDict() + self.jacobian = {} def simplify_specifications(self): systemvar_specs = [] @@ -726,7 +726,7 @@ def primary_ref_func(self, k, **kwargs): ref = self.get_attr(f"{variable}_ref").ref self.residual[k] = ( self.get_attr(variable).val_SI - - ref.obj.get_attr(variable).val_SI * ref.factor + ref.delta_SI + - (ref.obj.get_attr(variable).val_SI * ref.factor + ref.delta_SI) ) def primary_ref_deriv(self, k, **kwargs): @@ -739,6 +739,8 @@ def primary_ref_deriv(self, k, **kwargs): self.jacobian[k, ref.obj.get_attr(variable).J_col] = -ref.factor def calc_T(self, T0=None): + if T0 is None: + T0 = self.T.val_SI return T_mix_ph(self.p.val_SI, self.h.val_SI, self.fluid_data, self.mixing_rule, T0=T0) def T_func(self, k, **kwargs): @@ -761,7 +763,7 @@ def T_deriv(self, k, **kwargs): def T_ref_func(self, k, **kwargs): ref = self.T_ref.ref self.residual[k] = ( - self.calc_T() - ref.obj.calc_T() * ref.factor + ref.delta_SI + self.calc_T() - (ref.obj.calc_T() * ref.factor + ref.delta_SI) ) def T_ref_deriv(self, k, **kwargs): @@ -810,7 +812,7 @@ def v_ref_func(self, k, **kwargs): ref = self.v_ref.ref self.residual[k] = ( self.calc_vol(T0=self.T.val_SI) * self.m.val_SI - - ref.obj.calc_vol(T0=ref.obj.T.val_SI) * ref.obj.m.val_SI * ref.factor + ref.delta_SI + - (ref.obj.calc_vol(T0=ref.obj.T.val_SI) * ref.obj.m.val_SI * ref.factor + ref.delta_SI) ) def v_ref_deriv(self, k, **kwargs): @@ -919,7 +921,15 @@ def calc_results(self): ) logger.error(msg) _converged = False - + else: + _, Tmax = get_mixture_temperature_range(self.fluid_data) + if self.T.val_SI > Tmax: + msg = ( + "The temperature value of the mixture is above the " + "upper temperature limit of a mixture component. " + "The resulting temperature might not be erroneous." + ) + logger.warning(msg) else: try: if not self.x.is_set: diff --git a/src/tespy/networks/network.py b/src/tespy/networks/network.py index 9f699dd08..1fcf09011 100644 --- a/src/tespy/networks/network.py +++ b/src/tespy/networks/network.py @@ -15,7 +15,6 @@ """ import json import os -from collections import OrderedDict from time import time import numpy as np @@ -189,7 +188,7 @@ def set_defaults(self): # user defined function dictionary for fast access self.user_defined_eq = {} # bus dictionary - self.busses = OrderedDict() + self.busses = {} # results and specification dictionary self.results = {} self.specifications = {} @@ -455,6 +454,10 @@ def del_conns(self, *args): comps = list({cp for c in args for cp in [c.source, c.target]}) for c in args: self.conns.drop(c.label, inplace=True) + if "Connection" in self.results: + self.results["Connection"].drop( + c.label, inplace=True, errors="ignore" + ) msg = ('Deleted connection ' + c.label + ' from network.') logger.debug(msg) @@ -555,6 +558,9 @@ def _del_comps(self, comps): comp not in self.conns["target"].values ): self.comps.drop(comp.label, inplace=True) + self.results[comp.__class__.__name__].drop( + comp.label, inplace=True, errors="ignore" + ) msg = f"Deleted component {comp.label} from network." logger.debug(msg) @@ -742,7 +748,7 @@ def create_fluid_wrapper_branches(self): set(branch_data["connections"]) & set(ob_data["connections"]) ) - if len(common_connections) > 0: + if len(common_connections) > 0 and ob_name in merged: merged[branch_name]["connections"] = list( set(branch_data["connections"] + ob_data["connections"]) ) @@ -1262,7 +1268,7 @@ def init_design(self): c.m.design = np.nan c.p.design = np.nan c.h.design = np.nan - c.fluid.design = OrderedDict() + c.fluid.design = {} c.new_design = True @@ -1501,9 +1507,9 @@ def init_conn_design_params(self, c, df): if c.label not in df.index: # no matches in the connections of the network and the design files msg = ( - f"Could not find connection {c.label} in design case. Please " + f"Could not find connection '{c.label}' in design case. Please " "make sure no connections have been modified or components " - "havebeen relabeled for your offdesign calculation." + "have been relabeled for your offdesign calculation." ) logger.exception(msg) raise hlp.TESPyNetworkError(msg) diff --git a/src/tespy/tools/data_containers.py b/src/tespy/tools/data_containers.py index f55447d32..61375b466 100644 --- a/src/tespy/tools/data_containers.py +++ b/src/tespy/tools/data_containers.py @@ -12,8 +12,6 @@ SPDX-License-Identifier: MIT """ -import collections - import numpy as np from tespy.tools import logger @@ -354,15 +352,15 @@ def attr(): values. """ return { - 'val': collections.OrderedDict(), - 'val0': collections.OrderedDict(), + 'val': dict(), + 'val0': dict(), 'is_set': set(), - 'design': collections.OrderedDict(), - 'wrapper': collections.OrderedDict(), - 'back_end': collections.OrderedDict(), - 'engine': collections.OrderedDict(), + 'design': dict(), + 'wrapper': dict(), + 'back_end': dict(), + 'engine': dict(), "is_var": set(), - "J_col": collections.OrderedDict(), + "J_col": dict(), } def serialize(self): diff --git a/src/tespy/tools/fluid_properties/helpers.py b/src/tespy/tools/fluid_properties/helpers.py index 27740208a..c085a4509 100644 --- a/src/tespy/tools/fluid_properties/helpers.py +++ b/src/tespy/tools/fluid_properties/helpers.py @@ -67,9 +67,11 @@ def get_molar_fractions(fluid_data): def inverse_temperature_mixture(p=None, target_value=None, fluid_data=None, T0=None, f=None): # calculate the fluid properties for fluid mixtures valmin, valmax = get_mixture_temperature_range(fluid_data) - if T0 is None: + if T0 is None or T0 == 0 or np.isnan(T0): T0 = (valmin + valmax) / 2.0 + valmax *= 2 + function_kwargs = { "p": p, "fluid_data": fluid_data, "T": T0, "function": f, "parameter": "T" , "delta": 0.01 diff --git a/src/tespy/tools/fluid_properties/mixtures.py b/src/tespy/tools/fluid_properties/mixtures.py index b156a133f..645537783 100644 --- a/src/tespy/tools/fluid_properties/mixtures.py +++ b/src/tespy/tools/fluid_properties/mixtures.py @@ -253,7 +253,7 @@ def exergy_chemical_ideal_cond(pamb, Tamb, fluid_data, Chem_Ex): fluid_aliases = fluid_data[fluid]["wrapper"]._aliases - if molar_liquid > 0: + if molar_liquid > 0 and "water" in fluid_aliases: y = [ Chem_Ex[k][2] for k in fluid_aliases if k in Chem_Ex ] diff --git a/src/tespy/tools/fluid_properties/wrappers.py b/src/tespy/tools/fluid_properties/wrappers.py index bc2f84df7..565c98bf4 100644 --- a/src/tespy/tools/fluid_properties/wrappers.py +++ b/src/tespy/tools/fluid_properties/wrappers.py @@ -191,15 +191,6 @@ def h_ps(self, p, s): return self.AS.hmass() def h_pT(self, p, T): - if self.back_end == "INCOMP": - if T == (self._T_max + self._T_min) / 2: - T += ERR - self.AS.update(CP.PT_INPUTS, p, T) - h = self.AS.hmass() * 0.5 - T -= 2 * ERR - self.AS.update(CP.PT_INPUTS, p, T) - h += self.AS.hmass() * 0.5 - return h self.AS.update(CP.PT_INPUTS, p, T) return self.AS.hmass() diff --git a/src/tespy/tools/global_vars.py b/src/tespy/tools/global_vars.py index 37a9273e5..8cd773f1a 100644 --- a/src/tespy/tools/global_vars.py +++ b/src/tespy/tools/global_vars.py @@ -8,14 +8,13 @@ SPDX-License-Identifier: MIT """ -from collections import OrderedDict ERR = 1e-6 molar_masses = {} gas_constants = {} gas_constants['uni'] = 8.314462618 -fluid_property_data = OrderedDict({ +fluid_property_data = { 'm': { 'text': 'mass flow', 'SI_unit': 'kg / s', @@ -99,6 +98,6 @@ 'latex_eq': r'0 = s_\mathrm{spec} - s\left(p, h \right)', 'documentation': {'float_fmt': '{:,.2f}'} } -}) +} combustion_gases = ['methane', 'ethane', 'propane', 'butane', 'hydrogen', 'nDodecane'] diff --git a/src/tespy/tools/helpers.py b/src/tespy/tools/helpers.py index e177474ec..efd5b7720 100644 --- a/src/tespy/tools/helpers.py +++ b/src/tespy/tools/helpers.py @@ -11,7 +11,6 @@ import json import os -from collections import OrderedDict from collections.abc import Mapping from copy import deepcopy @@ -65,27 +64,6 @@ def merge_dicts(dict1, dict2): return result -def nested_OrderedDict(dictionary): - """Create a nested OrderedDict from a nested dict. - - Parameters - ---------- - dictionary : dict - Nested dict. - - Returns - ------- - dictionary : collections.OrderedDict - Nested OrderedDict. - """ - dictionary = OrderedDict(dictionary) - for key, value in dictionary.items(): - if isinstance(value, dict): - dictionary[key] = nested_OrderedDict(value) - - return dictionary - - class TESPyNetworkError(Exception): """Custom message for network related errors.""" @@ -628,7 +606,7 @@ def newton(func, deriv, params, y, **kwargs): logger.debug(msg) break - if tol_mode == 'abs': + if tol_mode == 'abs' or y == 0: expr = abs(res) >= tol_abs elif tol_mode == 'rel': expr = abs(res / y) >= tol_rel @@ -680,7 +658,7 @@ def newton_with_kwargs( logger.debug(msg) break - if tol_mode == 'abs': + if tol_mode == 'abs' or target_value == 0: expr = abs(residual) >= tol_abs elif tol_mode == 'rel': expr = abs(residual / target_value) >= tol_rel diff --git a/src/tespy/tools/optimization.py b/src/tespy/tools/optimization.py index b7a513878..4154d1a95 100644 --- a/src/tespy/tools/optimization.py +++ b/src/tespy/tools/optimization.py @@ -6,7 +6,6 @@ import pandas as pd from tespy.tools.helpers import merge_dicts -from tespy.tools.helpers import nested_OrderedDict class OptimizationProblem: @@ -70,14 +69,8 @@ def __init__(self, model, variables={}, constraints={}, objective="objective"): "upper limits": {"Connections": {}, "Components": {}} } # merge the passed values into the default dictionary structure - variables = merge_dicts(variables, default_variables) - constraints = merge_dicts(constraints, default_constraints) - - # pygmo creates a vector for the variables and constraints, which has - # to be in consistent order. Therefore use OrderedDicts instead of - # dictionaries - self.variables = nested_OrderedDict(variables) - self.constraints = nested_OrderedDict(constraints) + self.variables = merge_dicts(variables, default_variables) + self.constraints = merge_dicts(constraints, default_constraints) self.objective = objective self.variable_list = [] self.constraint_list = [] @@ -307,3 +300,5 @@ def run(self, algo, pop, num_ind, num_gen): round(pop.champion_x[i], 4) ) ) + + return pop diff --git a/tests/test_components/test_combustion.py b/tests/test_components/test_combustion.py index 7912781fb..b8290babd 100644 --- a/tests/test_components/test_combustion.py +++ b/tests/test_components/test_combustion.py @@ -9,9 +9,10 @@ SPDX-License-Identifier: MIT """ - import shutil +import pytest + from tespy.components import CombustionChamber from tespy.components import CombustionEngine from tespy.components import DiabaticCombustionChamber @@ -103,6 +104,20 @@ def test_CombustionChamber(self): str(round(self.c3.fluid.val['O2'], 4)) + '.') assert 0.0 == round(self.c3.fluid.val['O2'], 4), msg + def test_CombustionChamberHighTemperature(self): + instance = CombustionChamber('combustion chamber') + self.setup_CombustionChamber_network(instance) + + # connection parameter specification + air = {'N2': 0.8, 'O2': 0.2} + fuel = {'CH4': 1} + self.c1.set_attr(fluid=air, p=1, T=30, m=1) + self.c2.set_attr(fluid=fuel, T=30) + instance.set_attr(lamb=1) + self.nw.solve('design') + self.nw._convergence_check() + assert self.c3.T.val_SI == pytest.approx(2110, abs=0.1) + def test_DiabaticCombustionChamber(self): """ Test component properties of diabatic combustion chamber. diff --git a/tests/test_connections.py b/tests/test_connections.py index acc620a20..6d35bdf80 100644 --- a/tests/test_connections.py +++ b/tests/test_connections.py @@ -58,7 +58,7 @@ def test_volumetric_flow_reference(self): c2.set_attr(v=Ref(c1, 2, 10)) self.nw.solve('design') - v_expected = round(c1.v.val * 2 - 10, 4) + v_expected = round(c1.v.val * 2 + 10, 4) v_is = round(c2.v.val, 4) msg = ( 'The mass flow of the connection 2 should be equal to ' @@ -87,7 +87,7 @@ def test_temperature_reference(self): c2.set_attr(T=Ref(c1, 1.5, -75)) self.nw.solve('design') - T_expected = round(convert_from_SI("T", c1.T.val_SI * 1.5, c1.T.unit) + 75, 4) + T_expected = round(convert_from_SI("T", c1.T.val_SI * 1.5, c1.T.unit) - 75, 4) T_is = round(c2.T.val, 4) msg = ( 'The temperature of the connection 2 should be equal to ' @@ -116,7 +116,7 @@ def test_primary_reference(self): c2.set_attr(m=Ref(c1, 2, -0.5)) self.nw.solve('design') - m_expected = round(convert_from_SI("m", c1.m.val_SI * 2, c1.m.unit) + 0.5, 4) + m_expected = round(convert_from_SI("m", c1.m.val_SI * 2, c1.m.unit) - 0.5, 4) m_is = round(c2.m.val, 4) msg = ( 'The mass flow of the connection 2 should be equal to ' diff --git a/tests/test_networks/test_binary_incompressible.py b/tests/test_networks/test_binary_incompressible.py index c5aa7be94..9683e71cd 100644 --- a/tests/test_networks/test_binary_incompressible.py +++ b/tests/test_networks/test_binary_incompressible.py @@ -51,4 +51,4 @@ def setup_method(self): self.nw.solve("design") def test_binaries(self): - self.nw._convergence_check() \ No newline at end of file + self.nw._convergence_check() diff --git a/tutorial/heat_pump_exergy/NH3_calculations.py b/tutorial/heat_pump_exergy/NH3_calculations.py index a602ae0dc..af56e171f 100644 --- a/tutorial/heat_pump_exergy/NH3_calculations.py +++ b/tutorial/heat_pump_exergy/NH3_calculations.py @@ -19,6 +19,7 @@ import plotly.graph_objects as go from fluprodia import FluidPropertyDiagram import pandas as pd +import matplotlib.pyplot as plt # %% network pamb = 1.013 # ambient pressure @@ -166,16 +167,18 @@ for key, data in result_dict.items(): result_dict[key]['datapoints'] = diagram.calc_individual_isoline(**data) -diagram.set_limits(x_min=0, x_max=2100, y_min=1e0, y_max=2e2) diagram.calc_isolines() -diagram.draw_isolines('logph') + +fig, ax = plt.subplots(1, figsize=(16, 10)) +diagram.draw_isolines(fig, ax, 'logph', x_min=0, x_max=2100, y_min=1e0, y_max=2e2) for key in result_dict.keys(): datapoints = result_dict[key]['datapoints'] - diagram.ax.plot(datapoints['h'], datapoints['p'], color='#ff0000') - diagram.ax.scatter(datapoints['h'][0], datapoints['p'][0], color='#ff0000') + ax.plot(datapoints['h'], datapoints['p'], color='#ff0000') + ax.scatter(datapoints['h'][0], datapoints['p'][0], color='#ff0000') -diagram.save('NH3_logph.svg') +plt.tight_layout() +fig.savefig('NH3_logph.svg') # %% exergy analysis diff --git a/tutorial/heat_pump_exergy/R410A_calculations.py b/tutorial/heat_pump_exergy/R410A_calculations.py index 457a10472..5cb3e6a01 100644 --- a/tutorial/heat_pump_exergy/R410A_calculations.py +++ b/tutorial/heat_pump_exergy/R410A_calculations.py @@ -19,6 +19,7 @@ import plotly.graph_objects as go from fluprodia import FluidPropertyDiagram import pandas as pd +import matplotlib.pyplot as plt # %% network pamb = 1.013 # ambient pressure @@ -165,16 +166,18 @@ for key, data in result_dict.items(): result_dict[key]['datapoints'] = diagram.calc_individual_isoline(**data) -diagram.set_limits(x_min=200, x_max=500, y_min=0.8e1, y_max=0.8e2) diagram.calc_isolines() -diagram.draw_isolines('logph') + +fig, ax = plt.subplots(1, figsize=(16, 10)) +diagram.draw_isolines(fig, ax, 'logph', x_min=200, x_max=500, y_min=0.8e1, y_max=0.8e2) for key in result_dict.keys(): datapoints = result_dict[key]['datapoints'] - diagram.ax.plot(datapoints['h'], datapoints['p'], color='#ff0000') - diagram.ax.scatter(datapoints['h'][0], datapoints['p'][0], color='#ff0000') + ax.plot(datapoints['h'], datapoints['p'], color='#ff0000') + ax.scatter(datapoints['h'][0], datapoints['p'][0], color='#ff0000') -diagram.save('R410A_logph.svg') +plt.tight_layout() +fig.savefig('R410A_logph.svg') # %% exergy analysis