def run_simple_heat_pump_model(): nw = Network(['NH3'], T_unit='C', p_unit='bar', h_unit='kJ / kg') nw.set_attr(iterinfo=False) cp = Compressor('compressor') cc = CycleCloser('cycle_closer') cd = HeatExchangerSimple('condenser') va = Valve('expansion valve') ev = HeatExchangerSimple('evaporator') cc_cd = Connection(cc, 'out1', cd, 'in1') cd_va = Connection(cd, 'out1', va, 'in1') va_ev = Connection(va, 'out1', ev, 'in1') ev_cp = Connection(ev, 'out1', cp, 'in1') cp_cc = Connection(cp, 'out1', cc, 'in1') nw.add_conns(cc_cd, cd_va, va_ev, ev_cp, cp_cc) cd.set_attr(pr=0.95, Q=-1e6) ev.set_attr(pr=0.9) cp.set_attr(eta_s=0.9) cc_cd.set_attr(fluid={'NH3': 1}) cd_va.set_attr(Td_bp=-5, T=85) ev_cp.set_attr(Td_bp=5, T=15) nw.solve('design') result_dict = {} result_dict.update({ cp.label: cp.get_plotting_data()[1] for cp in nw.comps.index if cp.get_plotting_data() is not None }) return result_dict
def test_Network_instanciation_no_fluids(): nw = Network([]) so = Source('source') si = Sink('sink') conn = Connection(so, 'out1', si, 'in1') nw.add_conns(conn) with raises(TESPyNetworkError): nw.solve('design', init_only=True)
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
def test_CombustionChamber_missing_oxygen(): """Test no oxygen in network.""" nw = Network(['H2O', 'N2', 'Ar', 'CO2', 'CH4']) instance = CombustionChamber('combustion chamber') c1 = Connection(Source('air'), 'out1', instance, 'in1') c2 = Connection(Source('fuel'), 'out1', instance, 'in2') c3 = Connection(instance, 'out1', Sink('flue gas'), 'in1') nw.add_conns(c1, c2, c3) with raises(TESPyComponentError): nw.solve('design', init_only=True)
def test_Turbine_missing_char_parameter(): """Turbine with invalid parameter for eta_s_char function.""" nw = Network(['CH4']) so = Source('source') si = Sink('sink') instance = Turbine('turbine') c1 = Connection(so, 'out1', instance, 'in1') c2 = Connection(instance, 'out1', si, 'in1') nw.add_conns(c1, c2) instance.set_attr(eta_s_char={ 'char_func': CharLine([0, 1], [1, 2]), 'is_set': True, 'param': None}) nw.solve('design', init_only=True) with raises(ValueError): instance.eta_s_char_func()
cp1 = Compressor('compressor 1') cp2 = Compressor('compressor 2') ic = HeatExchanger('intercooler') # %% connections # consumer system c_in_cd = Connection(cc, 'out1', cd, 'in1') cb_dhp = Connection(cc_cons, 'out1', dhp, 'in1') dhp_cd = Connection(dhp, 'out1', cd, 'in2') cd_cons = Connection(cd, 'out2', cons, 'in1') cons_cf = Connection(cons, 'out1', cc_cons, 'in1') nw.add_conns(c_in_cd, cb_dhp, dhp_cd, cd_cons, cons_cf) # connection condenser - evaporator system cd_ves = Connection(cd, 'out1', ves, 'in1') nw.add_conns(cd_ves) # evaporator system ves_dr = Connection(ves, 'out1', dr, 'in1') dr_erp = Connection(dr, 'out1', erp, 'in1') erp_ev = Connection(erp, 'out1', ev, 'in2') ev_dr = Connection(ev, 'out2', dr, 'in2') dr_su = Connection(dr, 'out2', su, 'in2')
ghp = Pump('ground heat loop pump') # heating system hs_feed = Sink('heating system feed flow') hs_ret = Source('heating system return flow') hsp = Pump('heating system pump') # %% connections # heat pump system cc_cd = Connection(cc, 'out1', cd, 'in1') cd_va = Connection(cd, 'out1', va, 'in1') va_ev = Connection(va, 'out1', ev, 'in2') ev_cp = Connection(ev, 'out2', cp, 'in1') cp_cc = Connection(cp, 'out1', cc, 'in1') nw.add_conns(cc_cd, cd_va, va_ev, ev_cp, cp_cc) # geothermal heat collector gh_in_ghp = Connection(gh_in, 'out1', ghp, 'in1') ghp_ev = Connection(ghp, 'out1', ev, 'in1') ev_gh_out = Connection(ev, 'out1', gh_out, 'in1') nw.add_conns(gh_in_ghp, ghp_ev, ev_gh_out) # heating system hs_ret_hsp = Connection(hs_ret, 'out1', hsp, 'in1') hsp_cd = Connection(hsp, 'out1', cd, 'in2') cd_hs_feed = Connection(cd, 'out2', hs_feed, 'in1') nw.add_conns(hs_ret_hsp, hsp_cd, cd_hs_feed) # %% component parametrization
ev = HeatExchanger('evaporator') su = HeatExchanger('superheater') pump_ev = Pump('evaporator reciculation pump') # compressor-system cp = Compressor('compressor 1') # %% connections # heating system interface sys_IF = Connection(sys_rf, 'out1', cd, 'in2') IF_sys = Connection(cd, 'out2', sys_ff, 'in1') nw.add_conns(sys_IF, IF_sys) # condenser, expansion valve c_in_cd = Connection(close_cycle, 'out1', cd, 'in1') cd_valve = Connection(cd, 'out1', valve, 'in1') nw.add_conns(c_in_cd, cd_valve) # evaporator system valve_dr = Connection(valve, 'out1', dr, 'in1') dr_pu = Connection(dr, 'out1', pump_ev, 'in1') pu_ev = Connection(pump_ev, 'out1', ev, 'in2') ev_dr = Connection(ev, 'out2', dr, 'in2') dr_su = Connection(dr, 'out2', su, 'in2')
class TestFluidPropertyBackEnds: """Testing full models with different fluid property back ends.""" def setup_clausius_rankine(self, fluid_list): """Setup a Clausius-Rankine cycle.""" self.nw = Network(fluids=fluid_list) self.nw.set_attr(p_unit='bar', T_unit='C', iterinfo=True) # %% components # main components turb = Turbine('turbine') con = Condenser('condenser') pu = Pump('pump') steam_generator = HeatExchangerSimple('steam generator') closer = CycleCloser('cycle closer') # cooling water so_cw = Source('cooling water inlet') si_cw = Sink('cooling water outlet') # %% connections # main cycle fs_in = Connection(closer, 'out1', turb, 'in1', label='livesteam') ws = Connection(turb, 'out1', con, 'in1', label='wastesteam') cond = Connection(con, 'out1', pu, 'in1', label='condensate') fw = Connection(pu, 'out1', steam_generator, 'in1', label='feedwater') fs_out = Connection(steam_generator, 'out1', closer, 'in1') self.nw.add_conns(fs_in, ws, cond, fw, fs_out) # cooling water cw_in = Connection(so_cw, 'out1', con, 'in2') cw_out = Connection(con, 'out2', si_cw, 'in1') self.nw.add_conns(cw_in, cw_out) # %% parametrization of components turb.set_attr(eta_s=0.9) con.set_attr(pr1=1, pr2=0.99, ttd_u=5) steam_generator.set_attr(pr=0.9) # %% parametrization of connections fs_in.set_attr(p=100, T=500, m=100, fluid={self.nw.fluids[0]: 1}) fw.set_attr(h=200e3) cw_in.set_attr(T=20, p=5, fluid={self.nw.fluids[0]: 1}) cw_out.set_attr(T=30) # %% solving self.nw.solve('design') pu.set_attr(eta_s=0.7) fw.set_attr(h=None) self.nw.solve('design') def setup_pipeline_network(self, fluid_list): """Setup a pipeline network.""" self.nw = Network(fluids=fluid_list) self.nw.set_attr(p_unit='bar', T_unit='C', iterinfo=False) # %% components # main components pu = Pump('pump') pi = Pipe('pipeline') es = HeatExchangerSimple('energy balance closing') closer = CycleCloser('cycle closer') pu_pi = Connection(pu, 'out1', pi, 'in1') pi_es = Connection(pi, 'out1', es, 'in1') es_closer = Connection(es, 'out1', closer, 'in1') closer_pu = Connection(closer, 'out1', pu, 'in1') self.nw.add_conns(pu_pi, pi_es, es_closer, closer_pu) # %% parametrization of components pu.set_attr(eta_s=0.7) pi.set_attr(pr=0.95, L=100, ks=1e-5, D='var', Q=0) es.set_attr(pr=1) # %% parametrization of connections pu_pi.set_attr(p=20, T=100, m=10, fluid={self.nw.fluids[0]: 1}) # %% solving self.nw.solve('design') @pytest.mark.skipif( os.environ.get('TRAVIS') == 'true', reason='Travis CI cannot handle the tabular CoolProp back ends, ' 'skipping this test. The test should run on your local machine.') def test_clausius_rankine_tabular(self): """Test the Clausius-Rankine cycle with different back ends.""" fluid = 'water' back_ends = ['HEOS', 'BICUBIC', 'TTSE'] results = {} for back_end in back_ends: # delete the fluid from the memorisation class if fluid in fp.Memorise.state.keys(): del fp.Memorise.state[fluid] del fp.Memorise.back_end[fluid] self.setup_clausius_rankine([back_end + '::' + fluid]) results[back_end] = ( 1 - abs(self.nw.get_comp('condenser').Q.val) / self.nw.get_comp('steam generator').Q.val) efficiency = results['HEOS'] if fluid in fp.Memorise.state.keys(): del fp.Memorise.state[fluid] del fp.Memorise.back_end[fluid] for back_end in back_ends: if back_end == 'HEOS': continue d_rel = (abs(results[back_end] - efficiency) / efficiency) msg = ( 'The deviation in thermal efficiency of the Clausius-Rankine ' 'cycle calculated with ' + back_end + ' back end is ' + str(d_rel) + ' but should not be larger than 1e-4.') assert d_rel <= 1e-4, msg def test_clausius_rankine(self): """Test the Clausius-Rankine cycle with different back ends.""" fluid = 'water' back_ends = ['HEOS', 'IF97'] results = {} for back_end in back_ends: # delete the fluid from the memorisation class if fluid in fp.Memorise.state.keys(): del fp.Memorise.state[fluid] del fp.Memorise.back_end[fluid] self.setup_clausius_rankine([back_end + '::' + fluid]) results[back_end] = ( 1 - abs(self.nw.get_comp('condenser').Q.val) / self.nw.get_comp('steam generator').Q.val) efficiency = results['HEOS'] if fluid in fp.Memorise.state.keys(): del fp.Memorise.state[fluid] del fp.Memorise.back_end[fluid] for back_end in back_ends: if back_end == 'HEOS': continue d_rel = (abs(results[back_end] - efficiency) / efficiency) msg = ( 'The deviation in thermal efficiency of the Clausius-Rankine ' 'cycle calculated with ' + back_end + ' back end is ' + str(d_rel) + ' but should not be larger than 1e-4.') assert d_rel <= 1e-4, msg def test_pipeline_network(self): """Test a pipeline network with fluids from different back ends.""" fluids_back_ends = {'DowJ': 'INCOMP', 'water': 'HEOS'} for fluid, back_end in fluids_back_ends.items(): # delete the fluid from the memorisation class if fluid in fp.Memorise.state.keys(): del fp.Memorise.state[fluid] self.setup_pipeline_network([back_end + '::' + fluid]) convergence_check(self.nw.lin_dep) value = round(self.nw.get_comp('pipeline').pr.val, 5) msg = ( 'The pressure ratio of the pipeline must be at 0.95, but ' 'is at ' + str(value) + ' for the fluid ' + fluid + '.') assert value == 0.95, msg value = round(self.nw.get_comp('pump').pr.val, 5) msg = ( 'The pressure ratio of the pipeline must be at ' + str(round(1 / 0.95, 5)) + ', but is at ' + str(value) + ' for the fluid ' + fluid + '.') assert value == round(1 / 0.95, 5), 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 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 TestNetworks: def setup_Network_tests(self): self.nw = Network(['water'], p_unit='bar', v_unit='m3 / s') self.source = Source('source') self.sink = Sink('sink') def offdesign_TESPyNetworkError(self, **kwargs): with raises(TESPyNetworkError): self.nw.solve('offdesign', **kwargs) def test_Network_linear_dependency(self): """Test network linear dependency.""" self.setup_Network_tests() a = Connection(self.source, 'out1', self.sink, 'in1', m=1, p=1, x=1, T=280) self.nw.add_conns(a) self.nw.solve('design') msg = ('This test must result in a linear dependency of the jacobian ' 'matrix.') assert self.nw.lin_dep, msg def test_Network_no_progress(self): """Test no convergence progress.""" self.setup_Network_tests() pi = Pipe('pipe', pr=1, Q=-100e3) a = Connection(self.source, 'out1', pi, 'in1', m=1, p=1, T=280, fluid={'water': 1}) b = Connection(pi, 'out1', self.sink, 'in1') self.nw.add_conns(a, b) self.nw.solve('design') msg = ('This test must result in a calculation making no progress, as ' 'the pipe\'s outlet enthalpy is below fluid property range.') assert self.nw.progress is False, msg def test_Network_max_iter(self): """Test reaching maximum iteration count.""" self.setup_Network_tests() pi = Pipe('pipe', pr=1, Q=100e3) a = Connection(self.source, 'out1', pi, 'in1', m=1, p=1, T=280, fluid={'water': 1}) b = Connection(pi, 'out1', self.sink, 'in1') self.nw.add_conns(a, b) self.nw.solve('design', max_iter=2) msg = ('This test must result in the itercount being equal to the max ' 'iter statement.') assert self.nw.max_iter == self.nw.iter + 1, msg def test_Network_delete_conns(self): """Test deleting a network's connection.""" self.setup_Network_tests() a = Connection(self.source, 'out1', self.sink, 'in1') self.nw.add_conns(a) self.nw.check_network() msg = ('After the network check, the .checked-property must be True.') assert self.nw.checked, msg self.nw.del_conns(a) msg = ('A connection has been deleted, the network consistency check ' 'must be repeated (.checked-property must be False).') assert self.nw.checked is False, msg def test_Network_missing_connection_in_init_path(self): """Test debug message for missing connection in init_path.""" self.setup_Network_tests() IF = SubsystemInterface('IF') a = Connection(self.source, 'out1', self.sink, 'in1') self.nw.add_conns(a) self.nw.solve('design', init_only=True) self.nw.save('tmp') msg = ('After the network check, the .checked-property must be True.') assert self.nw.checked, msg self.nw.del_conns(a) a = Connection(self.source, 'out1', IF, 'in1') b = Connection(IF, 'out1', self.sink, 'in1') self.nw.add_conns(a, b) self.nw.solve('design', init_path='tmp', init_only=True) msg = ('After the network check, the .checked-property must be True.') assert self.nw.checked, msg shutil.rmtree('./tmp', ignore_errors=True) def test_Network_export_no_chars_busses(self): """Test export of network without characteristics or busses.""" self.setup_Network_tests() a = Connection(self.source, 'out1', self.sink, 'in1') self.nw.add_conns(a) self.nw.solve('design', init_only=True) self.nw.save('tmp') msg = ('The exported network does not contain any char_line, there ' 'must be no file char_line.csv!') assert os.path.isfile('tmp/components/char_line.csv') is False, msg msg = ('The exported network does not contain any char_map, there ' 'must be no file char_map.csv!') assert os.path.isfile('tmp/components/char_map.csv') is False, msg msg = ('The exported network does not contain any busses, there ' 'must be no file bus.csv!') assert os.path.isfile('tmp/components/bus.csv') is False, msg shutil.rmtree('./tmp', ignore_errors=True) def test_Network_reader_no_chars_busses(self): """Test import of network without characteristics or busses.""" self.setup_Network_tests() a = Connection(self.source, 'out1', self.sink, 'in1') self.nw.add_conns(a) self.nw.solve('design', init_only=True) self.nw.save('tmp') imported_nwk = load_network('tmp') imported_nwk.solve('design', init_only=True) msg = ('If the network import was successful the network check ' 'should have been successful, too, but it is not.') assert imported_nwk.checked, msg shutil.rmtree('./tmp', ignore_errors=True) def test_Network_reader_deleted_chars(self): """Test import of network with missing characteristics.""" self.setup_Network_tests() comp = Compressor('compressor') a = Connection(self.source, 'out1', comp, 'in1') b = Connection(comp, 'out1', self.sink, 'in1') self.nw.add_conns(a, b) self.nw.solve('design', init_only=True) self.nw.save('tmp') # # remove char_line and char_map folders os.unlink('tmp/components/char_line.csv') os.unlink('tmp/components/char_map.csv') # import network with missing files imported_nwk = load_network('tmp') imported_nwk.solve('design', init_only=True) msg = ('If the network import was successful the network check ' 'should have been successful, too, but it is not.') assert imported_nwk.checked, msg shutil.rmtree('./tmp', ignore_errors=True) def test_Network_missing_data_in_design_case_files(self): """Test for missing data in design case files.""" self.setup_Network_tests() pi = Pipe('pipe', Q=0, pr=0.95, design=['pr'], offdesign=['zeta']) a = Connection(self.source, 'out1', pi, 'in1', m=1, p=1, T=293.15, fluid={'water': 1}) b = Connection(pi, 'out1', self.sink, 'in1') self.nw.add_conns(a, b) self.nw.solve('design') self.nw.save('tmp') self.nw.save('tmp2') inputs = open('./tmp/connections.csv') all_lines = inputs.readlines() all_lines.pop(len(all_lines) - 1) inputs.close() with open('./tmp2/connections.csv', 'w') as out: for line in all_lines: out.write(line.strip() + '\n') self.offdesign_TESPyNetworkError(design_path='tmp2', init_only=True) shutil.rmtree('./tmp', ignore_errors=True) shutil.rmtree('./tmp2', ignore_errors=True) def test_Network_missing_data_in_individual_design_case_file(self): """Test for missing data in individual design case files.""" self.setup_Network_tests() pi = Pipe('pipe', Q=0, pr=0.95, design=['pr'], offdesign=['zeta']) a = Connection(self.source, 'out1', pi, 'in1', m=1, p=1, T=293.15, fluid={'water': 1}) b = Connection(pi, 'out1', self.sink, 'in1', design_path='tmp2') self.nw.add_conns(a, b) self.nw.solve('design') self.nw.save('tmp') self.nw.save('tmp2') inputs = open('./tmp/connections.csv') all_lines = inputs.readlines() all_lines.pop(len(all_lines) - 1) inputs.close() with open('./tmp2/connections.csv', 'w') as out: for line in all_lines: out.write(line.strip() + '\n') self.offdesign_TESPyNetworkError(design_path='tmp', init_only=True) shutil.rmtree('./tmp', ignore_errors=True) shutil.rmtree('./tmp2', ignore_errors=True) def test_Network_missing_connection_in_design_path(self): """Test for missing connection data in design case files.""" self.setup_Network_tests() pi = Pipe('pipe', Q=0, pr=0.95, design=['pr'], offdesign=['zeta']) a = Connection(self.source, 'out1', pi, 'in1', m=1, p=1, T=293.15, fluid={'water': 1}) b = Connection(pi, 'out1', self.sink, 'in1') self.nw.add_conns(a, b) self.nw.solve('design') self.nw.save('tmp') inputs = open('./tmp/connections.csv') all_lines = inputs.readlines() all_lines.pop(len(all_lines) - 1) inputs.close() with open('./tmp/connections.csv', 'w') as out: for line in all_lines: out.write(line.strip() + '\n') self.offdesign_TESPyNetworkError(design_path='tmp') shutil.rmtree('./tmp', ignore_errors=True)
class TestNetworkIndividualOffdesign: def setup_Network_individual_offdesign(self): """Set up network for individual offdesign tests.""" self.nw = Network(['H2O'], T_unit='C', p_unit='bar', v_unit='m3 / s') so = Source('source') sp = Splitter('splitter', num_out=2) self.pump1 = Pump('pump 1') self.sc1 = SolarCollector('collector field 1') v1 = Valve('valve1') self.pump2 = Pump('pump 2') self.sc2 = SolarCollector('collector field 2') v2 = Valve('valve2') me = Merge('merge', num_in=2) si = Sink('sink') self.pump1.set_attr(eta_s=0.8, design=['eta_s'], offdesign=['eta_s_char']) self.pump2.set_attr(eta_s=0.8, design=['eta_s'], offdesign=['eta_s_char']) self.sc1.set_attr(pr=0.95, lkf_lin=3.33, lkf_quad=0.011, A=1252, E=700, Tamb=20, eta_opt=0.92, design=['pr'], offdesign=['zeta']) self.sc2.set_attr(pr=0.95, lkf_lin=3.5, lkf_quad=0.011, A=700, E=800, Tamb=20, eta_opt=0.92, design=['pr'], offdesign=['zeta']) fl = {'H2O': 1} inlet = Connection(so, 'out1', sp, 'in1', T=50, p=3, fluid=fl) outlet = Connection(me, 'out1', si, 'in1', p=3) self.sp_p1 = Connection(sp, 'out1', self.pump1, 'in1') self.p1_sc1 = Connection(self.pump1, 'out1', self.sc1, 'in1') self.sc1_v1 = Connection(self.sc1, 'out1', v1, 'in1', p=3.1, T=90) v1_me = Connection(v1, 'out1', me, 'in1') self.sp_p2 = Connection(sp, 'out2', self.pump2, 'in1') self.p2_sc2 = Connection(self.pump2, 'out1', self.sc2, 'in1') self.sc2_v2 = Connection(self.sc2, 'out1', v2, 'in1', p=3.1, m=0.1) v2_me = Connection(v2, 'out1', me, 'in2') self.nw.add_conns(inlet, outlet, self.sp_p1, self.p1_sc1, self.sc1_v1, v1_me, self.sp_p2, self.p2_sc2, self.sc2_v2, v2_me) def test_individual_design_path_on_connections_and_components(self): """Test individual design path specification.""" self.setup_Network_individual_offdesign() self.nw.solve('design') convergence_check(self.nw.lin_dep) self.sc2_v2.set_attr(m=0) self.nw.solve('design') convergence_check(self.nw.lin_dep) self.nw.save('design1') v1_design = self.sc1_v1.v.val_SI zeta_sc1_design = self.sc1.zeta.val self.sc2_v2.set_attr(T=95, state='l', m=None) self.sc1_v1.set_attr(m=0.001, T=None) self.nw.solve('design') convergence_check(self.nw.lin_dep) self.nw.save('design2') v2_design = self.sc2_v2.v.val_SI zeta_sc2_design = self.sc2.zeta.val self.sc1_v1.set_attr(m=np.nan) self.sc1_v1.set_attr(design=['T'], offdesign=['v'], state='l') self.sc2_v2.set_attr(design=['T'], offdesign=['v'], state='l') self.sc2.set_attr(design_path='design2') self.pump2.set_attr(design_path='design2') self.sp_p2.set_attr(design_path='design2') self.p2_sc2.set_attr(design_path='design2') self.sc2_v2.set_attr(design_path='design2') self.nw.solve('offdesign', design_path='design1') convergence_check(self.nw.lin_dep) self.sc1.set_attr(E=500) self.sc2.set_attr(E=950) self.nw.solve('offdesign', design_path='design1') convergence_check(self.nw.lin_dep) self.sc2_v2.set_attr(design_path=np.nan) # volumetric flow comparison msg = ('Design path was set to None, is ' + str(self.sc2_v2.design_path) + '.') assert self.sc2_v2.design_path is None, msg # volumetric flow comparison msg = ('Value of volumetric flow must be ' + str(v1_design) + ', is ' + str(self.sc1_v1.v.val_SI) + '.') assert round(v1_design, 5) == round(self.sc1_v1.v.val_SI, 5), msg msg = ('Value of volumetric flow must be ' + str(v2_design) + ', is ' + str(self.sc2_v2.v.val_SI) + '.') assert round(v2_design, 5) == round(self.sc2_v2.v.val_SI, 5), msg # zeta value of solar collector comparison msg = ('Value of zeta must be ' + str(zeta_sc1_design) + ', is ' + str(self.sc1.zeta.val) + '.') assert round(zeta_sc1_design, 0) == round(self.sc1.zeta.val, 0), msg msg = ('Value of zeta must be ' + str(zeta_sc2_design) + ', is ' + str(self.sc2.zeta.val) + '.') assert round(zeta_sc2_design, 0) == round(self.sc2.zeta.val, 0), msg shutil.rmtree('./design1', ignore_errors=True) shutil.rmtree('./design2', ignore_errors=True) def test_local_offdesign_on_connections_and_components(self): """Test local offdesign feature.""" self.setup_Network_individual_offdesign() self.nw.solve('design') convergence_check(self.nw.lin_dep) self.sc2_v2.set_attr(m=0) self.nw.solve('design') convergence_check(self.nw.lin_dep) self.nw.save('design1') self.sc1_v1.set_attr(design=['T'], offdesign=['v'], state='l') self.sc2_v2.set_attr(design=['T'], offdesign=['v'], state='l') self.sc1.set_attr(local_offdesign=True, design_path='design1') self.pump1.set_attr(local_offdesign=True, design_path='design1') self.sp_p1.set_attr(local_offdesign=True, design_path='design1') self.p1_sc1.set_attr(local_offdesign=True, design_path='design1') self.sc1_v1.set_attr(local_offdesign=True, design_path='design1') self.sc1.set_attr(E=500) self.sc2_v2.set_attr(T=95, m=np.nan) self.nw.solve('design') convergence_check(self.nw.lin_dep) self.nw.save('design2') # connections and components on side 1 must have switched to offdesign msg = ('Solar collector outlet temperature must be different from ' + 'design value ' + str(round(self.sc1_v1.T.design - 273.15, 1)) + ', is ' + str(round(self.sc1_v1.T.val, 1)) + '.') assert self.sc1_v1.T.design > self.sc1_v1.T.val, msg msg = ('Parameter eta_s_char must be set for pump one.') assert self.pump1.eta_s_char.is_set, msg msg = ('Parameter v must be set for connection from solar collector1 ' 'to pump1.') assert self.sc1_v1.v.val_set, msg shutil.rmtree('./design1', ignore_errors=True) shutil.rmtree('./design2', ignore_errors=True) def test_missing_design_path_local_offdesign_on_connections(self): """Test missing design path on connections in local offdesign mode.""" self.setup_Network_individual_offdesign() self.nw.solve('design') convergence_check(self.nw.lin_dep) self.sc2_v2.set_attr(m=0) self.nw.solve('design') convergence_check(self.nw.lin_dep) self.nw.save('design1') self.sc1_v1.set_attr(design=['T'], offdesign=['v'], state='l') self.sc2_v2.set_attr(design=['T'], offdesign=['v'], state='l') self.sc1.set_attr(local_offdesign=True, design_path='design1') self.pump1.set_attr(local_offdesign=True, design_path='design1') self.sp_p1.set_attr(local_offdesign=True, design_path='design1') self.p1_sc1.set_attr(local_offdesign=True, design_path='design1') self.sc1_v1.set_attr(local_offdesign=True) self.sc1.set_attr(E=500) self.sc2_v2.set_attr(T=95, m=np.nan) try: self.nw.solve('design', init_only=True) except TESPyNetworkError: pass shutil.rmtree('./design1', ignore_errors=True)
dh_out = Sink('district heating feedflow') # cooling water cw_in = Source('cooling water backflow') cw_out = Sink('cooling water feedflow') # %% connections # gas turbine part c_in = Connection(air, 'out1', comp, 'in1') c_out = Connection(comp, 'out1', c_c, 'in1') fuel_comp = Connection(CH4, 'out1', comp_fuel, 'in1') comp_cc = Connection(comp_fuel, 'out1', c_c, 'in2') gt_in = Connection(c_c, 'out1', g_turb, 'in1') gt_out = Connection(g_turb, 'out1', suph, 'in1') nw.add_conns(c_in, c_out, fuel_comp, comp_cc, gt_in, gt_out) # waste heat recovery (flue gas side) suph_evap = Connection(suph, 'out1', evap, 'in1') evap_eco = Connection(evap, 'out1', eco, 'in1') eco_ch = Connection(eco, 'out1', ch, 'in1') nw.add_conns(suph_evap, evap_eco, eco_ch) # waste heat recovery (water side) eco_drum = Connection(eco, 'out2', drum, 'in1') drum_evap = Connection(drum, 'out1', evap, 'in2') evap_drum = Connection(evap, 'out2', drum, 'in2') drum_suph = Connection(drum, 'out2', suph, 'in2') nw.add_conns(eco_drum, drum_evap, evap_drum, drum_suph)
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 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
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)
# compressor-system cp = Compressor('compressor') # %% connections # consumer system c_in_cd = Connection(cc, 'out1', cd, 'in1') cb_dhp = Connection(cb, 'out1', dhp, 'in1') dhp_cd = Connection(dhp, 'out1', cd, 'in2') cd_cons = Connection(cd, 'out2', cons, 'in1') cons_cf = Connection(cons, 'out1', cf, 'in1') nw.add_conns(c_in_cd, cb_dhp, dhp_cd, cd_cons, cons_cf) # connection condenser - evaporator system cd_va = Connection(cd, 'out1', va, 'in1') nw.add_conns(cd_va) # evaporator system va_dr = Connection(va, 'out1', dr, 'in1') dr_erp = Connection(dr, 'out1', erp, 'in1') erp_ev = Connection(erp, 'out1', ev, 'in2') ev_dr = Connection(ev, 'out2', dr, 'in2') dr_cp = Connection(dr, 'out2', cp, 'in1')
import numpy as np from matplotlib import pyplot as plt nw = Network(['water'], p_unit='bar', T_unit='C', h_unit='kJ / kg') # %% components pi = Pipe('pipe') si = Sink('sink') so = Source('source') # %% connections a = Connection(so, 'out1', pi, 'in1') b = Connection(pi, 'out1', si, 'in1') nw.add_conns(a, b) # %% connection parameters a.set_attr(h=40, fluid={'water': 1}, p=1, m=10) # %% component parameters pi.set_attr(ks=1e-5, L=100, D='var', Q=0) # %% solve nw.set_attr(iterinfo=False) # specify different pressure ratios for the pipe, # calculate the diameter required
steam_generator = HeatExchangerSimple('steam generator') closer = CycleCloser('cycle closer') # cooling water so_cw = Source('cooling water inlet') si_cw = Sink('cooling water outlet') # %% connections # main cycle fs_in = Connection(closer, 'out1', turb, 'in1') ws = Connection(turb, 'out1', con, 'in1') cond = Connection(con, 'out1', pu, 'in1') fw = Connection(pu, 'out1', steam_generator, 'in1') fs_out = Connection(steam_generator, 'out1', closer, 'in1') nw.add_conns(fs_in, ws, cond, fw, fs_out) # cooling water cw_in = Connection(so_cw, 'out1', con, 'in2') cw_out = Connection(con, 'out2', si_cw, 'in1') 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
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)
# sinks & sources amb = Source('ambient') sf = Source('fuel') fg = Sink('flue gas outlet') # combustion chamber comb = CombustionChamber(label='combustion chamber') # %% connections amb_comb = Connection(amb, 'out1', comb, 'in1') sf_comb = Connection(sf, 'out1', comb, 'in2') comb_fg = Connection(comb, 'out1', fg, 'in1') nw.add_conns(sf_comb, amb_comb, comb_fg) # %% component parameters # set combustion chamber air to stoichometric air ratio and thermal input comb.set_attr(lamb=3, ti=2e6) # %% connection parameters # air from abient (ambient pressure and temperature), air composition must be # stated component wise. amb_comb.set_attr(p=1, T=20, fluid={ 'Ar': 0.0129, 'N2': 0.7553,
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)
h1_pib2 = Connection(h1.comps['merge_0'], 'out1', pib2, 'in1') pib2_k1 = Connection(pib2, 'out1', k1.comps['valve_0'], 'in1', T=Ref(h1_pib2, 1, -pib2.L.val / dT_return), design=['T']) k1_pib1 = Connection(k1.comps['merge'], 'out1', pib1, 'in1', p=11) pib1_si = Connection(pib1, 'out1', si, 'in1', T=Ref(k1_pib1, 1, -pib1.L.val / dT_return), design=['T']) nw.add_conns(so_pif1, pif1_k1f, k1f_pif2, pif2_h1) nw.add_conns(h1_pib2, pib2_k1, k1_pib1, pib1_si) nw.add_subsys(h1) # %%industrial area # feed k1_pif4 = Connection(k1.comps['splitter'], 'out2', pif4, 'in1') pif4_v = Connection(pif4, 'out1', ia.comps['splitter_0'], 'in1', T=Ref(k1_pif4, 1, -pif4.L.val / dT_feed), design=['T']) v_pif7 = Connection(ia.comps['splitter_2'], 'out2', pif7, 'in1') pif7_k2 = Connection(pif7,
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 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 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 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)
pu = Pump('feed water pump') cc = CycleCloser('ls cycle closer') # district heating dh_in = Source('district heating backflow') dh_out = Sink('district heating feedflow') # %% connections # gas turbine part c_in = Connection(air, 'out1', comp, 'in1') c_out = Connection(comp, 'out1', c_c, 'in1') fuel = Connection(CH4, 'out1', c_c, 'in2') gt_in = Connection(c_c, 'out1', g_turb, 'in1') gt_out = Connection(g_turb, 'out1', suph, 'in1') nw.add_conns(c_in, c_out, fuel, gt_in, gt_out) # waste heat recovery (flue gas side) suph_evap = Connection(suph, 'out1', evap, 'in1') evap_eco = Connection(evap, 'out1', eco, 'in1') eco_dh = Connection(eco, 'out1', dh_whr, 'in1') dh_ch = Connection(dh_whr, 'out1', ch, 'in1') nw.add_conns(suph_evap, evap_eco, eco_dh, dh_ch) # waste heat recovery (water side) eco_drum = Connection(eco, 'out2', dr, 'in1') drum_evap = Connection(dr, 'out1', evap, 'in2') evap_drum = Connection(evap, 'out2', dr, 'in2') drum_suph = Connection(dr, 'out2', suph, 'in2')
class TestTurbomachinery: def setup_network(self, instance): self.nw = Network(['INCOMP::DowQ', 'NH3', 'N2', 'O2', 'Ar'], T_unit='C', p_unit='bar', v_unit='m3 / s') self.source = Source('source') self.sink = Sink('sink') self.c1 = Connection(self.source, 'out1', instance, 'in1') self.c2 = Connection(instance, 'out1', self.sink, 'in1') self.nw.add_conns(self.c1, self.c2) def test_Compressor(self): """Test component properties of compressors.""" instance = Compressor('compressor') self.setup_network(instance) # compress NH3, other fluids in network are for turbine, pump, ... fl = {'N2': 1, 'O2': 0, 'Ar': 0, 'DowQ': 0, 'NH3': 0} self.c1.set_attr(fluid=fl, v=1, p=1, T=5) self.c2.set_attr(p=6) instance.set_attr(eta_s=0.8) self.nw.solve('design') convergence_check(self.nw.lin_dep) self.nw.save('tmp') # test isentropic efficiency value eta_s_d = ((isentropic(self.c1.get_flow(), self.c2.get_flow()) - self.c1.h.val_SI) / (self.c2.h.val_SI - self.c1.h.val_SI)) msg = ('Value of isentropic efficiency must be ' + str(eta_s_d) + ', is ' + str(instance.eta_s.val) + '.') assert round(eta_s_d, 3) == round(instance.eta_s.val, 3), msg # trigger invalid value for isentropic efficiency instance.set_attr(eta_s=1.1) self.nw.solve('design') convergence_check(self.nw.lin_dep) # test calculated value eta_s = ((isentropic(self.c1.get_flow(), self.c2.get_flow()) - self.c1.h.val_SI) / (self.c2.h.val_SI - self.c1.h.val_SI)) msg = ('Value of isentropic efficiency must be ' + str(eta_s) + ', is ' + str(instance.eta_s.val) + '.') assert round(eta_s, 3) == round(instance.eta_s.val, 3), msg # remove pressure at outlet, use characteristic map for pressure # rise calculation self.c2.set_attr(p=np.nan) instance.set_attr(char_map_pr={ 'char_func': ldc('compressor', 'char_map_pr', 'DEFAULT', CharMap), 'is_set': True }, char_map_eta_s={ 'char_func': ldc('compressor', 'char_map_eta_s', 'DEFAULT', CharMap), 'is_set': True }, eta_s=np.nan, igva=0) # offdesign test, efficiency value should be at design value self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) msg = ('Value of isentropic efficiency (' + str(instance.eta_s.val) + ') must be identical to design case (' + str(eta_s) + ').') assert round(eta_s_d, 2) == round(instance.eta_s.val, 2), msg # move to highest available speedline, mass flow below lowest value # at that line self.c1.set_attr(v=np.nan, m=self.c1.m.val * 0.8, T=-30) self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) # should be value eta_s = eta_s_d * instance.char_map_eta_s.char_func.z[6, 0] msg = ('Value of isentropic efficiency (' + str(instance.eta_s.val) + ') must be at (' + str(round(eta_s, 4)) + ').') assert round(eta_s, 4) == round(instance.eta_s.val, 4), msg # going below lowest available speedline, above highest mass flow at # that line self.c1.set_attr(T=175) self.nw.solve('offdesign', design_path='tmp', init_path='tmp') convergence_check(self.nw.lin_dep) # should be value eta_s = eta_s_d * instance.char_map_eta_s.char_func.z[0, 9] msg = ('Value of isentropic efficiency (' + str(instance.eta_s.val) + ') must be at (' + str(round(eta_s, 4)) + ').') assert round(eta_s, 4) == round(instance.eta_s.val, 4), msg # back to design properties, test eta_s_char self.c2.set_attr(p=6) self.c1.set_attr(v=1, T=5, m=np.nan) # test parameter specification for eta_s_char with unset char map instance.set_attr( eta_s_char={ 'char_func': ldc('compressor', 'eta_s_char', 'DEFAULT', CharLine), 'is_set': True, 'param': 'm' }) instance.char_map_eta_s.is_set = False instance.char_map_pr.is_set = False self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) msg = ('Value of isentropic efficiency must be ' + str(eta_s_d) + ', is ' + str(instance.eta_s.val) + '.') assert round(eta_s_d, 3) == round(instance.eta_s.val, 3), msg # move up in volumetric flow self.c1.set_attr(v=1.5) self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) eta_s = round( eta_s_d * instance.eta_s_char.char_func.evaluate( self.c1.m.val_SI / self.c1.m.design), 3) msg = ('Value of isentropic efficiency must be ' + str(eta_s) + ', is ' + str(round(instance.eta_s.val, 3)) + '.') assert eta_s == round(instance.eta_s.val, 3), msg # test parameter specification for pr instance.eta_s_char.set_attr(param='pr') self.c1.set_attr(v=1) self.c2.set_attr(p=6) self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) expr = (self.c2.p.val_SI * self.c1.p.design / (self.c2.p.design * self.c1.p.val_SI)) eta_s = round(eta_s_d * instance.eta_s_char.char_func.evaluate(expr), 3) msg = ('Value of isentropic efficiency must be ' + str(eta_s) + ', is ' + str(round(instance.eta_s.val, 3)) + '.') assert eta_s == round(instance.eta_s.val, 3), msg shutil.rmtree('./tmp', ignore_errors=True) def test_Pump(self): """Test component properties of pumps.""" instance = Pump('pump') self.setup_network(instance) fl = {'N2': 0, 'O2': 0, 'Ar': 0, 'DowQ': 1, 'NH3': 0} self.c1.set_attr(fluid=fl, v=1, p=5, T=50) self.c2.set_attr(p=7) instance.set_attr(eta_s=1) self.nw.solve('design') convergence_check(self.nw.lin_dep) # test calculated value for efficiency eta_s = ((isentropic(self.c1.get_flow(), self.c2.get_flow()) - self.c1.h.val_SI) / (self.c2.h.val_SI - self.c1.h.val_SI)) msg = ('Value of isentropic efficiency must be ' + str(eta_s) + ', is ' + str(instance.eta_s.val) + '.') assert eta_s == instance.eta_s.val, msg # isentropic efficiency of 1 means inlet and outlet entropy are # identical s1 = round(s_mix_ph(self.c1.get_flow()), 4) s2 = round(s_mix_ph(self.c2.get_flow()), 4) msg = ('Value of entropy must be identical for inlet (' + str(s1) + ') and outlet (' + str(s2) + ') at 100 % isentropic efficiency.') assert s1 == s2, msg # specify realistic value for efficiency, outlet pressure from flow # char eta_s_d = 0.8 instance.set_attr(eta_s=eta_s_d) self.nw.solve('design') convergence_check(self.nw.lin_dep) self.nw.save('tmp') self.c2.set_attr(p=np.nan) # flow char (pressure rise vs. volumetric flow) x = [0, 0.2, 0.4, 0.6, 0.8, 1, 1.2, 1.4] y = np.array([14, 13.5, 12.5, 11, 9, 6.5, 3.5, 0]) * 1e5 char = {'char_func': CharLine(x, y), 'is_set': True} # apply flow char and eta_s char instance.set_attr(flow_char=char, eta_s=np.nan, eta_s_char={ 'char_func': ldc('pump', 'eta_s_char', 'DEFAULT', CharLine), 'is_set': True }) self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) # value for difference pressure dp = 650000.0 msg = ('Value of pressure rise must be ' + str(dp) + ', is ' + str(self.c2.p.val_SI - self.c1.p.val_SI) + '.') assert round(self.c2.p.val_SI - self.c1.p.val_SI, 0) == dp, msg # test ohter volumetric flow on flow char self.c1.set_attr(v=0.9) self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) dp = 775000.0 msg = ('Value of pressure rise must be ' + str(dp) + ', is ' + str(round(self.c2.p.val_SI - self.c1.p.val_SI, 0)) + '.') assert round(self.c2.p.val_SI - self.c1.p.val_SI, 0) == dp, msg # test value of isentropic efficiency eta_s = round( eta_s_d * instance.eta_s_char.char_func.evaluate( self.c1.v.val_SI / self.c1.v.design), 3) msg = ('Value of isentropic efficiency must be ' + str(eta_s) + ', is ' + str(instance.eta_s.val) + '.') assert eta_s == round(instance.eta_s.val, 3), msg instance.eta_s_char.is_set = False # test boundaries of characteristic line: # lower boundary instance.set_attr(eta_s=0.8) self.c1.set_attr(m=0, v=None) self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of power must be ' + str(14e5) + ', is ' + str(round(self.c2.p.val_SI - self.c1.p.val_SI, 0)) + '.') assert round(self.c2.p.val_SI - self.c1.p.val_SI, 0) == 14e5, msg # upper boundary self.c1.set_attr(v=1.5, m=None) self.nw.solve('design') convergence_check(self.nw.lin_dep) msg = ('Value of power must be 0, is ' + str(round(self.c2.p.val_SI - self.c1.p.val_SI, 0)) + '.') assert round(self.c2.p.val_SI - self.c1.p.val_SI, 0) == 0, msg shutil.rmtree('./tmp', ignore_errors=True) def test_Turbine(self): """Test component properties of turbines.""" instance = Turbine('turbine') self.setup_network(instance) fl = {'N2': 0.7556, 'O2': 0.2315, 'Ar': 0.0129, 'DowQ': 0, 'NH3': 0} self.c1.set_attr(fluid=fl, m=15, p=10) self.c2.set_attr(p=1, T=25) instance.set_attr(eta_s=0.85) self.nw.solve('design') convergence_check(self.nw.lin_dep) self.nw.save('tmp') # design value of isentropic efficiency eta_s_d = ((self.c2.h.val_SI - self.c1.h.val_SI) / (isentropic(self.c1.get_flow(), self.c2.get_flow()) - self.c1.h.val_SI)) msg = ('Value of isentropic efficiency must be ' + str(round(eta_s_d, 3)) + ', is ' + str(instance.eta_s.val) + '.') assert round(eta_s_d, 3) == round(instance.eta_s.val, 3), msg # trigger invalid value for isentropic efficiency instance.set_attr(eta_s=1.1) self.nw.solve('design') convergence_check(self.nw.lin_dep) eta_s = ((self.c2.h.val_SI - self.c1.h.val_SI) / (isentropic(self.c1.get_flow(), self.c2.get_flow()) - self.c1.h.val_SI)) msg = ('Value of isentropic efficiency must be ' + str(eta_s) + ', is ' + str(instance.eta_s.val) + '.') assert round(eta_s, 3) == round(instance.eta_s.val, 3), msg # unset isentropic efficiency and inlet pressure, # use characteristcs and cone law instead, parameters have to be in # design state self.c1.set_attr(p=np.nan) instance.cone.is_set = True instance.eta_s_char.is_set = True instance.eta_s.is_set = False self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) # check efficiency msg = ('Value of isentropic efficiency (' + str(instance.eta_s.val) + ') must be identical to design case (' + str(eta_s_d) + ').') assert round(eta_s_d, 2) == round(instance.eta_s.val, 2), msg # check pressure msg = ('Value of inlet pressure (' + str(round(self.c1.p.val_SI)) + ') must be identical to design case (' + str(round(self.c1.p.design)) + ').') assert round(self.c1.p.design) == round(self.c1.p.val_SI), msg # lowering mass flow, inlet pressure must sink according to cone law self.c1.set_attr(m=self.c1.m.val * 0.8) self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) msg = ('Value of pressure ratio (' + str(instance.pr.val) + ') must be at (' + str(0.128) + ').') assert 0.128 == round(instance.pr.val, 3), msg # testing more parameters for eta_s_char # test parameter specification v self.c1.set_attr(m=10) instance.eta_s_char.param = 'v' self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) expr = self.c1.v.val_SI / self.c1.v.design eta_s = round(eta_s_d * instance.eta_s_char.char_func.evaluate(expr), 3) msg = ('Value of isentropic efficiency (' + str(round(instance.eta_s.val, 3)) + ') must be (' + str(eta_s) + ').') assert eta_s == round(instance.eta_s.val, 3), msg # test parameter specification pr instance.eta_s_char.param = 'pr' self.nw.solve('offdesign', design_path='tmp') convergence_check(self.nw.lin_dep) expr = (self.c2.p.val_SI * self.c1.p.design / (self.c2.p.design * self.c1.p.val_SI)) eta_s = round(eta_s_d * instance.eta_s_char.char_func.evaluate(expr), 3) msg = ('Value of isentropic efficiency (' + str(round(instance.eta_s.val, 3)) + ') must be (' + str(eta_s) + ').') assert eta_s == round(instance.eta_s.val, 3), msg shutil.rmtree('./tmp', ignore_errors=True) def test_Turbomachine(self): """Test component properties of turbomachines.""" instance = Turbomachine('turbomachine') msg = ('Component name must be turbomachine, is ' + instance.component() + '.') assert 'turbomachine' == instance.component(), msg self.setup_network(instance) fl = {'N2': 0.7556, 'O2': 0.2315, 'Ar': 0.0129, 'DowQ': 0, 'NH3': 0} self.c1.set_attr(fluid=fl, m=10, p=1, h=1e5) self.c2.set_attr(p=3, h=2e5) # pressure ratio and power are the basic functions for turbomachines, # these are inherited by all children, thus only tested here self.nw.solve('design') convergence_check(self.nw.lin_dep) power = self.c1.m.val_SI * (self.c2.h.val_SI - self.c1.h.val_SI) pr = self.c2.p.val_SI / self.c1.p.val_SI msg = ('Value of power must be ' + str(power) + ', is ' + str(instance.P.val) + '.') assert power == instance.P.val, msg msg = ('Value of power must be ' + str(pr) + ', is ' + str(instance.pr.val) + '.') assert pr == instance.pr.val, msg # test pressure ratio specification self.c2.set_attr(p=np.nan) instance.set_attr(pr=5) self.nw.solve('design') convergence_check(self.nw.lin_dep) pr = self.c2.p.val_SI / self.c1.p.val_SI msg = ('Value of power must be ' + str(pr) + ', is ' + str(instance.pr.val) + '.') assert pr == instance.pr.val, msg # test power specification self.c2.set_attr(h=np.nan) instance.set_attr(P=1e5) self.nw.solve('design') convergence_check(self.nw.lin_dep) power = self.c1.m.val_SI * (self.c2.h.val_SI - self.c1.h.val_SI) msg = ('Value of power must be ' + str(power) + ', is ' + str(instance.P.val) + '.') assert power == instance.P.val, msg