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)
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 TestClausiusRankine: def setup(self): """Set up clausis rankine cycle with turbine driven feed water pump.""" self.Tamb = 20 self.pamb = 1 fluids = ['water'] self.nw = Network(fluids=fluids) self.nw.set_attr(p_unit='bar', T_unit='C', h_unit='kJ / kg') # create components splitter1 = Splitter('splitter 1') merge1 = Merge('merge 1') turb = Turbine('turbine') fwp_turb = Turbine('feed water pump turbine') condenser = HeatExchangerSimple('condenser') fwp = Pump('pump') steam_generator = HeatExchangerSimple('steam generator') cycle_close = CycleCloser('cycle closer') # create busses # power output bus self.power = Bus('power_output') self.power.add_comps({'comp': turb, 'char': 1}) # turbine driven feed water pump internal bus self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps({ 'comp': fwp_turb, 'char': 1 }, { 'comp': fwp, 'char': 1, 'base': 'bus' }) # heat input bus self.heat = Bus('heat_input') self.heat.add_comps({'comp': steam_generator, 'base': 'bus'}) self.nw.add_busses(self.power, self.fwp_power, self.heat) # create connections fs_in = Connection(cycle_close, 'out1', splitter1, 'in1', label='fs') fs_fwpt = Connection(splitter1, 'out1', fwp_turb, 'in1') fs_t = Connection(splitter1, 'out2', turb, 'in1') fwpt_ws = Connection(fwp_turb, 'out1', merge1, 'in1') t_ws = Connection(turb, 'out1', merge1, 'in2') ws = Connection(merge1, 'out1', condenser, 'in1') cond = Connection(condenser, 'out1', fwp, 'in1', label='cond') fw = Connection(fwp, 'out1', steam_generator, 'in1', label='fw') fs_out = Connection(steam_generator, 'out1', cycle_close, 'in1') self.nw.add_conns(fs_in, fs_fwpt, fs_t, fwpt_ws, t_ws, ws, cond, fw, fs_out) # component parameters turb.set_attr(eta_s=1) fwp_turb.set_attr(eta_s=1) condenser.set_attr(pr=1) fwp.set_attr(eta_s=1) steam_generator.set_attr(pr=1) # connection parameters fs_in.set_attr(m=10, p=120, T=600, fluid={'water': 1}) cond.set_attr(T=self.Tamb, x=0) # solve network self.nw.solve('design') convergence_check(self.nw.lin_dep) def test_exergy_analysis_perfect_cycle(self): """Test exergy analysis in the perfect clausius rankine cycle.""" ean = ExergyAnalysis(self.nw, E_P=[self.power], E_F=[self.heat], internal_busses=[self.fwp_power]) ean.analyse(pamb=self.pamb, Tamb=self.Tamb) msg = ('Exergy destruction of this network must be 0 (smaller than ' + str(err**0.5) + ') for this test but is ' + str(round(abs(ean.network_data.E_D), 4)) + ' .') assert abs(ean.network_data.E_D) <= err**0.5, msg msg = ('Exergy efficiency of this network must be 1 for this test but ' 'is ' + str(round(ean.network_data.epsilon, 4)) + ' .') assert round(ean.network_data.epsilon, 4) == 1, msg exergy_balance = (ean.network_data.E_F - ean.network_data.E_P - ean.network_data.E_L - ean.network_data.E_D) msg = ('Exergy balance must be closed (residual value smaller than ' + str(err**0.5) + ') for this test but is ' + str(round(abs(exergy_balance), 4)) + ' .') assert abs(exergy_balance) <= err**0.5, msg msg = ( 'Fuel exergy and product exergy must be identical for this test. ' 'Fuel exergy value: ' + str(round(ean.network_data.E_F, 4)) + '. Product exergy value: ' + str(round(ean.network_data.E_P, 4)) + '.') delta = round(abs(ean.network_data.E_F - ean.network_data.E_P), 4) assert delta < err**0.5, msg def test_exergy_analysis_plotting_data(self): """Test exergy analysis plotting.""" self.nw.get_comp('steam generator').set_attr(pr=0.9) self.nw.get_comp('turbine').set_attr(eta_s=0.9) self.nw.get_comp('feed water pump turbine').set_attr(eta_s=0.85) self.nw.get_comp('pump').set_attr(eta_s=0.75) self.nw.get_conn('cond').set_attr(T=self.Tamb + 3) # specify efficiency values for the internal bus and power bus self.nw.del_busses(self.fwp_power, self.power) self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps( { 'comp': self.nw.get_comp('feed water pump turbine'), 'char': 0.99 }, { 'comp': self.nw.get_comp('pump'), 'char': 0.98, 'base': 'bus' }) self.power = Bus('power_output') self.power.add_comps({ 'comp': self.nw.get_comp('turbine'), 'char': 0.98 }) self.nw.add_busses(self.fwp_power, self.power) # solve network self.nw.solve('design') convergence_check(self.nw.lin_dep) ean = ExergyAnalysis(self.nw, E_P=[self.power], E_F=[self.heat], internal_busses=[self.fwp_power]) ean.analyse(pamb=self.pamb, Tamb=self.Tamb) exergy_balance = (ean.network_data.E_F - ean.network_data.E_P - ean.network_data.E_L - ean.network_data.E_D) msg = ('Exergy balance must be closed (residual value smaller than ' + str(err**0.5) + ') for this test but is ' + str(round(abs(exergy_balance), 4)) + ' .') assert abs(exergy_balance) <= err**0.5, msg nodes = [ 'E_F', 'steam generator', 'splitter 1', 'feed water pump turbine', 'turbine', 'merge 1', 'condenser', 'pump', 'E_D', 'E_P' ] links, nodes = ean.generate_plotly_sankey_input(node_order=nodes) # checksum for targets and source checksum = sum(links['target'] + links['source']) msg = ('The checksum of all target and source values in the link lists' 'must be 148, but is ' + str(checksum) + '.') assert 148 == checksum, msg def test_exergy_analysis_violated_balance(self): """Test exergy analysis with violated balance.""" # specify efficiency values for the internal bus self.nw.del_busses(self.fwp_power) self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps( { 'comp': self.nw.get_comp('feed water pump turbine'), 'char': 0.99 }, { 'comp': self.nw.get_comp('pump'), 'char': 0.98, 'base': 'bus' }) self.nw.add_busses(self.fwp_power) self.nw.solve('design') convergence_check(self.nw.lin_dep) # miss out on internal bus in exergy_analysis ean = ExergyAnalysis(self.nw, E_P=[self.power], E_F=[self.heat]) ean.analyse(pamb=self.pamb, Tamb=self.Tamb) exergy_balance = (ean.network_data.E_F - ean.network_data.E_P - ean.network_data.E_L - ean.network_data.E_D) msg = ('Exergy balance must be violated for this test (larger than ' + str(err**0.5) + ') but is ' + str(round(abs(exergy_balance), 4)) + ' .') assert abs(exergy_balance) > err**0.5, msg def test_exergy_analysis_bus_conversion(self): """Test exergy analysis bus conversion factors.""" # specify efficiency values for the internal bus self.nw.del_busses(self.fwp_power) self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps( { 'comp': self.nw.get_comp('feed water pump turbine'), 'char': 0.99 }, { 'comp': self.nw.get_comp('pump'), 'char': 0.98, 'base': 'bus' }) self.nw.add_busses(self.fwp_power) self.nw.solve('design') convergence_check(self.nw.lin_dep) # no exergy losses in this case ean = ExergyAnalysis(self.nw, E_P=[self.power], E_F=[self.heat], internal_busses=[self.fwp_power]) ean.analyse(pamb=self.pamb, Tamb=self.Tamb) label = 'pump' eps = ean.bus_data.loc[label, 'epsilon'] msg = ('Pump exergy efficiency must be 0.98 but is ' + str(round(eps, 4)) + ' .') assert round(eps, 4) == 0.98, msg label = 'feed water pump turbine' eps = ean.bus_data.loc[label, 'epsilon'] msg = ( 'Feed water pump turbine exergy efficiency must be 0.99 but is ' + str(round(eps, 4)) + ' .') assert round(eps, 4) == 0.99, msg def test_exergy_analysis_missing_E_F_E_P_information(self): """Test exergy analysis errors with missing information.""" with raises(TESPyNetworkError): ExergyAnalysis(self.nw, E_P=[self.power], E_F=[]) with raises(TESPyNetworkError): ExergyAnalysis(self.nw, E_P=[], E_F=[self.heat]) def test_exergy_analysis_component_on_two_busses(self): """Test exergy analysis errors with components on more than one bus.""" with raises(TESPyNetworkError): ean = ExergyAnalysis(self.nw, E_P=[self.power], E_F=[self.heat, self.power]) ean.analyse(pamb=self.pamb, Tamb=self.Tamb)
class TestClausiusRankine: def setup(self): """Set up clausis rankine cycle with turbine driven feed water pump.""" self.Tamb = 20 self.pamb = 1 fluids = ['water'] self.nw = Network(fluids=fluids) self.nw.set_attr(p_unit='bar', T_unit='C', h_unit='kJ / kg') # create components splitter1 = Splitter('splitter 1') merge1 = Merge('merge 1') turb = Turbine('turbine') fwp_turb = Turbine('feed water pump turbine') condenser = HeatExchangerSimple('condenser') fwp = Pump('pump') steam_generator = HeatExchangerSimple('steam generator') cycle_close = CycleCloser('cycle closer') # create busses # power output bus self.power = Bus('power_output') self.power.add_comps({'comp': turb, 'char': 1}) # turbine driven feed water pump internal bus self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps({ 'comp': fwp_turb, 'char': 1 }, { 'comp': fwp, 'char': 1, 'base': 'bus' }) # heat input bus self.heat = Bus('heat_input') self.heat.add_comps({'comp': steam_generator, 'base': 'bus'}) self.nw.add_busses(self.power, self.fwp_power, self.heat) # create connections fs_in = Connection(cycle_close, 'out1', splitter1, 'in1', label='fs') fs_fwpt = Connection(splitter1, 'out1', fwp_turb, 'in1') fs_t = Connection(splitter1, 'out2', turb, 'in1') fwpt_ws = Connection(fwp_turb, 'out1', merge1, 'in1') t_ws = Connection(turb, 'out1', merge1, 'in2') ws = Connection(merge1, 'out1', condenser, 'in1') cond = Connection(condenser, 'out1', fwp, 'in1', label='cond') fw = Connection(fwp, 'out1', steam_generator, 'in1', label='fw') fs_out = Connection(steam_generator, 'out1', cycle_close, 'in1') self.nw.add_conns(fs_in, fs_fwpt, fs_t, fwpt_ws, t_ws, ws, cond, fw, fs_out) # component parameters turb.set_attr(eta_s=1) fwp_turb.set_attr(eta_s=1) condenser.set_attr(pr=1) fwp.set_attr(eta_s=1) steam_generator.set_attr(pr=1) # connection parameters fs_in.set_attr(m=10, p=120, T=600, fluid={'water': 1}) cond.set_attr(T=self.Tamb, x=0) # solve network self.nw.solve('design') convergence_check(self.nw.lin_dep) def test_entropy_perfect_cycle(self): """Test entropy values in the perfect clausius rankine cycle.""" labels = [ 'turbine', 'feed water pump turbine', 'condenser', 'steam generator', 'pump' ] for label in labels: cp = self.nw.get_comp(label) msg = ( 'Entropy production due to irreversibility must be 0 for all ' 'components in this test but is ' + str(round(cp.S_irr, 4)) + ' at component ' + label + ' of type ' + cp.component() + '.') assert round(cp.S_irr, 4) == 0, msg sg = self.nw.get_comp('steam generator') cd = self.nw.get_comp('condenser') msg = ( 'Value of entropy production due to heat input at steam generator ' '(S_Q=' + str(round(sg.S_Q, 4)) + ') must equal the negative ' 'value of entropy reduction in condenser (S_Q=' + str(round(cd.S_Q, 4)) + ').') assert round(sg.S_Q, 4) == -round(cd.S_Q, 4), msg
class TestClausiusRankine: def setup(self): """Set up clausis rankine cycle with turbine driven feed water pump.""" self.Tamb = 20 self.pamb = 1 fluids = ['water'] self.nw = Network(fluids=fluids) self.nw.set_attr(p_unit='bar', T_unit='C', h_unit='kJ / kg') # create components splitter1 = Splitter('splitter 1') merge1 = Merge('merge 1') turb = Turbine('turbine') fwp_turb = Turbine('feed water pump turbine') condenser = HeatExchangerSimple('condenser') fwp = Pump('pump') steam_generator = HeatExchangerSimple('steam generator') cycle_close = CycleCloser('cycle closer') # create busses # power output bus self.power = Bus('power_output') self.power.add_comps({'comp': turb, 'char': 1}) # turbine driven feed water pump internal bus self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps({ 'comp': fwp_turb, 'char': 1 }, { 'comp': fwp, 'char': 1, 'base': 'bus' }) # heat input bus self.heat = Bus('heat_input') self.heat.add_comps({'comp': steam_generator, 'base': 'bus'}) self.nw.add_busses(self.power, self.fwp_power, self.heat) # create connections fs_in = Connection(cycle_close, 'out1', splitter1, 'in1', label='fs') fs_fwpt = Connection(splitter1, 'out1', fwp_turb, 'in1') fs_t = Connection(splitter1, 'out2', turb, 'in1') fwpt_ws = Connection(fwp_turb, 'out1', merge1, 'in1') t_ws = Connection(turb, 'out1', merge1, 'in2') ws = Connection(merge1, 'out1', condenser, 'in1') cond = Connection(condenser, 'out1', fwp, 'in1', label='cond') fw = Connection(fwp, 'out1', steam_generator, 'in1', label='fw') fs_out = Connection(steam_generator, 'out1', cycle_close, 'in1') self.nw.add_conns(fs_in, fs_fwpt, fs_t, fwpt_ws, t_ws, ws, cond, fw, fs_out) # component parameters turb.set_attr(eta_s=1) fwp_turb.set_attr(eta_s=1) condenser.set_attr(pr=1) fwp.set_attr(eta_s=1) steam_generator.set_attr(pr=1) # connection parameters fs_in.set_attr(m=10, p=120, T=600, fluid={'water': 1}) cond.set_attr(T=self.Tamb, x=0) # solve network self.nw.solve('design') convergence_check(self.nw.lin_dep) def test_exergy_analysis_perfect_cycle(self): """Test exergy analysis in the perfect clausius rankine cycle.""" self.nw.exergy_analysis(self.pamb, self.Tamb, E_P=[self.power], E_F=[self.heat], internal_busses=[self.fwp_power]) msg = ('Exergy destruction of this network must be 0 (smaller than ' + str(err**0.5) + ') for this test but is ' + str(round(abs(self.nw.E_D), 4)) + ' .') assert abs(self.nw.E_D) <= err**0.5, msg msg = ('Exergy efficiency of this network must be 1 for this test but ' 'is ' + str(round(self.nw.epsilon, 4)) + ' .') assert round(self.nw.epsilon, 4) == 1, msg exergy_balance = self.nw.E_F - self.nw.E_P - self.nw.E_L - self.nw.E_D msg = ('Exergy balance must be closed (residual value smaller than ' + str(err**0.5) + ') for this test but is ' + str(round(abs(exergy_balance), 4)) + ' .') assert abs(exergy_balance) <= err**0.5, msg msg = ( 'Fuel exergy and product exergy must be identical for this test. ' 'Fuel exergy value: ' + str(round(self.nw.E_F, 4)) + '. Product exergy value: ' + str(round(self.nw.E_P, 4)) + '.') assert round(abs(self.nw.E_F - self.nw.E_P), 4) < err**0.5, msg def test_entropy_perfect_cycle(self): """Test entropy values in the perfect clausius rankine cycle.""" labels = [ 'turbine', 'feed water pump turbine', 'condenser', 'steam generator', 'pump' ] for label in labels: cp = self.nw.get_comp(label) msg = ( 'Entropy production due to irreversibility must be 0 for all ' 'components in this test but is ' + str(round(cp.S_irr, 4)) + ' at component ' + label + ' of type ' + cp.component() + '.') assert round(cp.S_irr, 4) == 0, msg sg = self.nw.get_comp('steam generator') cd = self.nw.get_comp('condenser') msg = ( 'Value of entropy production due to heat input at steam generator ' '(S_Q=' + str(round(sg.S_Q, 4)) + ') must equal the negative ' 'value of entropy reduction in condenser (S_Q=' + str(round(cd.S_Q, 4)) + ').') assert round(sg.S_Q, 4) == -round(cd.S_Q, 4), msg def test_exergy_analysis_violated_balance(self): """Test exergy analysis with violated balance.""" # specify efficiency values for the internal bus self.nw.del_busses(self.fwp_power) self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps( { 'comp': self.nw.get_comp('feed water pump turbine'), 'char': 0.99 }, { 'comp': self.nw.get_comp('pump'), 'char': 0.98, 'base': 'bus' }) self.nw.add_busses(self.fwp_power) self.nw.solve('design') convergence_check(self.nw.lin_dep) # miss out on internal bus in exergy_analysis self.nw.exergy_analysis(self.pamb, self.Tamb, E_P=[self.power], E_F=[self.heat]) exergy_balance = self.nw.E_F - self.nw.E_P - self.nw.E_L - self.nw.E_D msg = ('Exergy balance must be violated for this test (larger than ' + str(err**0.5) + ') but is ' + str(round(abs(exergy_balance), 4)) + ' .') assert abs(exergy_balance) > err**0.5, msg def test_exergy_analysis_bus_conversion(self): """Test exergy analysis bus conversion factors.""" # specify efficiency values for the internal bus self.nw.del_busses(self.fwp_power) self.fwp_power = Bus('feed water pump power', P=0) self.fwp_power.add_comps( { 'comp': self.nw.get_comp('feed water pump turbine'), 'char': 0.99 }, { 'comp': self.nw.get_comp('pump'), 'char': 0.98, 'base': 'bus' }) self.nw.add_busses(self.fwp_power) self.nw.solve('design') convergence_check(self.nw.lin_dep) # no exergy losses in this case self.nw.exergy_analysis(self.pamb, self.Tamb, E_P=[self.power], E_F=[self.heat], internal_busses=[self.fwp_power]) label = 'pump on bus feed water pump power' eps = self.nw.component_exergy_data.loc[label, 'epsilon'] msg = ('Pump exergy efficiency must be 0.98 but is ' + str(round(eps, 4)) + ' .') assert round(eps, 4) == 0.98, msg label = 'feed water pump turbine on bus feed water pump power' eps = self.nw.component_exergy_data.loc[label, 'epsilon'] eps = self.nw.component_exergy_data.loc[label, 'epsilon'] msg = ( 'Feed water pump turbine exergy efficiency must be 0.99 but is ' + str(round(eps, 4)) + ' .') assert round(eps, 4) == 0.99, msg def test_exergy_analysis_missing_E_F_E_P_information(self): """Test exergy analysis errors with missing information.""" with raises(TESPyNetworkError): self.nw.exergy_analysis(self.pamb, self.Tamb, E_P=[self.power], E_F=[]) with raises(TESPyNetworkError): self.nw.exergy_analysis(self.pamb, self.Tamb, E_P=[], E_F=[self.heat]) def test_exergy_analysis_component_on_two_busses(self): """Test exergy analysis errors with components on more than one bus.""" with raises(TESPyNetworkError): self.nw.exergy_analysis(self.pamb, self.Tamb, E_P=[self.power], E_F=[self.heat, self.power])
class TestBusses: def setup(self): """Set up the model.""" # %% network setup fluid_list = ['Ar', 'N2', 'O2', 'CO2', 'CH4', 'H2O'] self.nw = Network(fluids=fluid_list, p_unit='bar', T_unit='C', p_range=[0.5, 20]) # %% components amb = Source('ambient') sf = Source('fuel') cc = CombustionChamber('combustion') cp = Compressor('compressor') gt = Turbine('turbine') fg = Sink('flue gas outlet') # %% connections amb_cp = Connection(amb, 'out1', cp, 'in1', label='ambient air flow') cp_cc = Connection(cp, 'out1', cc, 'in1') sf_cc = Connection(sf, 'out1', cc, 'in2') cc_gt = Connection(cc, 'out1', gt, 'in1') gt_fg = Connection(gt, 'out1', fg, 'in1') self.nw.add_conns(amb_cp, cp_cc, sf_cc, cc_gt, gt_fg) # %% component parameters cc.set_attr(lamb=3) cp.set_attr(eta_s=0.9, pr=15) gt.set_attr(eta_s=0.9) # %% connection parameters amb_cp.set_attr(T=20, p=1, m=100, fluid={ 'Ar': 0.0129, 'N2': 0.7553, 'H2O': 0, 'CH4': 0, 'CO2': 0.0004, 'O2': 0.2314 }) sf_cc.set_attr(T=20, fluid={ 'CO2': 0.04, 'Ar': 0, 'N2': 0, 'O2': 0, 'H2O': 0, 'CH4': 0.96 }) gt_fg.set_attr(p=1) # motor efficiency x = np.array([ 0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1, 1.05, 1.1, 1.15, 1.2, 10 ]) y = np.array([ 0.01, 0.3148, 0.5346, 0.6843, 0.7835, 0.8477, 0.8885, 0.9145, 0.9318, 0.9443, 0.9546, 0.9638, 0.9724, 0.9806, 0.9878, 0.9938, 0.9982, 0.999, 0.9995, 0.9999, 1, 0.9977, 0.9947, 0.9909, 0.9853, 0.9644 ]) * 0.975 self.motor_bus_based = CharLine(x=x, y=y) self.motor_comp_based = CharLine(x=x, y=1 / y) # generator efficiency x = np.array([ 0.100, 0.345, 0.359, 0.383, 0.410, 0.432, 0.451, 0.504, 0.541, 0.600, 0.684, 0.805, 1.000, 1.700, 10 ]) y = np.array([ 0.976, 0.989, 0.990, 0.991, 0.992, 0.993, 0.994, 0.995, 0.996, 0.997, 0.998, 0.999, 1.000, 0.999, 0.99 ]) * 0.975 self.generator = CharLine(x=x, y=y) power_bus_total = Bus('total power output') power_bus_total.add_comps( { 'comp': cp, 'char': self.motor_bus_based, 'base': 'bus' }, { 'comp': gt, 'char': self.generator }) thermal_input = Bus('thermal input') thermal_input.add_comps({'comp': cc}) compressor_power_comp = Bus('compressor power input') compressor_power_comp.add_comps({ 'comp': cp, 'char': self.motor_comp_based }) compressor_power_bus = Bus('compressor power input bus based') compressor_power_bus.add_comps({ 'comp': cp, 'char': self.motor_bus_based, 'base': 'bus' }) self.nw.add_busses(power_bus_total, thermal_input, compressor_power_comp, compressor_power_bus) # %% solving self.nw.solve('design') self.nw.save('tmp') def test_model(self): """Test the bus functionalities in a gas turbine model.""" tpo = self.nw.busses['total power output'] ti = self.nw.busses['thermal input'] cpi = self.nw.busses['compressor power input'] cpibb = self.nw.busses['compressor power input bus based'] cp = self.nw.get_comp('compressor') gt = self.nw.get_comp('turbine') cc = self.nw.get_comp('combustion') # test results of design case eta_cpi = round(1 / cp.calc_bus_efficiency(cpi), 6) eta_cp_tpo = round(cp.calc_bus_efficiency(tpo), 6) msg = ('The efficiency value of the compressor on the bus ' + tpo.label + ' (' + str(eta_cp_tpo) + ') must be identical to the efficiency ' 'on the bus ' + cpi.label + ' (' + str(eta_cpi) + ').') assert eta_cp_tpo == eta_cpi, msg P_cp_tpo = cp.calc_bus_value(tpo) eta_cp_tpo = cp.calc_bus_efficiency(tpo) P_cp = round(P_cp_tpo * eta_cp_tpo, 0) msg = ('The compressor power must be ' + str(round(cp.P.val, 0)) + ' on ' 'the bus ' + tpo.label + ' but is ' + str(P_cp) + ').') assert round(cp.P.val, 0) == P_cp, msg P_cp_tpo = round( cp.calc_bus_value(tpo) * cp.calc_bus_efficiency(tpo), 0) P_cp_cpi = round( cp.calc_bus_value(cpi) / cp.calc_bus_efficiency(cpi), 0) P_cp_cpibb = round( cp.calc_bus_value(cpibb) * cp.calc_bus_efficiency(cpibb), 0) msg = ( 'The busses\' component power value for the compressor on bus ' + tpo.label + ' (' + str(P_cp_tpo) + ') must be equal to the ' 'component power on all other busses. Bus ' + cpi.label + ' (' + str(P_cp_cpi) + ') and bus ' + cpibb.label + ' (' + str(P_cp_cpibb) + ').') assert P_cp_tpo == P_cp_cpi and P_cp_tpo == P_cp_cpibb, msg eta_gt_tpo = gt.calc_bus_efficiency(tpo) msg = ('The efficiency value of the turbine on the bus ' + tpo.label + ' (' + str(eta_gt_tpo) + ') must be equal to 0.975.') assert eta_gt_tpo == 0.975, msg eta_ti = cc.calc_bus_efficiency(ti) msg = ('The efficiency value of the combustion chamber on the bus ' + ti.label + ' (' + str(eta_ti) + ') must be equal to 1.0.') assert eta_ti == 1.0, msg # test partload for bus functions # first test in identical conditions self.nw.get_conn('ambient air flow').set_attr(m=None) P_design = cpibb.P.val cpibb.set_attr(P=P_design) self.nw.solve('offdesign', design_path='tmp') eta_cpi = round(1 / cp.calc_bus_efficiency(cpi), 6) eta_cp_tpo = round(cp.calc_bus_efficiency(tpo), 6) msg = ('The efficiency value of the compressor on the bus ' + tpo.label + ' (' + str(eta_cp_tpo) + ') must be identical to the efficiency ' 'on the bus ' + cpi.label + ' (' + str(eta_cpi) + ').') assert eta_cp_tpo == eta_cpi, msg eta_gt_tpo = gt.calc_bus_efficiency(tpo) msg = ('The efficiency value of the turbine on the bus ' + tpo.label + ' (' + str(eta_gt_tpo) + ') must be equal to 0.975.') assert eta_gt_tpo == 0.975, msg P_cp_tpo = round( cp.calc_bus_value(tpo) * cp.calc_bus_efficiency(tpo), 0) P_cp_cpi = round( cp.calc_bus_value(cpi) / cp.calc_bus_efficiency(cpi), 0) P_cp_cpibb = round( cp.calc_bus_value(cpibb) * cp.calc_bus_efficiency(cpibb), 0) msg = ( 'The busses\' component power value for the compressor on bus ' + tpo.label + ' (' + str(P_cp_tpo) + ') must be equal to the ' 'component power on all other busses. Bus ' + cpi.label + ' (' + str(P_cp_cpi) + ') and bus ' + cpibb.label + ' (' + str(P_cp_cpibb) + ').') assert P_cp_tpo == P_cp_cpi and P_cp_tpo == P_cp_cpibb, msg # 60 % load load = 0.6 cpibb.set_attr(P=P_design * load) self.nw.solve('offdesign', design_path='tmp') eta_cp_tpo = round(cp.calc_bus_efficiency(tpo), 6) eta_cp_char = self.motor_bus_based.evaluate(load) msg = ('The efficiency value of the compressor on the bus ' + tpo.label + ' (' + str(eta_cp_tpo) + ') must be identical to the efficiency ' 'on the characteristic line (' + str(eta_cp_char) + ').') assert eta_cp_tpo == eta_cp_char, msg load_frac = round( cp.calc_bus_value(tpo) / tpo.comps.loc[cp, 'P_ref'], 6) msg = ('The load fraction value of the compressor on the bus ' + tpo.label + ' (' + str(load_frac) + ') must be identical to the ' 'load fraction value on the bus ' + cpibb.label + ' (' + str(load) + ').') assert load == load_frac, msg eta_cpi = round(1 / cp.calc_bus_efficiency(cpi), 6) eta_cp_tpo = round(cp.calc_bus_efficiency(tpo), 6) msg = ('The efficiency value of the compressor on the bus ' + tpo.label + ' (' + str(eta_cp_tpo) + ') must be higher than the efficiency ' 'on the bus ' + cpi.label + ' (' + str(eta_cpi) + ').') assert eta_cp_tpo > eta_cpi, msg P_cp_tpo = round( cp.calc_bus_value(tpo) * cp.calc_bus_efficiency(tpo), 0) P_cp_cpi = round( cp.calc_bus_value(cpi) / cp.calc_bus_efficiency(cpi), 0) P_cp_cpibb = round( cp.calc_bus_value(cpibb) * cp.calc_bus_efficiency(cpibb), 0) msg = ( 'The busses\' component power value for the compressor on bus ' + tpo.label + ' (' + str(P_cp_tpo) + ') must be equal to the ' 'component power on all other busses. Bus ' + cpi.label + ' (' + str(P_cp_cpi) + ') and bus ' + cpibb.label + ' (' + str(P_cp_cpibb) + ').') assert P_cp_tpo == P_cp_cpi and P_cp_tpo == P_cp_cpibb, msg shutil.rmtree('tmp', ignore_errors=True)