From c38e969927fc4d7384ed57457d3b635301e0e323 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Sun, 10 Sep 2023 12:23:30 +0200 Subject: [PATCH] Fix API for fluid back end selection and adjust corresponding tests --- src/tespy/components/heat_exchangers/base.py | 10 ++--- .../heat_exchangers/parabolic_trough.py | 2 +- .../components/heat_exchangers/simple.py | 2 +- src/tespy/connections/connection.py | 26 +++++++----- src/tespy/networks/network.py | 40 ++++++++++++------- src/tespy/tools/fluid_properties/wrappers.py | 5 ++- tests/test_components/test_heat_exchangers.py | 30 +++++++------- tests/test_components/test_turbomachinery.py | 26 ++++++------ tests/test_errors.py | 1 + .../test_solar_energy_generating_system.py | 8 ++-- 10 files changed, 87 insertions(+), 63 deletions(-) diff --git a/src/tespy/components/heat_exchangers/base.py b/src/tespy/components/heat_exchangers/base.py index 38a2ec7e4..478a34dc7 100644 --- a/src/tespy/components/heat_exchangers/base.py +++ b/src/tespy/components/heat_exchangers/base.py @@ -148,8 +148,7 @@ class HeatExchanger(Component): >>> from tespy.networks import Network >>> from tespy.tools import document_model >>> import shutil - >>> nw = Network(fluids=['water', 'air'], T_unit='C', p_unit='bar', - ... h_unit='kJ / kg', iterinfo=False) + >>> nw = Network(T_unit='C', p_unit='bar', h_unit='kJ / kg', iterinfo=False) >>> exhaust_hot = Source('Exhaust air outlet') >>> exhaust_cold = Sink('Exhaust air inlet') >>> cw_cold = Source('cooling water inlet') @@ -169,9 +168,9 @@ class HeatExchanger(Component): >>> he.set_attr(pr1=0.98, pr2=0.98, ttd_u=5, ... design=['pr1', 'pr2', 'ttd_u'], offdesign=['zeta1', 'zeta2', 'kA_char']) - >>> cw_he.set_attr(fluid={'air': 0, 'water': 1}, T=10, p=3, + >>> cw_he.set_attr(fluid={'water': 1}, T=10, p=3, ... offdesign=['m']) - >>> ex_he.set_attr(fluid={'air': 1, 'water': 0}, v=0.1, T=35) + >>> ex_he.set_attr(fluid={'air': 1}, v=0.1, T=35) >>> he_ex.set_attr(T=17.5, p=1, design=['T']) >>> nw.solve('design') >>> nw.save('tmp') @@ -700,7 +699,8 @@ def bus_func(self, bus): h_{out,1} - h_{in,1} \right) """ return self.inl[0].m.val_SI * ( - self.outl[0].h.val_SI - self.inl[0].h.val_SI) + self.outl[0].h.val_SI - self.inl[0].h.val_SI + ) def bus_func_doc(self, bus): r""" diff --git a/src/tespy/components/heat_exchangers/parabolic_trough.py b/src/tespy/components/heat_exchangers/parabolic_trough.py index 25ec81ba5..a25d678cc 100644 --- a/src/tespy/components/heat_exchangers/parabolic_trough.py +++ b/src/tespy/components/heat_exchangers/parabolic_trough.py @@ -182,7 +182,7 @@ class ParabolicTrough(SimpleHeatExchanger): >>> pt.set_attr(pr=1, aoi=aoi, doc=1, ... Tamb=20, A=1, eta_opt=0.816, c_1=0.0622, c_2=0.00023, E=E, ... iam_1=-1.59e-3, iam_2=9.77e-5) - >>> inc.set_attr(fluid={'S800': 1}, T=220, p=2) + >>> inc.set_attr(fluid={'S800': 1}, fluid_back_ends={'S800': 'INCOMP'}, T=220, p=2) >>> outg.set_attr(T=260) >>> nw.solve('design') >>> round(pt.Q.val, 0) diff --git a/src/tespy/components/heat_exchangers/simple.py b/src/tespy/components/heat_exchangers/simple.py index 23ffdcafc..0d06b5421 100644 --- a/src/tespy/components/heat_exchangers/simple.py +++ b/src/tespy/components/heat_exchangers/simple.py @@ -492,7 +492,7 @@ def hazen_williams_func(self): return ( (i.p.val_SI - o.p.val_SI) * np.sign(i.m.val_SI) - - (10.67 * abs(i[0]) ** 1.852 * self.L.val / + (10.67 * abs(i.m.val_SI) ** 1.852 * self.L.val / (self.ks.val ** 1.852 * self.D.val ** 4.871)) * (9.81 * ((v_i + v_o) / 2) ** 0.852)) diff --git a/src/tespy/connections/connection.py b/src/tespy/connections/connection.py index 1636395df..dba0ca67a 100644 --- a/src/tespy/connections/connection.py +++ b/src/tespy/connections/connection.py @@ -398,6 +398,8 @@ def set_attr(self, **kwargs): msg = 'Label can only be specified on instance creation.' logger.error(msg) raise TESPyConnectionError(msg) + elif 'fluid' in key: + self._fluid_specification(key, kwargs[key]) elif key in self.property_data or key in self.property_data0: # fluid specification try: @@ -405,10 +407,8 @@ def set_attr(self, **kwargs): is_numeric = True except (TypeError, ValueError): is_numeric = False - if 'fluid' in key: - self._fluid_specification(key, kwargs[key]) - elif key == 'state': + if key == 'state': if kwargs[key] in ['l', 'g']: self.state.set_attr(val=kwargs[key], is_set=True) elif kwargs[key] is None: @@ -528,7 +528,7 @@ def _fluid_specification(self, key, value): if key == "fluid": for fluid, fraction in value.items(): if fraction is not None: - self.fluid.val["fluid"] = fraction + self.fluid.val[fluid] = fraction self.fluid.is_set.add(fluid) if fluid in self.fluid.is_var: self.fluid.is_var.remove(fluid) @@ -554,7 +554,7 @@ def _fluid_specification(self, key, value): logger.error(msg) raise KeyError(msg) - def _check_fluid_datatypes(key, value): + def _check_fluid_datatypes(self, key, value): if key == "fluid_balance": if not isinstance(value, bool): msg = "Datatype for 'fluid_balance' must be boolean." @@ -590,6 +590,8 @@ def get_attr(self, key): def _create_fluid_wrapper(self): for fluid in self.fluid.val: + if fluid in self.fluid.wrapper: + continue if fluid in self.fluid.engine and fluid in self.fluid.back_end: pass elif fluid in self.fluid.engine: @@ -814,10 +816,16 @@ def calc_results(self): self.s.val_SI = self.calc_s() if number_fluids == 1: - if not self.x.is_set: - self.x.val_SI = self.calc_x() - if not self.Td_bp.is_set: - self.Td_bp.val_SI = self.calc_Td_bp() + try: + if not self.x.is_set: + self.x.val_SI = self.calc_x() + except ValueError: + self.x.val_SI = np.nan + try: + if not self.Td_bp.is_set: + self.Td_bp.val_SI = self.calc_Td_bp() + except ValueError: + self.x.val_SI = np.nan for prop in fpd.keys(): self.get_attr(prop).val = convert_from_SI( diff --git a/src/tespy/networks/network.py b/src/tespy/networks/network.py index 73cc5d810..fdd606407 100644 --- a/src/tespy/networks/network.py +++ b/src/tespy/networks/network.py @@ -763,6 +763,7 @@ def init_components(self): self.results[comp_type] = pd.DataFrame( columns=cols, dtype='float64') if comp_type not in self.specifications: + cols, groups, chars = [], [], [] for col, data in comp.parameters.items(): if isinstance(data, dc_cp): @@ -907,7 +908,13 @@ def propagate_fluid_wrappers(self): if f not in c.fluid.wrapper and f in fluid_set_wrappers: c.fluid.wrapper[f] = fluid_set_wrappers[f] elif f not in c.fluid.wrapper: - c._create_fluid_wrapper(f, fp.CoolPropWrapper, "HEOS") + msg = ( + f"The fluid {f} seems to be a potential fluid for " + "connection, however, there has is no fluid " + "wrapper available for this fluid on the same " + "branch. Creating a default wrapper." + ) + c._create_fluid_wrapper() def presolve_massflow_topology(self): @@ -1056,6 +1063,21 @@ def presolve_fluid_topology(self): } self.num_conn_vars += 1 + def _reset_topology_reduction_specifications(self): + for c in self.conns["object"]: + if hasattr(c, "_m_tmp"): + value = c.m.val_SI + unit = c.m.unit + c.m = c._m_tmp + c.m.val_SI = value + c.m.unit = unit + del c._m_tmp + if hasattr(c, "_fluid_tmp"): + val = c.fluid.val + c.fluid = c._fluid_tmp + c.fluid.val = val + del c._fluid_tmp + def init_set_properties(self): """Specification of SI values for user set values.""" self.all_fluids = [] @@ -1887,6 +1909,7 @@ def solve(self, mode, init_path=None, design_path=None, self.initialise() if init_only: + self._reset_topology_reduction_specifications() return msg = 'Starting solver.' @@ -2396,20 +2419,9 @@ def postprocessing(self): def process_connections(self): """Process the Connection results.""" - for c in self.conns['object']: - if hasattr(c, "_m_tmp"): - value = c.m.val_SI - unit = c.m.unit - c.m = c._m_tmp - c.m.val_SI = value - c.m.unit = unit - del c._m_tmp - if hasattr(c, "_fluid_tmp"): - val = c.fluid.val - c.fluid = c._fluid_tmp - c.fluid.val = val - del c._fluid_tmp + self._reset_topology_reduction_specifications() + for c in self.conns['object']: c.good_starting_values = True c.calc_results() diff --git a/src/tespy/tools/fluid_properties/wrappers.py b/src/tespy/tools/fluid_properties/wrappers.py index 248fae340..9be48d39f 100644 --- a/src/tespy/tools/fluid_properties/wrappers.py +++ b/src/tespy/tools/fluid_properties/wrappers.py @@ -77,7 +77,10 @@ def __init__(self, fluid, back_end=None) -> None: def _set_constants(self): self._T_min = self.AS.trivial_keyed_output(CP.iT_min) self._T_max = self.AS.trivial_keyed_output(CP.iT_max) - self._aliases = CP.CoolProp.get_aliases(self.fluid) + try: + self._aliases = CP.CoolProp.get_aliases(self.fluid) + except RuntimeError: + self._aliases = [self.fluid] if self.back_end == "INCOMP": self._p_min = 1e2 diff --git a/tests/test_components/test_heat_exchangers.py b/tests/test_components/test_heat_exchangers.py index 5b593e028..ce867130f 100644 --- a/tests/test_components/test_heat_exchangers.py +++ b/tests/test_components/test_heat_exchangers.py @@ -57,7 +57,7 @@ def test_SimpleHeatExchanger(self): """Test component properties of simple heat exchanger.""" instance = SimpleHeatExchanger('heat exchanger') self.setup_SimpleHeatExchanger_network(instance) - fl = {'Ar': 0, 'H2O': 1, 'S800': 0} + fl = {'H2O': 1} self.c1.set_attr(fluid=fl, m=1, p=10, T=100) # trigger heat exchanger parameter groups instance.set_attr(hydro_group='HW', L=100, ks=100, pr=0.99, Tamb=20) @@ -156,8 +156,9 @@ def test_ParabolicTrough(self): """Test component properties of parabolic trough.""" instance = ParabolicTrough('parabolic trough') self.setup_SimpleHeatExchanger_network(instance) - fl = {'Ar': 0, 'H2O': 0, 'S800': 1} - self.c1.set_attr(fluid=fl, p=2, T=200) + fl = {'S800': 1} + fbe = {"S800": "INCOMP"} + self.c1.set_attr(fluid=fl, fluid_back_ends=fbe, p=2, T=200) self.c2.set_attr(T=350) # test grouped parameter settings with missing parameters @@ -261,7 +262,7 @@ def test_SolarCollector(self): """Test component properties of solar collector.""" instance = SolarCollector('solar collector') self.setup_SimpleHeatExchanger_network(instance) - fl = {'Ar': 0, 'H2O': 1, 'S800': 0} + fl = {'H2O': 1} self.c1.set_attr(fluid=fl, p=10, T=30) self.c2.set_attr(T=70) @@ -341,9 +342,9 @@ def test_HeatExchanger(self): instance.set_attr(pr1=0.98, pr2=0.98, ttd_u=5, design=['pr1', 'pr2', 'ttd_u'], offdesign=['zeta1', 'zeta2', 'kA_char']) - self.c1.set_attr(T=120, p=3, fluid={'Ar': 0, 'H2O': 1, 'S800': 0}) + self.c1.set_attr(T=120, p=3, fluid={'H2O': 1}) self.c2.set_attr(T=70) - self.c3.set_attr(T=40, p=5, fluid={'Ar': 1, 'H2O': 0, 'S800': 0}) + self.c3.set_attr(T=40, p=5, fluid={'Ar': 1}) b = Bus('heat transfer', P=-80e3) b.add_comps({'comp': instance}) self.nw.add_busses(b) @@ -447,10 +448,11 @@ def test_Condenser(self): self.setup_HeatExchanger_network(instance) # design specification - instance.set_attr(pr1=0.98, pr2=0.98, ttd_u=5, - offdesign=['zeta2', 'kA_char']) - self.c1.set_attr(T=100, p0=0.5, fluid={'Ar': 0, 'H2O': 1, 'S800': 0}) - self.c3.set_attr(T=30, p=5, fluid={'Ar': 0, 'H2O': 1, 'S800': 0}) + instance.set_attr( + pr1=0.98, pr2=0.98, ttd_u=5, offdesign=['zeta2', 'kA_char'] + ) + self.c1.set_attr(T=100, p0=0.5, fluid={'H2O': 1}) + self.c3.set_attr(T=30, p=5, fluid={'H2O': 1}) self.c4.set_attr(T=40) instance.set_attr(Q=-80e3) self.nw.solve('design') @@ -483,7 +485,7 @@ def test_Condenser(self): # test upper terminal temperature difference. For the component # condenser the temperature of the condensing fluid is relevant. - ttd_u = round(T_sat_p(self.c1.get_flow()) - self.c4.T.val_SI, 1) + ttd_u = round(self.c1.calc_T_sat() - self.c4.T.val_SI, 1) p = round(self.c1.p.val_SI, 5) msg = ('Value of terminal temperature difference must be ' + str(round(instance.ttd_u.val, 1)) + ', is ' + @@ -517,10 +519,8 @@ def test_CondenserWithEvaporation(self): # design specification instance.set_attr(pr1=1, pr2=1, offdesign=["kA"]) - self.c1.set_attr(x=1, p=1, fluid={'Ar': 0, 'H2O': 1, 'S800': 0}, m=1) - self.c3.set_attr( - x=0, p=0.7, fluid={'Ar': 0, 'H2O': 1, 'S800': 0}, m=2, design=["m"] - ) + self.c1.set_attr(x=1, p=1, fluid={'H2O': 1}, m=1) + self.c3.set_attr(x=0, p=0.7, fluid={'H2O': 1}, m=2, design=["m"]) self.nw.solve('design') self.nw._convergence_check() ttd_l = round(instance.ttd_l.val, 3) diff --git a/tests/test_components/test_turbomachinery.py b/tests/test_components/test_turbomachinery.py index 3c7c95a6a..6564e0279 100644 --- a/tests/test_components/test_turbomachinery.py +++ b/tests/test_components/test_turbomachinery.py @@ -44,7 +44,7 @@ def test_Compressor(self): self.setup_network(instance) # compress NH3, other fluids in network are for turbine, pump, ... - fl = {'N2': 1, 'O2': 0, 'Ar': 0, 'DowQ': 0, 'NH3': 0} + fl = {'N2': 1} self.c1.set_attr(fluid=fl, v=1, p=1, T=5) self.c2.set_attr(p=6) instance.set_attr(eta_s=0.8) @@ -54,7 +54,7 @@ def test_Compressor(self): # test isentropic efficiency value eta_s_d = ( - (isentropic(self.c1.get_flow(), self.c2.get_flow()) - + (isentropic(self.c1.p.val_SI, self.c1.h.val_SI, self.c2.p.val_SI, self.c1.fluid_data, self.c1.mixing_rule) - self.c1.h.val_SI) / (self.c2.h.val_SI - self.c1.h.val_SI)) msg = ('Value of isentropic efficiency must be ' + str(eta_s_d) + ', is ' + str(instance.eta_s.val) + '.') @@ -67,7 +67,7 @@ def test_Compressor(self): # test calculated value eta_s = ( - (isentropic(self.c1.get_flow(), self.c2.get_flow()) - + (isentropic(self.c1.p.val_SI, self.c1.h.val_SI, self.c2.p.val_SI, self.c1.fluid_data, self.c1.mixing_rule) - self.c1.h.val_SI) / (self.c2.h.val_SI - self.c1.h.val_SI)) msg = ('Value of isentropic efficiency must be ' + str(eta_s) + ', is ' + str(instance.eta_s.val) + '.') @@ -160,8 +160,9 @@ def test_Pump(self): """Test component properties of pumps.""" instance = Pump('pump') self.setup_network(instance) - fl = {'N2': 0, 'O2': 0, 'Ar': 0, 'DowQ': 1, 'NH3': 0} - self.c1.set_attr(fluid=fl, v=1, p=5, T=50) + fl = {'DowQ': 1} + fbe = {'DowQ': 'INCOMP'} + self.c1.set_attr(fluid=fl, fluid_back_ends=fbe, v=1, p=5, T=50) self.c2.set_attr(p=7) instance.set_attr(eta_s=1) self.nw.solve('design') @@ -169,7 +170,7 @@ def test_Pump(self): # test calculated value for efficiency eta_s = ( - (isentropic(self.c1.get_flow(), self.c2.get_flow()) - + (isentropic(self.c1.p.val_SI, self.c1.h.val_SI, self.c2.p.val_SI, self.c1.fluid_data, self.c1.mixing_rule) - self.c1.h.val_SI) / (self.c2.h.val_SI - self.c1.h.val_SI)) msg = ('Value of isentropic efficiency must be ' + str(eta_s) + ', is ' + str(instance.eta_s.val) + '.') @@ -177,8 +178,8 @@ def test_Pump(self): # isentropic efficiency of 1 means inlet and outlet entropy are # identical - s1 = round(s_mix_ph(self.c1.get_flow()), 4) - s2 = round(s_mix_ph(self.c2.get_flow()), 4) + s1 = round(self.c1.calc_s(), 4) + s2 = round(self.c2.calc_s(), 4) msg = ('Value of entropy must be identical for inlet (' + str(s1) + ') and outlet (' + str(s2) + ') at 100 % isentropic efficiency.') @@ -251,8 +252,7 @@ def test_Turbine(self): """Test component properties of turbines.""" instance = Turbine('turbine') self.setup_network(instance) - fl = {'N2': 0.7556, 'O2': 0.2315, 'Ar': 0.0129, 'DowQ': 0, - 'NH3': 0} + fl = {'N2': 0.7556, 'O2': 0.2315, 'Ar': 0.0129} self.c1.set_attr(fluid=fl, m=15, p=10) self.c2.set_attr(p=1, T=25) instance.set_attr(eta_s=0.85) @@ -263,7 +263,7 @@ def test_Turbine(self): # design value of isentropic efficiency eta_s_d = ( (self.c2.h.val_SI - self.c1.h.val_SI) / ( - isentropic(self.c1.get_flow(), self.c2.get_flow()) - + isentropic(self.c1.p.val_SI, self.c1.h.val_SI, self.c2.p.val_SI, self.c1.fluid_data, self.c1.mixing_rule) - self.c1.h.val_SI)) msg = ('Value of isentropic efficiency must be ' + str(round(eta_s_d, 3)) + ', is ' + str(instance.eta_s.val) + @@ -276,7 +276,7 @@ def test_Turbine(self): self.nw._convergence_check() eta_s = ( (self.c2.h.val_SI - self.c1.h.val_SI) / ( - isentropic(self.c1.get_flow(), self.c2.get_flow()) - + isentropic(self.c1.p.val_SI, self.c1.h.val_SI, self.c2.p.val_SI, self.c1.fluid_data, self.c1.mixing_rule) - self.c1.h.val_SI)) msg = ('Value of isentropic efficiency must be ' + str(eta_s) + ', is ' + str(instance.eta_s.val) + '.') @@ -342,7 +342,7 @@ def test_Turbomachine(self): instance.component() + '.') assert 'turbomachine' == instance.component(), msg self.setup_network(instance) - fl = {'N2': 0.7556, 'O2': 0.2315, 'Ar': 0.0129, 'DowQ': 0, 'NH3': 0} + fl = {'N2': 0.7556, 'O2': 0.2315, 'Ar': 0.0129} self.c1.set_attr(fluid=fl, m=10, p=1, h=1e5) self.c2.set_attr(p=3, h=2e5) diff --git a/tests/test_errors.py b/tests/test_errors.py index 0e3a85ec7..f0b7d0008 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -655,6 +655,7 @@ def test_missing_CharMap_files(): def test_h_mix_pQ_on_mixtures(): c = Connection(Source("test"), "out1", Sink("test2"), "in1") c.set_attr(fluid={"O2": 0.24, "N2": 0.76}) + c._create_fluid_wrapper() c.build_fluid_data() with raises(ValueError): h_mix_pQ(1e5, 0.5, c.fluid_data, c.mixing_rule) diff --git a/tests/test_models/test_solar_energy_generating_system.py b/tests/test_models/test_solar_energy_generating_system.py index a4b30bbae..0f2692a33 100644 --- a/tests/test_models/test_solar_energy_generating_system.py +++ b/tests/test_models/test_solar_energy_generating_system.py @@ -282,7 +282,7 @@ def setup_method(self): # connection parameters # parabolic trough cycle - c70.set_attr(fluid={'TVP1': 1, 'water': 0, 'air': 0}, T=390, p=23.304) + c70.set_attr(fluid={'TVP1': 1}, fluid_back_ends={"TVP1": "INCOMP"}, T=390, p=23.304) c76.set_attr(m=Ref(c70, 0.1284, 0)) c73.set_attr(p=22.753) c74.set_attr(p=21.167) @@ -291,16 +291,16 @@ def setup_method(self): # cooling water c62.set_attr( - fluid={'TVP1': 0, 'water': 1, 'air': 0}, T=30, p=self.pamb) + fluid={'water': 1}, T=30, p=self.pamb) # cooling tower c64.set_attr( - fluid={'water': 0, 'TVP1': 0, 'air': 1}, p=self.pamb, T=self.Tamb) + fluid={'air': 1}, p=self.pamb, T=self.Tamb) c65.set_attr(p=self.pamb + 0.0005) c66.set_attr(p=self.pamb, T=30) # power cycle c32.set_attr(Td_bp=-2) c34.set_attr(x=0.5) - c1.set_attr(fluid={'water': 1, 'TVP1': 0, 'air': 0}, p=100, T=371) + c1.set_attr(fluid={'water': 1}, p=100, T=371) # steam generator pressure values c31.set_attr(p=103.56)