class TestCompressedAirIn: def setup(self): """Set up air compressor.""" self.Tamb = 20 self.pamb = 1 fluids = ['Air'] # compressor part self.nw = Network(fluids=fluids) self.nw.set_attr(p_unit='bar', T_unit='C', h_unit='kJ / kg') # components amb = Source('air intake') cp = Compressor('compressor') cooler = HeatExchangerSimple('cooling') cas = Sink('compressed air storage') # power input bus self.power_in = Bus('power input') self.power_in.add_comps({'comp': cp, 'char': 1, 'base': 'bus'}) # compressed air bus (not sure about this!) self.cas_in = Bus('massflow into storage') self.cas_in.add_comps({'comp': cas}, {'comp': amb, 'base': 'bus'}) self.nw.add_busses(self.power_in, self.cas_in) # create connections amb_cp = Connection(amb, 'out1', cp, 'in1') cp_cool = Connection(cp, 'out1', cooler, 'in1') cool_cas = Connection(cooler, 'out1', cas, 'in1') self.nw.add_conns(amb_cp, cp_cool, cool_cas) # component parameters cp.set_attr(eta_s=1) cooler.set_attr(pr=1) # connection parameters amb_cp.set_attr(m=2, T=self.Tamb, p=self.pamb, fluid={'Air': 1}) cool_cas.set_attr(T=self.Tamb, p=10) # solve network self.nw.solve('design') convergence_check(self.nw.lin_dep) def test_exergy_analysis_bus_conversion(self): """Test exergy analysis at product exergy with T < Tamb.""" self.nw.exergy_analysis(self.pamb, self.Tamb, E_P=[self.cas_in], E_F=[self.power_in]) exergy_balance = (self.nw.E_F - self.nw.E_P - self.nw.E_L - self.nw.E_D) msg = ('Exergy balance must be closed (residual value smaller than ' + str(err**0.5) + ') for this test but is ' + str(round(abs(exergy_balance), 4)) + ' .') assert abs(exergy_balance) <= err**0.5, msg
class TestReactors: def setup(self): """Set up network for electrolyzer tests.""" self.nw = Network(['O2', 'H2', 'H2O'], T_unit='C', p_unit='bar') self.instance = WaterElectrolyzer('electrolyzer') fw = Source('feed water') cw_in = Source('cooling water') o2 = Sink('oxygen sink') h2 = Sink('hydrogen sink') cw_out = Sink('cooling water sink') self.instance.set_attr(pr=0.99, eta=1) cw_el = Connection(cw_in, 'out1', self.instance, 'in1', fluid={ 'H2O': 1, 'H2': 0, 'O2': 0 }, T=20, p=1) el_cw = Connection(self.instance, 'out1', cw_out, 'in1', T=45) self.nw.add_conns(cw_el, el_cw) fw_el = Connection(fw, 'out1', self.instance, 'in2', label='h2o') el_o2 = Connection(self.instance, 'out2', o2, 'in1') el_h2 = Connection(self.instance, 'out3', h2, 'in1', label='h2') self.nw.add_conns(fw_el, el_o2, el_h2) def test_WaterElectrolyzer(self): """Test component properties of water electrolyzer.""" # check bus function: # power output on component and bus must be indentical self.nw.get_conn('h2o').set_attr(T=25, p=1) self.nw.get_conn('h2').set_attr(T=25) power = Bus('power') power.add_comps({'comp': self.instance, 'param': 'P', 'base': 'bus'}) power.set_attr(P=2.5e6) self.nw.add_busses(power) self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of power must be ' + str(power.P.val) + ', is ' + str(self.instance.P.val) + '.') assert round(power.P.val, 1) == round(self.instance.P.val), msg # effieciency was set to 100 % with inlet and outlet states of the # reaction educts and products beeing identical to reference state # therefore Q must be equal to 0 msg = ('Value of heat output must be 0.0, is ' + str(self.instance.Q.val) + '.') assert round(self.instance.Q.val, 4) == 0.0, msg # reset power, change efficiency value and specify heat bus value power.set_attr(P=np.nan) self.nw.get_conn('h2o').set_attr(T=25, p=1) self.nw.get_conn('h2').set_attr(T=50) self.instance.set_attr(eta=0.8) # check bus function: # heat output on component and bus must be indentical heat = Bus('heat') heat.add_comps({'comp': self.instance, 'param': 'Q'}) heat.set_attr(P=-8e5) self.nw.add_busses(heat) self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of heat flow must be ' + str(heat.P.val) + ', is ' + str(self.instance.Q.val) + '.') assert round(heat.P.val, 1) == round(self.instance.Q.val), msg self.nw.save('tmp') # check bus function: # heat output on component and bus must identical (offdesign test) Q = heat.P.val * 0.9 heat.set_attr(P=Q) self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) msg = ('Value of heat flow must be ' + str(Q) + ', is ' + str(self.instance.Q.val) + '.') assert round(Q, 1) == round(self.instance.Q.val), msg # delete both busses again self.nw.del_busses(heat, power) # test efficiency vs. specific energy consumption self.nw.get_conn('h2').set_attr(m=0.1) self.instance.set_attr(eta=0.9, e='var') self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of efficiency must be ' + str(self.instance.eta.val) + ', is ' + str(self.instance.e0 / self.instance.e.val) + '.') eta = round(self.instance.eta.val, 2) eta_calc = round(self.instance.e0 / self.instance.e.val, 2) assert eta == eta_calc, msg # test efficiency value > 1, Q must be larger than 0 e = 130e6 self.instance.set_attr(e=np.nan, eta=np.nan) self.instance.set_attr(e=e) self.nw.solve('design') convergence_check(self.nw.lin_dep) # test efficiency msg = ('Value of efficiency must be ' + str(self.instance.e0 / e) + ', is ' + str(self.instance.eta.val) + '.') eta = round(self.instance.e0 / e, 2) eta_calc = round(self.instance.eta.val, 2) assert eta == eta_calc, msg # test Q msg = ('Value of heat must be larger than zero, is ' + str(self.instance.Q.val) + '.') assert self.instance.Q.val > 0, msg # test specific energy consumption e = 150e6 self.instance.set_attr(e=np.nan, eta=np.nan) self.instance.set_attr(e=e) self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of specific energy consumption e must be ' + str(e) + ', is ' + str(self.instance.e.val) + '.') assert round(e, 1) == round(self.instance.e.val, 1), msg # test cooling loop pressure ratio, zeta as variable value pr = 0.95 self.instance.set_attr(pr=pr, e=None, eta=None, zeta='var', P=2e7, design=['pr']) self.nw.solve('design') shutil.rmtree('./tmp', ignore_errors=True) self.nw.save('tmp') convergence_check(self.nw.lin_dep) msg = ('Value of pressure ratio must be ' + str(pr) + ', is ' + str(self.instance.pr.val) + '.') assert round(pr, 2) == round(self.instance.pr.val, 2), msg # use zeta as offdesign parameter, at design point pressure # ratio must not change self.instance.set_attr(zeta=np.nan, offdesign=['zeta']) self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) msg = ('Value of pressure ratio must be ' + str(pr) + ', is ' + str(self.instance.pr.val) + '.') assert round(pr, 2) == round(self.instance.pr.val, 2), msg # test heat output specification in offdesign mode Q = self.instance.Q.val * 0.9 self.instance.set_attr(Q=Q, P=np.nan) self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) msg = ('Value of heat must be ' + str(Q) + ', is ' + str(self.instance.Q.val) + '.') assert round(Q, 0) == round(self.instance.Q.val, 0), msg shutil.rmtree('./tmp', ignore_errors=True)
class TestHeatExchangers: def setup(self): self.nw = Network( ['H2O', 'Ar', 'INCOMP::S800'], T_unit='C', p_unit='bar', v_unit='m3 / s') self.inl1 = Source('inlet 1') self.outl1 = Sink('outlet 1') def setup_HeatExchangerSimple_network(self, instance): self.c1 = Connection(self.inl1, 'out1', instance, 'in1') self.c2 = Connection(instance, 'out1', self.outl1, 'in1') self.nw.add_conns(self.c1, self.c2) def setup_HeatExchanger_network(self, instance): self.inl2 = Source('inlet 2') self.outl2 = Sink('outlet 2') self.c1 = Connection(self.inl1, 'out1', instance, 'in1') self.c2 = Connection(instance, 'out1', self.outl1, 'in1') self.c3 = Connection(self.inl2, 'out1', instance, 'in2') self.c4 = Connection(instance, 'out2', self.outl2, 'in1') self.nw.add_conns(self.c1, self.c2, self.c3, self.c4) def test_HeatExhangerSimple(self): """Test component properties of simple heat exchanger.""" instance = HeatExchangerSimple('heat exchanger') self.setup_HeatExchangerSimple_network(instance) fl = {'Ar': 0, 'H2O': 1, 'S800': 0} 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) # test grouped parameter settings with missing parameters instance.hydro_group.is_set = True instance.kA_group.is_set = True instance.kA_char_group.is_set = True self.nw.solve('design', init_only=True) msg = ('Hydro group must no be set, if one parameter is missing!') assert instance.hydro_group.is_set is False, msg msg = ('kA group must no be set, if one parameter is missing!') assert instance.kA_group.is_set is False, msg msg = ('kA char group must no be set, if one parameter is missing!') assert instance.kA_char_group.is_set is False, msg # test diameter calculation from specified dimensions (as pipe) # with Hazen-Williams method instance.set_attr(hydro_group='HW', D='var', L=100, ks=100, pr=0.99, Tamb=20) b = Bus('heat', P=-1e5) b.add_comps({'comp': instance}) self.nw.add_busses(b) self.nw.solve('design') convergence_check(self.nw.lin_dep) pr = round(self.c2.p.val_SI / self.c1.p.val_SI, 3) msg = ('Value of pressure ratio must be ' + str(pr) + ', is ' + str(instance.pr.val) + '.') assert pr == round(instance.pr.val, 3), msg # make zeta system variable and use previously calculated diameter # to calculate zeta. The value for zeta must not change zeta = round(instance.zeta.val, 0) instance.set_attr(D=instance.D.val, zeta='var', pr=np.nan) instance.D.is_var = False self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of zeta must be ' + str(zeta) + ', is ' + str(round(instance.zeta.val, 0)) + '.') assert zeta == round(instance.zeta.val, 0), msg # same test with pressure ratio as sytem variable pr = round(instance.pr.val, 3) instance.set_attr(zeta=np.nan, pr='var') self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of pressure ratio must be ' + str(pr) + ', is ' + str(round(instance.pr.val, 3)) + '.') assert pr == round(instance.pr.val, 3), msg # test heat transfer coefficient as variable of the system (ambient # temperature required) instance.set_attr(kA='var', pr=np.nan) b.set_attr(P=-5e4) self.nw.solve('design') convergence_check(self.nw.lin_dep) # due to heat output being half of reference (for Tamb) kA should be # somewhere near to that (actual value is 677) msg = ('Value of heat transfer coefficient must be 677, is ' + str(instance.kA.val) + '.') assert 677 == round(instance.kA.val, 0), msg # test heat transfer as variable of the system instance.set_attr(Q='var', kA=np.nan) Q = -5e4 b.set_attr(P=Q) self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of heat transfer must be ' + str(Q) + ', is ' + str(instance.Q.val) + '.') assert Q == round(instance.Q.val, 0), msg def test_ParabolicTrough(self): """Test component properties of parabolic trough.""" instance = ParabolicTrough('parabolic trough') self.setup_HeatExchangerSimple_network(instance) fl = {'Ar': 0, 'H2O': 0, 'S800': 1} self.c1.set_attr(fluid=fl, p=2, T=200) self.c2.set_attr(T=350) # test grouped parameter settings with missing parameters instance.hydro_group.is_set = True instance.energy_group.is_set = True self.nw.solve('design', init_only=True) msg = ('Hydro group must no be set, if one parameter is missing!') assert instance.hydro_group.is_set is False, msg msg = ('Energy group must no be set, if one parameter is missing!') assert instance.energy_group.is_set is False, msg # test solar collector params as system variables instance.set_attr( pr=1, aoi=10, doc=0.95, Q=1e6, Tamb=25, A='var', eta_opt=0.816, c_1=0.0622, c_2=0.00023, E=8e2, iam_1=-1.59e-3, iam_2=9.77e-5) self.nw.solve('design') convergence_check(self.nw.lin_dep) # heat loss must be identical to E * A - Q (internal heat loss # calculation) T_diff = (self.c2.T.val + self.c1.T.val) / 2 - instance.Tamb.val iam = ( 1 - instance.iam_1.val * abs(instance.aoi.val) - instance.iam_2.val * instance.aoi.val ** 2) Q_loss = -round(instance.A.val * ( instance.E.val * ( 1 - instance.eta_opt.val * instance.doc.val ** 1.5 * iam ) + T_diff * instance.c_1.val + T_diff ** 2 * instance.c_2.val), 0) msg = ( 'Value for heat loss of parabolic trough must be ' + str(Q_loss) + ', is ' + str(round(instance.Q_loss.val, 0)) + '.') assert Q_loss == round(instance.Q_loss.val, 0), msg # test all parameters of the energy group: E # going to a different operating point first area = instance.A.val instance.set_attr(A=area * 1.2, E='var') self.nw.solve('design') instance.set_attr(A=area) self.nw.solve('design') convergence_check(self.nw.lin_dep) assert Q_loss == round(instance.Q_loss.val, 0), msg # test all parameters of the energy group: eta_opt instance.set_attr(E=5e2, eta_opt='var') self.nw.solve('design') instance.set_attr(E=8e2) self.nw.solve('design') convergence_check(self.nw.lin_dep) assert Q_loss == round(instance.Q_loss.val, 0), msg # test all parameters of the energy group: c_1 instance.set_attr(E=5e2, eta_opt=instance.eta_opt.val, c_1='var') self.nw.solve('design') instance.set_attr(E=8e2) self.nw.solve('design') convergence_check(self.nw.lin_dep) assert Q_loss == round(instance.Q_loss.val, 0), msg # test all parameters of the energy group: c_2 instance.set_attr(E=5e2, c_1=instance.c_1.val, c_2='var') self.nw.solve('design') instance.set_attr(E=8e2) self.nw.solve('design') convergence_check(self.nw.lin_dep) assert Q_loss == round(instance.Q_loss.val, 0), msg # test all parameters of the energy group: iam_1 instance.set_attr(E=5e2, c_2=instance.c_2.val, iam_1='var') self.nw.solve('design') instance.set_attr(E=8e2) self.nw.solve('design') convergence_check(self.nw.lin_dep) assert Q_loss == round(instance.Q_loss.val, 0), msg # test all parameters of the energy group: iam_2 instance.set_attr(E=5e2, iam_1=instance.iam_1.val, iam_2='var') self.nw.solve('design') instance.set_attr(E=8e2) self.nw.solve('design') convergence_check(self.nw.lin_dep) assert Q_loss == round(instance.Q_loss.val, 0), msg # test all parameters of the energy group: aoi instance.set_attr(E=5e2, iam_2=instance.iam_2.val, aoi='var') self.nw.solve('design') instance.set_attr(E=8e2) self.nw.solve('design') convergence_check(self.nw.lin_dep) assert Q_loss == round(instance.Q_loss.val, 0), msg # test all parameters of the energy group: doc instance.set_attr(E=5e2, aoi=instance.aoi.val, doc='var') self.nw.solve('design') instance.set_attr(E=8e2) self.nw.solve('design') convergence_check(self.nw.lin_dep) assert Q_loss == round(instance.Q_loss.val, 0), msg def test_SolarCollector(self): """Test component properties of solar collector.""" instance = SolarCollector('solar collector') self.setup_HeatExchangerSimple_network(instance) fl = {'Ar': 0, 'H2O': 1, 'S800': 0} self.c1.set_attr(fluid=fl, p=10, T=30) self.c2.set_attr(T=70) # test grouped parameter settings with missing parameters instance.hydro_group.is_set = True instance.energy_group.is_set = True self.nw.solve('design', init_only=True) msg = ('Hydro group must no be set, if one parameter is missing!') assert instance.hydro_group.is_set is False, msg msg = ('Energy group must no be set, if one parameter is missing!') assert instance.energy_group.is_set is False, msg # test solar collector params as system variables instance.set_attr(E=1e3, lkf_lin=1.0, lkf_quad=0.005, A='var', eta_opt=0.9, Q=1e5, Tamb=20, pr=0.99) self.nw.solve('design') convergence_check(self.nw.lin_dep) # heat loss must be identical to E * A - Q (internal heat loss # calculation) T_diff = (self.c2.T.val + self.c1.T.val) / 2 - instance.Tamb.val Q_loss = -round(instance.A.val * ( instance.E.val * (1 - instance.eta_opt.val) + T_diff * instance.lkf_lin.val + T_diff ** 2 * instance.lkf_quad.val), 0) msg = ('Value for heat loss of solar collector must be ' + str(Q_loss) + ', is ' + str(round(instance.Q_loss.val, 0)) + '.') assert Q_loss == round(instance.Q_loss.val, 0), msg # test all parameters of the energy group: E area = instance.A.val instance.set_attr(A=area * 1.2, E='var') self.nw.solve('design') instance.set_attr(A=area) self.nw.solve('design') convergence_check(self.nw.lin_dep) assert Q_loss == round(instance.Q_loss.val, 0), msg # test all parameters of the energy group: eta_opt instance.set_attr(E=8e2, eta_opt='var') self.nw.solve('design') instance.set_attr(E=1e3) self.nw.solve('design') convergence_check(self.nw.lin_dep) assert Q_loss == round(instance.Q_loss.val, 0), msg # test all parameters of the energy group: lkf_lin instance.set_attr(E=8e2, eta_opt=instance.eta_opt.val, lkf_lin='var') self.nw.solve('design') instance.set_attr(E=1e3) self.nw.solve('design') convergence_check(self.nw.lin_dep) assert Q_loss == round(instance.Q_loss.val, 0), msg # test all parameters of the energy group: lkf_quad instance.set_attr(E=8e2, lkf_lin=instance.lkf_lin.val, lkf_quad='var') self.nw.solve('design') instance.set_attr(E=1e3) self.nw.solve('design') convergence_check(self.nw.lin_dep) assert Q_loss == round(instance.Q_loss.val, 0), msg # test all parameters of the energy group: Tamb instance.set_attr(E=8e2, lkf_lin=instance.lkf_lin.val, lkf_quad='var') self.nw.solve('design') instance.set_attr(E=1e3) self.nw.solve('design') convergence_check(self.nw.lin_dep) assert Q_loss == round(instance.Q_loss.val, 0), msg def test_HeatExchanger(self): """Test component properties of heat exchanger.""" instance = HeatExchanger('heat exchanger') self.setup_HeatExchanger_network(instance) # design specification 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.c2.set_attr(T=70) self.c3.set_attr(T=40, p=5, fluid={'Ar': 1, 'H2O': 0, 'S800': 0}) b = Bus('heat transfer', P=-80e3) b.add_comps({'comp': instance}) self.nw.add_busses(b) self.nw.solve('design') convergence_check(self.nw.lin_dep) self.nw.save('tmp') Q_design = instance.Q.val # test specified kA value instance.set_attr(kA=instance.kA.val * 2 / 3) b.set_attr(P=None) self.nw.solve('design') convergence_check(self.nw.lin_dep) # test heat transfer Q = self.c1.m.val_SI * (self.c2.h.val_SI - self.c1.h.val_SI) msg = ( 'Value of heat flow must be ' + str(round(Q_design * 2 / 3, 0)) + ', is ' + str(round(Q, 0)) + '.') assert round(Q, 1) == round(Q_design * 2 / 3, 1), msg # back to design case instance.set_attr(kA=None) b.set_attr(P=Q_design) self.nw.solve('design') convergence_check(self.nw.lin_dep) # check heat transfer Q = self.c1.m.val_SI * (self.c2.h.val_SI - self.c1.h.val_SI) td_log = ((self.c2.T.val - self.c3.T.val - self.c1.T.val + self.c4.T.val) / np.log((self.c2.T.val - self.c3.T.val) / (self.c1.T.val - self.c4.T.val))) kA = round(-Q / td_log, 0) msg = ('Value of heat transfer must be ' + str(round(Q, 0)) + ', is ' + str(round(instance.Q.val, 0)) + '.') assert round(Q, 0) == round(instance.Q.val, 0), msg # check upper terminal temperature difference msg = ('Value of terminal temperature difference must be ' + str(round(instance.ttd_u.val, 1)) + ', is ' + str(round(self.c1.T.val - self.c4.T.val, 1)) + '.') ttd_u_calc = round(self.c1.T.val - self.c4.T.val, 1) ttd_u = round(instance.ttd_u.val, 1) assert ttd_u_calc == ttd_u, msg # check lower terminal temperature difference self.c2.set_attr(T=np.nan) instance.set_attr(ttd_l=20) self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of terminal temperature difference must be ' + str(instance.ttd_l.val) + ', is ' + str(self.c2.T.val - self.c3.T.val) + '.') ttd_l_calc = round(self.c2.T.val - self.c3.T.val, 1) ttd_l = round(instance.ttd_l.val, 1) assert ttd_l_calc == ttd_l, msg # check specified kA value (by offdesign parameter), reset temperatures # to design state self.c2.set_attr(T=70) instance.set_attr(ttd_l=np.nan) self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) msg = ('Value of heat flow must be ' + str(instance.Q.val) + ', is ' + str(round(Q, 0)) + '.') assert round(Q, 0) == round(instance.Q.val, 0), msg msg = ('Value of heat transfer coefficient must be ' + str(kA) + ', is ' + str(round(instance.kA.val, 0)) + '.') assert kA == round(instance.kA.val, 0), msg # trigger negative lower terminal temperature difference as result self.c4.set_attr(T=np.nan) self.c2.set_attr(T=30) self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of upper terminal temperature differences must be ' 'smaller than zero, is ' + str(round(instance.ttd_l.val, 1)) + '.') assert instance.ttd_l.val < 0, msg # trigger negative upper terminal temperature difference as result self.c4.set_attr(T=100) self.c2.set_attr(h=200e3, T=np.nan) instance.set_attr(pr1=0.98, pr2=0.98, ttd_u=np.nan, design=['pr1', 'pr2']) self.c1.set_attr(h=150e3, T=np.nan) self.c3.set_attr(T=40) self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of upper terminal temperature differences must be ' 'smaller than zero, is ' + str(round(instance.ttd_u.val, 1)) + '.') assert instance.ttd_u.val < 0, msg shutil.rmtree('./tmp', ignore_errors=True) def test_Condenser(self): """Test component properties of Condenser.""" instance = Condenser('condenser') 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}) self.c4.set_attr(T=40) instance.set_attr(Q=-80e3) self.nw.solve('design') convergence_check(self.nw.lin_dep) self.nw.save('tmp') Q_design = instance.Q.val # test specified kA value instance.set_attr(kA=instance.kA.val * 2 / 3, Q=None) self.nw.solve('design') convergence_check(self.nw.lin_dep) # test heat transfer Q = self.c1.m.val_SI * (self.c2.h.val_SI - self.c1.h.val_SI) msg = ( 'Value of heat flow must be ' + str(round(Q_design * 2 / 3, 0)) + ', is ' + str(round(Q, 0)) + '.') assert round(Q, 1) == round(Q_design * 2 / 3, 1), msg # back to design case instance.set_attr(kA=None, Q=Q_design) self.nw.solve('design') convergence_check(self.nw.lin_dep) # test heat transfer Q = self.c1.m.val_SI * (self.c2.h.val_SI - self.c1.h.val_SI) msg = ('Value of heat flow must be ' + str(round(instance.Q.val, 0)) + ', is ' + str(round(Q, 0)) + '.') assert round(Q, 1) == round(instance.Q.val, 1), msg # test upper terminal temperature difference. For the component # condenser the temperature of the condensing fluid is relevant. ttd_u = round(T_bp_p(self.c1.get_flow()) - 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 ' + str(ttd_u) + '.') assert ttd_u == round(instance.ttd_u.val, 1), msg # test lower terminal temperature difference instance.set_attr(ttd_l=20, ttd_u=np.nan, design=['pr2', 'ttd_l']) self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of terminal temperature difference must be ' + str(instance.ttd_l.val) + ', is ' + str(self.c2.T.val - self.c3.T.val) + '.') ttd_l_calc = round(self.c2.T.val - self.c3.T.val, 1) ttd_l = round(instance.ttd_l.val, 1) assert ttd_l_calc == ttd_l, msg # check kA value with condensing pressure in offdesign mode: # no changes to design point means: identical pressure self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) msg = ('Value of condensing pressure be ' + str(p) + ', is ' + str(round(self.c1.p.val_SI, 5)) + '.') assert p == round(self.c1.p.val_SI, 5), msg shutil.rmtree('./tmp', ignore_errors=True)
class PowerPlant(): def __init__(self): self.nw = Network(fluids=['BICUBIC::water'], p_unit='bar', T_unit='C', h_unit='kJ / kg', iterinfo=False) # components # main cycle eco = HeatExchangerSimple('economizer') eva = HeatExchangerSimple('evaporator') sup = HeatExchangerSimple('superheater') cc = CycleCloser('cycle closer') hpt = Turbine('high pressure turbine') sp1 = Splitter('splitter 1', num_out=2) mpt = Turbine('mid pressure turbine') sp2 = Splitter('splitter 2', num_out=2) lpt = Turbine('low pressure turbine') con = Condenser('condenser') pu1 = Pump('feed water pump') fwh1 = Condenser('feed water preheater 1') fwh2 = Condenser('feed water preheater 2') dsh = Desuperheater('desuperheater') me2 = Merge('merge2', num_in=2) pu2 = Pump('feed water pump 2') pu3 = Pump('feed water pump 3') me = Merge('merge', num_in=2) # cooling water cwi = Source('cooling water source') cwo = Sink('cooling water sink') # connections # main cycle cc_hpt = Connection(cc, 'out1', hpt, 'in1', label='feed steam') hpt_sp1 = Connection(hpt, 'out1', sp1, 'in1', label='extraction1') sp1_mpt = Connection(sp1, 'out1', mpt, 'in1', state='g') mpt_sp2 = Connection(mpt, 'out1', sp2, 'in1', label='extraction2') sp2_lpt = Connection(sp2, 'out1', lpt, 'in1') lpt_con = Connection(lpt, 'out1', con, 'in1') con_pu1 = Connection(con, 'out1', pu1, 'in1') pu1_fwh1 = Connection(pu1, 'out1', fwh1, 'in2') fwh1_me = Connection(fwh1, 'out2', me, 'in1', state='l') me_fwh2 = Connection(me, 'out1', fwh2, 'in2', state='l') fwh2_dsh = Connection(fwh2, 'out2', dsh, 'in2', state='l') dsh_me2 = Connection(dsh, 'out2', me2, 'in1') me2_eco = Connection(me2, 'out1', eco, 'in1', state='l') eco_eva = Connection(eco, 'out1', eva, 'in1') eva_sup = Connection(eva, 'out1', sup, 'in1') sup_cc = Connection(sup, 'out1', cc, 'in1') self.nw.add_conns(cc_hpt, hpt_sp1, sp1_mpt, mpt_sp2, sp2_lpt, lpt_con, con_pu1, pu1_fwh1, fwh1_me, me_fwh2, fwh2_dsh, dsh_me2, me2_eco, eco_eva, eva_sup, sup_cc) # cooling water cwi_con = Connection(cwi, 'out1', con, 'in2') con_cwo = Connection(con, 'out2', cwo, 'in1') self.nw.add_conns(cwi_con, con_cwo) # preheating sp1_dsh = Connection(sp1, 'out2', dsh, 'in1') dsh_fwh2 = Connection(dsh, 'out1', fwh2, 'in1') fwh2_pu2 = Connection(fwh2, 'out1', pu2, 'in1') pu2_me2 = Connection(pu2, 'out1', me2, 'in2') sp2_fwh1 = Connection(sp2, 'out2', fwh1, 'in1') fwh1_pu3 = Connection(fwh1, 'out1', pu3, 'in1') pu3_me = Connection(pu3, 'out1', me, 'in2') self.nw.add_conns(sp1_dsh, dsh_fwh2, fwh2_pu2, pu2_me2, sp2_fwh1, fwh1_pu3, pu3_me) # busses # power bus self.power = Bus('power') self.power.add_comps({ 'comp': hpt, 'char': -1 }, { 'comp': mpt, 'char': -1 }, { 'comp': lpt, 'char': -1 }, { 'comp': pu1, 'char': -1 }, { 'comp': pu2, 'char': -1 }, { 'comp': pu3, 'char': -1 }) # heating bus self.heat = Bus('heat') self.heat.add_comps({ 'comp': eco, 'char': 1 }, { 'comp': eva, 'char': 1 }, { 'comp': sup, 'char': 1 }) self.nw.add_busses(self.power, self.heat) # parametrization # components hpt.set_attr(eta_s=0.9) mpt.set_attr(eta_s=0.9) lpt.set_attr(eta_s=0.9) pu1.set_attr(eta_s=0.8) pu2.set_attr(eta_s=0.8) pu3.set_attr(eta_s=0.8) eco.set_attr(pr=0.99) eva.set_attr(pr=0.99) sup.set_attr(pr=0.99) con.set_attr(pr1=1, pr2=0.99, ttd_u=5) fwh1.set_attr(pr1=1, pr2=0.99, ttd_u=5) fwh2.set_attr(pr1=1, pr2=0.99, ttd_u=5) dsh.set_attr(pr1=0.99, pr2=0.99) # connections eco_eva.set_attr(x=0) eva_sup.set_attr(x=1) cc_hpt.set_attr(m=200, T=650, p=100, fluid={'water': 1}) hpt_sp1.set_attr(p=20) mpt_sp2.set_attr(p=3) lpt_con.set_attr(p=0.05) cwi_con.set_attr(T=20, p=10, fluid={'water': 1}) # test run self.nw.solve('design') document_model(self.nw) def calculate_efficiency(self, x): # set extraction pressure self.nw.get_conn('extraction1').set_attr(p=x[0]) self.nw.get_conn('extraction2').set_attr(p=x[1]) self.nw.solve('design') for cp in self.nw.comps['object']: if isinstance(cp, Condenser) or isinstance(cp, Desuperheater): if cp.Q.val > 0: return np.nan elif isinstance(cp, Pump): if cp.P.val < 0: return np.nan elif isinstance(cp, Turbine): if cp.P.val > 0: return np.nan if self.nw.res[-1] > 1e-3 or self.nw.lin_dep: return np.nan else: return self.nw.busses['power'].P.val / self.nw.busses['heat'].P.val
class TestSEGS: def setup(self): """ Full model validation of SEGS model in TESPy vs. EBSILON. Find original models at https://github.com/fwitte/SEGS_exergy. """ # specification of ambient state self.pamb = 1.013 self.Tamb = 25 # setting up network self.nw = Network(fluids=['water', 'INCOMP::TVP1', 'air']) self.nw.set_attr(T_unit='C', p_unit='bar', h_unit='kJ / kg', m_unit='kg / s', s_unit="kJ / kgK") # components definition air_in = Source('Ambient air source', fkt_group='CW') air_out = Sink('Ambient air sink', fkt_group='CW') closer_pt = CycleCloser('Cycle closer pt', fkt_group='SF') pt = ParabolicTrough('Parabolic trough', fkt_group='SF') ptpump = Pump('HTF pump', fkt_group='SF') closer = CycleCloser('Cycle closer power cycle', fkt_group='SG') eco = HeatExchanger('Economizer', fkt_group='SG') eva = HeatExchanger('Evaporator', fkt_group='SG') sup = HeatExchanger('Superheater', fkt_group='SG') drum = Drum('Drum', fkt_group='SG') reh = HeatExchanger('Reheater', fkt_group='RH') hpt1 = Turbine('HP turbine 1', fkt_group='HPT') hpt2 = Turbine('HP turbine 2', fkt_group='HPT') lpt1 = Turbine('LP turbine 1', fkt_group='LPT') lpt2 = Turbine('LP turbine 2', fkt_group='LPT') lpt3 = Turbine('LP turbine 3', fkt_group='LPT') lpt4 = Turbine('LP turbine 4', fkt_group='LPT') lpt5 = Turbine('LP turbine 5', fkt_group='LPT') cond = Condenser('Condenser', fkt_group='CW') condpump = Pump('Condenser pump', fkt_group='CW') fwt = Merge('Feedwater tank', num_in=3, fkt_group='LPP') fwp = Pump('Feedwater pump', fkt_group='FWP') cwp = Pump('Cooling water pump', fkt_group='CW') closer_cw = CycleCloser('Cycle closer cw', fkt_group='CW') ct = HeatExchanger('Cooling tower', fkt_group='CW') fan = Compressor('Cooling tower fan', fkt_group='CW') sp1 = Splitter('Splitter 1', fkt_group='HPT') sp2 = Splitter('Splitter 2', fkt_group='HPT') sp3 = Splitter('Splitter 3', fkt_group='LPT') sp4 = Splitter('Splitter 4', fkt_group='LPT') sp5 = Splitter('Splitter 5', fkt_group='LPT') sp6 = Splitter('Splitter 6', fkt_group='LPT') sp7 = Splitter('Splitter 7', fkt_group='SF') m1 = Merge('Merge 1', fkt_group='CW') m2 = Merge('Merge 2', fkt_group='HPP') m3 = Merge('Merge 3', fkt_group='LPP') m4 = Merge('Merge 4', fkt_group='LPP') m5 = Merge('Merge 5', fkt_group='SF') v1 = Valve('Valve 1', fkt_group='HPP') v2 = Valve('Valve 2', fkt_group='HPP') v3 = Valve('Valve 3', fkt_group='LPP') v4 = Valve('Valve 4', fkt_group='LPP') v5 = Valve('Valve 5', fkt_group='LPP') hppre1 = Condenser('High pressure preheater 1', fkt_group='HPP') hppre2 = Condenser('High pressure preheater 2', fkt_group='HPP') hppre1_sub = HeatExchanger('High pressure preheater 1 subcooling', fkt_group='HPP') hppre2_sub = HeatExchanger('High pressure preheater 2 subcooling', fkt_group='HPP') lppre1 = Condenser('Low pressure preheater 1', fkt_group='LPP') lppre2 = Condenser('Low pressure preheater 2', fkt_group='LPP') lppre3 = Condenser('Low pressure preheater 3', fkt_group='LPP') lppre1_sub = HeatExchanger('Low pressure preheater 1 subcooling', fkt_group='LPP') lppre2_sub = HeatExchanger('Low pressure preheater 2 subcooling', fkt_group='LPP') lppre3_sub = HeatExchanger('Low pressure preheater 3 subcooling', fkt_group='LPP') # connections definition # power cycle c1 = Connection(sup, 'out2', closer, 'in1', label='1') c2 = Connection(closer, 'out1', hpt1, 'in1', label='2') c3 = Connection(hpt1, 'out1', sp1, 'in1', label='3') c4 = Connection(sp1, 'out1', hpt2, 'in1', label='4') c5 = Connection(hpt2, 'out1', sp2, 'in1', label='5') c6 = Connection(sp2, 'out1', reh, 'in2', label='6') c7 = Connection(reh, 'out2', lpt1, 'in1', label='7') c8 = Connection(lpt1, 'out1', sp3, 'in1', label='8') c9 = Connection(sp3, 'out1', lpt2, 'in1', label='9') c10 = Connection(lpt2, 'out1', sp4, 'in1', label='10') c11 = Connection(sp4, 'out1', lpt3, 'in1', label='11') c12 = Connection(lpt3, 'out1', sp5, 'in1', label='12') c13 = Connection(sp5, 'out1', lpt4, 'in1', label='13') c14 = Connection(lpt4, 'out1', sp6, 'in1', label='14') c15 = Connection(sp6, 'out1', lpt5, 'in1', label='15') c16 = Connection(lpt5, 'out1', m1, 'in1', label='16') c17 = Connection(m1, 'out1', cond, 'in1', label='17') c18 = Connection(cond, 'out1', condpump, 'in1', label='18') c19 = Connection(condpump, 'out1', lppre1, 'in2', label='19') # c19 = Connection(condpump, 'out1', lppre1_sub, 'in2', label='19') # c20 = Connection(lppre1_sub, 'out2', lppre1, 'in2', label='20') c21 = Connection(lppre1, 'out2', lppre2, 'in2', label='21') # c21 = Connection(lppre1, 'out2', lppre2_sub, 'in2', label='21') # c22 = Connection(lppre2_sub, 'out2', lppre2, 'in2', label='22') c23 = Connection(lppre2, 'out2', lppre3, 'in2', label='23') # c23 = Connection(lppre2, 'out2', lppre3_sub, 'in2', label='23') # c24 = Connection(lppre3_sub, 'out2', lppre3, 'in2', label='24') c25 = Connection(lppre3, 'out2', fwt, 'in1', label='25') c26 = Connection(fwt, 'out1', fwp, 'in1', label='26') c27 = Connection(fwp, 'out1', hppre1, 'in2', label='27') c29 = Connection(hppre1, 'out2', hppre2, 'in2', label='29') c31 = Connection(hppre2, 'out2', eco, 'in2', label='31') c36 = Connection(sp1, 'out2', hppre2, 'in1', label='36') c37 = Connection(hppre2, 'out1', v1, 'in1', label='37') c39 = Connection(v1, 'out1', m2, 'in2', label='39') c40 = Connection(sp2, 'out2', m2, 'in1', label='40') c41 = Connection(m2, 'out1', hppre1, 'in1', label='41') c42 = Connection(hppre1, 'out1', v2, 'in1', label='42') c44 = Connection(v2, 'out1', fwt, 'in2', label='44') c45 = Connection(sp3, 'out2', fwt, 'in3', label='45') c46 = Connection(sp4, 'out2', lppre3, 'in1', label='46') c47 = Connection(lppre3, 'out1', v3, 'in1', label='47') # c47 = Connection(lppre3, 'out1', lppre3_sub, 'in1', label='47') # c48 = Connection(lppre3_sub, 'out1', v3, 'in1', label='48') c49 = Connection(v3, 'out1', m3, 'in1', label='49') c50 = Connection(sp5, 'out2', m3, 'in2', label='50') c51 = Connection(m3, 'out1', lppre2, 'in1', label='51') c52 = Connection(lppre2, 'out1', v4, 'in1', label='52') # c52 = Connection(lppre2, 'out1', lppre2_sub, 'in1', label='52') # c53 = Connection(lppre2_sub, 'out1', v4, 'in1', label='53') c54 = Connection(v4, 'out1', m4, 'in2', label='54') c55 = Connection(sp6, 'out2', m4, 'in1', label='55') c56 = Connection(m4, 'out1', lppre1, 'in1', label='56') c57 = Connection(lppre1, 'out1', v5, 'in1', label='57') # c57 = Connection(lppre1, 'out1', lppre1_sub, 'in1', label='57') # c58 = Connection(lppre1_sub, 'out1', v5, 'in1', label='58') c59 = Connection(v5, 'out1', m1, 'in2', label='59') # components from subsystem c32 = Connection(eco, 'out2', drum, 'in1', label='32') c33 = Connection(drum, 'out1', eva, 'in2', label='33') c34 = Connection(eva, 'out2', drum, 'in2', label='34') c35 = Connection(drum, 'out2', sup, 'in2', label='35') c73 = Connection(sup, 'out1', eva, 'in1', label='73') c74 = Connection(eva, 'out1', eco, 'in1', label='74') # cooling water c60 = Connection(cond, 'out2', closer_cw, 'in1', label='60') c61 = Connection(closer_cw, 'out1', ct, 'in1', label='61') c62 = Connection(ct, 'out1', cwp, 'in1', label='62') c63 = Connection(cwp, 'out1', cond, 'in2', label='63') # cooling tower c64 = Connection(air_in, 'out1', fan, 'in1', label='64') c65 = Connection(fan, 'out1', ct, 'in2', label='65') c66 = Connection(ct, 'out2', air_out, 'in1', label='66') # parabolic trough cycle c70 = Connection(pt, 'out1', closer_pt, 'in1', label='67') c71 = Connection(closer_pt, 'out1', sp7, 'in1', label='71') c72 = Connection(sp7, 'out1', sup, 'in1', label='72') c75 = Connection(eco, 'out1', m5, 'in1', label='75') c76 = Connection(sp7, 'out2', reh, 'in1', label='76') c77 = Connection(reh, 'out1', m5, 'in2', label='77') c78 = Connection(m5, 'out1', ptpump, 'in1', label='78') c79 = Connection(ptpump, 'out1', pt, 'in1', label='79') # add connections to network self.nw.add_conns(c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c21, c23, c25, c26, c27, c29, c31, c32, c33, c34, c35, c36, c37, c39, c40, c41, c42, c44, c45, c46, c47, c49, c50, c51, c52, c54, c55, c56, c57, c59, c60, c61, c62, c63, c64, c65, c66, c70, c71, c72, c73, c74, c75, c76, c77, c78, c79) # power bus power = Bus('total output power') power.add_comps({ 'comp': hpt1, 'char': 0.97, 'base': 'component' }, { 'comp': hpt2, 'char': 0.97, 'base': 'component' }, { 'comp': lpt1, 'char': 0.97, 'base': 'component' }, { 'comp': lpt2, 'char': 0.97, 'base': 'component' }, { 'comp': lpt3, 'char': 0.97, 'base': 'component' }, { 'comp': lpt4, 'char': 0.97, 'base': 'component' }, { 'comp': lpt5, 'char': 0.97, 'base': 'component' }, { 'comp': fwp, 'char': 0.95, 'base': 'bus' }, { 'comp': condpump, 'char': 0.95, 'base': 'bus' }, { 'comp': ptpump, 'char': 0.95, 'base': 'bus' }, { 'comp': cwp, 'char': 0.95, 'base': 'bus' }, { 'comp': fan, 'char': 0.95, 'base': 'bus' }) heat_input_bus = Bus('heat input') heat_input_bus.add_comps({'comp': pt, 'base': 'bus'}) exergy_loss_bus = Bus('exergy loss') exergy_loss_bus.add_comps({ 'comp': air_in, 'base': 'bus' }, {'comp': air_out}) self.nw.add_busses(power, heat_input_bus, exergy_loss_bus) # component parameters pt.set_attr(doc=0.95, aoi=0, Tamb=25, A='var', eta_opt=0.73, c_1=0.00496, c_2=0.000691, E=1000, iam_1=1, iam_2=1) ptpump.set_attr(eta_s=0.6) eco.set_attr() eva.set_attr(ttd_l=5) sup.set_attr() hpt1.set_attr(eta_s=0.8376) hpt2.set_attr(eta_s=0.8463) lpt1.set_attr(eta_s=0.8623) lpt2.set_attr(eta_s=0.917) lpt3.set_attr(eta_s=0.9352) lpt4.set_attr(eta_s=0.88) lpt5.set_attr(eta_s=0.6445) cond.set_attr(pr1=1, pr2=0.9, ttd_u=5) condpump.set_attr(eta_s=0.7) fwp.set_attr(eta_s=0.7) cwp.set_attr(eta_s=0.7) ct.set_attr(pr1=0.95) fan.set_attr(eta_s=0.6) lppre1.set_attr(pr1=1, ttd_u=5) lppre2.set_attr(pr1=1, ttd_u=5) lppre3.set_attr(pr1=1, ttd_u=5) hppre1.set_attr(pr1=1, ttd_u=5) hppre2.set_attr(pr1=1, ttd_u=5) lppre1_sub.set_attr(pr1=1, pr2=1, ttd_l=10) lppre2_sub.set_attr(pr1=1, pr2=1, ttd_l=10) lppre3_sub.set_attr(pr1=1, pr2=1, ttd_l=10) hppre1_sub.set_attr(pr1=1, pr2=1, ttd_l=10) hppre2_sub.set_attr(pr1=1, pr2=1, ttd_l=10) # connection parameters # parabolic trough cycle c70.set_attr(fluid={'TVP1': 1, 'water': 0, 'air': 0}, 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) c78.set_attr(p=20.34) c79.set_attr(p=41.024) # cooling water c62.set_attr(fluid={ 'TVP1': 0, 'water': 1, 'air': 0 }, T=30, p=self.pamb) # cooling tower c64.set_attr(fluid={ 'water': 0, 'TVP1': 0, '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) # steam generator pressure values c31.set_attr(p=103.56) c35.set_attr(p=103.42) # turbine pressure values c3.set_attr(p=33.61, m=38.969) c5.set_attr(p=18.58) c7.set_attr(p=17.1, T=371) c8.set_attr(p=7.98) c10.set_attr(p=2.73) c12.set_attr(p=0.96) c14.set_attr(p=0.29) # preheater pressure values c19.set_attr(p=14.755, state='l') c21.set_attr(p=9.9975, state='l') c23.set_attr(p=8.7012, state='l') c25.set_attr(state='l') c27.set_attr(p=125) c29.set_attr(p=112) # condensation c16.set_attr(p=0.08) # feedwater tank c26.set_attr(x=0) # a stable solution is generated for parts of the network self.nw.solve(mode='design') self.nw.del_conns(c19, c21, c23, c27, c29, c37, c42, c47, c52, c57) c19 = Connection(condpump, 'out1', lppre1_sub, 'in2', label='19') c20 = Connection(lppre1_sub, 'out2', lppre1, 'in2', label='20') c21 = Connection(lppre1, 'out2', lppre2_sub, 'in2', label='21') c22 = Connection(lppre2_sub, 'out2', lppre2, 'in2', label='22') c23 = Connection(lppre2, 'out2', lppre3_sub, 'in2', label='23') c24 = Connection(lppre3_sub, 'out2', lppre3, 'in2', label='24') c27 = Connection(fwp, 'out1', hppre1_sub, 'in2', label='27') c28 = Connection(hppre1_sub, 'out2', hppre1, 'in2', label='28') c29 = Connection(hppre1, 'out2', hppre2_sub, 'in2', label='29') c30 = Connection(hppre2_sub, 'out2', hppre2, 'in2', label='30') c37 = Connection(hppre2, 'out1', hppre2_sub, 'in1', label='37') c38 = Connection(hppre2_sub, 'out1', v1, 'in1', label='38') c42 = Connection(hppre1, 'out1', hppre1_sub, 'in1', label='42') c43 = Connection(hppre1_sub, 'out1', v2, 'in1', label='43') c47 = Connection(lppre3, 'out1', lppre3_sub, 'in1', label='47') c48 = Connection(lppre3_sub, 'out1', v3, 'in1', label='48') c52 = Connection(lppre2, 'out1', lppre2_sub, 'in1', label='52') c53 = Connection(lppre2_sub, 'out1', v4, 'in1', label='53') c57 = Connection(lppre1, 'out1', lppre1_sub, 'in1', label='57') c58 = Connection(lppre1_sub, 'out1', v5, 'in1', label='58') self.nw.add_conns(c19, c20, c21, c22, c23, c24, c27, c28, c29, c30, c37, c38, c42, c43, c47, c48, c52, c53, c57, c58) # specification of missing parameters c19.set_attr(p=14.755) c21.set_attr(p=9.9975, state='l') c23.set_attr(p=8.7012, state='l') c27.set_attr(p=125) c29.set_attr(p=112) # solve final state self.nw.solve(mode='design') def test_model(self): """Test the thermodynamic model.""" power_ebsilon = -31.769 power_tespy = round(self.nw.busses['total output power'].P.val / 1e6, 3) msg = ('The total power calculated (' + str(power_tespy) + ') does not ' 'match the power calculated with the EBSILON model (' + str(power_ebsilon) + ').') assert power_tespy == power_ebsilon, msg T_c79_ebsilon = 296.254 T_c79_tespy = round(self.nw.get_conn('79').T.val, 3) msg = ('The temperature at connection 79 calculated (' + str(T_c79_tespy) + ') does not match the temperature calculated ' 'with the EBSILON model (' + str(T_c79_ebsilon) + ').') assert T_c79_tespy == T_c79_ebsilon, msg def test_exergy_analysis(self): """Test the exergy analysis results.""" # carry out exergy analysis ean = ExergyAnalysis(self.nw, E_P=[self.nw.busses['total output power']], E_F=[self.nw.busses['heat input']], E_L=[self.nw.busses['exergy loss']]) ean.analyse(pamb=self.pamb, Tamb=self.Tamb) # generate Grassmann diagram links, nodes = ean.generate_plotly_sankey_input() # check if exergy product value in links is equal to total power # output position = links['target'].index(nodes.index('E_P')) power_links = round(links['value'][position], 0) power_bus = round(-self.nw.busses['total output power'].P.val, 0) msg = ('The exergy product value in the links (' + str(power_links) + ') must be equal to the power on the respective bus (' + str(power_bus) + ').') assert power_links == power_bus, msg
class TestCompressedAirOut: def setup(self): """Set up air compressed air turbine.""" self.Tamb = 20 self.pamb = 1 fluids = ['Air'] # turbine part self.nw = Network(fluids=fluids) self.nw.set_attr(p_unit='bar', T_unit='C', h_unit='kJ / kg') # components cas = Source('compressed air storage') reheater = HeatExchangerSimple('reheating') turb = Turbine('turbine') amb = Sink('air outlet') # power ouput bus self.power_out = Bus('power output') self.power_out.add_comps({'comp': turb, 'char': 1}) # compressed air bus self.cas_out = Bus('exergy in') self.cas_out.add_comps({ 'comp': cas, 'base': 'bus' }, { 'comp': reheater, 'base': 'bus' }) # exergy loss bus self.ex_loss = Bus('exergy loss') self.ex_loss.add_comps({'comp': amb, 'base': 'component'}) self.nw.add_busses(self.power_out, self.cas_out) # create connections cas_reheater = Connection(cas, 'out1', reheater, 'in1') reheater_turb = Connection(reheater, 'out1', turb, 'in1') turb_amb = Connection(turb, 'out1', amb, 'in1', label='outlet') self.nw.add_conns(cas_reheater, reheater_turb, turb_amb) # component parameters turb.set_attr(eta_s=1) reheater.set_attr(pr=1) # connection parameters cas_reheater.set_attr(m=2, T=self.Tamb, p=10, fluid={'Air': 1}) reheater_turb.set_attr() turb_amb.set_attr(p=self.pamb, T=self.Tamb) # solve network self.nw.solve('design') convergence_check(self.nw.lin_dep) def test_exergy_analysis_bus_conversion(self): """Test exergy analysis at product exergy with T < Tamb.""" ean = ExergyAnalysis(self.nw, E_P=[self.power_out], E_F=[self.cas_out], E_L=[self.ex_loss]) ean.analyse(pamb=self.pamb, Tamb=self.Tamb) exergy_balance = (ean.network_data.E_F - ean.network_data.E_P - ean.network_data.E_L - ean.network_data.E_D) msg = ('Exergy balance must be closed (residual value smaller than ' + str(err**0.5) + ') for this test but is ' + str(round(abs(exergy_balance), 4)) + '.') assert abs(exergy_balance) <= err**0.5, msg msg = ('Exergy efficiency must be equal to 1.0 for this test but is ' + str(round(ean.network_data.epsilon, 4)) + '.') assert round(ean.network_data.epsilon, 4) == 1, msg c = self.nw.get_conn('outlet') c.set_attr(T=self.Tamb - 20) self.nw.solve('design') convergence_check(self.nw.lin_dep) ean.analyse(pamb=self.pamb, Tamb=self.Tamb) msg = ( 'Exergy destruction must be equal to 0.0 for this test but is ' + str(round(ean.network_data.E_D, 4)) + '.') assert round(ean.network_data.E_D, 4) == 0, msg msg = ('Exergy loss must be equal to ' + str(round(c.Ex_physical, 4)) + ' for this test but is ' + str(round(ean.network_data.E_L, 4)) + '.') assert round(ean.network_data.E_L, 4) == round(c.Ex_physical, 4), msg
class TestHeatPump: def setup(self): # %% network setup self.nw = Network(fluids=['water', 'NH3'], T_unit='C', p_unit='bar', h_unit='kJ / kg', m_unit='kg / s') # %% components # sources & sinks cc_coolant = CycleCloser('coolant cycle closer') cc_consumer = CycleCloser('consumer cycle closer') amb_in = Source('source ambient') amb_out = Sink('sink ambient') ic_in = Source('source intercool') ic_out = Sink('sink intercool') # consumer system cd = HeatExchanger('condenser') rp = Pump('recirculation pump') cons = HeatExchangerSimple('consumer') # evaporator system va = Valve('valve') dr = Drum('drum') ev = HeatExchanger('evaporator') su = HeatExchanger('superheater') pu = Pump('pump evaporator') # compressor-system cp1 = Compressor('compressor 1') cp2 = Compressor('compressor 2') he = HeatExchanger('intercooler') # busses self.power = Bus('total compressor power') self.power.add_comps({ 'comp': cp1, 'base': 'bus' }, { 'comp': cp2, 'base': 'bus' }) self.heat = Bus('total delivered heat') self.heat.add_comps({'comp': cd, 'char': -1}) self.nw.add_busses(self.power, self.heat) # %% connections # consumer system c_in_cd = Connection(cc_coolant, 'out1', cd, 'in1') cb_rp = Connection(cc_consumer, 'out1', rp, 'in1') rp_cd = Connection(rp, 'out1', cd, 'in2') self.cd_cons = Connection(cd, 'out2', cons, 'in1') cons_cf = Connection(cons, 'out1', cc_consumer, 'in1') self.nw.add_conns(c_in_cd, cb_rp, rp_cd, self.cd_cons, cons_cf) # connection condenser - evaporator system cd_va = Connection(cd, 'out1', va, 'in1') self.nw.add_conns(cd_va) # evaporator system va_dr = Connection(va, 'out1', dr, 'in1') dr_pu = Connection(dr, 'out1', pu, 'in1') pu_ev = Connection(pu, 'out1', ev, 'in2') ev_dr = Connection(ev, 'out2', dr, 'in2') dr_su = Connection(dr, 'out2', su, 'in2') self.nw.add_conns(va_dr, dr_pu, pu_ev, ev_dr, dr_su) self.amb_in_su = Connection(amb_in, 'out1', su, 'in1') su_ev = Connection(su, 'out1', ev, 'in1') ev_amb_out = Connection(ev, 'out1', amb_out, 'in1') self.nw.add_conns(self.amb_in_su, su_ev, ev_amb_out) # connection evaporator system - compressor system su_cp1 = Connection(su, 'out2', cp1, 'in1') self.nw.add_conns(su_cp1) # compressor-system cp1_he = Connection(cp1, 'out1', he, 'in1') he_cp2 = Connection(he, 'out1', cp2, 'in1') cp2_c_out = Connection(cp2, 'out1', cc_coolant, 'in1') ic_in_he = Connection(ic_in, 'out1', he, 'in2') he_ic_out = Connection(he, 'out2', ic_out, 'in1') self.nw.add_conns(cp1_he, he_cp2, ic_in_he, he_ic_out, cp2_c_out) # %% component parametrization # condenser system x = np.array([ 0, 0.0625, 0.125, 0.1875, 0.25, 0.3125, 0.375, 0.4375, 0.5, 0.5625, 0.6375, 0.7125, 0.7875, 0.9, 0.9875, 1, 1.0625, 1.125, 1.175, 1.2125, 1.2375, 1.25 ]) y = np.array([ 0.0076, 0.1390, 0.2731, 0.4003, 0.5185, 0.6263, 0.7224, 0.8056, 0.8754, 0.9312, 0.9729, 1.0006, 1.0203, 1.0158, 1.0051, 1.0000, 0.9746, 0.9289, 0.8832, 0.8376, 0.7843, 0.7614 ]) rp.set_attr(eta_s=0.8, design=['eta_s'], offdesign=['eta_s_char'], eta_s_char={ 'char_func': CharLine(x, y), 'param': 'm' }) cons.set_attr(pr=1, design=['pr'], offdesign=['zeta']) # evaporator system x = np.linspace(0, 2.5, 26) y = np.array([ 0.000, 0.164, 0.283, 0.389, 0.488, 0.581, 0.670, 0.756, 0.840, 0.921, 1.000, 1.078, 1.154, 1.228, 1.302, 1.374, 1.446, 1.516, 1.585, 1.654, 1.722, 1.789, 1.855, 1.921, 1.986, 2.051 ]) kA_char1 = {'char_func': CharLine(x, y), 'param': 'm'} x = np.array([ 0.0100, 0.0400, 0.0700, 0.1100, 0.1500, 0.2000, 0.2500, 0.3000, 0.3500, 0.4000, 0.4500, 0.5000, 0.5500, 0.6000, 0.6500, 0.7000, 0.7500, 0.8000, 0.8500, 0.9000, 0.9500, 1.0000, 1.5000, 2.0000 ]) y = np.array([ 0.0185, 0.0751, 0.1336, 0.2147, 0.2997, 0.4118, 0.5310, 0.6582, 0.7942, 0.9400, 0.9883, 0.9913, 0.9936, 0.9953, 0.9966, 0.9975, 0.9983, 0.9988, 0.9992, 0.9996, 0.9998, 1.0000, 1.0008, 1.0014 ]) kA_char2 = {'char_func': CharLine(x, y), 'param': 'm'} ev.set_attr(pr1=1, pr2=.999, ttd_l=5, design=['ttd_l'], offdesign=['kA_char'], kA_char1=kA_char1, kA_char2=kA_char2) # no kA modification for hot side! x = np.array([0, 1]) y = np.array([1, 1]) kA_char1 = {'char_func': CharLine(x, y), 'param': 'm'} # characteristic line for superheater kA x = np.array( [0, 0.045, 0.136, 0.244, 0.43, 0.6, 0.7, 0.8, 0.9, 1, 1.1, 1.2]) y = np.array( [0, 0.037, 0.112, 0.207, 0.5, 0.8, 0.85, 0.9, 0.95, 1, 1.04, 1.07]) kA_char2 = {'char_func': CharLine(x, y), 'param': 'm'} su.set_attr(kA_char1=kA_char1, kA_char2=kA_char2, offdesign=['zeta1', 'zeta2', 'kA_char']) x = np.array([ 0, 0.0625, 0.125, 0.1875, 0.25, 0.3125, 0.375, 0.4375, 0.5, 0.5625, 0.6375, 0.7125, 0.7875, 0.9, 0.9875, 1, 1.0625, 1.125, 1.175, 1.2125, 1.2375, 1.25 ]) y = np.array([ 0.0076, 0.1390, 0.2731, 0.4003, 0.5185, 0.6263, 0.7224, 0.8056, 0.8754, 0.9312, 0.9729, 1.0006, 1.0203, 1.0158, 1.0051, 1.0000, 0.9746, 0.9289, 0.8832, 0.8376, 0.7843, 0.7614 ]) pu.set_attr(eta_s=0.8, design=['eta_s'], offdesign=['eta_s_char'], eta_s_char={ 'char_func': CharLine(x, y), 'param': 'm' }) # compressor system x = np.array([0, 0.4, 1, 1.2]) y = np.array([0.5, 0.9, 1, 1.1]) cp1.set_attr(eta_s=0.8, design=['eta_s'], offdesign=['eta_s_char'], eta_s_char={ 'char_func': CharLine(x, y), 'param': 'm' }) cp2.set_attr(eta_s=0.8, design=['eta_s'], offdesign=['eta_s_char'], eta_s_char={ 'char_func': CharLine(x, y), 'param': 'm' }) # characteristic line for intercooler kA x = np.linspace(0, 2.5, 26) y = np.array([ 0.0000, 0.2455, 0.3747, 0.4798, 0.5718, 0.6552, 0.7323, 0.8045, 0.8727, 0.9378, 1.0000, 1.0599, 1.1176, 1.1736, 1.2278, 1.2806, 1.3320, 1.3822, 1.4313, 1.4792, 1.5263, 1.5724, 1.6176, 1.6621, 1.7058, 1.7488 ]) kA_char1 = {'char_func': CharLine(x, y), 'param': 'm'} x = np.linspace(0, 2.5, 26) y = np.array([ 0.000, 0.164, 0.283, 0.389, 0.488, 0.581, 0.670, 0.756, 0.840, 0.921, 1.000, 1.078, 1.154, 1.228, 1.302, 1.374, 1.446, 1.516, 1.585, 1.654, 1.722, 1.789, 1.855, 1.921, 1.986, 2.051 ]) kA_char2 = {'char_func': CharLine(x, y), 'param': 'm'} he.set_attr(kA_char1=kA_char1, kA_char2=kA_char2, offdesign=['zeta1', 'zeta2', 'kA_char']) # characteristic line for condenser kA x = np.linspace(0, 2.5, 26) y = np.array([ 0.0000, 0.2455, 0.3747, 0.4798, 0.5718, 0.6552, 0.7323, 0.8045, 0.8727, 0.9378, 1.0000, 1.0599, 1.1176, 1.1736, 1.2278, 1.2806, 1.3320, 1.3822, 1.4313, 1.4792, 1.5263, 1.5724, 1.6176, 1.6621, 1.7058, 1.7488 ]) kA_char1 = {'char_func': CharLine(x, y), 'param': 'm'} x = np.linspace(0, 2.5, 26) y = np.array([ 0.000, 0.164, 0.283, 0.389, 0.488, 0.581, 0.670, 0.756, 0.840, 0.921, 1.000, 1.078, 1.154, 1.228, 1.302, 1.374, 1.446, 1.516, 1.585, 1.654, 1.722, 1.789, 1.855, 1.921, 1.986, 2.051 ]) kA_char2 = {'char_func': CharLine(x, y), 'param': 'm'} cd.set_attr(kA_char1=kA_char1, kA_char2=kA_char2, pr2=0.9998, design=['pr2'], offdesign=['zeta2', 'kA_char']) # %% connection parametrization # condenser system c_in_cd.set_attr(fluid={'water': 0, 'NH3': 1}, p=60) rp_cd.set_attr(T=60, fluid={'water': 1, 'NH3': 0}, p=10) self.cd_cons.set_attr(T=105) cd_va.set_attr(p=Ref(c_in_cd, 1, -0.01), Td_bp=-5, design=['Td_bp']) # evaporator system cold side pu_ev.set_attr(m=Ref(va_dr, 10, 0), p0=5) dr_su.set_attr(p0=5, T=5) su_cp1.set_attr(p=Ref(dr_su, 1, -0.05), Td_bp=5, design=['Td_bp', 'p']) # evaporator system hot side self.amb_in_su.set_attr(m=20, T=12, p=1, fluid={'water': 1, 'NH3': 0}) su_ev.set_attr(p=Ref(self.amb_in_su, 1, -0.001), design=['p']) ev_amb_out.set_attr() # compressor-system cp1_he.set_attr(p=15) he_cp2.set_attr(T=40, p=Ref(cp1_he, 1, -0.01), design=['T', 'p']) ic_in_he.set_attr(p=1, T=20, m=5, fluid={'water': 1, 'NH3': 0}) he_ic_out.set_attr(p=Ref(ic_in_he, 1, -0.002), design=['p']) def test_model(self): """ Test the operating points of the heat pump against a different model. By now, not all characteristic functions of the original model are available in detail, thus perfect matching is not possible! """ self.nw.solve('design') self.nw.save('tmp') self.nw.print_results() # input values from ebsilon T = [105, 100, 90, 80] m_source = np.array([[23, 22, 20, 18, 16], [27, 24, 20, 16, 12], [31, 25, 20, 15, 10], [33, 26, 20, 15, 10]]) COP = np.array([[2.436, 2.414, 2.368, 2.338, 2.287], [2.591, 2.523, 2.448, 2.355, 2.216], [2.777, 2.635, 2.557, 2.442, 2.243], [2.866, 2.711, 2.629, 2.528, 2.351]]) i = 0 for T in T: j = 0 self.cd_cons.set_attr(T=T) for m in m_source[i]: self.amb_in_su.set_attr(m=m) if j == 0: self.nw.solve('offdesign', design_path='tmp', init_path='tmp') else: self.nw.solve('offdesign', design_path='tmp') # relative deviation should not exceed 6.5 % # this should be much less, unfortunately not all ebsilon # characteristics are available, thus it is # difficult/impossible to match the models perfectly! d_rel_COP = abs(self.heat.P.val / self.power.P.val - COP[i, j]) / COP[i, j] msg = ('The deviation in COP should be less than 0.065, is ' + str(d_rel_COP) + ' at mass flow ' + str(m) + ' and temperature ' + str(T) + '.') assert d_rel_COP < 0.065, msg j += 1 i += 1 shutil.rmtree('./tmp', ignore_errors=True)
class TestNetworkErrors: def setup(self): self.nw = Network(['water']) def test_add_conns_TypeError(self): with raises(TypeError): self.nw.add_conns(Component('test')) def test_no_connections_error(self): with raises(TESPyNetworkError): self.nw.solve('design') def test_bad_fluids_in_fluid_vector(self): source1 = Source('source1') sink1 = Sink('sink1') a = Connection(source1, 'out1', sink1, 'in1', fluid={'air': 1}) self.nw.add_conns(a) with raises(TESPyNetworkError): self.nw.solve('design') def test_duplicate_Connection_labels(self): source1 = Source('source1') source2 = Source('source2') sink1 = Sink('sink1') sink2 = Sink('sink2') a = Connection(source1, 'out1', sink1, 'in1', label='myconn') b = Connection(source2, 'out1', sink2, 'in1', label='myconn') with raises(ValueError): self.nw.add_conns(a, b) def test_Connection_error_source(self): source = Source('source') sink1 = Sink('sink1') sink2 = Sink('sink2') a = Connection(source, 'out1', sink1, 'in1') b = Connection(source, 'out1', sink2, 'in1') self.nw.add_conns(a, b) with raises(TESPyNetworkError): self.nw.check_network() def test_Connection_error_target(self): source1 = Source('source1') source2 = Source('source2') sink = Sink('sink') a = Connection(source1, 'out1', sink, 'in1') b = Connection(source2, 'out1', sink, 'in1') self.nw.add_conns(a, b) with raises(TESPyNetworkError): self.nw.check_network() def test_consistency_inlets(self): merge = Merge('merge') sink = Sink('label') a = Connection(merge, 'out1', sink, 'in1') self.nw.add_conns(a) with raises(TESPyNetworkError): self.nw.check_network() def test_consistency_outlets(self): source = Source('source') splitter = Splitter('splitter') a = Connection(source, 'out1', splitter, 'in1') self.nw.add_conns(a) with raises(TESPyNetworkError): self.nw.check_network() def test_component_label_duplicates(self): source = Source('label') sink = Sink('label') a = Connection(source, 'out1', sink, 'in1') self.nw.add_conns(a) with raises(TESPyNetworkError): self.nw.check_network() def test_missing_offdesign_path(self): source = Source('source') sink = Sink('sink') a = Connection(source, 'out1', sink, 'in1') self.nw.add_conns(a) with raises(TESPyNetworkError): self.nw.solve('offdesign') def test_bad_mode_specification(self): source = Source('source') sink = Sink('sink') a = Connection(source, 'out1', sink, 'in1') self.nw.add_conns(a) with raises(ValueError): self.nw.solve('ofdesign') def test_underdetermination(self): source = Source('source') sink = Sink('sink') a = Connection(source, 'out1', sink, 'in1', m=1) self.nw.add_conns(a) with raises(TESPyNetworkError): self.nw.solve('design') def test_overdetermination(self): source = Source('source') sink = Sink('sink') a = Connection(source, 'out1', sink, 'in1', m=1, p=1e5, x=1, h=1e6, fluid={'water': 1}, fluid_balance=True) self.nw.add_conns(a) with raises(TESPyNetworkError): self.nw.solve('design') def test_add_Bus_TypeError(self): source = Source('label') sink = Sink('label') a = Connection(source, 'out1', sink, 'in1') with raises(TypeError): self.nw.add_busses(a) def test_Bus_duplicate(self): with raises(TESPyNetworkError): b = Bus('mybus') self.nw.add_busses(b, b) def test_buslabel_duplicate(self): with raises(TESPyNetworkError): a = Bus('mybus') b = Bus('mybus') self.nw.add_busses(a, b)
class TestOrcEvaporator: def setup(self): self.nw = Network(['water', 'Isopentane'], T_unit='C', p_unit='bar', h_unit='kJ / kg') self.inl1 = Source('inlet 1') self.outl1 = Sink('outlet 1') self.inl2 = Source('inlet 2') self.outl2 = Sink('outlet 2') self.inl3 = Source('inlet 3') self.outl3 = Sink('outlet 3') self.instance = ORCEvaporator('orc evaporator') self.c1 = Connection(self.inl1, 'out1', self.instance, 'in1') self.c2 = Connection(self.instance, 'out1', self.outl1, 'in1') self.c3 = Connection(self.inl2, 'out1', self.instance, 'in2') self.c4 = Connection(self.instance, 'out2', self.outl2, 'in1') self.c5 = Connection(self.inl3, 'out1', self.instance, 'in3') self.c6 = Connection(self.instance, 'out3', self.outl3, 'in1') self.nw.add_conns(self.c1, self.c2, self.c3, self.c4, self.c5, self.c6) def test_ORCEvaporator(self): """Test component properties of orc evaporator.""" # design specification self.instance.set_attr(pr1=0.95, pr2=0.975, pr3=0.975, design=['pr1', 'pr2', 'pr3'], offdesign=['zeta1', 'zeta2', 'zeta3']) self.c1.set_attr(T=146.6, p=4.34, m=20.4, state='g', fluid={ 'water': 1, 'Isopentane': 0 }) self.c3.set_attr(T=146.6, p=10.2, fluid={'water': 1, 'Isopentane': 0}) self.c4.set_attr(T=118.6) self.c5.set_attr(T=111.6, p=10.8, fluid={'water': 0, 'Isopentane': 1}) # test heat transfer Q = -6.64e+07 self.instance.set_attr(Q=Q) self.nw.solve('design') convergence_check(self.nw.lin_dep) Q_is = -self.c5.m.val_SI * (self.c6.h.val_SI - self.c5.h.val_SI) msg = ('Value of heat flow must be ' + str(round(Q, 0)) + ', is ' + str(round(Q_is, 0)) + '.') assert round(Q, 0) == round(Q_is, 0), msg # test bus self.instance.set_attr(Q=np.nan) P = -6.64e+07 b = Bus('heat transfer', P=P) b.add_comps({'comp': self.instance}) self.nw.add_busses(b) self.nw.solve('design') convergence_check(self.nw.lin_dep) self.nw.save('tmp') Q_is = -self.c5.m.val_SI * (self.c6.h.val_SI - self.c5.h.val_SI) msg = ('Value of heat flow must be ' + str(round(P, 0)) + ', is ' + str(round(Q_is, 0)) + '.') assert round(P, 0) == round(Q_is, 0), msg # Check the state of the steam and working fluid outlet: x_outl1_calc = self.c2.x.val x_outl3_calc = self.c6.x.val zeta1 = self.instance.zeta1.val zeta2 = self.instance.zeta2.val zeta3 = self.instance.zeta3.val msg = ('Vapor mass fraction of steam outlet must be 0.0, is ' + str(round(x_outl1_calc, 1)) + '.') assert round(x_outl1_calc, 1) == 0.0, msg msg = ('Vapor mass fraction of working fluid outlet must be 1.0, is ' + str(round(x_outl3_calc, 1)) + '.') assert round(x_outl3_calc, 1) == 1.0, msg # Check offdesign by zeta values # geometry independent friction coefficient self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) msg = ('Geometry independent friction coefficient ' 'at hot side 1 (steam) ' 'must be ' + str(round(zeta1, 1)) + ', is ' + str(round(self.instance.zeta1.val, 1)) + '.') assert round(self.instance.zeta1.val, 1) == round(zeta1, 1), msg msg = ('Geometry independent friction coefficient at ' 'hot side 2 (brine) ' 'must be ' + str(round(zeta2, 1)) + ', is ' + str(round(self.instance.zeta2.val, 1)) + '.') assert round(self.instance.zeta2.val, 1) == round(zeta2, 1), msg msg = ('Geometry independent friction coefficient at cold side ' '(Isopentane) must be ' + str(round(zeta3, 1)) + ', is ' + str(round(self.instance.zeta3.val, 1)) + '.') assert round(self.instance.zeta3.val, 1) == round(zeta3, 1), msg # test parameters of 'subcooling' and 'overheating' self.instance.set_attr(subcooling=True, overheating=True) dT = 0.5 self.c2.set_attr(Td_bp=-dT) self.c6.set_attr(Td_bp=dT) self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) T_steam = T_bp_p(self.c2.get_flow()) - dT T_isop = T_bp_p(self.c6.get_flow()) + dT msg = ('Temperature of working fluid outlet must be ' + str(round(T_isop, 1)) + ', is ' + str(round(self.c6.T.val_SI, 1)) + '.') assert round(T_isop, 1) == round(self.c6.T.val_SI, 1), msg msg = ('Temperature of steam outlet must be ' + str(round(T_steam, 1)) + ', is ' + str(round(self.c2.T.val_SI, 1)) + '.') assert round(T_steam, 1) == round(self.c2.T.val_SI, 1), msg shutil.rmtree('./tmp', ignore_errors=True)
class TestBusses: def setup(self): """Set up the model.""" # %% network setup fluid_list = ['Ar', 'N2', 'O2', 'CO2', 'CH4', 'H2O'] self.nw = Network(fluids=fluid_list, p_unit='bar', T_unit='C', p_range=[0.5, 20]) # %% components amb = Source('ambient') sf = Source('fuel') cc = CombustionChamber('combustion') cp = Compressor('compressor') gt = Turbine('turbine') fg = Sink('flue gas outlet') # %% connections amb_cp = Connection(amb, 'out1', cp, 'in1', label='ambient air flow') cp_cc = Connection(cp, 'out1', cc, 'in1') sf_cc = Connection(sf, 'out1', cc, 'in2') cc_gt = Connection(cc, 'out1', gt, 'in1') gt_fg = Connection(gt, 'out1', fg, 'in1') self.nw.add_conns(amb_cp, cp_cc, sf_cc, cc_gt, gt_fg) # %% component parameters cc.set_attr(lamb=3) cp.set_attr(eta_s=0.9, pr=15) gt.set_attr(eta_s=0.9) # %% connection parameters amb_cp.set_attr(T=20, p=1, m=100, fluid={ 'Ar': 0.0129, 'N2': 0.7553, 'H2O': 0, 'CH4': 0, 'CO2': 0.0004, 'O2': 0.2314 }) sf_cc.set_attr(T=20, fluid={ 'CO2': 0.04, 'Ar': 0, 'N2': 0, 'O2': 0, 'H2O': 0, 'CH4': 0.96 }) gt_fg.set_attr(p=1) # motor efficiency x = np.array([ 0, 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.05, 1.1, 1.15, 1.2, 10 ]) y = np.array([ 0.01, 0.3148, 0.5346, 0.6843, 0.7835, 0.8477, 0.8885, 0.9145, 0.9318, 0.9443, 0.9546, 0.9638, 0.9724, 0.9806, 0.9878, 0.9938, 0.9982, 0.999, 0.9995, 0.9999, 1, 0.9977, 0.9947, 0.9909, 0.9853, 0.9644 ]) * 0.975 self.motor_bus_based = CharLine(x=x, y=y) self.motor_comp_based = CharLine(x=x, y=1 / y) # generator efficiency x = np.array([ 0.100, 0.345, 0.359, 0.383, 0.410, 0.432, 0.451, 0.504, 0.541, 0.600, 0.684, 0.805, 1.000, 1.700, 10 ]) y = np.array([ 0.976, 0.989, 0.990, 0.991, 0.992, 0.993, 0.994, 0.995, 0.996, 0.997, 0.998, 0.999, 1.000, 0.999, 0.99 ]) * 0.975 self.generator = CharLine(x=x, y=y) power_bus_total = Bus('total power output') power_bus_total.add_comps( { 'comp': cp, 'char': self.motor_bus_based, 'base': 'bus' }, { 'comp': gt, 'char': self.generator }) thermal_input = Bus('thermal input') thermal_input.add_comps({'comp': cc}) compressor_power_comp = Bus('compressor power input') compressor_power_comp.add_comps({ 'comp': cp, 'char': self.motor_comp_based }) compressor_power_bus = Bus('compressor power input bus based') compressor_power_bus.add_comps({ 'comp': cp, 'char': self.motor_bus_based, 'base': 'bus' }) self.nw.add_busses(power_bus_total, thermal_input, compressor_power_comp, compressor_power_bus) # %% solving self.nw.solve('design') self.nw.save('tmp') def test_model(self): """Test the bus functionalities in a gas turbine model.""" tpo = self.nw.busses['total power output'] ti = self.nw.busses['thermal input'] cpi = self.nw.busses['compressor power input'] cpibb = self.nw.busses['compressor power input bus based'] cp = self.nw.components['compressor'] gt = self.nw.components['turbine'] cc = self.nw.components['combustion'] # test results of design case eta_cpi = round(1 / cp.calc_bus_efficiency(cpi), 6) eta_cp_tpo = round(cp.calc_bus_efficiency(tpo), 6) msg = ('The efficiency value of the compressor on the bus ' + tpo.label + ' (' + str(eta_cp_tpo) + ') must be identical to the efficiency ' 'on the bus ' + cpi.label + ' (' + str(eta_cpi) + ').') assert eta_cp_tpo == eta_cpi, msg P_cp_tpo = cp.calc_bus_value(tpo) eta_cp_tpo = cp.calc_bus_efficiency(tpo) P_cp = round(P_cp_tpo * eta_cp_tpo, 0) msg = ('The compressor power must be ' + str(round(cp.P.val, 0)) + ' on ' 'the bus ' + tpo.label + ' but is ' + str(P_cp) + ').') assert round(cp.P.val, 0) == P_cp, msg P_cp_tpo = round( cp.calc_bus_value(tpo) * cp.calc_bus_efficiency(tpo), 0) P_cp_cpi = round( cp.calc_bus_value(cpi) / cp.calc_bus_efficiency(cpi), 0) P_cp_cpibb = round( cp.calc_bus_value(cpibb) * cp.calc_bus_efficiency(cpibb), 0) msg = ( 'The busses\' component power value for the compressor on bus ' + tpo.label + ' (' + str(P_cp_tpo) + ') must be equal to the ' 'component power on all other busses. Bus ' + cpi.label + ' (' + str(P_cp_cpi) + ') and bus ' + cpibb.label + ' (' + str(P_cp_cpibb) + ').') assert P_cp_tpo == P_cp_cpi and P_cp_tpo == P_cp_cpibb, msg eta_gt_tpo = gt.calc_bus_efficiency(tpo) msg = ('The efficiency value of the turbine on the bus ' + tpo.label + ' (' + str(eta_gt_tpo) + ') must be equal to 0.975.') assert eta_gt_tpo == 0.975, msg eta_ti = cc.calc_bus_efficiency(ti) msg = ('The efficiency value of the combustion chamber on the bus ' + ti.label + ' (' + str(eta_ti) + ') must be equal to 1.0.') assert eta_ti == 1.0, msg # test partload for bus functions # first test in identical conditions self.nw.connections['ambient air flow'].set_attr(m=None) P_design = cpibb.P.val cpibb.set_attr(P=P_design) self.nw.solve('offdesign', design_path='tmp') eta_cpi = round(1 / cp.calc_bus_efficiency(cpi), 6) eta_cp_tpo = round(cp.calc_bus_efficiency(tpo), 6) msg = ('The efficiency value of the compressor on the bus ' + tpo.label + ' (' + str(eta_cp_tpo) + ') must be identical to the efficiency ' 'on the bus ' + cpi.label + ' (' + str(eta_cpi) + ').') assert eta_cp_tpo == eta_cpi, msg eta_gt_tpo = gt.calc_bus_efficiency(tpo) msg = ('The efficiency value of the turbine on the bus ' + tpo.label + ' (' + str(eta_gt_tpo) + ') must be equal to 0.975.') assert eta_gt_tpo == 0.975, msg P_cp_tpo = round( cp.calc_bus_value(tpo) * cp.calc_bus_efficiency(tpo), 0) P_cp_cpi = round( cp.calc_bus_value(cpi) / cp.calc_bus_efficiency(cpi), 0) P_cp_cpibb = round( cp.calc_bus_value(cpibb) * cp.calc_bus_efficiency(cpibb), 0) msg = ( 'The busses\' component power value for the compressor on bus ' + tpo.label + ' (' + str(P_cp_tpo) + ') must be equal to the ' 'component power on all other busses. Bus ' + cpi.label + ' (' + str(P_cp_cpi) + ') and bus ' + cpibb.label + ' (' + str(P_cp_cpibb) + ').') assert P_cp_tpo == P_cp_cpi and P_cp_tpo == P_cp_cpibb, msg # 60 % load load = 0.6 cpibb.set_attr(P=P_design * load) self.nw.solve('offdesign', design_path='tmp') eta_cp_tpo = round(cp.calc_bus_efficiency(tpo), 6) eta_cp_char = self.motor_bus_based.evaluate(load) msg = ('The efficiency value of the compressor on the bus ' + tpo.label + ' (' + str(eta_cp_tpo) + ') must be identical to the efficiency ' 'on the characteristic line (' + str(eta_cp_char) + ').') assert eta_cp_tpo == eta_cp_char, msg load_frac = round( cp.calc_bus_value(tpo) / tpo.comps.loc[cp, 'P_ref'], 6) msg = ('The load fraction value of the compressor on the bus ' + tpo.label + ' (' + str(load_frac) + ') must be identical to the ' 'load fraction value on the bus ' + cpibb.label + ' (' + str(load) + ').') assert load == load_frac, msg eta_cpi = round(1 / cp.calc_bus_efficiency(cpi), 6) eta_cp_tpo = round(cp.calc_bus_efficiency(tpo), 6) msg = ('The efficiency value of the compressor on the bus ' + tpo.label + ' (' + str(eta_cp_tpo) + ') must be higher than the efficiency ' 'on the bus ' + cpi.label + ' (' + str(eta_cpi) + ').') assert eta_cp_tpo > eta_cpi, msg P_cp_tpo = round( cp.calc_bus_value(tpo) * cp.calc_bus_efficiency(tpo), 0) P_cp_cpi = round( cp.calc_bus_value(cpi) / cp.calc_bus_efficiency(cpi), 0) P_cp_cpibb = round( cp.calc_bus_value(cpibb) * cp.calc_bus_efficiency(cpibb), 0) msg = ( 'The busses\' component power value for the compressor on bus ' + tpo.label + ' (' + str(P_cp_tpo) + ') must be equal to the ' 'component power on all other busses. Bus ' + cpi.label + ' (' + str(P_cp_cpi) + ') and bus ' + cpibb.label + ' (' + str(P_cp_cpibb) + ').') assert P_cp_tpo == P_cp_cpi and P_cp_tpo == P_cp_cpibb, msg shutil.rmtree('tmp', ignore_errors=True)
he_hs = Connection(he, 'out1', hsin, 'in1') nw.add_conns(tes_he, he_tes, hs_he, he_hs) # %% busses heat = Bus('heat system') heat_sto = Bus('heat storage') # needs these busses, even if they are empty power = Bus('power') ti = Bus('ti') heat.add_comps({'comp': he, 'char': -1}) heat_sto.add_comps({'comp': he, 'char': -1}) nw.add_busses(heat, heat_sto, power, ti) # %% parameters he.set_attr(pr1=0.98, pr2=0.98, design=['pr1', 'pr2'], offdesign=['zeta1', 'zeta2', 'kA_char']) # %% Schnittstellenparameter hs_he.set_attr(T=90, p=10, fluid={'water': 1}) he_hs.set_attr(T=60, design=['T']) tes_he.set_attr(T=40, p=10, fluid={'water': 1}) he_tes.set_attr(T=75) heat.set_attr(P=1000e3) heat_design = heat.P.val
class TestCombustion: def setup(self): self.nw = Network(['H2O', 'N2', 'O2', 'Ar', 'CO2', 'CH4'], T_unit='C', p_unit='bar', v_unit='m3 / s') self.fuel = Source('fuel') self.air = Source('ambient air') self.fg = Sink('flue gas') def setup_CombustionChamber_network(self, instance): self.c1 = Connection(self.air, 'out1', instance, 'in1') self.c2 = Connection(self.fuel, 'out1', instance, 'in2') self.c3 = Connection(instance, 'out1', self.fg, 'in1') self.nw.add_conns(self.c1, self.c2, self.c3) def setup_CombustionEngine_network(self, instance): self.cw1_in = Source('cooling water 1 source') self.cw2_in = Source('cooling water 2 source') self.cw1_out = Sink('cooling water 1 sink') self.cw2_out = Sink('cooling water 2 sink') self.c1 = Connection(self.air, 'out1', instance, 'in3') self.c2 = Connection(self.fuel, 'out1', instance, 'in4') self.c3 = Connection(instance, 'out3', self.fg, 'in1') self.c4 = Connection(self.cw1_in, 'out1', instance, 'in1') self.c5 = Connection(self.cw2_in, 'out1', instance, 'in2') self.c6 = Connection(instance, 'out1', self.cw1_out, 'in1') self.c7 = Connection(instance, 'out2', self.cw2_out, 'in1') self.nw.add_conns(self.c1, self.c2, self.c3, self.c4, self.c5, self.c6, self.c7) def test_CombustionChamber(self): """ Test component properties of combustion chamber. """ instance = CombustionChamber('combustion chamber') self.setup_CombustionChamber_network(instance) # connection parameter specification air = { 'N2': 0.7556, 'O2': 0.2315, 'Ar': 0.0129, 'H2O': 0, 'CO2': 0, 'CH4': 0 } fuel = {'N2': 0, 'O2': 0, 'Ar': 0, 'H2O': 0, 'CO2': 0.04, 'CH4': 0.96} self.c1.set_attr(fluid=air, p=1, T=30) self.c2.set_attr(fluid=fuel, T=30) self.c3.set_attr(T=1200) # test specified bus value on CombustionChamber (must be equal to ti) b = Bus('thermal input', P=1e6) b.add_comps({'comp': instance}) self.nw.add_busses(b) self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of thermal input must be ' + str(b.P.val) + ', is ' + str(instance.ti.val) + '.') assert round(b.P.val, 1) == round(instance.ti.val, 1), msg b.set_attr(P=np.nan) # test specified thermal input for CombustionChamber instance.set_attr(ti=1e6) self.nw.solve('design') convergence_check(self.nw.lin_dep) ti = (self.c2.m.val_SI * self.c2.fluid.val['CH4'] * instance.fuels['CH4']['LHV']) msg = ('Value of thermal input must be ' + str(instance.ti.val) + ', is ' + str(ti) + '.') assert round(ti, 1) == round(instance.ti.val, 1), msg # test specified lamb for CombustionChamber self.c3.set_attr(T=np.nan) instance.set_attr(lamb=1) self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of oxygen in flue gas must be 0.0, is ' + str(round(self.c3.fluid.val['O2'], 4)) + '.') assert 0.0 == round(self.c3.fluid.val['O2'], 4), msg def test_CombustionEngine(self): """Test component properties of combustion engine.""" instance = CombustionEngine('combustion engine') self.setup_CombustionEngine_network(instance) air = { 'N2': 0.7556, 'O2': 0.2315, 'Ar': 0.0129, 'H2O': 0, 'CO2': 0, 'CH4': 0 } fuel = {'N2': 0, 'O2': 0, 'Ar': 0, 'H2O': 0, 'CO2': 0.04, 'CH4': 0.96} water1 = {'N2': 0, 'O2': 0, 'Ar': 0, 'H2O': 1, 'CO2': 0, 'CH4': 0} water2 = {'N2': 0, 'O2': 0, 'Ar': 0, 'H2O': 1, 'CO2': 0, 'CH4': 0} # connection parametrisation instance.set_attr(pr1=0.99, pr2=0.99, lamb=1.0, design=['pr1', 'pr2'], offdesign=['zeta1', 'zeta2']) self.c1.set_attr(p=5, T=30, fluid=air) self.c2.set_attr(T=30, fluid=fuel) self.c4.set_attr(p=3, T=60, m=50, fluid=water1) self.c5.set_attr(p=3, T=80, m=50, fluid=water2) # create busses TI = Bus('thermal input') Q1 = Bus('heat output 1') Q2 = Bus('heat output 2') Q = Bus('heat output') Qloss = Bus('thermal heat loss') TI.add_comps({'comp': instance, 'param': 'TI'}) Q1.add_comps({'comp': instance, 'param': 'Q1'}) Q2.add_comps({'comp': instance, 'param': 'Q2'}) Q.add_comps({'comp': instance, 'param': 'Q'}) Qloss.add_comps({'comp': instance, 'param': 'Qloss'}) self.nw.add_busses(TI, Q1, Q2, Q, Qloss) # test specified thermal input bus value ti = 1e6 TI.set_attr(P=ti) self.nw.solve('design') convergence_check(self.nw.lin_dep) self.nw.save('tmp') # calculate in offdesign mode self.nw.solve('offdesign', init_path='tmp', design_path='tmp') convergence_check(self.nw.lin_dep) msg = ('Value of thermal input must be ' + str(TI.P.val) + ', is ' + str(instance.ti.val) + '.') assert round(TI.P.val, 1) == round(instance.ti.val, 1), msg # test specified thermal input in component TI.set_attr(P=np.nan) instance.set_attr(ti=ti) self.nw.solve('offdesign', init_path='tmp', design_path='tmp') convergence_check(self.nw.lin_dep) msg = ('Value of thermal input must be ' + str(ti) + ', is ' + str(instance.ti.val) + '.') assert round(ti, 1) == round(instance.ti.val, 1), msg instance.set_attr(ti=None) # test specified heat output 1 bus value Q1.set_attr(P=instance.Q1.val) self.nw.solve('offdesign', init_path='tmp', design_path='tmp') convergence_check(self.nw.lin_dep) # heat output is at design point value, thermal input must therefore # not have changed msg = ('Value of thermal input must be ' + str(ti) + ', is ' + str(instance.ti.val) + '.') assert round(ti, 1) == round(instance.ti.val, 1), msg # calculate heat output over cooling loop heat1 = self.c4.m.val_SI * (self.c6.h.val_SI - self.c4.h.val_SI) msg = ('Value of heat output 1 must be ' + str(-heat1) + ', is ' + str(instance.Q1.val) + '.') assert round(heat1, 1) == -round(instance.Q1.val, 1), msg Q1.set_attr(P=np.nan) # test specified heat output 2 bus value Q2.set_attr(P=1.2 * instance.Q2.val) self.nw.solve('offdesign', init_path='tmp', design_path='tmp') convergence_check(self.nw.lin_dep) # calculate heat output over cooling loop heat2 = self.c5.m.val_SI * (self.c7.h.val_SI - self.c5.h.val_SI) msg = ('Value of heat output 2 must be ' + str(-heat2) + ', is ' + str(instance.Q2.val) + '.') assert round(heat2, 1) == -round(instance.Q2.val, 1), msg # test specified heat output 2 in component Q2.set_attr(P=np.nan) instance.set_attr(Q2=-heat2) self.nw.solve('offdesign', init_path='tmp', design_path='tmp') convergence_check(self.nw.lin_dep) heat2 = self.c5.m.val_SI * (self.c7.h.val_SI - self.c5.h.val_SI) msg = ('Value of heat output 2 must be ' + str(-heat2) + ', is ' + str(instance.Q2.val) + '.') assert round(heat2, 1) == -round(instance.Q2.val, 1), msg # test total heat output bus value instance.set_attr(Q2=np.nan) Q.set_attr(P=1.5 * instance.Q1.val) self.nw.solve('offdesign', init_path='tmp', design_path='tmp') convergence_check(self.nw.lin_dep) heat = (self.c4.m.val_SI * (self.c6.h.val_SI - self.c4.h.val_SI) + self.c5.m.val_SI * (self.c7.h.val_SI - self.c5.h.val_SI)) msg = ('Value of total heat output must be ' + str(Q.P.val) + ', is ' + str(-heat) + '.') assert round(Q.P.val, 1) == -round(heat, 1), msg # test specified heat loss bus value Q.set_attr(P=np.nan) Qloss.set_attr(P=-1e5) self.nw.solve('offdesign', init_path='tmp', design_path='tmp') convergence_check(self.nw.lin_dep) msg = ('Value of heat loss must be ' + str(Qloss.P.val) + ', is ' + str(instance.Qloss.val) + '.') assert round(Qloss.P.val, 1) == round(instance.Qloss.val, 1), msg shutil.rmtree('./tmp', ignore_errors=True)
x = np.array([0, 0.2, 0.4, 0.6, 0.8, 1, 1.2]) y = np.array([0.5, 0.87, 0.91, 0.94, 0.96, 0.97, 0.96]) char = CharLine(x, y) # power bus power_bus = Bus('power') power_bus.add_comps( {'comp': turbine_hp, 'char': char, 'base': 'component'}, {'comp': turbine_lp, 'char': char, 'base': 'component'}, {'comp': pump, 'char': char, 'base': 'bus'}) # heating bus heat_bus = Bus('heat') heat_bus.add_comps({'comp': cond, 'char': -1}) nw.add_busses(power_bus, heat_bus) # %% parametrization of components turbine_hp.set_attr(eta_s=0.9, design=['eta_s'], offdesign=['eta_s_char', 'cone']) turbine_lp.set_attr(eta_s=0.9, design=['eta_s'], offdesign=['eta_s_char', 'cone']) cond.set_attr(pr1=1, pr2=0.99, ttd_u=12, design=['pr2', 'ttd_u'], offdesign=['zeta2', 'kA_char']) preheater.set_attr(pr1=1, pr2=0.99, ttd_u=5, design=['pr2', 'ttd_u', 'ttd_l'], offdesign=['zeta2', 'kA_char']) pump.set_attr(eta_s=0.8, design=['eta_s'], offdesign=['eta_s_char'])
class TestClausiusRankine: def setup(self): """Set up clausis rankine cycle with turbine driven feed water pump.""" self.Tamb = 20 self.pamb = 1 fluids = ['water'] self.nw = Network(fluids=fluids) self.nw.set_attr(p_unit='bar', T_unit='C', h_unit='kJ / kg') # create components splitter1 = Splitter('splitter 1') merge1 = Merge('merge 1') turb = Turbine('turbine') fwp_turb = Turbine('feed water pump turbine') condenser = HeatExchangerSimple('condenser') fwp = Pump('pump') steam_generator = HeatExchangerSimple('steam generator') cycle_close = CycleCloser('cycle closer') # create busses # power output bus self.power = Bus('power_output') self.power.add_comps({'comp': turb, 'char': 1}) # turbine driven feed water pump internal bus self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps({ 'comp': fwp_turb, 'char': 1 }, { 'comp': fwp, 'char': 1, 'base': 'bus' }) # heat input bus self.heat = Bus('heat_input') self.heat.add_comps({'comp': steam_generator, 'base': 'bus'}) self.nw.add_busses(self.power, self.fwp_power, self.heat) # create connections fs_in = Connection(cycle_close, 'out1', splitter1, 'in1', label='fs') fs_fwpt = Connection(splitter1, 'out1', fwp_turb, 'in1') fs_t = Connection(splitter1, 'out2', turb, 'in1') fwpt_ws = Connection(fwp_turb, 'out1', merge1, 'in1') t_ws = Connection(turb, 'out1', merge1, 'in2') ws = Connection(merge1, 'out1', condenser, 'in1') cond = Connection(condenser, 'out1', fwp, 'in1', label='cond') fw = Connection(fwp, 'out1', steam_generator, 'in1', label='fw') fs_out = Connection(steam_generator, 'out1', cycle_close, 'in1') self.nw.add_conns(fs_in, fs_fwpt, fs_t, fwpt_ws, t_ws, ws, cond, fw, fs_out) # component parameters turb.set_attr(eta_s=1) fwp_turb.set_attr(eta_s=1) condenser.set_attr(pr=1) fwp.set_attr(eta_s=1) steam_generator.set_attr(pr=1) # connection parameters fs_in.set_attr(m=10, p=120, T=600, fluid={'water': 1}) cond.set_attr(T=self.Tamb, x=0) # solve network self.nw.solve('design') convergence_check(self.nw.lin_dep) def test_exergy_analysis_perfect_cycle(self): """Test exergy analysis in the perfect clausius rankine cycle.""" self.nw.exergy_analysis(self.pamb, self.Tamb, E_P=[self.power], E_F=[self.heat], internal_busses=[self.fwp_power]) msg = ('Exergy destruction of this network must be 0 (smaller than ' + str(err**0.5) + ') for this test but is ' + str(round(abs(self.nw.E_D), 4)) + ' .') assert abs(self.nw.E_D) <= err**0.5, msg msg = ('Exergy efficiency of this network must be 1 for this test but ' 'is ' + str(round(self.nw.epsilon, 4)) + ' .') assert round(self.nw.epsilon, 4) == 1, msg exergy_balance = self.nw.E_F - self.nw.E_P - self.nw.E_L - self.nw.E_D msg = ('Exergy balance must be closed (residual value smaller than ' + str(err**0.5) + ') for this test but is ' + str(round(abs(exergy_balance), 4)) + ' .') assert abs(exergy_balance) <= err**0.5, msg msg = ( 'Fuel exergy and product exergy must be identical for this test. ' 'Fuel exergy value: ' + str(round(self.nw.E_F, 4)) + '. Product exergy value: ' + str(round(self.nw.E_P, 4)) + '.') assert round(abs(self.nw.E_F - self.nw.E_P), 4) < err**0.5, msg def test_entropy_perfect_cycle(self): """Test entropy values in the perfect clausius rankine cycle.""" labels = [ 'turbine', 'feed water pump turbine', 'condenser', 'steam generator', 'pump' ] for label in labels: cp = self.nw.get_comp(label) msg = ( 'Entropy production due to irreversibility must be 0 for all ' 'components in this test but is ' + str(round(cp.S_irr, 4)) + ' at component ' + label + ' of type ' + cp.component() + '.') assert round(cp.S_irr, 4) == 0, msg sg = self.nw.get_comp('steam generator') cd = self.nw.get_comp('condenser') msg = ( 'Value of entropy production due to heat input at steam generator ' '(S_Q=' + str(round(sg.S_Q, 4)) + ') must equal the negative ' 'value of entropy reduction in condenser (S_Q=' + str(round(cd.S_Q, 4)) + ').') assert round(sg.S_Q, 4) == -round(cd.S_Q, 4), msg def test_exergy_analysis_violated_balance(self): """Test exergy analysis with violated balance.""" # specify efficiency values for the internal bus self.nw.del_busses(self.fwp_power) self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps( { 'comp': self.nw.get_comp('feed water pump turbine'), 'char': 0.99 }, { 'comp': self.nw.get_comp('pump'), 'char': 0.98, 'base': 'bus' }) self.nw.add_busses(self.fwp_power) self.nw.solve('design') convergence_check(self.nw.lin_dep) # miss out on internal bus in exergy_analysis self.nw.exergy_analysis(self.pamb, self.Tamb, E_P=[self.power], E_F=[self.heat]) exergy_balance = self.nw.E_F - self.nw.E_P - self.nw.E_L - self.nw.E_D msg = ('Exergy balance must be violated for this test (larger than ' + str(err**0.5) + ') but is ' + str(round(abs(exergy_balance), 4)) + ' .') assert abs(exergy_balance) > err**0.5, msg def test_exergy_analysis_bus_conversion(self): """Test exergy analysis bus conversion factors.""" # specify efficiency values for the internal bus self.nw.del_busses(self.fwp_power) self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps( { 'comp': self.nw.get_comp('feed water pump turbine'), 'char': 0.99 }, { 'comp': self.nw.get_comp('pump'), 'char': 0.98, 'base': 'bus' }) self.nw.add_busses(self.fwp_power) self.nw.solve('design') convergence_check(self.nw.lin_dep) # no exergy losses in this case self.nw.exergy_analysis(self.pamb, self.Tamb, E_P=[self.power], E_F=[self.heat], internal_busses=[self.fwp_power]) label = 'pump on bus feed water pump power' eps = self.nw.component_exergy_data.loc[label, 'epsilon'] msg = ('Pump exergy efficiency must be 0.98 but is ' + str(round(eps, 4)) + ' .') assert round(eps, 4) == 0.98, msg label = 'feed water pump turbine on bus feed water pump power' eps = self.nw.component_exergy_data.loc[label, 'epsilon'] eps = self.nw.component_exergy_data.loc[label, 'epsilon'] msg = ( 'Feed water pump turbine exergy efficiency must be 0.99 but is ' + str(round(eps, 4)) + ' .') assert round(eps, 4) == 0.99, msg def test_exergy_analysis_missing_E_F_E_P_information(self): """Test exergy analysis errors with missing information.""" with raises(TESPyNetworkError): self.nw.exergy_analysis(self.pamb, self.Tamb, E_P=[self.power], E_F=[]) with raises(TESPyNetworkError): self.nw.exergy_analysis(self.pamb, self.Tamb, E_P=[], E_F=[self.heat]) def test_exergy_analysis_component_on_two_busses(self): """Test exergy analysis errors with components on more than one bus.""" with raises(TESPyNetworkError): self.nw.exergy_analysis(self.pamb, self.Tamb, E_P=[self.power], E_F=[self.heat, self.power])
class TestRefrigerator: def setup(self): """Set up simple refrigerator.""" self.Tamb = 20 self.pamb = 1 fluids = ['R134a'] self.nw = Network(fluids=fluids) self.nw.set_attr(p_unit='bar', T_unit='C', h_unit='kJ / kg') # create components va = Valve('expansion valve') cp = Compressor('compressor') cond = HeatExchangerSimple('condenser') eva = HeatExchangerSimple('evaporator') cc = CycleCloser('cycle closer') # create busses # power output bus self.power = Bus('power input') self.power.add_comps({'comp': cp, 'char': 1, 'base': 'bus'}) # cooling bus self.cool = Bus('heat from fridge') self.cool.add_comps({'comp': eva}) # heat input bus self.heat = Bus('heat to ambient') self.heat.add_comps({'comp': cond}) self.nw.add_busses(self.power, self.cool, self.heat) # create connections cc_cp = Connection(cc, 'out1', cp, 'in1', label='from eva') cp_cond = Connection(cp, 'out1', cond, 'in1', label='to cond') cond_va = Connection(cond, 'out1', va, 'in1', label='from cond') va_eva = Connection(va, 'out1', eva, 'in1', label='to eva') eva_cc = Connection(eva, 'out1', cc, 'in1') self.nw.add_conns(cc_cp, cp_cond, cond_va, va_eva, eva_cc) # component parameters cp.set_attr(eta_s=0.9) cond.set_attr(pr=0.97) eva.set_attr(pr=0.96) # connection parameters cc_cp.set_attr(m=1, x=1, T=-25, fluid={'R134a': 1}) cond_va.set_attr(x=0, T=self.Tamb + 1) # solve network self.nw.solve('design') convergence_check(self.nw.lin_dep) def test_exergy_analysis_bus_conversion(self): """Test exergy analysis at product exergy with T < Tamb.""" # no exergy losses in this case ean = ExergyAnalysis(self.nw, E_P=[self.cool], E_F=[self.power]) ean.analyse(pamb=self.pamb, Tamb=self.Tamb) exergy_balance = (ean.network_data.E_F - ean.network_data.E_P - ean.network_data.E_L - ean.network_data.E_D) msg = ('Exergy balance must be closed (residual value smaller than ' + str(err**0.5) + ') for this test but is ' + str(round(abs(exergy_balance), 4)) + ' .') assert abs(exergy_balance) <= err**0.5, msg
'base': 'bus' }) gt_power = Bus('gas turbine power output') gt_power.add_comps({'comp': g_turb}, {'comp': comp}) heat_out = Bus('heat output') heat_out.add_comps({'comp': cond_dh}) heat_cond = Bus('heat cond') heat_cond.add_comps({'comp': cond}) heat_in = Bus('heat input') heat_in.add_comps({'comp': c_c}) nw.add_busses(power, gt_power, heat_out, heat_cond, heat_in) # %% component parameters # characteristic line for compressor isentropic efficiency x = np.array([0.000, 0.400, 1.000, 1.600, 2.000]) y = np.array([0.500, 0.900, 1.000, 1.050, 0.9500]) cp_char1 = dict(char_func=CharLine(x, y), param='m') cp_char2 = dict(char_func=CharLine(x, y), param='m') # characteristic line for turbine isentropic efficiency x = np.array([ 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5 ]) y = np.array([
class TestClausiusRankine: def setup(self): """Set up clausis rankine cycle with turbine driven feed water pump.""" self.Tamb = 20 self.pamb = 1 fluids = ['water'] self.nw = Network(fluids=fluids) self.nw.set_attr(p_unit='bar', T_unit='C', h_unit='kJ / kg') # create components splitter1 = Splitter('splitter 1') merge1 = Merge('merge 1') turb = Turbine('turbine') fwp_turb = Turbine('feed water pump turbine') condenser = HeatExchangerSimple('condenser') fwp = Pump('pump') steam_generator = HeatExchangerSimple('steam generator') cycle_close = CycleCloser('cycle closer') # create busses # power output bus self.power = Bus('power_output') self.power.add_comps({'comp': turb, 'char': 1}) # turbine driven feed water pump internal bus self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps({ 'comp': fwp_turb, 'char': 1 }, { 'comp': fwp, 'char': 1, 'base': 'bus' }) # heat input bus self.heat = Bus('heat_input') self.heat.add_comps({'comp': steam_generator, 'base': 'bus'}) self.nw.add_busses(self.power, self.fwp_power, self.heat) # create connections fs_in = Connection(cycle_close, 'out1', splitter1, 'in1', label='fs') fs_fwpt = Connection(splitter1, 'out1', fwp_turb, 'in1') fs_t = Connection(splitter1, 'out2', turb, 'in1') fwpt_ws = Connection(fwp_turb, 'out1', merge1, 'in1') t_ws = Connection(turb, 'out1', merge1, 'in2') ws = Connection(merge1, 'out1', condenser, 'in1') cond = Connection(condenser, 'out1', fwp, 'in1', label='cond') fw = Connection(fwp, 'out1', steam_generator, 'in1', label='fw') fs_out = Connection(steam_generator, 'out1', cycle_close, 'in1') self.nw.add_conns(fs_in, fs_fwpt, fs_t, fwpt_ws, t_ws, ws, cond, fw, fs_out) # component parameters turb.set_attr(eta_s=1) fwp_turb.set_attr(eta_s=1) condenser.set_attr(pr=1) fwp.set_attr(eta_s=1) steam_generator.set_attr(pr=1) # connection parameters fs_in.set_attr(m=10, p=120, T=600, fluid={'water': 1}) cond.set_attr(T=self.Tamb, x=0) # solve network self.nw.solve('design') convergence_check(self.nw.lin_dep) def test_exergy_analysis_perfect_cycle(self): """Test exergy analysis in the perfect clausius rankine cycle.""" ean = ExergyAnalysis(self.nw, E_P=[self.power], E_F=[self.heat], internal_busses=[self.fwp_power]) ean.analyse(pamb=self.pamb, Tamb=self.Tamb) msg = ('Exergy destruction of this network must be 0 (smaller than ' + str(err**0.5) + ') for this test but is ' + str(round(abs(ean.network_data.E_D), 4)) + ' .') assert abs(ean.network_data.E_D) <= err**0.5, msg msg = ('Exergy efficiency of this network must be 1 for this test but ' 'is ' + str(round(ean.network_data.epsilon, 4)) + ' .') assert round(ean.network_data.epsilon, 4) == 1, msg exergy_balance = (ean.network_data.E_F - ean.network_data.E_P - ean.network_data.E_L - ean.network_data.E_D) msg = ('Exergy balance must be closed (residual value smaller than ' + str(err**0.5) + ') for this test but is ' + str(round(abs(exergy_balance), 4)) + ' .') assert abs(exergy_balance) <= err**0.5, msg msg = ( 'Fuel exergy and product exergy must be identical for this test. ' 'Fuel exergy value: ' + str(round(ean.network_data.E_F, 4)) + '. Product exergy value: ' + str(round(ean.network_data.E_P, 4)) + '.') delta = round(abs(ean.network_data.E_F - ean.network_data.E_P), 4) assert delta < err**0.5, msg def test_exergy_analysis_plotting_data(self): """Test exergy analysis plotting.""" self.nw.get_comp('steam generator').set_attr(pr=0.9) self.nw.get_comp('turbine').set_attr(eta_s=0.9) self.nw.get_comp('feed water pump turbine').set_attr(eta_s=0.85) self.nw.get_comp('pump').set_attr(eta_s=0.75) self.nw.get_conn('cond').set_attr(T=self.Tamb + 3) # specify efficiency values for the internal bus and power bus self.nw.del_busses(self.fwp_power, self.power) self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps( { 'comp': self.nw.get_comp('feed water pump turbine'), 'char': 0.99 }, { 'comp': self.nw.get_comp('pump'), 'char': 0.98, 'base': 'bus' }) self.power = Bus('power_output') self.power.add_comps({ 'comp': self.nw.get_comp('turbine'), 'char': 0.98 }) self.nw.add_busses(self.fwp_power, self.power) # solve network self.nw.solve('design') convergence_check(self.nw.lin_dep) ean = ExergyAnalysis(self.nw, E_P=[self.power], E_F=[self.heat], internal_busses=[self.fwp_power]) ean.analyse(pamb=self.pamb, Tamb=self.Tamb) exergy_balance = (ean.network_data.E_F - ean.network_data.E_P - ean.network_data.E_L - ean.network_data.E_D) msg = ('Exergy balance must be closed (residual value smaller than ' + str(err**0.5) + ') for this test but is ' + str(round(abs(exergy_balance), 4)) + ' .') assert abs(exergy_balance) <= err**0.5, msg nodes = [ 'E_F', 'steam generator', 'splitter 1', 'feed water pump turbine', 'turbine', 'merge 1', 'condenser', 'pump', 'E_D', 'E_P' ] links, nodes = ean.generate_plotly_sankey_input(node_order=nodes) # checksum for targets and source checksum = sum(links['target'] + links['source']) msg = ('The checksum of all target and source values in the link lists' 'must be 148, but is ' + str(checksum) + '.') assert 148 == checksum, msg def test_exergy_analysis_violated_balance(self): """Test exergy analysis with violated balance.""" # specify efficiency values for the internal bus self.nw.del_busses(self.fwp_power) self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps( { 'comp': self.nw.get_comp('feed water pump turbine'), 'char': 0.99 }, { 'comp': self.nw.get_comp('pump'), 'char': 0.98, 'base': 'bus' }) self.nw.add_busses(self.fwp_power) self.nw.solve('design') convergence_check(self.nw.lin_dep) # miss out on internal bus in exergy_analysis ean = ExergyAnalysis(self.nw, E_P=[self.power], E_F=[self.heat]) ean.analyse(pamb=self.pamb, Tamb=self.Tamb) exergy_balance = (ean.network_data.E_F - ean.network_data.E_P - ean.network_data.E_L - ean.network_data.E_D) msg = ('Exergy balance must be violated for this test (larger than ' + str(err**0.5) + ') but is ' + str(round(abs(exergy_balance), 4)) + ' .') assert abs(exergy_balance) > err**0.5, msg def test_exergy_analysis_bus_conversion(self): """Test exergy analysis bus conversion factors.""" # specify efficiency values for the internal bus self.nw.del_busses(self.fwp_power) self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps( { 'comp': self.nw.get_comp('feed water pump turbine'), 'char': 0.99 }, { 'comp': self.nw.get_comp('pump'), 'char': 0.98, 'base': 'bus' }) self.nw.add_busses(self.fwp_power) self.nw.solve('design') convergence_check(self.nw.lin_dep) # no exergy losses in this case ean = ExergyAnalysis(self.nw, E_P=[self.power], E_F=[self.heat], internal_busses=[self.fwp_power]) ean.analyse(pamb=self.pamb, Tamb=self.Tamb) label = 'pump' eps = ean.bus_data.loc[label, 'epsilon'] msg = ('Pump exergy efficiency must be 0.98 but is ' + str(round(eps, 4)) + ' .') assert round(eps, 4) == 0.98, msg label = 'feed water pump turbine' eps = ean.bus_data.loc[label, 'epsilon'] msg = ( 'Feed water pump turbine exergy efficiency must be 0.99 but is ' + str(round(eps, 4)) + ' .') assert round(eps, 4) == 0.99, msg def test_exergy_analysis_missing_E_F_E_P_information(self): """Test exergy analysis errors with missing information.""" with raises(TESPyNetworkError): ExergyAnalysis(self.nw, E_P=[self.power], E_F=[]) with raises(TESPyNetworkError): ExergyAnalysis(self.nw, E_P=[], E_F=[self.heat]) def test_exergy_analysis_component_on_two_busses(self): """Test exergy analysis errors with components on more than one bus.""" with raises(TESPyNetworkError): ean = ExergyAnalysis(self.nw, E_P=[self.power], E_F=[self.heat, self.power]) ean.analyse(pamb=self.pamb, Tamb=self.Tamb)
class TestClausiusRankine: def setup(self): """Set up clausis rankine cycle with turbine driven feed water pump.""" self.Tamb = 20 self.pamb = 1 fluids = ['water'] self.nw = Network(fluids=fluids) self.nw.set_attr(p_unit='bar', T_unit='C', h_unit='kJ / kg') # create components splitter1 = Splitter('splitter 1') merge1 = Merge('merge 1') turb = Turbine('turbine') fwp_turb = Turbine('feed water pump turbine') condenser = HeatExchangerSimple('condenser') fwp = Pump('pump') steam_generator = HeatExchangerSimple('steam generator') cycle_close = CycleCloser('cycle closer') # create busses # power output bus self.power = Bus('power_output') self.power.add_comps({'comp': turb, 'char': 1}) # turbine driven feed water pump internal bus self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps({ 'comp': fwp_turb, 'char': 1 }, { 'comp': fwp, 'char': 1, 'base': 'bus' }) # heat input bus self.heat = Bus('heat_input') self.heat.add_comps({'comp': steam_generator, 'base': 'bus'}) self.nw.add_busses(self.power, self.fwp_power, self.heat) # create connections fs_in = Connection(cycle_close, 'out1', splitter1, 'in1', label='fs') fs_fwpt = Connection(splitter1, 'out1', fwp_turb, 'in1') fs_t = Connection(splitter1, 'out2', turb, 'in1') fwpt_ws = Connection(fwp_turb, 'out1', merge1, 'in1') t_ws = Connection(turb, 'out1', merge1, 'in2') ws = Connection(merge1, 'out1', condenser, 'in1') cond = Connection(condenser, 'out1', fwp, 'in1', label='cond') fw = Connection(fwp, 'out1', steam_generator, 'in1', label='fw') fs_out = Connection(steam_generator, 'out1', cycle_close, 'in1') self.nw.add_conns(fs_in, fs_fwpt, fs_t, fwpt_ws, t_ws, ws, cond, fw, fs_out) # component parameters turb.set_attr(eta_s=1) fwp_turb.set_attr(eta_s=1) condenser.set_attr(pr=1) fwp.set_attr(eta_s=1) steam_generator.set_attr(pr=1) # connection parameters fs_in.set_attr(m=10, p=120, T=600, fluid={'water': 1}) cond.set_attr(T=self.Tamb, x=0) # solve network self.nw.solve('design') convergence_check(self.nw.lin_dep) def test_entropy_perfect_cycle(self): """Test entropy values in the perfect clausius rankine cycle.""" labels = [ 'turbine', 'feed water pump turbine', 'condenser', 'steam generator', 'pump' ] for label in labels: cp = self.nw.get_comp(label) msg = ( 'Entropy production due to irreversibility must be 0 for all ' 'components in this test but is ' + str(round(cp.S_irr, 4)) + ' at component ' + label + ' of type ' + cp.component() + '.') assert round(cp.S_irr, 4) == 0, msg sg = self.nw.get_comp('steam generator') cd = self.nw.get_comp('condenser') msg = ( 'Value of entropy production due to heat input at steam generator ' '(S_Q=' + str(round(sg.S_Q, 4)) + ') must equal the negative ' 'value of entropy reduction in condenser (S_Q=' + str(round(cd.S_Q, 4)) + ').') assert round(sg.S_Q, 4) == -round(cd.S_Q, 4), msg
class PowerPlant(): def __init__(self, working_fluid): """Set up model.""" self.working_fluid = working_fluid fluids = ['water', self.working_fluid, 'air'] self.nw = Network(fluids=fluids) self.nw.set_attr(p_unit='bar', T_unit='C', h_unit='kJ / kg') # geo parameters self.geo_mass_flow = 200 geo_steam_share = 0.1 self.T_brine_in = 140 # ambient parameters self.T_amb = 5 self.p_amb = 0.6 # main components geo_steam = Source('geosteam source') geo_brine = Source('geobrine source') geo_reinjection = Sink('re-injection') air_in = Source('air source') air_out = Sink('air sink') air_fan = Compressor('air fan') air_cond = Condenser('condenser') orc_cc = CycleCloser('orc cycle closer') evap_splitter = Splitter('splitter evaporation') evap_merge = Merge('merge evaporation') evap_steam = Condenser('geosteam evaporator') evap_brine = HeatExchanger('geobrine evaporator') dr = Drum('drum') geo_merge = Merge('merge brine') pre = HeatExchanger('preheater') feed_working_fluid_pump = Pump('feed pump') tur = Turbine('turbine') ihe = HeatExchanger('internal heat exchanger') # busses net_power = Bus('net power output') net_power.add_comps( {'comp': tur, 'char': 0.97}, {'comp': feed_working_fluid_pump, 'char': 0.97, 'base': 'bus'}, {'comp': air_fan, 'char': 0.97, 'base': 'bus'} ) ORC_power_bus = Bus('cycle gross power output') ORC_power_bus.add_comps( {'comp': tur}, {'comp': feed_working_fluid_pump} ) geothermal_bus = Bus('thermal input') geothermal_bus.add_comps( {'comp': pre, 'char': -1}, {'comp': evap_brine, 'char': -1}, {'comp': evap_steam, 'char': -1} ) self.nw.add_busses(net_power, ORC_power_bus, geothermal_bus) # turbine to condenser c1 = Connection(orc_cc, 'out1', tur, 'in1', label='1') c2 = Connection(tur, 'out1', ihe, 'in1', label='2') c3 = Connection(ihe, 'out1', air_cond, 'in1', label='3') self.nw.add_conns(c1, c2, c3) # condenser to steam generator c4 = Connection(air_cond, 'out1', feed_working_fluid_pump, 'in1', label='4') c5 = Connection(feed_working_fluid_pump, 'out1', ihe, 'in2', label='5') self.nw.add_conns(c4, c5) # steam generator c6 = Connection(ihe, 'out2', pre, 'in2', label='6') c7 = Connection(pre, 'out2', dr, 'in1', label='7') c8 = Connection(dr, 'out1', evap_splitter, 'in1', label='8') c9 = Connection(evap_splitter, 'out2', evap_steam, 'in2', label='9') c10 = Connection(evap_steam, 'out2', evap_merge, 'in2', label='10') c11 = Connection(evap_splitter, 'out1', evap_brine, 'in2', label='11') c12 = Connection(evap_brine, 'out2', evap_merge, 'in1', label='12') c13 = Connection(evap_merge, 'out1', dr, 'in2', label='13') c0 = Connection(dr, 'out2', orc_cc, 'in1', label='0') self.nw.add_conns(c6, c7, c8, c11, c9, c12, c10, c13, c0) # condenser cold side c20 = Connection(air_in, 'out1', air_fan, 'in1', label='20') c21 = Connection(air_fan, 'out1', air_cond, 'in2', label='21') c22 = Connection(air_cond, 'out2', air_out, 'in1', label='22') self.nw.add_conns(c20, c21, c22) # geo source c30 = Connection(geo_steam, 'out1', evap_steam, 'in1', label='30') c31 = Connection(evap_steam, 'out1', geo_merge, 'in1', label='31') c32 = Connection(geo_brine, 'out1', geo_merge, 'in2', label='32') c33 = Connection(geo_merge, 'out1', evap_brine, 'in1', label='33') self.nw.add_conns(c30, c31, c32, c33) c34 = Connection(evap_brine, 'out1', pre, 'in1', label='34') c35 = Connection(pre, 'out1', geo_reinjection, 'in1', label='35') self.nw.add_conns(c34, c35) # generate a set of stable starting values of every working fluid # fluid settings c6.set_attr(fluid={self.working_fluid: 1.0, 'air': 0.0, 'water': 0.0}) c20.set_attr(fluid={self.working_fluid: 0.0, 'air': 1.0, 'water': 0.0}) c30.set_attr(fluid={self.working_fluid: 0.0, 'air': 0.0, 'water': 1.0}) c32.set_attr(fluid={self.working_fluid: 0.0, 'air': 0.0, 'water': 1.0}) # connection parameters p0 = PSI('P', 'T', self.T_brine_in + 273.15, 'Q', 1, self.working_fluid) c1.set_attr(p0=p0 / 1e5) ws_stable_h0 = ( PSI('H', 'T', self.T_amb + 273.15, 'Q', 1, self.working_fluid) + 0.5 * ( PSI('H', 'T', self.T_brine_in + 273.15, 'Q', 1, self.working_fluid) - PSI('H', 'T', self.T_amb + 273.15, 'Q', 1, self.working_fluid) ) ) / 1e3 c2.set_attr(h=ws_stable_h0) p0 = PSI('P', 'T', self.T_amb + 273.15, 'Q', 1, self.working_fluid) c3.set_attr(Td_bp=5, design=['Td_bp'], p0=p0 / 1e5) c5.set_attr(h=Ref(c4, 1, 1)) # steam generator c30.set_attr( m=self.geo_mass_flow * geo_steam_share, T=self.T_brine_in, x=1, p0=5) c32.set_attr( m=self.geo_mass_flow * (1 - geo_steam_share), T=self.T_brine_in, x=0) c13.set_attr() c12.set_attr(x=0.5) c10.set_attr(x=0.5, design=['x']) c34.set_attr(h=Ref(c33, 1, -50)) c7.set_attr(Td_bp=-2) # main condenser c20.set_attr(p=self.p_amb, T=self.T_amb) c22.set_attr(T=self.T_amb + 15, p=self.p_amb) # component parameters # condensing ihe.set_attr(pr1=0.98, pr2=0.98) air_cond.set_attr(pr1=1, pr2=0.995, ttd_u=10) air_fan.set_attr(eta_s=0.6) # steam generator evap_brine.set_attr(pr1=0.98, ttd_l=8) pre.set_attr(pr1=0.98, pr2=0.98) self.nw.set_attr(iterinfo=False) self.nw.solve('design') self.nw.save('stable_' + self.working_fluid) # specify actual parameters tur.set_attr(eta_s=0.9) feed_working_fluid_pump.set_attr(eta_s=0.75) c2.set_attr(h=None) c5.set_attr(h=None) c34.set_attr(h=None, T=Ref(c33, 1, -10)) self.nw.solve('design') c22.set_attr(T=None) c3.set_attr(Td_bp=None) self.ude_IHE_size = UserDefinedEquation( label='ihe deshuperheat ratio', func=desuperheat, deriv=desuperheat_deriv, latex={ 'equation': r'0 = h_3 - h_2 - x_\mathrm{IHE} \cdot \left(h_3 -' r'h\left(p_2, T_5 + \Delta T_\mathrm{t,u,min} \right)' r'\right)'}, conns=[ self.nw.get_conn('2'), self.nw.get_conn('3'), self.nw.get_conn('5')], params={'distance': 0.0, 'ttd_min': 2} ) if self.nw.lin_dep or self.nw.res[-1] > 1e-3: msg = 'No stable solution found.' raise TESPyNetworkError(msg) print( 'Generated stable starting values for working fluid ' + self.working_fluid + '.') def run_simulation( self, p_before_tur=None, Q_ihe=None, Q_brine_ev=None, T_before_tur=None, T_reinjection=None, brine_evap_Td=None, dT_air=None, IHE_sizing=None, geo_steam_share=None): """Run simulation on specified parameter set.""" self.nw.get_comp('internal heat exchanger').set_attr(Q=Q_ihe) self.nw.get_conn('1').set_attr(p=p_before_tur, T=T_before_tur) self.nw.get_conn('35').set_attr(T=T_reinjection) self.nw.get_comp('geobrine evaporator').set_attr(Q=Q_brine_ev) if geo_steam_share is not None: self.nw.get_conn('30').set_attr( m=self.geo_mass_flow * geo_steam_share) self.nw.get_conn('32').set_attr( m=self.geo_mass_flow * (1 - geo_steam_share)) if brine_evap_Td is not None: self.nw.get_conn('34').set_attr( T=Ref(self.nw.get_conn('33'), 1, brine_evap_Td)) else: self.nw.get_conn('34').set_attr(T=None) if dT_air is not None: self.nw.get_conn('22').set_attr(T=Ref(self.nw.get_conn('21'), 1, dT_air)) else: self.nw.get_conn('22').set_attr(T=None) if IHE_sizing is None: if self.ude_IHE_size in self.nw.user_defined_eq.values(): self.nw.del_ude(self.ude_IHE_size) self.nw.get_comp('internal heat exchanger').set_attr(pr1=0.98, pr2=0.98) else: if self.ude_IHE_size not in self.nw.user_defined_eq.values(): self.nw.add_ude(self.ude_IHE_size) self.ude_IHE_size.params['distance'] = IHE_sizing if IHE_sizing == 0: self.nw.get_comp('internal heat exchanger').set_attr(pr1=1, pr2=1) else: self.nw.get_comp('internal heat exchanger').set_attr(pr1=0.98, pr2=0.98) try: self.nw.solve('design') # self.nw.print_results() except ValueError: self.nw.res = [1] pass def check_simulation(self, value): """Check if simulation converged.""" if self.nw.lin_dep or self.nw.res[-1] > 1e-3: self.nw.solve( 'design', init_path='stable_' + self.working_fluid, init_only=True) return np.nan else: for cp in self.nw.comps['object']: if isinstance(cp, HeatExchanger): if cp.Q.val > 0: print(cp.label) return np.nan elif cp.kA.val <= 0 or (np.isnan(cp.kA.val) and cp.Q.val != 0): print(cp.label) return np.nan return value def get_power(self): """Calculate ORC gross power (main cycle only).""" return self.check_simulation(self.nw.busses['cycle gross power output'].P.val) def get_net_power(self): """Calculate net power.""" return self.check_simulation(self.nw.busses['net power output'].P.val) def get_thermal_efficiency(self): """Calculate thermal efficiency.""" return self.check_simulation( -self.nw.busses['cycle gross power output'].P.val / self.nw.busses['thermal input'].P.val) def get_net_efficiency(self): """Calculate net efficiency.""" return self.check_simulation( -self.nw.busses['net power output'].P.val / self.nw.busses['thermal input'].P.val) def get_geosteam_share(self): """Return a geosteam share.""" return self.check_simulation( self.nw.get_conn('geosteam').m.val_SI / self.geo_mass_flow) def get_connection_param(self, conn, param): """Return a connection parameter.""" return self.check_simulation( self.nw.get_conn(conn).get_attr(param).val) def get_component_param(self, comp, param): """Return a component parameter.""" return self.check_simulation( self.nw.get_comp(comp).get_attr(param).val) def get_misc_param(self, param): """Get non component or connection parameters.""" if param == 'gross power output': return self.get_power() elif param == 'net power output': return self.get_net_power() elif param == 'thermal efficiency': return self.get_thermal_efficiency() elif param == 'net efficiency': return self.get_net_efficiency() elif param == 'IHE sizing factor': return self.ude_IHE_size.params['distance'] def get_objective_func(self, objective): """Return corresponding objective function.""" if objective == 'net power output': return self.get_net_power elif objective == 'gross power output': return self.get_power else: msg = ( 'Please specify valid objective function: "net power output" ' 'or "gross power output".') raise ValueError(msg)
'char': mot1 }, { 'comp': pu, 'char': mot2 }, { 'comp': dhp, 'char': mot3 }, { 'comp': erp, 'char': mot4 }) heat = Bus('total delivered heat') heat.add_comps({'comp': cd}) nw.add_busses(power, heat) # %% component parametrization # condenser system cd.set_attr(pr1=0.99, pr2=0.99, ttd_u=5, design=['pr2', 'ttd_u'], offdesign=['zeta2', 'kA']) dhp.set_attr(eta_s=0.8, design=['eta_s'], offdesign=['eta_s_char']) cons.set_attr(pr=0.99, design=['pr'], offdesign=['zeta']) # low temp water system
'comp': cp, 'char': mot, 'base': 'bus' }, { 'comp': pump_ev, 'char': mot, 'base': 'bus' }) heat = Bus('heat system') heat.add_comps({'comp': cd, 'char': -1}) heat_storage = Bus('heat storage') heat_storage.add_comps({'comp': ev, 'char': -1}, {'comp': su, 'char': -1}) ti = Bus('ti') nw.add_busses(power, heat, ti, heat_storage) # %% characteristics # %% component parametrization # consumer system cd.set_attr(pr1=0.99, pr2=0.99, ttd_u=5, subcooling=True, design=['pr2', 'ttd_u'], offdesign=['zeta2', 'kA_char']) # evaporator system
nw.add_conns(cw_in, cw_out) # %% busses # characteristic function for generator efficiency x = np.array([0, 0.2, 0.4, 0.6, 0.8, 1, 1.2]) y = np.array([0, 0.86, 0.9, 0.93, 0.95, 0.96, 0.95]) char = CharLine(x=x, y=y) # motor of pump has a constant efficiency power = Bus('total output power') power.add_comps( {'comp': turb, 'char': char}, {'comp': pu, 'char': char, 'base': 'bus'}) nw.add_busses(power) # %% parametrization of components turb.set_attr(eta_s=0.9, design=['eta_s'], offdesign=['eta_s_char', 'cone']) con.set_attr(pr1=1, pr2=0.98, ttd_u=5, design=['pr2', 'ttd_u'], offdesign=['zeta2', 'kA_char']) pu.set_attr(eta_s=0.8, design=['eta_s'], offdesign=['eta_s_char']) steam_generator.set_attr(pr=0.95) # %% parametrization of connections # offdesign calculation: use parameter design for auto deactivation # turbine inlet pressure is deriven by stodolas law, outlet pressure by # characteristic of condenser fs_in.set_attr(p=100, T=500, fluid={'water': 1}, design=['p'])
'base': 'bus' }, { 'comp': hsp, 'char': char, 'base': 'bus' }) # consumer heat bus heat_cons = Bus('heating system') heat_cons.add_comps({'comp': hs_ret, 'base': 'bus'}, {'comp': hs_feed}) # geothermal heat bus heat_geo = Bus('geothermal heat') heat_geo.add_comps({'comp': gh_in, 'base': 'bus'}, {'comp': gh_out}) nw.add_busses(power, heat_cons, heat_geo) # %% key parameter cd.set_attr(Q=-4e3) # %% design calculation path = 'R410A' nw.solve('design') # alternatively use: # nw.solve('design', init_path=path) print("\n##### DESIGN CALCULATION #####\n") nw.print_results() nw.save(path)
# %% busses # heat_losses = Bus('network losses') heat_consumer = Bus('network consumer') nw.check_network() for comp in nw.comps.index: if isinstance(comp, Pipe): comp.set_attr(Tamb=0) heat_losses.add_comps({'comp': comp}) if (isinstance(comp, HeatExchangerSimple) and not isinstance(comp, Pipe)): heat_consumer.add_comps({'comp': comp}) nw.add_busses(heat_losses, heat_consumer) # %% solve # design case: 0 °C ambient temperature nw.solve('design') nw.save('grid') document_model(nw) # no documentation of offedesign state added, as report creation takes # quite long with all characteristics applied, try it out yourself :) print('Heat demand consumer:', heat_consumer.P.val) print('network losses at 0 °C outside temperature (design):', heat_losses.P.val) # offdesign case: 10 °C ambient temperature
}, { 'comp': turb, 'char': char }, { 'comp': pu, 'char': char, 'base': 'bus' }) heat_out = Bus('heat output') heat_out.add_comps({'comp': cond}, {'comp': dh_whr}) heat_in = Bus('heat input') heat_in.add_comps({'comp': c_c}) nw.add_busses(power, heat_out, heat_in) # %% component parameters # gas turbine comp.set_attr(pr=14, eta_s=0.91, design=['pr', 'eta_s'], offdesign=['eta_s_char']) g_turb.set_attr(eta_s=0.88, design=['eta_s'], offdesign=['eta_s_char', 'cone']) # steam turbine suph.set_attr(pr1=0.99, pr2=0.834, design=['pr1', 'pr2'], offdesign=['zeta1', 'zeta2', 'kA_char']) eco.set_attr(pr1=0.99,
'comp': ice, 'param': 'P', 'char': gen1 }) ice_power = Bus('ice_power') ice_power.add_comps({'comp': ice, 'param': 'P', 'char': gen2}) heat = Bus('heat') heat.add_comps({'comp': ice, 'param': 'Q'}, {'comp': fgc}) heat_cond = Bus('heat_cond') ti = Bus('ti') ti.add_comps({'comp': ice, 'param': 'TI'}) nw.add_busses(power, heat, ti, ice_power, heat_cond) # %% component parameters # pump isentropic efficiency char_line x = np.array([ 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5 ]) y = np.array([ 0.7, 0.7667, 0.8229, 0.8698, 0.9081, 0.9387, 0.9623, 0.9796, 0.9913, 0.9979, 1.0, 0.9981, 0.9926, 0.9839, 0.9725, 0.9586, 0.9426, 0.9248, 0.9055, 0.8848, 0.8631, 0.8405, 0.8171, 0.7932, 0.7689, 0.7444 ]) eta_s_char = dict(char_func=CharLine(x, y), param='m') pump.set_attr(eta_s=0.8,