예제 #1
0
    def __init__(self,
                 fluid,
                 fuel,
                 stage=None,
                 oxidizer=None,
                 dismiss_mixture=True):
        """
        """
        # Stage of the power plant:
        self.stage = stage

        # Dismiss mixture: If True, working fluid of the combuster is the object fluid. Otherwise
        # a mixture of fuel and oxidizer is considered. In case the oxidizer is not specified, it is
        # assumed the oxidizer is the working fluid.
        self._dismiss_mixture = dismiss_mixture

        # Initialize superclass methods
        super().__init__(fluid)

        # Check fuel and fluid objects
        if not (isinstance(fluid, PerfectIdealGas)
                or isinstance(fluid, SemiperfectIdealGas)):
            # Check the flow is a Perfect or a Semiperfect gas from pyturb
            raise TypeError(
                "Object must be PerfectIdealGas, SemiperfectIdealGas. Instead received {}"
                .format(fluid))

        if not (isinstance(fuel, PerfectIdealGas)
                or isinstance(fuel, SemiperfectIdealGas)):
            # Check the fuel is a Perfect or a Semiperfect gas from pyturb
            raise TypeError(
                "Object must be PerfectIdealGas, SemiperfectIdealGas. Instead received {}"
                .format(fuel))

        # If dismiss_mixture:
        if self._dismiss_mixture:
            self.combustion = Combustion(fuel, fluid)
        else:
            if oxidizer is None:
                self.combustion = Combustion(fuel, fluid)
                raise NotImplementedError(
                    "Mixture of fuel+fluid not implemented")
            else:
                if not (isinstance(fuel, PerfectIdealGas)
                        or isinstance(fuel, SemiperfectIdealGas)):
                    # Check the fuel is a Perfect or a Semiperfect gas from pyturb
                    raise TypeError(
                        "Object must be PerfectIdealGas, SemiperfectIdealGas. Instead received {}"
                        .format(fuel))
                self.combustion = Combustion(fuel, oxidizer)
                raise NotImplementedError(
                    "Mixture of fuel+fluid not implemented")

        # Isentropic flow:
        self.isent_flow = IsentropicFlow(self.fluid)

        return None
예제 #2
0
    def __init__(self, fluid, stage=None):
        """
        """
        # Stage of the power plant:
        self.stage = stage
        
        # Initialize superclass methods
        super().__init__(fluid)

        # Isentropic flow:
        if not(isinstance(fluid, PerfectIdealGas) or isinstance(fluid, SemiperfectIdealGas)):
            # Check the flow is a Perfect or a Semiperfect gas fom pyturb
            raise TypeError("Object must be PerfectIdealGas, SemiperfectIdealGas. Instead received {}".format(fluid))
    
        self.isent_flow = IsentropicFlow(self.fluid)

            
        return None
예제 #3
0
class Nozzle(ControlVolume):
    """
    Nozzle:
    -------

    Defines a generic nozzle stage control volume, between one entrance and one
    exit to the control volume. the nozzle may be solved considering:
        + Adapted discharge
        + Critical, convergent nozzle
        + Critical, con-di (Laval) nozzle
        + Generic nozzle

    Contents:
    ---------
        + initialize_nozzle: initializes the control volume.
        + solve_basic_properties: partially solves basic thermodynamic properties of the nozzle
        + solve_adapted_nozzle: Completes the solution considering adapted discharge of the nozzle
        +
        +

    A total of 34 variables of a control volume are defined:
    + 12 thermodynamic properties at the entrance of the CV:
        + Static properties:
            + p_e, T_e, rho_e, h_e (enthalpy)
        + Stagnation properties
            + p_et, T_et, rho_et, h_et (enthalpy)
        + Other:
            + vel_e (flow velocity), mach_e, ec_e (kin. energy), mflow_e (mass flow)

    + 12 thermodynamic properties at the exit of the CV
        + Static properties:
            + p_s, T_s, rho_s, h_s (enthalpy)
        + Stagnation properties
            + p_st, T_st, rho_st, h_st (enthalpy)
        + Other:
            + vel_s (flow velocity), mach_s, ec_s (kin. energy), mflow_s (mass flow)

    + 6 variables of the CV:
        + specific power:
            + q_se (heating/cooling power along the CV), w_se (mechanical power along the CV)
        + adiab_efficiency, A_e (entrance area), A_s (exit area), delta_massflow (mass flow variation along CV)
    
    + 4 properties at the nozzle throat, considering choked nozzle:
        + A_star, p_star, T_star

    """
    def __init__(self, fluid, stage=None):
        """
        """
        # Stage of the power plant:
        self.stage = stage
        
        # Initialize superclass methods
        super().__init__(fluid)

        # Isentropic flow:
        if not(isinstance(fluid, PerfectIdealGas) or isinstance(fluid, SemiperfectIdealGas)):
            # Check the flow is a Perfect or a Semiperfect gas fom pyturb
            raise TypeError("Object must be PerfectIdealGas, SemiperfectIdealGas. Instead received {}".format(fluid))
    
        self.isent_flow = IsentropicFlow(self.fluid)

            
        return None

    # **************************
    # *** Entrance of the CV *** 
    # **************************
    # Static thermodynamic properties:
    @property
    def p_e(self):
        """
        Static pressure at the entrance of the CV. [Pa]
        """
        return self._p_e
    

    @property
    def T_e(self):
        """
        Static temperature at the entrance of the CV. [K]
        """
        return self._T_e
    

    @property
    def rho_e(self):
        """
        Static density at the entrance of the CV. [kg/m**3]
        """
        return self._rho_e


    @property
    def h_e(self):
        """
        Specific static enthalpy at the entrance of the CV. [W/kg/s]
        """
        return self._h_e


    # Stagnation thermodynamic properties
    @property
    def p_et(self):
        """
        Stagnation pressure at the entrance of the CV. [Pa]
        """
        return self._p_et


    @property
    def T_et(self):
        """
        Stagnation temperature at the entrance of the CV. [K]
        """
        return self._T_et


    @property
    def rho_et(self):
        """
        Stagnation density at the entrance of the CV. [kg/m**3]
        """
        return self._rho_et


    @property
    def h_et(self):
        """
        Specific stagnation enthalpy at the entrance of the CV. [W/kg/s]
        """
        return self._h_et


    # Velocity and kinetic energy:
    @property
    def vel_e(self):
        """
        Flow velocity at the entrance of the CV. [m/s]
        """
        return self._vel_e


    @property
    def mach_e(self):
        """
        """
        return self._mach_e
    

    @property
    def ekin_e(self):
        """
        Specific kinetic energy at the entrance of the CV. [W/kg/s]
        """
        return self._ekin_e


    # Mass flow and area:
    @property
    def mflow_e(self):
        """
        Mass flow at the entrance of the CV. [kg/s]
        """
        return self._mflow_e


    @property
    def A_e(self):
        """
        Mass flow at the entrance of the CV. [kg/s]
        """
        return self._A_e


    # **********************
    # *** Exit of the CV *** 
    # **********************
    # Static thermodynamic properties:
    @property
    def p_s(self):
        """
        Static pressure at the entrance of the CV. [Pa]
        """
        return self._p_s


    @property
    def T_s(self):
        """
        Static temperature at the entrance of the CV. [K]
        """
        return self._T_s


    @property
    def rho_s(self):
        """
        Static density at the entrance of the CV. [mg/m**3]
        """
        return self._rho_s


    @property
    def h_s(self):
        """
        Specific static enthalpy at the entrance of the CV. [W/kg/s]
        """
        return self._h_s


    # Stagnation thermodynamic properties
    @property
    def p_st(self):
        """
        Stagnation pressure at the entrance of the CV. [Pa]
        """
        return self._p_st


    @property
    def T_st(self):
        """
        Stagnation temperature at the entrance of the CV. [K]
        """
        return self._T_st


    @property
    def rho_st(self):
        """
        Stagnation density at the entrance of the CV. [kg/m**3]
        """
        return self._rho_st


    @property
    def h_st(self):
        """
        Specific stagnation enthalpy at the entrance of the CV. [W/kg/s]
        """
        return self._h_st


    # Velocity and kinetic energy:
    @property
    def vel_s(self):
        """
        Flow velocity at the entrance of the CV. [m/s]
        """
        return self._vel_s


    @property
    def mach_s(self):
        """
        Mach number at the entrance of the CV. [dimensionless]
        """
        return self._mach_s


    @property
    def ekin_s(self):
        """
        Specific kinetic energy at the entrance of the CV. [W/kg/s]
        """
        return self._ekin_s
    
    
    # Mass flow and area:
    @property
    def mflow_s(self):
        """
        Mass flow at the entrance of the CV. [kg/s]
        """
        return self._mflow_s


    @property
    def A_s(self):
        """
        Area at the entrance of the CV. [m**2]
        """
        return self._A_s


    # ***************************
    # *** Transfer variables: *** 
    # ***************************
    @property
    def w_se(self):
        """
        Speficic mechanical power along the CV. [W/mg/s]
        """
        return self._w_se


    @property
    def q_se(self):
        """
        Speficic heating power along the CV. [W/mg/s]
        """
        return self._q_se


    @property
    def delta_massflow(self):
        """
        Mass flow variation along the CV. [W/mg/s]
        """
        return self._delta_massflow


    @property
    def adiab_efficiency(self):
        """
        Adiabatic efficiency along the CV. [dimensionless]
        """
        return self._adiab_efficiency


    ## Critical conditions:
    @property
    def exit_regime(self):
        """
        Flow regime at the exit of the nozzle.
        """
        return self._exit_regime


    @property
    def T_s_star(self):
        """
        Static temperature for choked nozzle throat.
        """
        return self._T_s_star


    @property
    def p_s_star(self):
        """
        Static temperature for choked nozzle throat.
        """
        return self._p_s_star


    @property
    def A_star(self):
        """
        Static temperature for choked nozzle throat.
        """
        return self._A_star


    @property
    def pchoke(self):
        """
        Static pressure to have a supersonic nozzle.
        """
        return self._pchoke


    @property
    def padapt(self):
        """
        Static pressure to have a supersonic, adapted nozzle.
        """
        return self._padapt
  


    ## Initializes the nozzle with the exit properties of a CV:
    def initialize_from_cv(self, cv):
        """
        Initialize nozzle from the exit properties of last CV.
        """

        # Isentropic flow:
        if not(isinstance(cv, ControlVolume)):
            # The control volume from which the nozzle has to be initialized is not a ControlVolume object
            raise TypeError("Object must be of type ControlVolume, SemiperfectIdealGas. Instead received {}".format(cv))
    

        self._mflow_e = cv.mflow_s
        self._p_et = cv.p_st
        self._T_et = cv.T_st
        self._A_e = cv.A_s

        self.solve_basic_properties()

        return


    ## Collects basic inputs of a nozzle:
    def initialize_nozzle(self, mflow_e, pet, Tet, Ae=None):
        """
        Set basic inputs of a generic nozzle:
            + mflow_e: float. Mass flow at the entrance [kg/s]
            + pet: float. stagnation pressure at the entrance [Pa]
            + Tet: float. stagnation tmeperature at the entrance [K]
            + Ae: float. Area at the entrance. [m**2]. If no area is provided
              related properties are set to np.nan in solve_basic_properties()
        """

        self._mflow_e = mflow_e
        self._p_et = pet
        self._T_et = Tet
        self._A_e = Ae

        self.solve_basic_properties()

        return None


    def solve_basic_properties(self):
        """
        Solves basic thermodyamic properties and CV variables at the entrance of
        the nozzle.
        """
        
        # Mass flow
        self._mflow_s = self.mflow_e # By definition
        self._delta_massflow = 0     # By definition
        
        # Enthalpies:
        self._T_st = self.T_et  # All nozzles isoenthalpic
        self._h_et = self.fluid.cp(self.T_et) * self.T_et
        self._h_st = self.fluid.cp(self.T_st) * self.T_st

        # By definition, no work/heat is done
        self._q_se = 0
        self._w_se = 0

        if self.A_e is None:
            # If the area at the entrance is not provided all area-related properties are set to nan
            self._T_e = np.nan
            self._p_e = np.nan
            self._rho_e = np.nan
            self._rho_et = np.nan
            self._vel_e = np.nan
            self._h_e = np.nan
            self._ekin_e = np.nan
            self._mach_e = np.nan

        else:
            # If the area is provided, the mach number is calculated
            gamma_to = self.fluid.gamma(self.T_et)

            # Solve mach at the entrance with variable size iterator:
            # Subsonic solution:
            var_aux = self.mflow_e/self.A_e*np.sqrt(self.fluid.Rg/gamma_to) * np.sqrt(self.T_et)/self.p_et
            expon = -(gamma_to+1)/(gamma_to-1)/2

            mach_func = lambda M: M*(1+(gamma_to-1)/2*M**2)**(expon) -var_aux
            mach_solution, _, _ = num_iters.variable_step_roots(x0=0, func=mach_func, dxmax=.2, verbosity=True)

            self._mach_e = mach_solution
            
            # Static properties at the entrance:
            self._T_e = self.isent_flow.stat_temp_from_mach(self.mach_e, self.T_et)
            self._p_e = self.isent_flow.stat_pressure_from_mach(self.mach_e, self.p_et, self.T_et)
            self._rho_e = self.p_e / self.T_e / self.fluid.Rg
            self._rho_et = self.isent_flow.stag_density_from_mach(self.mach_e, self.rho_e, self.T_e)
            self._vel_e = self.isent_flow.vel_from_mach(self.mach_e, self.T_e)
            self._h_e = self.fluid.cp(self.T_e) * self.T_e
            self._ekin_e = 0.5 * self.vel_e**2

        return


    def solve_from_static_exit_pressure(self, ps, As=None, adiab_efficiency=1, nozzle_type='con-di'):
        """
        Solve nozzle with static discharge pressure known (exit section).
        The nozzle is assumed to be adiabatic.
            + If As is None, the discharge area is calculated with the provided
              adiabatic efficiency
            + If As is provided, the corresponding adiabatic efficiency is
              calculated. If the efficiency is impossible a warning is raised
        
        Inputs:
        -------
            ps: float. Static pressure at the exit section (stage #9). [Pa]
            As: float. Area of the exit section (stage #9) of a nozzle. If no area is 
                provided, it is calculated assuming the adiabatic efficiency set in
                initialize_nozzle. Otherwise the efficiency is recalculated. A warning
                is raised if the adiabatic efficiency is unfeasible.
            adiab_effiency: float. Adiabatic efficiency of the nozzle. If the exit area is
                not provided, isentropic nozzle is assumed. Otherwise it is calculated
            nozzle_type: string. May be 'con-di' for Laval/convergent-divergent nozzle or
                'convergent' for a convergent nozzle. By default 'con-di' is selected.

        """

        # Nozzle type
        if nozzle_type.lower()=='condi' or nozzle_type.lower()=='laval':
            nozzle_type = 'con-di'
        elif nozzle_type.lower()=='con' or nozzle_type.lower()=='conv':
            nozzle_type = 'convergent'
        elif not nozzle_type.lower() in ['con-di', 'convergent']:
            warnings.warn('Unknown nozzle type: {}. Nozzle will be set to con-di (default)'.format(nozzle_type))

        # Store static pressure
        self._p_s = ps

        gamma_to = self.fluid.gamma(self.T_et)

        if As is None:
            # Calculate discharge area assuming adapted nozzle with a given adiabatic efficiency
            self._adiab_efficiency = adiab_efficiency

            Ts_Tet =(1 + self.adiab_efficiency*((self.p_s/self.p_et)**((gamma_to-1)/gamma_to)- 1))
            self._T_s = self.T_et * Ts_Tet

            if self.T_s <= 2/(gamma_to + 1)*self.T_st:
                self._exit_regime = 'supersonic'
                self._T_s_star = self.isent_flow.stat_temp_from_mach(1, self.T_st)
                self._p_s_star = self.isent_flow.stat_pressure_from_mach(1, self.p_st, self.T_st)
                self._A_star = self.A_s*self.mach_s*((gamma_to+1)/2/(1+(gamma_to-1)/2*self.mach_s**2))**((gamma_to+1)/2/(gamma_to-1))

            else:
                self._exit_regime = 'subsonic'
                self._T_s_star = np.nan
                self._p_s_star = np.nan
                self._A_star = np.nan

            self._vel_s = self.isent_flow.vel_from_stag_temp(self.T_st, self.T_s)
            self._mach_s = self.isent_flow.mach_number(self.vel_s, self.T_s)

            self._p_st = self.isent_flow.stag_pressure_from_mach(self.mach_s, self.p_s, self.T_s)

            self._rho_s = self.p_s / self.fluid.Rg / self.T_s
            self._A_s = self.mflow_s / self.rho_s / self.vel_s

            self._rho_st = self.isent_flow.stag_density_from_mach(self.mach_s, self.rho_s, self.T_s)
            self._ekin_s = 0.5 * self.vel_s**2
            self._h_s = self.h_st - self.ekin_s

        else:
            # As is provided, check the adiabatic efficiency
            self._A_s = As

            # Solve static temperature from mass flow 2nd order equation:
            aux_var = 2*self.fluid.cp(self.T_st)*(self.p_s*self.A_s/self.fluid.Rg/self.mflow_s)**2
            T9 = (-aux_var + np.sqrt(aux_var**2 + 4*aux_var*self.T_st))/2
            self._T_s = T9

            if self.T_s <= 2/(gamma_to + 1)*self.T_st:
                self._exit_regime = 'supersonic'
                self._T_s_star = self.isent_flow.stat_temp_from_mach(1, self.T_st)
                self._p_s_star = self.isent_flow.stat_pressure_from_mach(1, self.p_st, self.T_st)
                self._A_star = self.A_s*self.mach_s*((gamma_to+1)/2/(1+(gamma_to-1)/2*self.mach_s**2))**((gamma_to+1)/2/(gamma_to-1))

            else:
                self._exit_regime = 'subsonic'
                self._T_s_star = np.nan
                self._p_s_star = np.nan
                self._A_star = np.nan

            self._vel_s = self.isent_flow.vel_from_stag_temp(self.T_st, self.T_s)
            self._mach_s = self.isent_flow.mach_number(self.vel_s, self.T_s)
            self._p_st = self.isent_flow.stag_pressure_from_mach(self.mach_s, self.p_s, self.T_s)
            self._rho_s = self.p_s / self.fluid.Rg / self.T_s

            self._rho_st = self.isent_flow.stag_density_from_mach(self.mach_s, self.rho_s, self.T_s)
            self._ekin_s = 0.5 * self.vel_s**2
            self._h_s = self.h_st - self.ekin_s

            # Recalculate adiabatic efficiency
            adiab_eff = (self.T_s/self.T_et-1)/((self.p_s/self.p_et)**((gamma_to-1)/gamma_to)-1)
            self._adiab_efficiency = adiab_eff
            if not 0<=adiab_eff<=1:
                warnings.warn('Unfeasible nozzle adiabatic efficiency (ad_eff={0}) for adapted nozzle (ps={1}) and fixed area (As={2})'.format(self.adiab_efficiency, self.p_s, self.A_s), UserWarning)
            

        if self.exit_regime=='supersonic':
            if nozzle_type=='convergent':
                # Recalculate solution assuming critical, convergent nozzles
                self.solve_critical_convergent_nozzle(self.ps, adiab_efficiency=self.adiab_efficiency)
            else:
                # Calculate nozzle regime. Solution is iterated:
                expon = (gamma_to+1)/(2*(gamma_to-1))
                area_rela = self.A_star/self.A_s

                # Mach function
                mach_func = lambda M: M*((gamma_to + 1)/2/( 1+(gamma_to-1)/2*M**2 ))**(expon) - area_rela

                # Subsonic solution (pressure at wich choking occurs)
                subsonic_mach, _, _ = num_iters.variable_step_roots(x0=0.5, func=mach_func, dxmax=.2, verbosity=True)

                # Supersonic solution (pressure at wich supersonic nozzle is adapted).
                supersonic_mach, _, _ = num_iters.variable_step_roots(x0=1.5, func=mach_func, dxmax=.2, verbosity=True)

                self._pchoke = self.isent_flow.stat_pressure_from_mach(subsonic_mach, self.p_st, self.T_st)
                self._padapt = self.isent_flow.stat_pressure_from_mach(supersonic_mach, self.p_st, self.T_st)

        return
    

    def solve_critical_convergent_nozzle(self, ps=None, As=None, adiab_efficiency=1):
        """
        Critical nozzle (choked) with convergent geometry (thus critical area is
        the discharge area A_s). Solves the nozzle assuming critical conditions and:
            + ps: If ps is provided, the discharge area and adiabatic efficiency are
                calculated
            + As: if As is provided, the corresponding adiabatic efficiency and static
              pressure are calculated. If the efficiency is impossible a warning is raised
            + adiab_efficiency: If no ps nor As are provided, the adiabatic efficiency is
                used to calculate the nozzle. By default the nozzle is assumed isentropic.

        Inputs:
        -------
            ps: float. Static discharge pressure [Pa]
            As: float. Discharge, critical area [m**2]
            adiab_efficiency: float. Adiabatic efficiency of the nozzle. By default is 1 (isentropic)
        
        """

        gamma_to = self.fluid.gamma(self.T_et)

        # Exit mach number and static temperature:
        self._mach_s = 1
        self._exit_regime = 'supersonic'
        self._T_s = self.isent_flow.stat_temp_from_mach(1, self.T_st)
        self._vel_s = self.isent_flow.sound_speed(self.T_s)

        if (As is None) and not (ps is None):
            # Store static pressure
            self._p_s = ps

            # Calculate the critical discharge area given the adiabatic efficiency
            self._p_st = self.isent_flow.stag_pressure_from_mach(self.mach_s, self.p_s, self.T_s)
            self._rho_s = self.p_s / self.fluid.Rg / self.T_s
            self._A_s = self.mflow_s / self.rho_s / self.vel_s

            self._rho_st = self.isent_flow.stag_density_from_mach(self.mach_s, self.rho_s, self.T_s)
            self._ekin_s = 0.5 * self.vel_s**2
            self._h_s = self.h_st - self.ekin_s

            # Recalculate adiabatic efficiency
            adiab_eff = (self.T_s/self.T_et-1)/((self.p_s/self.p_et)**((gamma_to-1)/gamma_to)-1)
            self._adiab_efficiency = adiab_eff
            if not 0<=adiab_eff<=1:
                warnings.warn('Unfeasible nozzle adiabatic efficiency (ad_eff={0}) for adapted nozzle (ps={1}) and fixed area (As={2})'.format(self.adiab_efficiency, self.p_s, self.T_s), UserWarning)

        elif not (As is None) and (ps is None):
           # As is provided
            self._A_s = As

            self._rho_s = self.mflow_s / self.A_s / self.vel_s
            self._rho_st = self.isent_flow.stag_density_from_mach(self.mach_s, self.rho_s, self.T_s)
            self._p_s = self.rho_s * self.fluid.Rg * self.T_s
            self._p_st = self.isent_flow.stag_pressure_from_mach(self.mach_s, self.p_s, self.T_s)

            self._rho_st = self.isent_flow.stag_density_from_mach(self.mach_s, self.rho_s, self.T_s)
            self._ekin_s = 0.5 * self.vel_s**2
            self._h_s = self.h_st - self.ekin_s

            # Recalculate adiabatic efficiency
            adiab_eff = (self.T_s/self.T_et-1)/((self.p_s/self.p_et)**((gamma_to-1)/gamma_to)-1)
            self._adiab_efficiency = adiab_eff
            if not 0<=adiab_eff<=1:
                warnings.warn('Unfeasible nozzle adiabatic efficiency (ad_eff={0}) for adapted nozzle (ps={1}) and fixed area (As={2})'.format(self.adiab_efficiency, self.p_s, self.T_s), UserWarning)

        elif (As is None) and (ps is None):
            # Calculate ps form adiabatic efficiency
            self._adiab_efficiency = adiab_efficiency

            # Static pressure
            self._p_s = self.p_et * (1 + (self.T_s/self.T_et -1)/self.adiab_efficiency)**(gamma_to/(gamma_to-1))
            self._rho_s = self.p_s / self.fluid.Rg / self.T_s
            self._A_s = self.mflow_s / self.rho_s / self.vel_s
            self._rho_st = self.isent_flow.stag_density_from_mach(self.mach_s, self.rho_s, self.T_s)            
            self._p_st = self.isent_flow.stag_pressure_from_mach(self.mach_s, self.p_s, self.T_s)

            self._ekin_s = 0.5 * self.vel_s**2
            self._h_s = self.h_st - self.ekin_s

        else:
            warnings.warn('If the nozzle is critical, the area and the static pressure at the discharge section cannot be set at the same time. The static pressure at the discharge section will be dismissed and recalculated.')
           # As is provided
            self._A_s = As

            self._rho_s = self.mflow_s / self.A_s / self.vel_s
            self._rho_st = self.isent_flow.stag_density_from_mach(self.mach_s, self.rho_s, self.T_s)
            self._p_s = self.rho_s * self.fluid.Rg * self.T_s
            self._p_st = self.isent_flow.stag_pressure_from_mach(self.mach_s, self.p_s, self.T_s)

            self._rho_st = self.isent_flow.stag_density_from_mach(self.mach_s, self.rho_s, self.T_s)
            self._ekin_s = 0.5 * self.vel_s**2
            self._h_s = self.h_st - self.ekin_s

            # Recalculate adiabatic efficiency
            adiab_eff = (self.T_s/self.T_et-1)/((self.p_s/self.p_et)**((gamma_to-1)/gamma_to)-1)
            self._adiab_efficiency = adiab_eff
            if not 0<=adiab_eff<=1:
                warnings.warn('Unfeasible nozzle adiabatic efficiency (ad_eff={0}) for adapted nozzle (ps={1}) and fixed area (As={2})'.format(self.adiab_efficiency, self.p_s, self.T_s), UserWarning)

        # Critical conditions:
        self._p_s_star = self.p_s
        self._T_s_star = self.T_s
        self._A_star = self.A_s

        if self.A_s>=self.A_e:
            warnings.warn('Exit area must be smaller than entry area in a convergent, critical nozzle. Results may be unfeasible')

        return


    def solve_generic_nozzle(self, ps=None, As=None, adiab_efficiency=1, nozzle_type='con-di'):
        """
        Generic nozzle solver. 

        """

        # Nozzle type
        if nozzle_type.lower()=='condi' or nozzle_type.lower()=='laval':
            nozzle_type = 'con-di'
        elif nozzle_type.lower()=='con':
            nozzle_type = 'convergent'
        elif not nozzle_type.lower() in ['con-di', 'convergent']:
            warnings.warn('Unknown nozzle type: {}. Nozzle will be set to con-di (default)'.format(nozzle_type))
        

        gamma_to = self.fluid.gamma(self.T_et)

        if (As is None) and (ps is None):
            warnings.warn("Exit area or exit static pressure must be provided to solve a generic nozzle.")
            
        elif (As is None) and not (ps is None):
            # Calculate discharge area assuming known adiabatic efficiency and static pressure
            self._adiab_efficiency = adiab_efficiency
            self._p_s = ps
            self.solve_from_static_exit_pressure(self.p_s, adiab_efficiency=self.adiab_efficiency)

        elif not (As is None) and (ps is None):
            # As is provided
            self._A_s = As

            T_s_function = lambda T_s: self.p_et * (1+ (T_s/self.T_et-1)/self.adiab_efficiency)**(gamma_to/(gamma_to-1)) - self.mflow_s*self.fluid.Rg*T_s/self.A_s/np.sqrt(2*self.fluid.cp(self.T_st - T_s))
            T_s_solution, _, _ = num_iters.variable_step_roots(x0=220, func=T_s_function, dxmax=5, verbosity=True)
            
            p_s = self.mflow_s*self.fluid.Rg*T_s_solution/self.A_s / self.isent_flow.vel_from_stag_temp(self.T_st, T_s_solution)

            self._p_s = p_s
            self._T_s = T_s_solution


            self._vel_s = self.isent_flow.vel_from_stag_temp(self.T_st, self.T_s)
            self._mach_s = self.isent_flow.mach_number(self.vel_s, self.T_s)


            if self.T_s <= 2/(gamma_to + 1)*self.T_st:
                self._exit_regime = 'supersonic'
                self._T_s_star = self.isent_flow.stat_temp_from_mach(1, self.T_st)
                self._p_s_star = self.isent_flow.stat_pressure_from_mach(1, self.p_st, self.T_st)
                self._A_star = self.A_s*self.mach_s*((gamma_to+1)/2/(1+(gamma_to-1)/2*self.mach_s**2))**((gamma_to+1)/2/(gamma_to-1))
            else:
                self._exit_regime = 'subsonic'
                self._T_s_star = np.nan
                self._p_s_star = np.nan
                self._A_star = np.nan


            self._rho_st = self.isent_flow.stag_density_from_mach(self.mach_s, self.rho_s, self.T_s)
            self._ekin_s = 0.5 * self.vel_s**2
            self._h_s = self.h_st - self.ekin_s


        if self.exit_regime=='supersonic':
            if nozzle_type=='convergent':
                # Recalculate solution assuming critical, convergent nozzles
                self.solve_critical_convergent_nozzle(self.ps, adiab_efficiency=self.adiab_efficiency)
            else:
                # Calculate nozzle regime. Solution is iterated:
                expon = (gamma_to+1)/(2*(gamma_to-1))
                area_rela = self.A_star/self.A_s

                # Mach function
                mach_func = lambda M: M*((gamma_to + 1)/2/( 1+(gamma_to-1)/2*M**2 ))**(expon) - area_rela

                # Subsonic solution (pressure at wich choking occurs)
                subsonic_mach, _, _ = num_iters.variable_step_roots(x0=0.5, func=mach_func, dxmax=.2, verbosity=True)

                # Supersonic solution (pressure at wich supersonic nozzle is adapted).
                supersonic_mach, _, _ = num_iters.variable_step_roots(x0=1.5, func=mach_func, dxmax=.2, verbosity=True)

                self._pchoke = self.isent_flow.stat_pressure_from_mach(subsonic_mach, self.p_st, self.T_st)
                self._padapt = self.isent_flow.stat_pressure_from_mach(supersonic_mach, self.p_st, self.T_st)


        return
예제 #4
0
M Rodriguez. 2020
"""

import sys
from sys import path
from os.path import dirname as dir
sys.path.append(dir(sys.path[0]))

from pyturb.gas_models.isentropic_flow import IsentropicFlow
from pyturb.gas_models.perfect_ideal_gas import PerfectIdealGas
from pyturb.power_plant.nozzle import Nozzle
import pyturb.gas_models.isa as isa
import numpy as np

air = PerfectIdealGas('Air')
isent_flow = IsentropicFlow(air)

h0 = 0.0
p0 = isa.pressure_isa(h0)
T0 = isa.temperature_isa(h0)
phi_1 = 1  # m
Ae = np.pi * phi_1**2 / 4
v0 = 300
G0 = p0 / T0 / air.Rg * Ae * v0
M0 = isent_flow.mach_number(v0, T0)
p0t = isent_flow.stag_pressure_from_mach(M0, p0, T0)
T0t = isent_flow.stag_temp_from_mach(M0, T0)

nozzle = Nozzle(air)
nozzle.initialize_nozzle(G0, p0t, T0t, Ae=Ae)
nozzle.solve_from_static_exit_pressure(ps=p0, As=0.85)
예제 #5
0
isentropic_flow tests

M Rodriguez. 2020
"""

import sys
from sys import path
from os.path import dirname as dir
sys.path.append(dir(sys.path[0]))

from pyturb.gas_models.perfect_ideal_gas import PerfectIdealGas, SemiperfectIdealGas
from pyturb.gas_models.isentropic_flow import IsentropicFlow
import pyturb.gas_models.isa as isa

air = PerfectIdealGas('Air')
isent = IsentropicFlow(air)

# Perfect gas
print('----------------------------PerfectGas----------------------------')
T = 288.15  #K
p = 101325  #Pa
rho = p / air.Rg / T
v = 100  # m/s

a = isent.sound_speed(T)
M = isent.mach_number(v, T)
print('M=', M, 'a=', a)

print('Tt_T=', isent.stagnation_static_rel(M))
print('Tt=', isent.stag_temp_from_mach(M, T))
print('Tt=', isent.stag_temp_from_vel(v, T))
예제 #6
0
class Intake(ControlVolume):
    """
    Intake:
    -------

    Defines a generic intake stage control volume, between one entrance and one
    exit to the control volume.

    Contents:
    ---------
        + initialize_intake: initializes the control volume with the minimum number
            of thermodynamic properties and variables needed to solve the intake.
        + solve_intake: solves all thermodynamic properties and variables involved
            in the control volume.

    A total of 30 variables of a control volume are defined:
    + 12 thermodynamic properties at the entrance of the CV:
        + Static properties:
            + p_e, T_e, rho_e, h_e (enthalpy)
        + Stagnation properties
            + p_et, T_et, rho_et, h_et (enthalpy)
        + Other:
            + vel_e (flow velocity), mach_e, ec_e (kin. energy), mflow_e (mass flow)

    + 12 thermodynamic properties at the exit of the CV
        + Static properties:
            + p_s, T_s, rho_s, h_s (enthalpy)
        + Stagnation properties
            + p_st, T_st, rho_st, h_st (enthalpy)
        + Other:
            + vel_s (flow velocity), mach_s, ec_s (kin. energy), mflow_s (mass flow)

    + 6 variables of the CV:
        + specific power:
            + q_se (heating/cooling power along the CV), w_se (mechanical power along the CV)
        + adiab_efficiency, A_e (entrance area), A_s (exit area), delta_massflow (mass flow variation along CV)

    """
    def __init__(self, fluid, stage=None):
        """
        Initializes Intake and ControlVolume.
        """
        # Stage of the power plant:
        self.stage = stage

        # Initialize superclass methods
        super().__init__(fluid)

        # Isentropic flow:
        if not (isinstance(fluid, PerfectIdealGas)
                or isinstance(fluid, SemiperfectIdealGas)):
            # Check the flow is a Perfect or a Semiperfect gas fom pyturb
            raise TypeError(
                "Object must be PerfectIdealGas, SemiperfectIdealGas. Instead received {}"
                .format(fluid))

        self.isent_flow = IsentropicFlow(self.fluid)

        return None

    # **************************
    # *** Entrance of the CV ***
    # **************************
    # Static thermodynamic properties:
    @property
    def p_e(self):
        """
        Static pressure at the entrance of the CV. [Pa]
        """
        return self._p_e

    @property
    def T_e(self):
        """
        Static temperature at the entrance of the CV. [K]
        """
        return self._T_e

    @property
    def rho_e(self):
        """
        Static density at the entrance of the CV. [kg/m**3]
        """
        return self._rho_e

    @property
    def h_e(self):
        """
        Specific static enthalpy at the entrance of the CV. [W/kg/s]
        """
        return self._h_e

    # Stagnation thermodynamic properties
    @property
    def p_et(self):
        """
        Stagnation pressure at the entrance of the CV. [Pa]
        """
        return self._p_et

    @property
    def T_et(self):
        """
        Stagnation temperature at the entrance of the CV. [K]
        """
        return self._T_et

    @property
    def rho_et(self):
        """
        Stagnation density at the entrance of the CV. [kg/m**3]
        """
        return self._rho_et

    @property
    def h_et(self):
        """
        Specific stagnation enthalpy at the entrance of the CV. [W/kg/s]
        """
        return self._h_et

    # Velocity and kinetic energy:
    @property
    def vel_e(self):
        """
        Flow velocity at the entrance of the CV. [m/s]
        """
        return self._vel_e

    @property
    def mach_e(self):
        """
        """
        return self._mach_e

    @property
    def ekin_e(self):
        """
        Specific kinetic energy at the entrance of the CV. [W/kg/s]
        """
        return self._ekin_e

    # Mass flow and area:
    @property
    def mflow_e(self):
        """
        Mass flow at the entrance of the CV. [kg/s]
        """
        return self._mflow_e

    @property
    def A_e(self):
        """
        Mass flow at the entrance of the CV. [kg/s]
        """
        return self._A_e

    # **********************
    # *** Exit of the CV ***
    # **********************
    # Static thermodynamic properties:
    @property
    def p_s(self):
        """
        Static pressure at the entrance of the CV. [Pa]
        """
        return self._p_s

    @property
    def T_s(self):
        """
        Static temperature at the entrance of the CV. [K]
        """
        return self._T_s

    @property
    def rho_s(self):
        """
        Static density at the entrance of the CV. [mg/m**3]
        """
        return self._rho_s

    @property
    def h_s(self):
        """
        Specific static enthalpy at the entrance of the CV. [W/kg/s]
        """
        return self._h_s

    # Stagnation thermodynamic properties
    @property
    def p_st(self):
        """
        Stagnation pressure at the entrance of the CV. [Pa]
        """
        return self._p_st

    @property
    def T_st(self):
        """
        Stagnation temperature at the entrance of the CV. [K]
        """
        return self._T_st

    @property
    def rho_st(self):
        """
        Stagnation density at the entrance of the CV. [kg/m**3]
        """
        return self._rho_st

    @property
    def h_st(self):
        """
        Specific stagnation enthalpy at the entrance of the CV. [W/kg/s]
        """
        return self._h_st

    # Velocity and kinetic energy:
    @property
    def vel_s(self):
        """
        Flow velocity at the entrance of the CV. [m/s]
        """
        return self._vel_s

    @property
    def mach_s(self):
        """
        Mach number at the entrance of the CV. [dimensionless]
        """
        return self._mach_s

    @property
    def ekin_s(self):
        """
        Specific kinetic energy at the entrance of the CV. [W/kg/s]
        """
        return self._ekin_s

    # Mass flow and area:
    @property
    def mflow_s(self):
        """
        Mass flow at the entrance of the CV. [kg/s]
        """
        return self._mflow_s

    @property
    def A_s(self):
        """
        Area at the entrance of the CV. [m**2]
        """
        return self._A_s

    # ***************************
    # *** Transfer variables: ***
    # ***************************
    @property
    def w_se(self):
        """
        Speficic mechanical power along the CV. [W/mg/s]
        """
        return self._w_se

    @property
    def q_se(self):
        """
        Speficic heating power along the CV. [W/mg/s]
        """
        return self._q_se

    @property
    def delta_massflow(self):
        """
        Mass flow variation along the CV. [W/mg/s]
        """
        return self._delta_massflow

    @property
    def adiab_efficiency(self):
        """
        Adiabatic efficiency along the CV. [dimensionless]
        """
        return self._adiab_efficiency

    ## Collects basic inputs of an intake
    def initialize_intake(self, pe, Te, ve, Ae, adiab_efficiency=1, As=None):
        """
        Sets basic inputs of a generic intake (diffuser) control volume:
            + Static pressure, static temperature and flow velocity at the entrance
            + Adiabatic efficiency of the diffuser (by default the intake is considered
              to be isentropic)
            + Exit area of the intake (if no exit area is provided, properties dependent
            on the area are set to np.nan).
        """

        self._A_e = Ae
        self._p_e = pe
        self._T_e = Te
        self._vel_e = ve
        self._adiab_efficiency = adiab_efficiency
        self._A_s = As

        self.solve_intake()

        return None

    ## Solves the intake at the entrance:
    def solve_intake(self):
        """
        Solves the thermodynamic properties and variables of the intake.

        Note that the followwing variables and properties are solved only if an exit area
        is provided in initialize_intake(), otherwise those variables are set to np.nan:
            vel_s, p_s, T_s, rho_s, ekin_s, h_s, mach_s
        
        If the exit area is provided, Sympy is used to solve the mach at the exit
        of the intake.
        """

        # Solve basic thermodyamic properties at the entrance:
        self._rho_e = self.p_e / self.T_e / self.fluid.Rg
        self._mach_e = self.isent_flow.mach_number(self.vel_e, self.T_e)
        self._T_et = self.isent_flow.stag_temp_from_mach(self.mach_e, self.T_e)
        self._p_et = self.isent_flow.stag_pressure_from_mach(
            self.mach_e, self.p_e, self.T_e)
        self._rho_et = self.isent_flow.stag_density_from_mach(
            self.mach_e, self.rho_e, self.T_e)

        # Mass flow along the control volume
        self._mflow_e = self.rho_e * self.A_e * self.vel_e
        # TODO: If the intake is used with a fan, bypass ratio may be obtained here
        self._mflow_s = self.mflow_e
        self._delta_massflow = self.mflow_s - self.mflow_e

        # Enthalpies:
        self._h_e = self.fluid.cp(self.T_e) * self.T_e
        self._ekin_e = 0.5 * self.vel_e**2
        self._h_et = self.h_e + self.ekin_e
        self._h_st = self.h_et

        # Thermodynamic properties at the exit fo the CV
        self._T_st = self.T_et

        gamma_d = self.fluid.gamma(self.T_et)
        self._p_st = self.p_e * (
            (self.T_st / self.T_e - 1) * self.adiab_efficiency +
            1)**(gamma_d / (gamma_d - 1))
        self._rho_st = self.rho_e * (
            (self.T_st / self.T_e - 1) * self.adiab_efficiency +
            1)**(1 / (gamma_d - 1))

        # By definition, no work/heat is applied in a diffuser cv
        self._w_se = 0
        self._q_se = 0

        if self.A_s is None:
            # Is exit area is not provided:
            self._vel_s = np.nan
            self._p_s = np.nan
            self._T_s = np.nan
            self._rho_s = np.nan
            self._ekin_s = np.nan
            self._h_s = np.nan
            self._mach_s = np.nan
        else:
            # With the exit area, solve the mach number at the exit
            var_aux = self.mflow_s / self.A_s * np.sqrt(
                self.fluid.Rg / gamma_d) * np.sqrt(self.T_st) / self.p_st
            var_aux = var_aux**(-2 * (gamma_d - 1) / (gamma_d + 1))

            Ms = symbols('Ms')

            ec1 = Eq(var_aux, (Ms + (1 + (gamma_d - 1) / 2 * Ms**2)))

            # Dismiss negative solutions
            M_s = solveset(ec1, Ms, domain=S.Reals)
            M_s_ = list(M_s)
            M_s_.sort(reverse=True)
            mach_s_value = M_s_[0] if M_s_[0] > 0 else None

            if mach_s_value is None:
                raise ValueError(
                    "Mach number at exit of the CV is not possitive: {0}".
                    format(mach_s_value))
            else:
                self._mach_s = float(mach_s_value)

            # Calculate static properties at the exit of the CV
            self._T_s = self.isent_flow.stat_temp_from_mach(
                self.mach_s, self.T_st)
            self._p_s = self.isent_flow.stat_pressure_from_mach(
                self.mach_s, self.p_st, self.T_st)
            self._vel_s = self.isent_flow.vel_from_mach(self.mach_s, self.T_s)
            self._rho_s = self.p_s / self.T_s / self.fluid.Rg
            self._ekin_s = 0.5 * self.vel_s**2
            self._h_s = self.fluid.cp(self.T_s) * self.T_s

        return