def maxSubcritORCBoilTemp(orc_fluid): P_crit = FluidState.getPcrit(orc_fluid) # assume min pressure is condensing at 0C P_min = FluidState.getStateFromTQ(0, 1, orc_fluid).P_Pa dP = (P_crit - P_min) / 1000 # find pressure with maximum entropy P = np.arange(P_min, P_crit, dP) s = FluidState.getStateFromPQ(P, 1, orc_fluid).s_JK max_s = np.max(s) row_index = np.argmax(s, axis=0) max_P = P[row_index] return FluidState.getStateFromPS(max_P, max_s, orc_fluid).T_C
def solve(self, initialState, T_boil_C=False, dT_pinch=False): T_in_C = initialState.T_C if not T_boil_C: T_boil_C = np.interp( T_in_C, self.data[self.params.opt_mode][self.params.orc_fluid][:, 0], self.data[self.params.opt_mode][self.params.orc_fluid][:, 1]) if not dT_pinch: dT_pinch = np.interp( T_in_C, self.data[self.params.opt_mode][self.params.orc_fluid][:, 0], self.data[self.params.opt_mode][self.params.orc_fluid][:, 2]) # run some checks if T_in_C and T_boil_C are valid if np.isnan(T_in_C): raise Exception( 'GenGeo::ORCCycleTboil:T_in_NaN - ORC input temperature is NaN!' ) if np.isnan(T_boil_C): raise Exception( 'GenGeo::ORCCycleTboil:T_boil_NaN - ORC boil temperature is NaN!' ) if T_boil_C > FluidState.getTcrit(self.params.orc_fluid): raise Exception( 'GenGeo::ORCCycleTboil:Tboil_Too_Large - Boiling temperature above critical point' ) if dT_pinch <= 0: raise Exception( 'GenGeo::ORCCycleTboil:dT_pinch_Negative - dT_pinch is negative!' ) if T_in_C < T_boil_C + dT_pinch: raise Exception( 'GenGeo::ORCCycleTboil:Tboil_Too_Large - Boiling temperature of %s is greater than input temp of %s less pinch dT of %s.' % (T_boil_C, T_in_C, dT_pinch)) # only refresh T_boil_max if orc_fluid has changed from initial if self.params.orc_fluid != self.orc_fluid: self.T_boil_max = maxSubcritORCBoilTemp(self.params.orc_fluid) self.orc_fluid = self.params.orc_fluid if T_boil_C > self.T_boil_max: raise Exception( 'GenGeo::ORCCycleTboil:Tboil_Too_Large - Boiling temperature of %s is greater than maximum allowed of %s.' % (T_boil_C, self.T_boil_max)) T_condense_C = self.params.T_ambient_C + self.params.dT_approach # create empty list to compute cycle of 6 states state = [None] * 6 #State 1 (Condenser -> Pump) #saturated liquid state[0] = FluidState.getStateFromTQ(T_condense_C, 0, self.params.orc_fluid) #State 6 (Desuperheater -> Condenser) #saturated vapor state[5] = FluidState.getStateFromTQ(state[0].T_C, 1, self.params.orc_fluid) # state[5].P_Pa = state[0].P_Pa #State 3 (Preheater -> Boiler) #saturated liquid state[2] = FluidState.getStateFromTQ(T_boil_C, 0, self.params.orc_fluid) #State 4 (Boiler -> Turbine) #saturated vapor state[3] = FluidState.getStateFromTQ(state[2].T_C, 1, self.params.orc_fluid) # state[3].P_Pa = state[2].P_Pa #State 5 (Turbine -> Desuperheater) h_5s = FluidState.getStateFromPS(state[0].P_Pa, state[3].s_JK, self.params.orc_fluid).h_Jkg h_5 = state[3].h_Jkg - self.params.eta_turbine_orc * (state[3].h_Jkg - h_5s) state[4] = FluidState.getStateFromPh(state[0].P_Pa, h_5, self.params.orc_fluid) # #State 2 (Pump -> Preheater) h_2s = FluidState.getStateFromPS(state[2].P_Pa, state[0].s_JK, self.params.orc_fluid).h_Jkg h_2 = state[0].h_Jkg - ( (state[0].h_Jkg - h_2s) / self.params.eta_pump_orc) state[1] = FluidState.getStateFromPh(state[2].P_Pa, h_2, self.params.orc_fluid) results = PowerPlantOutput() # #Calculate orc heat/work w_pump_orc = state[0].h_Jkg - state[1].h_Jkg q_preheater_orc = -1 * (state[1].h_Jkg - state[2].h_Jkg) q_boiler_orc = -1 * (state[2].h_Jkg - state[3].h_Jkg) w_turbine_orc = state[3].h_Jkg - state[4].h_Jkg q_desuperheater_orc = -1 * (state[4].h_Jkg - state[5].h_Jkg) q_condenser_orc = -1 * (state[5].h_Jkg - state[0].h_Jkg) results.dP_pump_orc = state[1].P_Pa - state[0].P_Pa results.P_boil = state[2].P_Pa # Cooling Tower Parasitic load dT_range = state[4].T_C - state[5].T_C parasiticPowerFraction = CoolingCondensingTower.parasiticPowerFraction( self.params.T_ambient_C, self.params.dT_approach, dT_range, self.params.cooling_mode) w_cooler_orc = q_desuperheater_orc * parasiticPowerFraction('cooling') w_condenser_orc = q_condenser_orc * parasiticPowerFraction( 'condensing') #water (assume pressure 100 kPa above saturation) P_sat = FluidState.getStateFromTQ(T_in_C, 0, 'Water').P_Pa cp = FluidState.getStateFromPT(P_sat + 100e3, T_in_C, 'Water').cp_JK #Water state 11, inlet, 12, mid, 13 exit T_C_11 = T_in_C T_C_12 = T_boil_C + dT_pinch #mdot_ratio = mdot_orc / mdot_water mdot_ratio = cp * (T_C_11 - T_C_12) / q_boiler_orc T_C_13 = T_C_12 - mdot_ratio * q_preheater_orc / cp # check that T_C(13) isn't below pinch constraint if T_C_13 < (state[1].T_C + dT_pinch): # pinch constraint is here, not at 12 # outlet is pump temp plus pinch T_C_13 = state[1].T_C + dT_pinch R = q_boiler_orc / (q_boiler_orc + q_preheater_orc) T_C_12 = T_C_11 - (T_C_11 - T_C_13) * R mdot_ratio = cp * (T_C_11 - T_C_12) / q_boiler_orc #Calculate water heat/work results.q_preheater = mdot_ratio * q_preheater_orc results.q_boiler = mdot_ratio * q_boiler_orc results.q_desuperheater = mdot_ratio * q_desuperheater_orc results.q_condenser = mdot_ratio * q_condenser_orc results.w_turbine = mdot_ratio * w_turbine_orc results.w_pump = mdot_ratio * w_pump_orc results.w_cooler = mdot_ratio * w_cooler_orc results.w_condenser = mdot_ratio * w_condenser_orc results.w_net = results.w_turbine + results.w_pump + results.w_cooler + results.w_condenser # Calculate temperatures results.dT_range_CT = state[4].T_C - state[5].T_C dT_A_p = T_C_13 - state[1].T_C dT_B_p = T_C_12 - state[2].T_C if dT_A_p == dT_B_p: results.dT_LMTD_preheater = dT_A_p else: div = dT_A_p / dT_B_p results.dT_LMTD_preheater = (dT_A_p - dT_B_p) / ( math.log(abs(div)) * np.sign(div)) dT_A_b = T_C_12 - state[2].T_C dT_B_b = T_C_11 - state[3].T_C if dT_A_b == dT_B_b: results.dT_LMTD_boiler = dT_A_b else: div = dT_A_b / dT_B_b results.dT_LMTD_boiler = (dT_A_b - dT_B_b) / (math.log(abs(div)) * np.sign(div)) # return temperature results.state = FluidState.getStateFromPT(initialState.P_Pa, T_C_13, self.params.working_fluid) return results
def solve(self): results = FluidSystemCO2Output() results.pp = PowerPlantEnergyOutput() # Find condensation pressure T_condensation = self.params.T_ambient_C + self.params.dT_approach P_condensation = FluidState.getStateFromTQ(T_condensation, 0, self.params.working_fluid).P_Pa + 50e3 dP_pump = 0 dP_downhole_threshold = 1e3 dP_downhole = np.nan dP_loops = 1 dP_solver = Solver() while np.isnan(dP_downhole) or np.abs(dP_downhole) >= dP_downhole_threshold: # Find Injection Conditions P_pump_inlet = P_condensation # Only add pump differential if positive. If negative, add as a throttle at the bottom of the injection well if (dP_pump > 0): P_pump_outlet = P_pump_inlet + dP_pump else: P_pump_outlet = P_pump_inlet T_pump_inlet = T_condensation pump_inlet_state = FluidState.getStateFromPT(P_pump_inlet, T_pump_inlet, self.params.working_fluid) if dP_pump > 0: h_pump_outletS = FluidState.getStateFromPS(P_pump_outlet, pump_inlet_state.s_JK, self.params.working_fluid).h_Jkg h_pump_outlet = pump_inlet_state.h_Jkg + (h_pump_outletS - pump_inlet_state.h_Jkg) / self.params.eta_pump_co2 else: h_pump_outlet = pump_inlet_state.h_Jkg results.pp.w_pump = -1 * (h_pump_outlet - pump_inlet_state.h_Jkg) surface_injection_state = FluidState.getStateFromPh(P_pump_outlet, h_pump_outlet, self.params.working_fluid) results.injection_well = self.injection_well.solve(surface_injection_state) # if dP_pump is negative, this is a throttle after the injection well if (dP_pump < 0): results.injection_well_downhole_throttle = FluidState.getStateFromPh( results.injection_well.state.P_Pa + dP_pump, results.injection_well.state.h_Jkg, self.params.working_fluid) else: results.injection_well_downhole_throttle = FluidState.getStateFromPh( results.injection_well.state.P_Pa, results.injection_well.state.h_Jkg, self.params.working_fluid) results.reservoir = self.reservoir.solve(results.injection_well_downhole_throttle) # find downhole pressure difference (negative means # overpressure dP_downhole = self.params.P_reservoir() - results.reservoir.state.P_Pa dP_pump = dP_solver.addDataAndEstimate(dP_pump, dP_downhole) if np.isnan(dP_pump): dP_pump = 0.5 * dP_downhole if dP_loops > 10: print('GenGeo::Warning::FluidSystemCO2:dP_loops is large: %s'%dP_loops) dP_loops += 1 if results.reservoir.state.P_Pa >= self.params.P_reservoir_max(): raise Exception('GenGeo::FluidSystemCO2:ExceedsMaxReservoirPressure - ' 'Exceeds Max Reservoir Pressure of %.3f MPa!'%(self.params.P_reservoir_max()/1e6)) results.production_well = self.production_well.solve(results.reservoir.state) # Subtract surface frictional losses between production wellhead and surface plant ff = frictionFactor(self.params.well_radius, results.production_well.state.P_Pa, results.production_well.state.h_Jkg, self.params.m_dot_IP, self.params.working_fluid, self.params.epsilon) if self.params.has_surface_gathering_system == True: dP_surfacePipes = ff * self.params.well_spacing / (self.params.well_radius*2)**5 * 8 * self.params.m_dot_IP**2 / results.production_well.state.rho_kgm3 / 3.14159**2 else: dP_surfacePipes = 0 results.surface_plant_inlet = FluidState.getStateFromPh( results.production_well.state.P_Pa - dP_surfacePipes, results.production_well.state.h_Jkg, self.params.working_fluid) # Calculate Turbine Power h_turbine_outS = FluidState.getStateFromPS(P_condensation, results.surface_plant_inlet.s_JK, self.params.working_fluid).h_Jkg h_turbine_out = results.surface_plant_inlet.h_Jkg - self.params.eta_turbine_co2 * (results.surface_plant_inlet.h_Jkg - h_turbine_outS) results.pp.w_turbine = results.surface_plant_inlet.h_Jkg - h_turbine_out if results.pp.w_turbine < 0: raise Exception('GenGeo::FluidSystemCO2:TurbinePowerNegative - Turbine Power is Negative') # heat rejection h_satVapor = FluidState.getStateFromPQ(P_condensation, 1, self.params.working_fluid).h_Jkg h_condensed = FluidState.getStateFromPQ(P_condensation, 0, self.params.working_fluid).h_Jkg if h_turbine_out > h_satVapor: # desuperheating needed results.pp.q_cooler = h_satVapor - h_turbine_out results.pp.q_condenser = h_condensed - h_satVapor T_turbine_out = FluidState.getStateFromPh(P_condensation, h_turbine_out, self.params.working_fluid).T_C dT_range = T_turbine_out - T_condensation else: # no desuperheating results.pp.q_cooler = 0 results.pp.q_condenser = h_condensed - h_turbine_out dT_range = 0 parasiticPowerFraction = CoolingCondensingTower.parasiticPowerFraction(self.params.T_ambient_C, self.params.dT_approach, dT_range, self.params.cooling_mode) results.pp.w_cooler = results.pp.q_cooler * parasiticPowerFraction('cooling') results.pp.w_condenser = results.pp.q_condenser * parasiticPowerFraction('condensing') results.pp.w_net = results.pp.w_turbine + results.pp.w_pump + results.pp.w_cooler + results.pp.w_condenser results.pp.dP_surface = results.surface_plant_inlet.P_Pa - P_condensation results.pp.dP_pump = dP_pump results.pp.state_out = surface_injection_state return results
def solve(self, initialState, P_boil_Pa=False): T_in_C = initialState.T_C if not P_boil_Pa: P_boil_Pa = np.interp(T_in_C, self.data[:, 0], self.data[:, 1]) # Critical point of R245fa # if Pboil is below critical, throw error if P_boil_Pa < FluidState.getPcrit(self.params.orc_fluid): raise Exception( 'GenGeo::ORCCycleSupercritPboil:lowBoilingPressure - Boiling Pressure Below Critical Pressure' ) # The line of minimum entropy to keep the fluid vapor in turbine is # entropy at saturated vapor at 125C. So inlet temp must provide this # minimum entropy. s_min = FluidState.getStateFromTQ(125., 1, self.params.orc_fluid).s_JK T_min = FluidState.getStateFromPS(P_boil_Pa, s_min, self.params.orc_fluid).T_C if (T_in_C - self.params.dT_pinch) < T_min: raise Exception( 'GenGeo::ORCCycleSupercritPboil:lowInletTemp - Inlet Temp below %.1f C for Supercritical Fluid' % (T_min + self.params.dT_pinch)) T_condense_C = self.params.T_ambient_C + self.params.dT_approach # create empty list to compute cycle of 7 states state = [None] * 7 #State 1 (Condenser -> Pump) #saturated liquid state[0] = FluidState.getStateFromTQ(T_condense_C, 0, self.params.orc_fluid) # State 7 (Desuperheater -> Condenser) # saturated vapor state[6] = FluidState.getStateFromTQ(state[0].T_C, 1, self.params.orc_fluid) # State 2 (Pump -> Recuperator) h_2s = FluidState.getStateFromPS(P_boil_Pa, state[0].s_JK, self.params.orc_fluid).h_Jkg h2 = state[0].h_Jkg - ( (state[0].h_Jkg - h_2s) / self.params.eta_pump_orc) state[1] = FluidState.getStateFromPh(P_boil_Pa, h2, self.params.orc_fluid) # water (assume pressure 100 kPa above saturation) P_water = FluidState.getStateFromTQ(T_in_C, 0, 'Water').P_Pa + 100e3 # Guess orc_in fluid is state[1].T_C state[2] = FluidState.getStateFromPT(state[1].P_Pa, state[1].T_C, self.params.orc_fluid) # Water in temp is T_in_C T_C_11 = T_in_C P_4 = state[1].P_Pa # initialize state 2 with pressure state 2 and dummy temperature state[3] = FluidState.getStateFromPT(P_4, 15., self.params.orc_fluid) # initialize state 3 with pressure state 1 and dummy enthalpy state[4] = FluidState.getStateFromPh(state[0].P_Pa, 1e4, self.params.orc_fluid) # initialize state 6 with pressure state 1 and dummy temperature state[5] = FluidState.getStateFromPT(state[0].P_Pa, 15., self.params.orc_fluid) results = PowerPlantOutput() dT = 1 while abs(dT) >= 1: # State 4 (Boiler -> Turbine) # Input orc/geo heat exchanger opt_heatExchanger_results = heatExchangerOptMdot( state[2].T_C, P_4, self.params.orc_fluid, T_C_11, P_water, 'Water', self.params.dT_pinch, T_min) # state[3] = FluidStateFromPT(P_4, opt_heatExchanger_results.T_1_out, self.params.orc_fluid) state[3].T_C = opt_heatExchanger_results.T_1_out #State 5 (Turbine -> Recuperator) h_5s = FluidState.getStateFromPS(state[0].P_Pa, state[3].s_JK, self.params.orc_fluid).h_Jkg h_5 = state[3].h_Jkg - self.params.eta_turbine_orc * ( state[3].h_Jkg - h_5s) state[4].h_Jkg = h_5 # state[4] = FluidStateFromPh(state[0].P_Pa(), h_5, self.params.orc_fluid) # State 3 (Recuperator -> Boiler) # State 6 (Recuperator -> Desuperheater) # Assume m_dot for each fluid is 1, then output is specific heat # exchange heatExchanger_results = heatExchanger(state[1].T_C, state[1].P_Pa, 1, self.params.orc_fluid, state[4].T_C, state[0].P_Pa, 1, self.params.orc_fluid, self.params.dT_pinch) # state[2] = FluidStateFromPT(state[2].P_Pa, state[2].T_C, self.params.orc_fluid) # state[5] = FluidStateFromPT(state[0].P_Pa, heatExchanger_results.T_2_out, self.params.orc_fluid) state[5].T_C = heatExchanger_results.T_2_out dT = state[2].T_C - heatExchanger_results.T_1_out state[2].T_C = heatExchanger_results.T_1_out #Calculate orc heat/work w_pump_orc = state[0].h_Jkg - state[1].h_Jkg q_boiler_orc = -1 * (state[2].h_Jkg - state[3].h_Jkg) w_turbine_orc = state[3].h_Jkg - state[4].h_Jkg q_desuperheater_orc = -1 * (state[5].h_Jkg - state[6].h_Jkg) q_condenser_orc = -1 * (state[6].h_Jkg - state[0].h_Jkg) # Cooling Tower Parasitic load results.dT_range_CT = state[5].T_C - state[6].T_C parasiticPowerFraction = CoolingCondensingTower.parasiticPowerFraction( self.params.T_ambient_C, self.params.dT_approach, results.dT_range_CT, self.params.cooling_mode) w_cooler_orc = q_desuperheater_orc * parasiticPowerFraction('cooling') w_condenser_orc = q_condenser_orc * parasiticPowerFraction( 'condensing') #Calculate water heat/work results.w_pump = opt_heatExchanger_results.mdot_ratio * w_pump_orc results.q_boiler = opt_heatExchanger_results.mdot_ratio * q_boiler_orc results.w_turbine = opt_heatExchanger_results.mdot_ratio * w_turbine_orc results.q_recuperator = opt_heatExchanger_results.mdot_ratio * heatExchanger_results.Q_exchanged results.q_desuperheater = opt_heatExchanger_results.mdot_ratio * q_desuperheater_orc results.q_condenser = opt_heatExchanger_results.mdot_ratio * q_condenser_orc results.w_cooler = opt_heatExchanger_results.mdot_ratio * w_cooler_orc results.w_condenser = opt_heatExchanger_results.mdot_ratio * w_condenser_orc results.w_net = results.w_turbine + results.w_pump + results.w_cooler + results.w_condenser results.end_T_C = opt_heatExchanger_results.T_2_out results.dT_LMTD_boiler = opt_heatExchanger_results.dT_LMTD results.dT_LMTD_recuperator = heatExchanger_results.dT_LMTD # return temperature results.state = FluidState.getStateFromPT( initialState.P_Pa, opt_heatExchanger_results.T_2_out, self.params.working_fluid) return results