Beispiel #1
0
    def testProductionWell(self):
        ###
        #  Testing SemiAnalyticalWell for vertical production well settings
        ###
        # Water
        params = SimulationParameters(working_fluid = 'water',
                                        time_years = 10.,
                                        m_dot_IP=136.)

        # initial state
        initial_state = FluidState.getStateFromPT(25.e6, 97., params.working_fluid)
        
        well = SemiAnalyticalWell(params, T_e_initial=102.5, dz_total=2500.)
        wellresult = well.solve(initial_state)

        self.assertMessages(well.params.working_fluid,
                        (wellresult.state.P_Pa, 1.2372e6),
                        (wellresult.state.T_C, 95.0133),
                        (wellresult.state.h_Jkg, 3.9902e5))

        # CO2
        well.params.working_fluid = 'CO2'
        # initial state
        wellresult = well.solve(initial_state)

        self.assertMessages(well.params.working_fluid,
                        (wellresult.state.P_Pa, 1.1674e7),
                        (wellresult.state.T_C, 55.9783),
                        (wellresult.state.h_Jkg, 3.7225e5))
Beispiel #2
0
 def computeSurfacePipeFrictionFactor(self):
     self.ff_m_dot = self.params.m_dot_IP
     initial_P = 1e6 + self.params.P_system_min()
     initial_T = 60.
     initial_h = FluidState.getStateFromPT(initial_P, initial_T,
                                           self.params.working_fluid).h_Jkg
     self.friction_factor = frictionFactor(self.params.well_radius, initial_P, initial_h, \
                         self.params.m_dot_IP, self.params.working_fluid, self.params.epsilon)
    def testORCCycleTboilFail(self):

        initialState = FluidState.getStateFromPT(1.e6, 15., 'water')
        try:
            results = cycle.solve(initialState, T_boil_C=100., dT_pinch=5.)
        except Exception as ex:
            self.assertTrue(
                str(ex).find('GenGeo::ORCCycleTboil:Tboil_Too_Large') > -1,
                'test1_fail_not_found')
Beispiel #4
0
    def minimizeFunction(self, initial_T):

        initial_state = FluidState.getStateFromPT(self.initial_P, initial_T, self.fluid_system.fluid)
        system_state = self.fluid_system.solve(initial_state)

        diff = (system_state.pp.state.T_C - initial_T)

        print('find T ', system_state.pp.state.T_C)
        return diff
    def testORCCycleTboil(self):

        initialState = FluidState.getStateFromPT(1.e6, 150., 'water')
        results = cycle.solve(initialState, T_boil_C=100., dT_pinch=5.)

        self.assertTrue(*testAssert(results.state.T_C, 68.36, 'test1_temp'))
        self.assertTrue(*testAssert(results.w_net, 3.8559e4, 'test1_w_net'))
        self.assertTrue(
            *testAssert(results.w_turbine, 4.7773e4, 'test1_w_turbine'))
        self.assertTrue(
            *testAssert(results.q_preheater, 1.5778e5, 'test1_q_preheater'))
        self.assertTrue(
            *testAssert(results.q_boiler, 1.9380e5, 'test1_q_boiler'))
Beispiel #6
0
    def testInjectionWellWater(self):
        ###
        #  Testing SemiAnalyticalWell for vertical and horizontal injection well settings
        ###
        # load parameters
        gpp = SimulationParameters(working_fluid = 'water')
        gpp.time_years = 10.
        gpp.dT_dz = 0.06
        gpp.well_radius = 0.279
        gpp.m_dot_IP = 5.

        vertical_well = SemiAnalyticalWell(params = gpp,
                                        dz_total = -3500.,
                                        T_e_initial = 15.)

        # initial state
        initial_state = FluidState.getStateFromPT(1.e6, 25., gpp.working_fluid)
        vertical_well_results = vertical_well.solve(initial_state)

        self.assertMessages(gpp.working_fluid,
                        (vertical_well_results.state.P_Pa, 3.533e7),
                        (vertical_well_results.state.T_C, 67.03),
                        (vertical_well_results.state.h_Jkg, 3.0963e5))

        horizontal_well = SemiAnalyticalWell(params = gpp,
                                        dr_total = 3000.,
                                        T_e_initial = vertical_well.T_e_initial + gpp.dT_dz * abs(vertical_well.dz_total))

        # initial state
        initial_state = FluidState.getStateFromPT(vertical_well_results.state.P_Pa, vertical_well_results.state.T_C, gpp.working_fluid)
        horizontal_well_results = horizontal_well.solve(initial_state)

        self.assertMessages(gpp.working_fluid,
                        (horizontal_well_results.state.P_Pa, 3.533e7),
                        (horizontal_well_results.state.T_C, 121.99),
                        (horizontal_well_results.state.h_Jkg, 5.3712e5))
Beispiel #7
0
    def testInjectionWellCO2(self):
        ###
        #  Testing SemiAnalyticalWell for horizontal injection well settings
        ###
        # load global physical properties
        gpp = SimulationParameters(working_fluid = 'co2')
        gpp.time_years = 10.
        gpp.dT_dz = 0.06
        gpp.well_radius = 0.279
        gpp.m_dot_IP = 5.

        vertical_well = SemiAnalyticalWell(params = gpp,
                                            dz_total = -3500.,
                                            T_e_initial = 15.)

        # initial state
        initial_state = FluidState.getStateFromPT(1.e6, 25., gpp.working_fluid)
        vertical_well_results = vertical_well.solve(initial_state)

        self.assertMessages(gpp.working_fluid,
                        (vertical_well_results.state.P_Pa, 1.7245e6),
                        (vertical_well_results.state.T_C, 156.08),
                        (vertical_well_results.state.h_Jkg, 6.1802e5))

        horizontal_well = SemiAnalyticalWell(params = gpp,
                                            dr_total = 3000.,
                                            T_e_initial = vertical_well.T_e_initial + gpp.dT_dz * abs(vertical_well.dz_total))

        # initial state
        initial_state = FluidState.getStateFromPT(vertical_well_results.state.P_Pa, vertical_well_results.state.T_C, gpp.working_fluid)
        horizontal_well_results = horizontal_well.solve(initial_state)

        self.assertMessages(gpp.working_fluid,
                        (horizontal_well_results.state.P_Pa, 1.7235e6),
                        (horizontal_well_results.state.T_C, 212.746),
                        (horizontal_well_results.state.h_Jkg, 6.755e5))
Beispiel #8
0
    def solve(self):

        self.fluid_system.params.initial_P = 1e6 + self.fluid_system.params.P_system_min()
        self.fluid_system.params.initial_T = 60.
        initial_T = self.fluid_system.params.initial_T

        dT_inj = np.nan
        dT_loops = 1
        solv = Solver()
        while np.isnan(dT_inj) or abs(dT_inj) >= 0.5:

            initial_state = FluidState.getStateFromPT(self.fluid_system.params.initial_P, initial_T, self.fluid_system.params.working_fluid)
            system_state = self.fluid_system.solve(initial_state)

            dT_inj = initial_T - system_state.pp.state.T_C

            initial_T = solv.addDataAndEstimate(initial_T, dT_inj)

            if np.isnan(initial_T):
                initial_T = system_state.pp.state.T_C

            # add lower bounds
            if initial_T < 1:
                initial_T = 1

            # add upper bounds
            T_prod_surface_C = system_state.pump.well.state.T_C
            if initial_T > T_prod_surface_C and initial_T > 50:
                initial_T = T_prod_surface_C

            if dT_loops >  10:
                print('GenGeo::Warning:FluidSystemWaterSolver:dT_loops is large: %s'%dT_loops)
            dT_loops += 1

        # check if silica precipitation is allowed
        if self.fluid_system.params.silica_precipitation:
            # prevent silica precipitation by DiPippo 1985
            maxSurface_dT = 89
            if (T_prod_surface_C - initial_T) > maxSurface_dT:
                raise Exception('GenGeo::FluidSystemWaterSolver:ExceedsMaxTemperatureDecrease - '
                            'Exceeds Max Temp Decrease of  %.3f C to prevent silica precipitation!'%(maxSurface_dT))
        else:
            maxSurface_dT = np.inf

        return system_state
Beispiel #9
0
    def testInjectionWellCO2SmallWellR(self):
        ###
        #  Testing SemiAnalyticalWell for horizontal injection well settings
        ###
        # load global physical properties
        gpp = SimulationParameters(working_fluid = 'co2')
        gpp.time_years = 10.
        gpp.dT_dz = 0.06
        gpp.well_radius = 0.02
        gpp.m_dot_IP = 0.1

        vertical_well = SemiAnalyticalWell(params = gpp,
                                            dz_total = -3500.,
                                            T_e_initial = 15.)

        # initial state
        initial_state = FluidState.getStateFromPT(1.e6, 25., gpp.working_fluid)
        vertical_well_results = vertical_well.solve(initial_state)

        self.assertMessages(gpp.working_fluid,
                        (vertical_well_results.state.P_Pa, 1.1431e6),
                        (vertical_well_results.state.T_C, 222.2246),
                        (vertical_well_results.state.h_Jkg, 6.8717e5))
Beispiel #10
0
    def testORCCycleSupercritPboil(self):

        params = SimulationParameters(orc_fluid = 'R245fa')

        cycle = ORCCycleSupercritPboil(params = params)

        initialState = FluidState.getStateFromPT(1.e6, 190., 'water')
        results = cycle.solve(initialState = initialState,
                                P_boil_Pa = 5e6)

        self.assertTrue(*testAssert(results.dT_range_CT, 6.8948, 'test1_dT_range_CT'))
        self.assertTrue(*testAssert(results.w_pump, -1.9676e+03, 'test1_w_pump'))
        self.assertTrue(*testAssert(results.q_boiler, 1.2030e+05, 'test1_q_boiler'))
        self.assertTrue(*testAssert(results.w_turbine, 2.4353e+04, 'test1_w_turbine'))
        self.assertTrue(*testAssert(results.q_recuperator, 9.8023e+03, 'test1_q_recuperator'))
        self.assertTrue(*testAssert(results.q_desuperheater, -3.0484e+03, 'test1_q_desuperheater'))
        self.assertTrue(*testAssert(results.q_condenser, -9.4878e+04, 'test1_q_condenser'))
        self.assertTrue(*testAssert(results.w_cooler, -51.2720, 'test1_w_cooler'))
        self.assertTrue(*testAssert(results.w_condenser, -2.5484e+03, 'test1_w_condenser'))
        self.assertTrue(*testAssert(results.w_net, 1.9786e+04, 'test1_w_net'))
        self.assertTrue(*testAssert(results.state.T_C, 73.7974, 'test1_end_T_C'))
        self.assertTrue(*testAssert(results.dT_LMTD_boiler, 9.6698, 'test1_dT_LMTD_boiler'))
        self.assertTrue(*testAssert(results.dT_LMTD_recuperator, 7.4340, 'test1_dT_LMTD_recuperator'))
Beispiel #11
0
def heatExchanger(T_1_in, P_1, m_dot_1, fluid_1, T_2_in, P_2, m_dot_2, fluid_2,
                  dT_pinch):

    results = HeatExchangerResults()

    # Only tested where Temp1 < Temp2
    if T_1_in > T_2_in:
        # throw(MException('HeatExchanger:BadTemps','Temp 1 is greater than Temp 2!'));
        # No heat exchanged
        results.T_1_out = T_1_in
        results.T_2_out = T_2_in
        return results

    if m_dot_1 <= 0 or m_dot_2 <= 0:
        raise Exception(
            'GenGeo::HeatExchanger:NegativeMassFlow - Negative Massflow in Heat Exchanger'
        )

    increments = 20
    direction = np.sign(T_1_in - T_2_in)

    # Check the phase on incoming fluid
    P_crit_1 = FluidState.getPcrit(fluid_1)
    if P_1 < P_crit_1:
        T_sat_1 = FluidState.getStateFromPQ(P_1, 1, fluid_1).T_C
        if T_sat_1 == T_1_in or T_sat_1 == T_2_in:
            raise Exception(
                'GenGeo::HeatExchanger:TwoPhaseFluid - Fluid 1 enters or leaves two-phase!'
            )

    P_crit_2 = FluidState.getPcrit(fluid_2)
    if P_2 < P_crit_2:
        T_sat_2 = FluidState.getStateFromPQ(P_2, 1, fluid_2).T_C
        if T_sat_2 == T_1_in or T_sat_2 == T_2_in:
            raise Exception(
                'GenGeo::HeatExchanger:TwoPhaseFluid - Fluid 2 enters or leaves two-phase!'
            )

    h_1_in = FluidState.getStateFromPT(P_1, T_1_in, fluid_1).h_Jkg
    T_1_max = T_2_in
    h_1_max = FluidState.getStateFromPT(P_1, T_1_max, fluid_1).h_Jkg
    T_1_max_practical = T_2_in + direction * dT_pinch
    h_1_max_practical = FluidState.getStateFromPT(P_1, T_1_max_practical,
                                                  fluid_1).h_Jkg
    h_2_in = FluidState.getStateFromPT(P_2, T_2_in, fluid_2).h_Jkg
    T_2_max = T_1_in
    h_2_max = FluidState.getStateFromPT(P_2, T_2_max, fluid_2).h_Jkg
    T_2_max_practical = T_1_in - direction * dT_pinch
    h_2_max_practical = FluidState.getStateFromPT(P_2, T_2_max_practical,
                                                  fluid_2).h_Jkg

    Q_1_max = abs(m_dot_1 * (h_1_in - h_1_max))
    Q_2_max = abs(m_dot_2 * (h_2_in - h_2_max))
    Q_1_max_practical = abs(m_dot_1 * (h_1_in - h_1_max_practical))
    Q_2_max_practical = abs(m_dot_2 * (h_2_in - h_2_max_practical))

    if abs(Q_1_max) < abs(Q_2_max):
        # limitingFluid = 1;
        Q_max = Q_1_max
        Q_max_practical = Q_1_max_practical
    else:
        # limtingFluid = 2;
        Q_max = Q_2_max
        Q_max_practical = Q_2_max_practical

    results.Q_exchanged = Q_max_practical
    ddT_pinch = 1

    length = increments + 1
    results.Q = np.zeros(length)
    h_1 = np.zeros(length)
    results.T_1 = np.zeros(length)
    h_2 = np.zeros(length)
    results.T_2 = np.zeros(length)
    dT = np.zeros(length)
    UA = np.zeros(length)

    while ddT_pinch > 0.1:

        dQ = results.Q_exchanged / increments

        results.Q[0] = 0.
        h_1[0] = h_1_in
        results.T_1[0] = T_1_in
        h_2[0] = (Q_2_max - results.Q_exchanged) / m_dot_2 + h_2_max
        results.T_2[0] = FluidState.getStateFromPh(P_2, h_2[0], fluid_2).T_C
        dT[0] = direction * (results.T_1[0] - results.T_2[0])
        UA[0] = dQ / dT[0]

        for i in range(1, increments + 1):
            results.Q[i] = results.Q[i - 1] + dQ
            h_1[i] = h_1[i - 1] + dQ / m_dot_1
            h_2[i] = h_2[i - 1] + dQ / m_dot_2
            results.T_1[i] = FluidState.getStateFromPh(P_1, h_1[i],
                                                       fluid_1).T_C
            results.T_2[i] = FluidState.getStateFromPh(P_2, h_2[i],
                                                       fluid_2).T_C
            dT[i] = direction * (results.T_1[i] - results.T_2[i])
            UA[i] = dQ / dT[i]

        min_dT = min(dT)
        ddT_pinch = dT_pinch - min_dT

        # Adjust Q_exchanged
        # Use proportional error approach
        change = ddT_pinch / (T_2_in - T_1_in)
        results.Q_exchanged = (1 - change) * results.Q_exchanged

    results.dT_LMTD = results.Q_exchanged / sum(UA)
    effectiveness = results.Q_exchanged / Q_max
    results.T_1_out = results.T_1[-1]
    results.T_2_out = results.T_2[0]

    # Check the phase on leaving fluid
    if P_1 < P_crit_1:
        T_sat_1 = FluidState.getStateFromPQ(P_1, 1, fluid_1).T_C
        if T_sat_1 > T_1_in and T_sat_1 < results.T_1_out:
            print('Caution: Fluid 1 is phase changing in heat exchanger')
        if T_sat_1 == results.T_1_out:
            raise Exception(
                'GenGeo::HeatExchanger:TwoPhaseFluid - Fluid 1 leaves two-phase!'
            )

    if P_2 < P_crit_2:
        T_sat_2 = FluidState.getStateFromPQ(P_2, 1, fluid_2).T_C
        if T_sat_2 > T_2_in and T_sat_2 < results.T_2_out:
            print('Caution: Fluid 2 is phase changing in heat exchanger')
        if T_sat_2 == results.T_2_out:
            raise Exception(
                'GenGeo::HeatExchanger:TwoPhaseFluid - Fluid 2 leaves two-phase!'
            )

    return results
Beispiel #12
0
    def solve(self, initial_state):

        m_dot = self.params.m_dot_IP * self.m_dot_multiplier

        # results
        results = SemiAnalyticalWellResults(self.params.well_segments, self.params.working_fluid)

        P_f_initial = initial_state.P_Pa
        T_f_initial = initial_state.T_C
        time_seconds = self.params.time_years * ConversionConstants.secPerYear

        # set geometry
        dz = self.dz_total/self.params.well_segments             # m
        dr = self.dr_total/self.params.well_segments             # m
        dL = (dz**2 + dr**2)**0.5                       # m
        A_c = np.pi * self.params.well_radius**2        # m**2
        P_c = np.pi * 2 * self.params.well_radius       # m

        # set states
        results.T_C_f[0] = T_f_initial             # C
        results.T_C_e[0] = self.T_e_initial        # C
        results.P_Pa[0] = P_f_initial              # Pa
        results.h_Jkg[0] = FluidState.getStateFromPT(results.P_Pa[0], results.T_C_f[0], self.params.working_fluid).h_Jkg
        results.rho_kgm3[0] = FluidState.getStateFromPT(results.P_Pa[0], results.T_C_f[0], self.params.working_fluid).rho_kgm3
        results.v_ms[0] = m_dot / A_c / results.rho_kgm3[0]  #m/s

        # Calculate the Friction Factor
        # Use Colebrook-white equation for wellbore friction loss.
        # Calculate for first element and assume constant in remainder
        ff = frictionFactor(self.params.well_radius, results.P_Pa[0], results.h_Jkg[0], \
                            m_dot, self.params.working_fluid, self.params.epsilon)

        alpha_rock = self.params.k_rock/self.params.rho_rock/self.params.c_rock  #D rock
        t_d = alpha_rock*time_seconds/(self.params.well_radius**2)  #dim
        if t_d < 2.8:
            beta = ((np.pi*t_d)**-0.5 + 0.5 - 0.25*(t_d/np.pi)**0.5 + 0.125*t_d)
        else:
            beta = (2/(np.log(4*t_d)-2*0.58) - 2*0.58/(np.log(4*t_d)-2*0.58)**2)

        # loop over all well segments
        for i in range(1, self.params.well_segments+1):
            results.z_m[i] = results.z_m[i-1] + dz

            # far-field rock temp
            results.T_C_e[i] = results.T_C_e[0] - results.z_m[i] * self.params.dT_dz
            # fluid velocity
            results.v_ms[i] = m_dot / A_c / results.rho_kgm3[i-1]  #m/s

            # Calculate Pressure
            results.delta_P_loss[i] = ff * dL / ( 2 * self.params.well_radius) * \
                                            results.rho_kgm3[i-1] * \
                                            results.v_ms[i]**2. / 2.  #Pa
            results.rho_kgm3[i] = FluidState.getStateFromPh(results.P_Pa[i-1], results.h_Jkg[i-1], self.params.working_fluid).rho_kgm3
            results.P_Pa[i]     = results.P_Pa[i-1] - results.rho_kgm3[i] * \
                                        self.params.g * dz - results.delta_P_loss[i]

            # Throw exception if below saturation pressure of water at previous temperature
            if self.params.working_fluid.lower() == 'water':
                P_sat = FluidState.getStateFromTQ(results.T_C_f[i-1], 0, self.params.working_fluid).P_Pa
                if results.P_Pa[i] < P_sat:
                    raise Exception('GenGeo::SemiAnalyticalWell:BelowSaturationPressure - '
                    'Below saturation pressure of water at %s m !' %(results.z_m[i]))

            h_noHX = results.h_Jkg[i-1] - self.params.g * dz
            T_noHX = FluidState.getStateFromPh(results.P_Pa[i], h_noHX, self.params.working_fluid).T_C
            results.cp_JK[i] = FluidState.getStateFromPh(results.P_Pa[i], h_noHX, self.params.working_fluid).cp_JK

            #Find Fluid Temp
            if not self.params.useWellboreHeatLoss:
                results.T_C_f[i] = T_noHX
                results.h_Jkg[i] = h_noHX
                results.q[i] = 0.
            else:
                # See Zhang, Pan, Pruess, Finsterle (2011). A time-convolution
                # approach for modeling heat exchange between a wellbore and
                # surrounding formation. Geothermics 40, 261-266.
                x = dL * P_c * self.params.k_rock * beta / self.params.well_radius
                y = m_dot * results.cp_JK[i]
                if math.isinf(x):
                    results.T_C_f[i] = results.T_C_e[i]
                else:
                    results.T_C_f[i] = (y * T_noHX + x *results. T_C_e[i]) / (x + y)
                results.q[i] = y * (T_noHX - results.T_C_f[i])
                results.h_Jkg[i] = FluidState.getStateFromPT(results.P_Pa[i], results.T_C_f[i], self.params.working_fluid).h_Jkg
        # make sure state object is set
        results.createFinalState()

        return results
 def createFinalState(self):
     self.state = FluidState.getStateFromPT(self.P_Pa[-1], self.T_C_f[-1],
                                            self.fluid)
    def solve(self, initialState):
        results = PorousReservoirResults()

        A_reservoir = (self.params.well_spacing**2) / 2.

        # # TODO: fix this. using 365 instead of 365.25 changes the results
        time_seconds = self.params.time_years * ConversionConstants.secPerYear

        # from output properties
        prod_State = FluidState.getStateFromPT(self.params.P_reservoir(),
                                               self.params.T_reservoir(),
                                               self.params.working_fluid)

        mu_fluid = (initialState.mu_Pas + prod_State.mu_Pas) / 2
        rho_fluid = (initialState.rho_kgm3 + prod_State.rho_kgm3) / 2
        cp_fluid = (initialState.cp_JK + prod_State.cp_JK) / 2

        # reservoir impedance
        if self.params.well_spacing <= self.params.well_radius:
            RI = 0
        else:
            if self.params.wellFieldType == WellFieldType._5Spot_SharedNeighbor \
                    or self.params.wellFieldType == WellFieldType._5Spot \
                    or self.params.wellFieldType == WellFieldType._5Spot_Many:
                A_c_rock = np.log((4 * self.params.well_spacing) /
                                  (2 * self.params.well_radius * np.pi))
                RI = mu_fluid / rho_fluid / self.params.transmissivity * A_c_rock
            elif self.params.wellFieldType == WellFieldType.Doublet:
                A_c_rock = np.log(
                    (self.params.well_spacing) / (2 * self.params.well_radius))
                RI = mu_fluid / rho_fluid / self.params.transmissivity / np.pi * A_c_rock
            else:
                raise Exception(
                    'GenGeo::PorousReservoir:unknownReservoirConfiguration - '
                    'Unknown Reservoir Configuration')

        # Calculate heat extracted
        dT_initial = self.params.T_reservoir() - initialState.T_C
        res_energy = A_reservoir * self.params.reservoir_thickness * self.params.rho_rock * self.params.c_rock * dT_initial

        # Model pressure transient (Figure 4.2, Adams (2015)), only for CO2 drying out
        if self.modelPressureTransient == True and self.params.working_fluid.lower(
        ) == 'co2':
            R = self.params.well_spacing
            nu_inj_fluid = initialState.mu_Pas / initialState.rho_kgm3

            tau = time_seconds * nu_inj_fluid / R**2.

            b = -0.4574 * (abs(R) / abs(self.params.depth))**-0.27
            b = np.clip(b, -0.9, -0.4)
            a = 1.0342 * np.exp(10.989 * b)
            a = np.clip(a, 0., 0.012)
            coeff = (a * tau**b + 1)
            RI = RI * coeff

        # Model temperature transient (Figure 4.5, Adams (2015))
        # First-principles model for all fluids
        # At time zero, output is the same as no temp depletion
        if self.modelTemperatureDepletion == True and self.params.time_years > 0.:
            # p1
            coeff1 = self.params.reservoir_thickness * self.params.k_rock * initialState.rho_kgm3 / self.params.m_dot_IP / self.params.rho_rock / self.params.c_rock
            p1 = -890.97 * coeff1 - 1.30
            # limit to regression
            p1 = np.minimum(p1, -1.3)
            # p2
            p2 = 0.4095 * np.exp(-0.7815 * p1)
            # p3
            R = self.params.well_spacing
            coeff2 = self.params.k_rock * R * initialState.rho_kgm3 / self.params.rho_rock / initialState.cp_JK / self.params.m_dot_IP
            # A more realistic, exponential relation is found by fitting the same data
            # with an exponential, instead of linear, curve.
            p3 = 1 - (1.4646 * np.exp(-377.3 * coeff2))
            # p3 cant be greater than 1 or less than 0.
            p3 = np.clip(p3, 0., 1.)
            # psi
            # numerically integrate to get heat depletion
            # have roughly one increment a year
            increments = np.maximum(np.round(self.params.time_years), 1.)
            dt = time_seconds / increments
            res_energy_extracted = 0
            gamma = 1
            for i in range(int(increments)):
                res_energy_extracted = self.params.m_dot_IP * cp_fluid * gamma * dT_initial * dt + res_energy_extracted
                results.psi = res_energy_extracted / res_energy
                gamma = depletionCurve(results.psi, p1, p2, p3)
                # last gamma is res temp
        else:
            # gamma of 1 is undepleted reservoir
            gamma = 1
            res_energy_extracted = self.params.m_dot_IP * cp_fluid * gamma * dT_initial * time_seconds
            if res_energy == 0:
                results.psi = 0
            else:
                results.psi = res_energy_extracted / res_energy

        results.dP = self.params.m_dot_IP * RI
        end_P_Pa = initialState.P_Pa - results.dP
        end_T_C = gamma * dT_initial + initialState.T_C
        results.state = FluidState.getStateFromPT(end_P_Pa, end_T_C,
                                                  self.params.working_fluid)
        results.heat = self.params.m_dot_IP * (results.state.h_Jkg -
                                               initialState.h_Jkg)

        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
Beispiel #16
0
    def solve(self, initial_state):

        results = FluidSystemWaterOutput()

        injection_state = FluidState.getStateFromPT(initial_state.P_Pa,
                                                    initial_state.T_C,
                                                    initial_state.fluid)
        # Find necessary injection pressure
        dP_downhole = np.nan
        dP_solver = Solver()
        dP_loops = 1
        stop = False

        while np.isnan(dP_downhole) or abs(dP_downhole) > 10e3:
            results.injection_well = self.injection_well.solve(injection_state)
            results.reservoir = self.reservoir.solve(
                results.injection_well.state)

            # if already at P_system_min, stop looping
            if stop:
                break

            # find downhole pressure difference (negative means overpressure)
            dP_downhole = self.params.P_reservoir(
            ) - results.reservoir.state.P_Pa
            injection_state.P_Pa = dP_solver.addDataAndEstimate(
                injection_state.P_Pa, dP_downhole)

            if np.isnan(injection_state.P_Pa):
                injection_state.P_Pa = initial_state.P_Pa + dP_downhole

            if dP_loops > 10:
                print(
                    'GenGeo::Warning:FluidSystemWater:dP_loops is large: %s' %
                    dP_loops)
            dP_loops += 1

            # Set Limits
            if injection_state.P_Pa < self.params.P_system_min():
                # can't be below this temp or fluid will flash
                injection_state.P_Pa = self.params.P_system_min()
                # switch stop to run injection well and reservoir once more
                stop = True

        if results.reservoir.state.P_Pa >= self.params.P_reservoir_max():
            raise Exception(
                'GenGeo::FluidSystemWater:ExceedsMaxReservoirPressure - '
                'Exceeds Max Reservoir Pressure of %.3f MPa!' %
                (self.params.P_reservoir_max() / 1e6))

        # Production Well (Lower, to production pump)
        results.production_well1 = self.production_well1.solve(
            results.reservoir.state)
        # Upper half of production well
        results.pump = self.pump.solve(results.production_well1.state,
                                       injection_state.P_Pa)

        # Subtract surface frictional losses between production wellhead and surface plant
        ff = frictionFactor(self.params.well_radius,
                            results.pump.well.state.P_Pa,
                            results.pump.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.pump.well.state.rho_kgm3 / np.pi**2
        else:
            dP_surfacePipes = 0

        results.surface_plant_inlet = FluidState.getStateFromPh(
            results.pump.well.state.P_Pa - dP_surfacePipes,
            results.pump.well.state.h_Jkg, self.params.working_fluid)

        results.pp = self.pp.solve(results.surface_plant_inlet)
        return results
Beispiel #17
0
    def solve(self, initial_state, P_inj_surface):

        if self.ff_m_dot != self.params.m_dot_IP:
            self.computeSurfacePipeFrictionFactor()

        # initilize object with well output
        results = DownHolePumpOutput()

        # Pumping and second well
        d_dP_pump = 1e5
        dP_pump = 0
        dP_surface = np.nan
        dP_loops = 1
        dP_Solver = Solver()

        while np.isnan(dP_surface) or abs(dP_surface) > 100:
            try:
                if dP_pump > self.params.max_pump_dP:
                    dP_pump = self.params.max_pump_dP

                P_prod_pump_out = initial_state.P_Pa + dP_pump
                T_prod_pump_out = initial_state.T_C
                temp_at_pump_depth = self.well.T_e_initial + self.params.dT_dz * self.params.pump_depth

                state_in = FluidState.getStateFromPT(P_prod_pump_out,
                                                     T_prod_pump_out,
                                                     self.params.working_fluid)
                results.well = self.well.solve(state_in)

                # calculate surface pipe friction loss
                if self.params.has_surface_gathering_system:
                    dP_surface_pipes = self.friction_factor * self.params.well_spacing / \
                    (2 * self.params.well_radius)**5 * 8 * self.params.m_dot_IP**2 / results.well.state.rho_kgm3 / np.pi**2
                else:
                    dP_surface_pipes = 0.

                dP_surface = (results.well.state.P_Pa - P_inj_surface -
                              dP_surface_pipes)
                if dP_pump == self.params.max_pump_dP:
                    break

                # No pumping is needed in this system
                if dP_pump == 0 and dP_surface >= 0:
                    break

                dP_pump = dP_Solver.addDataAndEstimate(dP_pump, dP_surface)
                if np.isnan(dP_pump):
                    dP_pump = -1 * dP_surface

                # Pump can't be less than zero
                if dP_pump < 0:
                    dP_pump = 0

                # Warn against excessive loops
                if dP_loops > 10:
                    print('Warning::DownHolePump:dP_loops is large: %s' %
                          dP_loops)
                dP_loops += 1

            except ValueError as error:
                # Only catch problems of flashing fluid
                if str(error).find(
                        'GenGeo::SemiAnalyticalWell:BelowSaturationPressure'
                ) > -1:
                    dP_pump = dP_pump + d_dP_pump
                else:
                    raise error
        # if pump pressure greater than allowable, throw error
        if dP_pump >= self.params.max_pump_dP:
            raise Exception(
                'GenGeo::DownHolePump:ExceedsMaxProductionPumpPressure - '
                'Exceeds Max Pump Pressure of %.3f MPa!' %
                (self.params.max_pump_dP / 1e6))

        results.w_pump = 0
        results.dP_surface_pipes = dP_surface_pipes
        if dP_pump > 0:
            results.w_pump = (initial_state.h_Jkg -
                              state_in.h_Jkg) / self.params.eta_pump

        return results
Beispiel #18
0
import unittest
import numpy as np

from src.porousReservoir import PorousReservoir
from utils.depletionCurve import depletionCurve
from models.simulationParameters import SimulationParameters
from tests.testAssertion import testAssert
from utils.fluidState import FluidState

reservoir = PorousReservoir(working_fluid='co2',
                            reservoir_thickness=300,
                            permeability=50e-15,
                            time_years=30,
                            m_dot_IP=100)

initialState = FluidState.getStateFromPT(25.e6, 40.,
                                         reservoir.params.working_fluid)


class ReservoirDepletionTest(unittest.TestCase):
    def testDepletionCurve(self):
        Psi_1 = 2.
        p1 = -1.9376
        p2 = 1.743
        p3 = 0.182
        gamma = np.round(depletionCurve(Psi_1, p1, p2, p3), 4)

        self.assertTrue(*testAssert(gamma, 0.2531, 'testDepletionCurve'))

    def testNoTransient(self):
        reservoir.modelPressureTransient = False
        reservoir.modelTemperatureDepletion = False
Beispiel #19
0
    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, 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