示例#1
0
def convert(value, unit_from, unit_to):
    """
    Convert values from one unit to another.
    
    This function accepts conversion with units of length, velocity,
    acceleration, angles, angle velocity, angle accelerations,
    density, pressure, absolute temperature, mass, force and energy.

    Parameters
    ----------
    value : array_like
        Input values in original unit.
    unit_from : str
        Original unit as a string.
    unit_to : str
        Target unit as a string.

    Returns
    -------
    value : array_like
        Input values converted into target unit.
        
    Notes
    -----
    Use the source file as reference for accepted unit strings. Additional
    units and physical quantities can easily be added to this function.

    Examples
    --------
    >>> convert(23, 'C', 'K')
    296.14999999999998
    >>> convert(sp.linspace(0, 30, num=5), 'm', 'ft')
    array([0, 24.60629921, 49.21259843, 73.81889764, 98.42519685])
    """

    itype, value = to_ndarray(value)

    #loop through all converters and search for pairs
    for cnvt in CNVTS:
        if unit_from in cnvt and unit_to in cnvt:

            #convert to SI unit
            if type(cnvt[unit_from]) is ListType:
                value_si = cnvt[unit_from][0](value)
            else:
                value_si = value * cnvt[unit_from]

            #convert to target unit
            if type(cnvt[unit_to]) is ListType:
                value = cnvt[unit_to][1](value_si)
            else:
                value = value_si / cnvt[unit_to]

            return from_ndarray(itype, value)

    #no converter found
    raise ValueError('Could not find definition of selected units.')
def convert(value, unit_from, unit_to):
    """
    Convert values from one unit to another.
    
    This function accepts conversion with units of length, velocity,
    acceleration, angles, angle velocity, angle accelerations,
    density, pressure, absolute temperature, mass, force and energy.

    Parameters
    ----------
    value : array_like
        Input values in original unit.
    unit_from : str
        Original unit as a string.
    unit_to : str
        Target unit as a string.

    Returns
    -------
    value : array_like
        Input values converted into target unit.
        
    Notes
    -----
    Use the source file as reference for accepted unit strings. Additional
    units and physical quantities can easily be added to this function.

    Examples
    --------
    >>> convert(23, 'C', 'K')
    296.14999999999998
    >>> convert(sp.linspace(0, 30, num=5), 'm', 'ft')
    array([0, 24.60629921, 49.21259843, 73.81889764, 98.42519685])
    """

    itype, value = to_ndarray(value)

    # loop through all converters and search for pairs
    for cnvt in CNVTS:
        if unit_from in cnvt and unit_to in cnvt:
            
            # convert to SI unit
            if type(cnvt[unit_from]) == list:
                value_si = cnvt[unit_from][0](value)
            else:
                value_si = value * cnvt[unit_from]

            # convert to target unit
            if type(cnvt[unit_to]) == list:
                value = cnvt[unit_to][1](value_si)
            else:
                value = value_si / cnvt[unit_to]

            return from_ndarray(itype, value)

    # no converter found
    raise AerotbxValueError('Could not find definition of selected units.')
示例#3
0
def geoidheight(lat, lon):
    """
    Calculate geoid height using the EGM96 Geopotential Model.

    Parameters
    ----------
    lat : array_like
        Lateral coordinates [degrees]. Values must be -90 <= lat <= 90.
    lon : array_like
        Longitudinal coordinates [degrees]. Values must be 0 <= lon <= 360.

    Returns
    -------
    out : array_like
        Geoidheight [meters]

    Examples
    --------
    >>> geoidheight(30, 20)
    25.829999999999995
    >>> geoidheight([30, 20],[40, 20])
    [9.800000000000002, 14.43]
    """

    global _EGM96

    # convert the input value to array
    itype, lat = to_ndarray(lat)
    itype, lon = to_ndarray(lon)

    if lat.shape != lon.shape:
        raise AerotbxValueError("Inputs must contain equal number of values.")

    if (lat < -90).any() or (lat > 90).any() or not sp.isreal(lat).all():
        raise AerotbxValueError("Lateral coordinates must be real numbers "
                                "between -90 and 90 degrees.")

    if (lon < 0).any() or (lon > 360).any() or not sp.isreal(lon).all():
        raise AerotbxValueError("Longitudinal coordinates must be real numbers "
                                "between 0 and 360 degrees.")

    # if the model is not loaded, do so
    if _EGM96 is None:
        _EGM96 = _loadEGM96()

    # shift lateral values to the right reference and flatten coordinates
    lats = sp.deg2rad(-lat + 90).ravel()
    lons = sp.deg2rad(lon).ravel()

    # evaluate the spline and reshape the result
    evl = _EGM96.ev(lats, lons).reshape(lat.shape)

    return from_ndarray(itype, evl)
def geoidheight(lat, lon):
    """
    Calculate geoid height using the EGM96 Geopotential Model.

    Parameters
    ----------
    lat : array_like
        Lateral coordinates [degrees]. Values must be -90 <= lat <= 90.
    lon : array_like
        Longitudinal coordinates [degrees]. Values must be 0 <= lon <= 360.

    Returns
    -------
    out : array_like
        Geoidheight [meters]

    Examples
    --------
    >>> geoidheight(30, 20)
    25.829999999999995
    >>> geoidheight([30, 20],[40, 20])
    [9.800000000000002, 14.43]
    """

    global _EGM96

    #convert the input value to array
    itype, lat = to_ndarray(lat)
    itype, lon = to_ndarray(lon)

    if lat.shape != lon.shape:
        raise Exception("Inputs must contain equal number of values.")

    if (lat < -90).any() or (lat > 90).any() or not sp.isreal(lat).all():
        raise Exception("Lateral coordinates must be real numbers" \
            " between -90 and 90 degrees.")

    if (lon < 0).any() or (lon > 360).any() or not sp.isreal(lon).all():
        raise Exception("Longitudinal coordinates must be real numbers" \
            " between 0 and 360 degrees.")

    #if the model is not loaded, do so
    if _EGM96 is None:
        _EGM96 = _loadEGM96()

    #shift lateral values to the right reference and flatten coordinates
    lats = sp.deg2rad(-lat + 90).ravel()
    lons = sp.deg2rad(lon).ravel()

    #evaluate the spline and reshape the result
    evl = _EGM96.ev(lats, lons).reshape(lat.shape)

    return from_ndarray(itype, evl)
def flowisentropic(**flow):
    """
    Evaluate the isentropic relations with any flow variable.
    
    This function accepts a given set of specific heat ratios and
    a single input of isentropic flow variables. Inputs can be a single
    scalar or an array_like data structure.

    Parameters
    ----------
    gamma : array_like, optional
        Specific heat ratio. Values must be greater than 1.
    M : array_like
        Mach number. Values must be greater than or equal to 0.
    T : array_like
        Temperature ratio T/T0. Values must be 0 <= T <= 1. 
    P : array_like
        Pressure ratio P/P0. Values must be 0 <= P <= 1.
    rho : array_like
        Density ratio rho/rho0. Values must be 0 <= rho <= 1.
    sub : array_like
        Subsonic area ratio A/A*. Values must be greater than or equal
        to 1.
    sup : array_like
        Supersonic area ratio A/A*. Values must be greater than or
        equal to 1.
    
    Returns
    -------
    out : (M, T, P, rho, area)
        Tuple of Mach number, temperature ratio, pressure ratio, density
        ratio and area ratio.
        
    Notes
    -----
    This function accepts one and only one of the isentropic flow
    variables. It will raise an Exception when more than one input
    is given.
    
    Examples
    --------
    >>> flowisentropic(M=3)
    (3.0, 0.35714285714285715, 0.027223683703862824, 0.076226314370815895,
    4.2345679012345689)
    >>> flowisentropic(gamma=1.4, sup=1.6)
    (1.9352576078182122, 0.57174077399894296, 0.14131786852470815,
    0.24717122680666009, 1.6000000000000001)
    >>> flowisentropic(T=sp.linspace(0, 1, 100))
    (array, array, array, array, array)
    """

    #parse the input
    gamma, flow, mtype, itype = _flowinput(flow)

    #calculate gamma-ratios for use in the equations
    a = (gamma+1) / 2
    b = (gamma-1) / 2
    c = a / (gamma-1)

    #preshape mach array
    M = sp.empty(flow.shape, sp.float64)

    #use the isentropic relations to solve for the mach number
    if mtype in ["mach", "m"]:
        if (flow < 0).any() or not sp.isreal(flow).all():
            raise Exception("Mach number inputs must be real numbers" \
                " greater than or equal to 0.")
        M = flow
    elif mtype in ["temp", "t"]:
        if (flow < 0).any() or (flow > 1).any():
            raise Exception("Temperature ratio inputs must be real numbers" \
                " 0 <= T <= 1.")
        M[flow == 0] = sp.inf
        M[flow != 0] = sp.sqrt((1/b[flow != 0])*(flow[flow != 0]**(-1) - 1))
    elif mtype in ["pres", "p"]:
        if (flow < 0).any() or (flow > 1).any():
            raise Exception("Pressure ratio inputs must be real numbers" \
                " 0 <= P <= 1.")
        M[flow == 0] = sp.inf
        M[flow != 0] = sp.sqrt((1/b[flow != 0]) * \
            (flow[flow != 0]**((gamma[flow != 0]-1)/-gamma[flow != 0]) - 1))
    elif mtype in ["dens", "d", "rho"]:
        if (flow < 0).any() or (flow > 1).any():
            raise Exception("Density ratio inputs must be real numbers" \
                " 0 <= rho <= 1.")
        M[flow == 0] = sp.inf
        M[flow != 0] = sp.sqrt((1/b[flow != 0]) * \
            (flow[flow != 0]**((gamma[flow != 0]-1)/-1) - 1))
    elif mtype in ["sub", "sup"]:
        if (flow < 1).any():
            raise Exception("Area ratio inputs must be real numbers greater" \
                " than or equal to 1.")
        M[:] = 0.2 if mtype == "sub" else 1.8
        for _ in xrange(_AETB_iternum):
            K = M ** 2
            f = -flow + a**(-c) * ((1+b*K)**c) / M #mach-area relation
            g = a**(-c) * ((1+b*K)**(c-1)) * (b*(2*c - 1)*K - 1) / K #deriv
            M = M - (f / g) #Newton-Raphson
        M[flow == 1] = 1
        M[sp.isinf(flow)] = sp.inf
    else:
        raise Exception("Keyword input must be an acceptable string to" \
            " select input parameter.")

    d = 1 + b*M**2
    
    T = d**(-1)
    P = d**(-gamma/(gamma-1))
    rho = d**(-1/(gamma-1))

    area = sp.empty(M.shape, sp.float64)
    r = sp.logical_and(M != 0, sp.isfinite(M))
    area[r] = a[r]**(-c[r]) * d[r]**c[r] / M[r]
    area[sp.logical_not(r)] = sp.inf

    return from_ndarray(itype, M, T, P, rho, area)
def flowprandtlmeyer(**flow):
    """
    Prandtl-Meyer function for expansion waves.
    
    This function accepts a given set of specific heat ratios and
    an input of either Mach number, Mach angle or Prandtl-Meyer angle.
    Inputs can be a single scalar or an array_like data structure.

    Parameters
    ----------
    gamma : array_like, optional
        Specific heat ratio. Values must be greater than 1.
    M : array_like
        Mach number. Values must be greater than or equal to 1.
    nu : array_like
        Prandtl-Meyer angle [degrees]. Values must be
        0 <= M <= 90*(sqrt((g+1)/(g-1))-1).
    mu : array_like
        Mach angle [degrees]. Values must be 0 <= M <= 90.
    
    Returns
    -------
    out : (M, nu, mu)
        Tuple of Mach number, Prandtl-Meyer angle, Mach angle.
    
    Examples
    --------
    >>> flowprandtlmeyer(M=5)
    (5.0, 76.920215508538789, 11.536959032815489)
    """

    #parse the input
    gamma, flow, mtype, itype = _flowinput(flow)

    #calculate gamma-ratios for use in the equations
    l = sp.sqrt((gamma-1)/(gamma+1))

    #preshape mach array
    M = sp.empty(flow.shape, sp.float64)

    #use prandtl-meyer relation to solve for the mach number
    if mtype in ["mach", "m"]:
        if (flow < 1).any():
            raise Exception("Mach number inputs must be real numbers greater" \
                " than or equal to 1.")
        M = flow
    elif mtype in ["mu", "machangle"]:
        if (flow < 0).any() or  (flow > 90).any():
            raise Exception("Mach angle inputs must be real numbers" \
                " 0 <= M <= 90.")
        M = 1 / sp.sin(sp.deg2rad(flow))
    elif mtype in ["nu", "pm", "pmangle"]:
        if (flow < 0).any() or  (flow > 90*((1/l)-1)).any():
            raise Exception("Prandtl-Meyer angle inputs must be real" \
                " numbers 0 <= M <= 90*(sqrt((g+1)/(g-1))-1).")
        M[:] = 2 #initial guess for the solution
        for _ in xrange(_AETB_iternum):
            b = sp.sqrt(M**2 - 1)
            f = -sp.deg2rad(flow) + (1/l) * sp.arctan(l*b) - sp.arctan(b)
            g = b*(1 - l**2) / (M*(1 + (l**2)*(b**2))) #derivative
            M = M - (f / g) #Newton-Raphson
    else:
        raise Exception("Keyword input must be an acceptable string to" \
            " select input parameter.")

    #normal shock relations
    b = sp.sqrt(M**2 - 1)
    V = (1/l) * sp.arctan(l*b) - sp.arctan(b)
    U = sp.arcsin(1 / M)

    return from_ndarray(itype, M, sp.rad2deg(V), sp.rad2deg(U))
def flownormalshock(**flow):
    """
    Evaluate the normal shock wave relations with any flow variable.
    
    This function accepts a given set of specific heat ratios and a
    single input of normal shock wave flow variables. Inputs can be
    a scalar or an array_like data structure.

    Parameters
    ----------
    gamma : array_like, optional
        Specific heat ratio. Values must be greater than 1.
    M : array_like
        Upstream Mach number. Values must be greater than or equal to 1.
    M2 : array_like
        Downstream Mach number. Values must be
        SQRT((GAMMA-1)/(2*GAMMA)) <= M <= 1.
    T : array_like
        Temperature ratio T2/T1. Values must be greater than or equal to 1.
    P : array_like
        Pressure ratio P2/P1. Values must be greater than or equal to 1.
    rho : array_like
        Density ratio rho2/rho1. Values must be
        1 <= rho <= (GAMMA+1)/(GAMMA-1).
    P0 : array_like
        Total pressure ratio P02/P01. Values must be 0 <= P0 <= 1.
    Pitot : array_like
        Rayleigh-Pitot ratio P02/P1. Values must be greater than or equal
        to ((GAMMA+1)/2)**(-GAMMA/(GAMMA+1)).
    
    Returns
    -------
    out :  (M, M2, T, P, rho, P0, Pitot)
        Tuple of upstream Mach number, downstream Mach number, temperature
        ratio, pressure ratio, density ratio, total pressure ratio,
        rayleigh-pitot ratio.
        
    Notes
    -----
    This function accepts one and only one of the normal shock flow
    variables. It will raise an Exception when more than one input
    is given.
    
    Examples
    --------
    >>> flownormalshock(M=2)
    (2.0, 0.57735026918962573, 1.6874999999999998, 4.5, 2.666666666666667,
    0.72087386148474542, 5.640440812823317)
    >>> flownormalshock(gamma=1.4, Pitot=3.4)
    (1.4964833298836788, 0.70233741753226209, 1.3178766042516246,
    2.4460394160563674, 1.8560458605647578, 0.93089743233402389, 3.3999999999999977)
    >>> flownormalshock(M2=[0.5, 0.6, 0.7])
    (list, list, list, list, list, list, list)
    """

    #parse the input
    gamma, flow, mtype, itype = _flowinput(flow)

    #calculate gamma-ratios for use in the equations
    a = (gamma+1) / 2
    b = (gamma-1) / 2
    c = gamma / (gamma-1)

    #preshape mach array
    M = sp.empty(flow.shape, sp.float64)

    #use the normal shock relations to solve for the mach number
    if mtype in ["mach", "m1", "m"]:
        if (flow < 1).any():
            raise Exception("Mach number inputs must be real numbers" \
                " greater than or equal to 1.")
        M = flow
    elif mtype in ["down", "mach2", "m2", "md"]:
        lowerbound = sp.sqrt((gamma - 1)/(2*gamma))
        if (flow < lowerbound).any() or (flow > 1).any():
            raise Exception("Mach number downstream inputs must be real" \
                " numbers SQRT((GAMMA-1)/(2*GAMMA)) <= M <= 1.")
        M[flow <= lowerbound] = sp.inf
        M[flow > lowerbound] = sp.sqrt((1 + b*flow**2) / (gamma*flow**2 - b))
    elif mtype in ["pres", "p"]:
        if (flow < 1).any() or not sp.isreal(flow).all():
            raise Exception("Pressure ratio inputs must be real numbers" \
                " greater than or equal to 1.")
        M = sp.sqrt(((flow-1)*(gamma+1)/(2*gamma)) + 1)
    elif mtype in ["dens", "d", "rho"]:
        upperbound = (gamma+1) / (gamma-1)
        if (flow < 1).any() or (flow > upperbound).any():
            raise Exception("Density ratio inputs must be real numbers" \
                " 1 <= rho <= (GAMMA+1)/(GAMMA-1).")
        M[flow >= upperbound] = sp.inf
        M[flow < upperbound] = sp.sqrt(2*flow / (1 + gamma + flow - flow*gamma))
    elif mtype in ["temp", "t"]:
        if (flow < 1).any():
            raise Exception("Temperature ratio inputs must be real numbers" \
                " greater than or equal to 1.")
        B = b + gamma/a - gamma*b/a - flow*a
        M = sp.sqrt((-B + sp.sqrt(B**2 - \
            4*b*gamma*(1-gamma/a)/a)) / (2*gamma*b/a))
    elif mtype in ["totalp", "p0"]:
        if (flow < 0).any() or (flow > 1).any():
            raise Exception("Total pressure ratio inputs must be real" \
                " numbers 0 <= P0 <= 1.")
        M[:] = 2.0 #initial guess for the solution
        for _ in xrange(_AETB_iternum):
            f = -flow + (1 + (gamma/a)*(M**2 - 1))**(1-c) \
                * (a*M**2 / (1 + b*M**2))**c
            g = 2*M*(a*M**2 / (b*M**2 + 1))**(c-1)*((gamma* \
                (M**2-1))/a + 1)**(-c)*(a*c + gamma*(-c*(b*M**4 + 1) \
                + b*M**4 + M**2)) / (b*M**2 + 1)**2
            M = M - (f / g) #Newton-Raphson
        M[flow == 0] = sp.inf
    elif mtype in ["pito", "pitot", "rp"]:
        lowerbound = a**c
        if (flow < lowerbound).any():
            raise Exception("Rayleigh-Pitot ratio inputs must be real" \
                " numbers greater than or equal to ((G+1)/2)**(-G/(G+1)).")
        M[:] = 5.0 #initial guess for the solution
        K = a**(2*c - 1)
        for _ in xrange(_AETB_iternum):
            f = -flow + K * M**(2*c) / (gamma*M**2 - b)**(c-1) #Rayleigh-Pitot
            g = 2*K*M**(2*c - 1) * (gamma*M**2 - b)**(-c) * (gamma*M**2 - b*c)
            M = M - (f / g) #Newton-Raphson
    else:
        raise Exception("Keyword input must be an acceptable string to" \
            " select input parameter.")

    #normal shock relations
    M2 = sp.sqrt((1 + b*M**2) / (gamma*M**2 - b))
    rho = ((gamma+1)*M**2) / (2 + (gamma-1)*M**2)
    P = 1 + (M**2 - 1)*gamma / a
    T = P / rho
    P0 = P**(1-c) * rho**(c)
    P1 = a**(2*c - 1) * M**(2*c) / (gamma*M**2 - b)**(c-1)

    #handle infinite mach
    M2[M == sp.inf] = sp.sqrt((gamma[M == sp.inf] - 1)/(2*gamma[M == sp.inf]))
    T[M == sp.inf] = sp.inf
    P[M == sp.inf] = sp.inf
    rho[M == sp.inf] = (gamma[M == sp.inf]+1) / (gamma[M == sp.inf]-1)
    P0[M == sp.inf] = 0
    P1[M == sp.inf] = sp.inf    

    return from_ndarray(itype, M, M2, T, P, rho, P0, P1)
示例#8
0
def stdatmos(**altitude):
    """
    Evaluate the standard atmosphere at any given altitude.
    
    This function allows input of a single variable to calculate
    the atmospheric properties at different altitudes. The function
    can work with different types of standard models. The default model
    values are set as defined by the International Standard Atmosphere.
    
    Parameters
    ----------
    model : dict, optional
        A standard atmosphere model as obtained from stdmodel. Partial
        models are allowed. The remaining model values default to
        the International Standard Atmosphere.
    h or geom : array_like
        Geometrical altitude [meters].
    geop : array_like
        Geopotential altitude [meters].
    abs : array_like
        Absolute altitude [meters].
    T : array_like
        Temperature altitude [K].
    P : array_like
        Pressure altitude [Pa].
    rho : array_like
        Density altitude [kg/m^3].
        
    Returns
    -------
    out : (h, T, P, rho, a)
        Tuple of geometrical altitude, temperature, pressure, density
        and speed of sound at given altitudes.
    
    Notes
    -----
    This function assumes a continues lapserate below 0 altitude and above
    the top layer, which allows for extrapolation outside the specified
    region (0 to 86km in ISA). Temperature altitude is obtained as the
    first altitude from 0 where the specified temperature exists.
        
    See Also
    --------
    stdmodel
    
    Examples
    --------
    >>> stdatmos(P=[1e5, 1e4, 1e3])[0]
    [110.8864127251899, 16221.007939493587, 31207.084373790043]
    >>> stdatmos(h=sp.linspace(-2000, 81000))
    (array, array, array, array, array)
    """
    
    # pop atmospherical model from input
    model = altitude.pop("model", { })

    # check if model is a dictionary
    if type(model) != dict:
        raise AerotbxValueError("Custom atmosphere model is incompatible.")

    # check if a single remaining input exists
    if len(altitude) is not 1:
        raise TypeError("Function needs exactly one altitude input.")

    # pop the altitude input
    mtype, alt = altitude.popitem()

    # check if the altitude input type is valid
    if mtype not in ["h", "geom", "geop", "abs", "T", "P", "rho"]:
        raise AerotbxValueError("The altitude input should be a valid input type.")

    # convert the input to numpy arrays
    itype, alt = to_ndarray(alt)

    # model values
    R = model.get("R", 287.053)  # gas constant [J/kg/K] (air)
    gamma = model.get("gamma", 1.4)  # specific heat ratio [-] (air)

    g = model.get("g0", 9.80665)  # gravity [m/s^2] (earth)
    radius = model.get("radius", 6356766.0)  # earth radius [m] (earth)

    Tb = model.get("T0", 288.15)  # base temperature [K]
    Pb = model.get("P0", 101325.0)  # base pressure [Pa]
    
    # model lapse rate and height layers
    Hb = sp.array([0, 11, 20, 32, 47, 51, 71, sp.inf], sp.float64) * 1000
    Lr = sp.array([-6.5, 0, 1, 2.8, 0, -2.8, -2], sp.float64) * 0.001

    Hb = model.get("layers", Hb)  # layer height [km]
    Lr = model.get("lapserate", Lr)  # lapse rate [K/km]

    # preshape solution arrays
    T = sp.ones(alt.shape, sp.float64) * sp.nan
    P = sp.ones(alt.shape, sp.float64) * sp.nan

    # define the height array
    if mtype in ["h", "geom"]:
        h = alt * radius / (radius + alt)
    elif mtype is "geop":
        h = alt
    elif mtype is "abs":
        h = alt - radius
    else:
        h = sp.ones(alt.shape, sp.float64) * sp.nan

    for lr, hb, ht in zip(Lr, Hb[:-1], Hb[1:]):
        # calculate the temperature at layer top
        Tt = Tb + lr * (ht - hb)
        
        if mtype is "T":
            # break the loop if there are no nans in the solution array
            if not sp.isnan(h).any():
                break

            # select all temperatures in current layer
            if lr == 0:
                sel = (alt == Tb)
            else:
                s = sp.sign(lr)
                bot = -sp.inf if hb == 0 else Tb * s
                top = sp.inf if ht == Hb[-1] else Tt * s
                sel = sp.logical_and(alt * s >= bot, alt * s < top)

            # only select when not already solved
            sel = sp.logical_and(sel, sp.isnan(h))

            # temperature is given as input
            T[sel] = alt[sel]

            # solve for height and pressure
            if lr == 0:
                h[sel] = hb
                P[sel] = Pb
            else:
                h[sel] = hb + (1.0 / lr) * (T[sel] - Tb)
                P[sel] = Pb * (T[sel] / Tb) ** (-g / (lr * R))

        elif mtype in ["P", "rho"]:
            # choose base value as pressure or density
            vb = Pb if mtype is "P" else Pb / (R * Tb)

            # select all input values below given pressure or density
            sel = alt <= (sp.inf if hb == 0 else vb)

            # break if nothing is selected
            if not sel.any():
                break

            # solve for temperature and height
            if lr == 0:
                T[sel] = Tb
                h[sel] = hb - sp.log(alt[sel] / vb) * R * Tb / g
            else:
                x = g if mtype is "P" else (lr * R + g)
                T[sel] = Tb * (alt[sel] / vb) ** (-lr * R / x)
                h[sel] = hb + (T[sel] - Tb) / lr

            # pressure is given as input
            P[sel] = alt[sel] if mtype is "P" else alt[sel] * R * T[sel]

        else:
            # select all height values above layer base
            sel = h >= (-sp.inf if hb == 0 else hb)

            # break if nothing is selected
            if not sel.any():
                break

            # solve for temperature and pressure
            if lr == 0:
                T[sel] = Tb
                P[sel] = Pb * sp.exp((-g / (R * Tb)) * (h[sel] - hb))
            else:
                T[sel] = Tb + lr * (h[sel] - hb)
                P[sel] = Pb * (T[sel] / Tb) ** (-g / (lr * R))

        # update pressure base value
        if lr == 0:
            Pb *= sp.exp((-g / (R * Tb)) * (ht - hb))
        else:
            Pb *= (Tt / Tb) ** (-g / (lr * R))

        # update temperature base value
        Tb = Tt

    # convert geopotential altitude to geometrical altitude
    h *= radius / (radius - h)

    # density
    rho = P / (R * T)

    # speed of sound
    a = sp.sqrt(gamma * R * T)
    
    return from_ndarray(itype, h, T, P, rho, a)
def stdatmos(**altitude):
    """
    Evaluate the standard atmosphere at any given altitude.
    
    This function allows input of a single variable to calculate
    the atmospheric properties at different altitudes. The function
    can work with different types of standard models. The default model
    values are set as defined by the International Standard Atmosphere.
    
    Parameters
    ----------
    model : dict, optional
        A standard atmosphere model as obtained from stdmodel. Partial
        models are allowed. The remaining model values default to
        the International Standard Atmosphere.
    h or geom : array_like
        Geometrical altitude [meters].
    geop : array_like
        Geopotential altitude [meters].
    abs : array_like
        Absolute altitude [meters].
    T : array_like
        Temperature altitude [K].
    P : array_like
        Pressure altitude [Pa].
    rho : array_like
        Density altitude [kg/m^3].
        
    Returns
    -------
    out : (h, T, P, rho, a)
        Tuple of geometrical altitude, temperature, pressure, density
        and speed of sound at given altitudes.
    
    Notes
    -----
    This function assumes a continues lapserate below 0 altitude and above
    the top layer, which allows for extrapolation outside the specified
    region (0 to 86km in ISA). Temperature altitude is obtained as the
    first altitude from 0 where the specified temperature exists.
        
    See Also
    --------
    stdmodel
    
    Examples
    --------
    >>> stdatmos(P=[1e5, 1e4, 1e3])[0]
    [110.8864127251899, 16221.007939493587, 31207.084373790043]
    >>> stdatmos(h=sp.linspace(-2000, 81000))
    (array, array, array, array, array)
    """

    #pop atmospherical model from input
    model = altitude.pop("model", {})

    #check if model is a dictionary
    if type(model) is not DictType:
        raise Exception("Custom atmosphere model is incompatible.")

    #check if a single remaining input exists
    if len(altitude) is not 1:
        raise Exception("Function needs exactly one altitude input.")

    #pop the altitude input
    mtype, alt = altitude.popitem()

    #check if the altitude input type is valid
    if mtype not in ["h", "geom", "geop", "abs", "T", "P", "rho"]:
        raise Exception("The altitude input should be a valid input type.")

    #convert the input to numpy arrays
    itype, alt = to_ndarray(alt)

    #model values
    R = model.get("R", 287.053)  #gas constant [J/kg/K] (air)
    gamma = model.get("gamma", 1.4)  #specific heat ratio [-] (air)

    g = model.get("g0", 9.80665)  #gravity [m/s^2] (earth)
    radius = model.get("radius", 6356766.0)  #earth radius [m] (earth)

    Tb = model.get("T0", 288.15)  #base temperature [K]
    Pb = model.get("P0", 101325.0)  #base pressure [Pa]

    #model lapse rate and height layers
    Hb = sp.array([0, 11, 20, 32, 47, 51, 71, sp.inf], sp.float64) * 1000
    Lr = sp.array([-6.5, 0, 1, 2.8, 0, -2.8, -2], sp.float64) * 0.001

    Hb = model.get("layers", Hb)  #layer height [km]
    Lr = model.get("lapserate", Lr)  #lapse rate [K/km]

    #preshape solution arrays
    T = sp.ones(alt.shape, sp.float64) * sp.nan
    P = sp.ones(alt.shape, sp.float64) * sp.nan

    #define the height array
    if mtype in ["h", "geom"]:
        h = alt * radius / (radius + alt)
    elif mtype is "geop":
        h = alt
    elif mtype is "abs":
        h = alt - radius
    else:
        h = sp.ones(alt.shape, sp.float64) * sp.nan

    for lr, hb, ht in zip(Lr, Hb[:-1], Hb[1:]):
        #calculate the temperature at layer top
        Tt = Tb + lr * (ht - hb)

        if mtype is "T":
            #break the loop if there are no nans in the solution array
            if not sp.isnan(h).any():
                break

            #select all temperatures in current layer
            if lr == 0:
                sel = (alt == Tb)
            else:
                s = sp.sign(lr)
                bot = -sp.inf if hb == 0 else Tb * s
                top = sp.inf if ht == Hb[-1] else Tt * s
                sel = sp.logical_and(alt * s >= bot, alt * s < top)

            #only select when not already solved
            sel = sp.logical_and(sel, sp.isnan(h))

            #temperature is given as input
            T[sel] = alt[sel]

            #solve for height and pressure
            if lr == 0:
                h[sel] = hb
                P[sel] = Pb
            else:
                h[sel] = hb + (1.0 / lr) * (T[sel] - Tb)
                P[sel] = Pb * (T[sel] / Tb)**(-g / (lr * R))

        elif mtype in ["P", "rho"]:
            #choose base value as pressure or density
            vb = Pb if mtype is "P" else Pb / (R * Tb)

            #select all input values below given pressure or density
            sel = alt <= (sp.inf if hb == 0 else vb)

            #break if nothing is selected
            if not sel.any():
                break

            #solve for temperature and height
            if lr == 0:
                T[sel] = Tb
                h[sel] = hb - sp.log(alt[sel] / vb) * R * Tb / g
            else:
                x = g if mtype is "P" else (lr * R + g)
                T[sel] = Tb * (alt[sel] / vb)**(-lr * R / x)
                h[sel] = hb + (T[sel] - Tb) / lr

            #pressure is given as input
            P[sel] = alt[sel] if mtype is "P" else alt[sel] * R * T[sel]

        else:
            #select all height values above layer base
            sel = h >= (-sp.inf if hb == 0 else hb)

            #break if nothing is selected
            if not sel.any():
                break

            #solve for temperature and pressure
            if lr == 0:
                T[sel] = Tb
                P[sel] = Pb * sp.exp((-g / (R * Tb)) * (h[sel] - hb))
            else:
                T[sel] = Tb + lr * (h[sel] - hb)
                P[sel] = Pb * (T[sel] / Tb)**(-g / (lr * R))

        #update pressure base value
        if lr == 0:
            Pb *= sp.exp((-g / (R * Tb)) * (ht - hb))
        else:
            Pb *= (Tt / Tb)**(-g / (lr * R))

        #update temperature base value
        Tb = Tt

    #convert geopotential altitude to geometrical altitude
    h *= radius / (radius - h)

    #density
    rho = P / (R * T)

    #speed of sound
    a = sp.sqrt(gamma * R * T)

    return from_ndarray(itype, h, T, P, rho, a)
示例#10
0
def flowisentropic(**flow):
    """
    Evaluate the isentropic relations with any flow variable.
    
    This function accepts a given set of specific heat ratios and
    a single input of isentropic flow variables. Inputs can be a single
    scalar or an array_like data structure.

    Parameters
    ----------
    gamma : array_like, optional
        Specific heat ratio. Values must be greater than 1.
    M : array_like
        Mach number. Values must be greater than or equal to 0.
    T : array_like
        Temperature ratio T/T0. Values must be 0 <= T <= 1. 
    P : array_like
        Pressure ratio P/P0. Values must be 0 <= P <= 1.
    rho : array_like
        Density ratio rho/rho0. Values must be 0 <= rho <= 1.
    sub : array_like
        Subsonic area ratio A/A*. Values must be greater than or equal
        to 1.
    sup : array_like
        Supersonic area ratio A/A*. Values must be greater than or
        equal to 1.
    
    Returns
    -------
    out : (M, T, P, rho, area)
        Tuple of Mach number, temperature ratio, pressure ratio, density
        ratio and area ratio.
        
    Notes
    -----
    This function accepts one and only one of the isentropic flow
    variables. It will raise an Exception when more than one input
    is given.
    
    Examples
    --------
    >>> flowisentropic(M=3)
    (3.0, 0.35714285714285715, 0.027223683703862824, 0.076226314370815895,
    4.2345679012345689)
    >>> flowisentropic(gamma=1.4, sup=1.6)
    (1.9352576078182122, 0.57174077399894296, 0.14131786852470815,
    0.24717122680666009, 1.6000000000000001)
    >>> flowisentropic(T=sp.linspace(0, 1, 100))
    (array, array, array, array, array)
    """

    #parse the input
    gamma, flow, mtype, itype = _flowinput(flow)

    #calculate gamma-ratios for use in the equations
    a = (gamma + 1) / 2
    b = (gamma - 1) / 2
    c = a / (gamma - 1)

    #preshape mach array
    M = sp.empty(flow.shape, sp.float64)

    #use the isentropic relations to solve for the mach number
    if mtype in ["mach", "m"]:
        if (flow < 0).any() or not sp.isreal(flow).all():
            raise Exception("Mach number inputs must be real numbers" \
                " greater than or equal to 0.")
        M = flow
    elif mtype in ["temp", "t"]:
        if (flow < 0).any() or (flow > 1).any():
            raise Exception("Temperature ratio inputs must be real numbers" \
                " 0 <= T <= 1.")
        M[flow == 0] = sp.inf
        M[flow != 0] = sp.sqrt(
            (1 / b[flow != 0]) * (flow[flow != 0]**(-1) - 1))
    elif mtype in ["pres", "p"]:
        if (flow < 0).any() or (flow > 1).any():
            raise Exception("Pressure ratio inputs must be real numbers" \
                " 0 <= P <= 1.")
        M[flow == 0] = sp.inf
        M[flow != 0] = sp.sqrt((1/b[flow != 0]) * \
            (flow[flow != 0]**((gamma[flow != 0]-1)/-gamma[flow != 0]) - 1))
    elif mtype in ["dens", "d", "rho"]:
        if (flow < 0).any() or (flow > 1).any():
            raise Exception("Density ratio inputs must be real numbers" \
                " 0 <= rho <= 1.")
        M[flow == 0] = sp.inf
        M[flow != 0] = sp.sqrt((1/b[flow != 0]) * \
            (flow[flow != 0]**((gamma[flow != 0]-1)/-1) - 1))
    elif mtype in ["sub", "sup"]:
        if (flow < 1).any():
            raise Exception("Area ratio inputs must be real numbers greater" \
                " than or equal to 1.")
        M[:] = 0.2 if mtype == "sub" else 1.8
        for _ in xrange(_AETB_iternum):
            K = M**2
            f = -flow + a**(-c) * ((1 + b * K)**c) / M  #mach-area relation
            g = a**(-c) * (
                (1 + b * K)**(c - 1)) * (b * (2 * c - 1) * K - 1) / K  #deriv
            M = M - (f / g)  #Newton-Raphson
        M[flow == 1] = 1
        M[sp.isinf(flow)] = sp.inf
    else:
        raise Exception("Keyword input must be an acceptable string to" \
            " select input parameter.")

    d = 1 + b * M**2

    T = d**(-1)
    P = d**(-gamma / (gamma - 1))
    rho = d**(-1 / (gamma - 1))

    area = sp.empty(M.shape, sp.float64)
    r = sp.logical_and(M != 0, sp.isfinite(M))
    area[r] = a[r]**(-c[r]) * d[r]**c[r] / M[r]
    area[sp.logical_not(r)] = sp.inf

    return from_ndarray(itype, M, T, P, rho, area)
示例#11
0
def flowprandtlmeyer(**flow):
    """
    Prandtl-Meyer function for expansion waves.
    
    This function accepts a given set of specific heat ratios and
    an input of either Mach number, Mach angle or Prandtl-Meyer angle.
    Inputs can be a single scalar or an array_like data structure.

    Parameters
    ----------
    gamma : array_like, optional
        Specific heat ratio. Values must be greater than 1.
    M : array_like
        Mach number. Values must be greater than or equal to 1.
    nu : array_like
        Prandtl-Meyer angle [degrees]. Values must be
        0 <= M <= 90*(sqrt((g+1)/(g-1))-1).
    mu : array_like
        Mach angle [degrees]. Values must be 0 <= M <= 90.
    
    Returns
    -------
    out : (M, nu, mu)
        Tuple of Mach number, Prandtl-Meyer angle, Mach angle.
    
    Examples
    --------
    >>> flowprandtlmeyer(M=5)
    (5.0, 76.920215508538789, 11.536959032815489)
    """

    #parse the input
    gamma, flow, mtype, itype = _flowinput(flow)

    #calculate gamma-ratios for use in the equations
    l = sp.sqrt((gamma - 1) / (gamma + 1))

    #preshape mach array
    M = sp.empty(flow.shape, sp.float64)

    #use prandtl-meyer relation to solve for the mach number
    if mtype in ["mach", "m"]:
        if (flow < 1).any():
            raise Exception("Mach number inputs must be real numbers greater" \
                " than or equal to 1.")
        M = flow
    elif mtype in ["mu", "machangle"]:
        if (flow < 0).any() or (flow > 90).any():
            raise Exception("Mach angle inputs must be real numbers" \
                " 0 <= M <= 90.")
        M = 1 / sp.sin(sp.deg2rad(flow))
    elif mtype in ["nu", "pm", "pmangle"]:
        if (flow < 0).any() or (flow > 90 * ((1 / l) - 1)).any():
            raise Exception("Prandtl-Meyer angle inputs must be real" \
                " numbers 0 <= M <= 90*(sqrt((g+1)/(g-1))-1).")
        M[:] = 2  #initial guess for the solution
        for _ in xrange(_AETB_iternum):
            b = sp.sqrt(M**2 - 1)
            f = -sp.deg2rad(flow) + (1 / l) * sp.arctan(l * b) - sp.arctan(b)
            g = b * (1 - l**2) / (M * (1 + (l**2) * (b**2)))  #derivative
            M = M - (f / g)  #Newton-Raphson
    else:
        raise Exception("Keyword input must be an acceptable string to" \
            " select input parameter.")

    #normal shock relations
    b = sp.sqrt(M**2 - 1)
    V = (1 / l) * sp.arctan(l * b) - sp.arctan(b)
    U = sp.arcsin(1 / M)

    return from_ndarray(itype, M, sp.rad2deg(V), sp.rad2deg(U))
示例#12
0
def flownormalshock(**flow):
    """
    Evaluate the normal shock wave relations with any flow variable.
    
    This function accepts a given set of specific heat ratios and a
    single input of normal shock wave flow variables. Inputs can be
    a scalar or an array_like data structure.

    Parameters
    ----------
    gamma : array_like, optional
        Specific heat ratio. Values must be greater than 1.
    M : array_like
        Upstream Mach number. Values must be greater than or equal to 1.
    M2 : array_like
        Downstream Mach number. Values must be
        SQRT((GAMMA-1)/(2*GAMMA)) <= M <= 1.
    T : array_like
        Temperature ratio T2/T1. Values must be greater than or equal to 1.
    P : array_like
        Pressure ratio P2/P1. Values must be greater than or equal to 1.
    rho : array_like
        Density ratio rho2/rho1. Values must be
        1 <= rho <= (GAMMA+1)/(GAMMA-1).
    P0 : array_like
        Total pressure ratio P02/P01. Values must be 0 <= P0 <= 1.
    Pitot : array_like
        Rayleigh-Pitot ratio P02/P1. Values must be greater than or equal
        to ((GAMMA+1)/2)**(-GAMMA/(GAMMA+1)).
    
    Returns
    -------
    out :  (M, M2, T, P, rho, P0, Pitot)
        Tuple of upstream Mach number, downstream Mach number, temperature
        ratio, pressure ratio, density ratio, total pressure ratio,
        rayleigh-pitot ratio.
        
    Notes
    -----
    This function accepts one and only one of the normal shock flow
    variables. It will raise an Exception when more than one input
    is given.
    
    Examples
    --------
    >>> flownormalshock(M=2)
    (2.0, 0.57735026918962573, 1.6874999999999998, 4.5, 2.666666666666667,
    0.72087386148474542, 5.640440812823317)
    >>> flownormalshock(gamma=1.4, Pitot=3.4)
    (1.4964833298836788, 0.70233741753226209, 1.3178766042516246,
    2.4460394160563674, 1.8560458605647578, 0.93089743233402389, 3.3999999999999977)
    >>> flownormalshock(M2=[0.5, 0.6, 0.7])
    (list, list, list, list, list, list, list)
    """

    #parse the input
    gamma, flow, mtype, itype = _flowinput(flow)

    #calculate gamma-ratios for use in the equations
    a = (gamma + 1) / 2
    b = (gamma - 1) / 2
    c = gamma / (gamma - 1)

    #preshape mach array
    M = sp.empty(flow.shape, sp.float64)

    #use the normal shock relations to solve for the mach number
    if mtype in ["mach", "m1", "m"]:
        if (flow < 1).any():
            raise Exception("Mach number inputs must be real numbers" \
                " greater than or equal to 1.")
        M = flow
    elif mtype in ["down", "mach2", "m2", "md"]:
        lowerbound = sp.sqrt((gamma - 1) / (2 * gamma))
        if (flow < lowerbound).any() or (flow > 1).any():
            raise Exception("Mach number downstream inputs must be real" \
                " numbers SQRT((GAMMA-1)/(2*GAMMA)) <= M <= 1.")
        M[flow <= lowerbound] = sp.inf
        M[flow > lowerbound] = sp.sqrt(
            (1 + b * flow**2) / (gamma * flow**2 - b))
    elif mtype in ["pres", "p"]:
        if (flow < 1).any() or not sp.isreal(flow).all():
            raise Exception("Pressure ratio inputs must be real numbers" \
                " greater than or equal to 1.")
        M = sp.sqrt(((flow - 1) * (gamma + 1) / (2 * gamma)) + 1)
    elif mtype in ["dens", "d", "rho"]:
        upperbound = (gamma + 1) / (gamma - 1)
        if (flow < 1).any() or (flow > upperbound).any():
            raise Exception("Density ratio inputs must be real numbers" \
                " 1 <= rho <= (GAMMA+1)/(GAMMA-1).")
        M[flow >= upperbound] = sp.inf
        M[flow < upperbound] = sp.sqrt(2 * flow /
                                       (1 + gamma + flow - flow * gamma))
    elif mtype in ["temp", "t"]:
        if (flow < 1).any():
            raise Exception("Temperature ratio inputs must be real numbers" \
                " greater than or equal to 1.")
        B = b + gamma / a - gamma * b / a - flow * a
        M = sp.sqrt((-B + sp.sqrt(B**2 - \
            4*b*gamma*(1-gamma/a)/a)) / (2*gamma*b/a))
    elif mtype in ["totalp", "p0"]:
        if (flow < 0).any() or (flow > 1).any():
            raise Exception("Total pressure ratio inputs must be real" \
                " numbers 0 <= P0 <= 1.")
        M[:] = 2.0  #initial guess for the solution
        for _ in xrange(_AETB_iternum):
            f = -flow + (1 + (gamma/a)*(M**2 - 1))**(1-c) \
                * (a*M**2 / (1 + b*M**2))**c
            g = 2*M*(a*M**2 / (b*M**2 + 1))**(c-1)*((gamma* \
                (M**2-1))/a + 1)**(-c)*(a*c + gamma*(-c*(b*M**4 + 1) \
                + b*M**4 + M**2)) / (b*M**2 + 1)**2
            M = M - (f / g)  #Newton-Raphson
        M[flow == 0] = sp.inf
    elif mtype in ["pito", "pitot", "rp"]:
        lowerbound = a**c
        if (flow < lowerbound).any():
            raise Exception("Rayleigh-Pitot ratio inputs must be real" \
                " numbers greater than or equal to ((G+1)/2)**(-G/(G+1)).")
        M[:] = 5.0  #initial guess for the solution
        K = a**(2 * c - 1)
        for _ in xrange(_AETB_iternum):
            f = -flow + K * M**(2 * c) / (gamma * M**2 - b)**(
                c - 1)  #Rayleigh-Pitot
            g = 2 * K * M**(2 * c - 1) * (gamma * M**2 -
                                          b)**(-c) * (gamma * M**2 - b * c)
            M = M - (f / g)  #Newton-Raphson
    else:
        raise Exception("Keyword input must be an acceptable string to" \
            " select input parameter.")

    #normal shock relations
    M2 = sp.sqrt((1 + b * M**2) / (gamma * M**2 - b))
    rho = ((gamma + 1) * M**2) / (2 + (gamma - 1) * M**2)
    P = 1 + (M**2 - 1) * gamma / a
    T = P / rho
    P0 = P**(1 - c) * rho**(c)
    P1 = a**(2 * c - 1) * M**(2 * c) / (gamma * M**2 - b)**(c - 1)

    #handle infinite mach
    M2[M == sp.inf] = sp.sqrt(
        (gamma[M == sp.inf] - 1) / (2 * gamma[M == sp.inf]))
    T[M == sp.inf] = sp.inf
    P[M == sp.inf] = sp.inf
    rho[M == sp.inf] = (gamma[M == sp.inf] + 1) / (gamma[M == sp.inf] - 1)
    P0[M == sp.inf] = 0
    P1[M == sp.inf] = sp.inf

    return from_ndarray(itype, M, M2, T, P, rho, P0, P1)