Source code for pyBADA.atmosphere

"""Atmosphere module."""

import numpy as np

from pyBADA import constants as const
from pyBADA import conversions as conv
from pyBADA import utils


def _theta_core(arr_h, arr_dT):
    """Core formula only — both h and dT are already broadcasted arrays."""
    return np.where(
        arr_h < const.h_11,
        1 - const.temp_h * arr_h / const.temp_0 + arr_dT / const.temp_0,
        (const.temp_11 + arr_dT) / const.temp_0,
    )


[docs] def theta(h, DeltaTemp): """ Normalized temperature according to the ISA model, vectorized for xarray.DataArray, pandas Series/DataFrame, and numpy arrays/scalars. :param h: Altitude in meters (m). Can be scalar, numpy array, pandas Series/DataFrame, or xarray.DataArray. :param DeltaTemp: Deviation from ISA temperature in Kelvin (K). Same type as h. :type h: float or array-like or xarray.DataArray or pandas Series/DataFrame :type DeltaTemp: float or array-like or xarray.DataArray or pandas Series/DataFrame :returns: Normalized temperature [-]. """ arr_h = utils._extract(h) arr_dT = utils._extract(DeltaTemp) arr_h_b, arr_dT_b = utils._broadcast(arr_h, arr_dT) core = _theta_core(arr_h_b, arr_dT_b) return utils._wrap(core, original=h)
def _delta_core(arr_h, arr_dT): """ Core normalized pressure computation using numpy arrays. Applies the ISA model with tropopause correction. Requires arr_h and arr_dT; computes arr_theta internally. :param arr_h: Altitude array (np.ndarray or scalar). :param arr_dT: ISA temperature deviation array (np.ndarray or scalar). :returns: Normalized pressure ratio array (np.ndarray or scalar). """ arr_theta = _theta_core(arr_h, arr_dT) exponent = const.g / (const.temp_h * const.R) base = arr_theta - (arr_dT / const.temp_0) p = np.power(base, exponent) strato_factor = np.exp( -const.g / (const.R * const.temp_11) * (arr_h - const.h_11) ) return np.where(arr_h <= const.h_11, p, p * strato_factor)
[docs] def delta(h, DeltaTemp): """ Normalized pressure according to the ISA model, vectorized for xarray.DataArray, pandas Series/DataFrame, and numpy arrays/scalars. :param h: Altitude in meters (m). Can be scalar, numpy array, pandas Series/DataFrame, or xarray.DataArray. :param DeltaTemp: Deviation from ISA temperature in Kelvin (K). Same type as h. :type h: float or array-like or xarray.DataArray or pandas Series/DataFrame :type DeltaTemp: float or array-like or xarray.DataArray or pandas Series/DataFrame :returns: Normalized pressure [-]. """ arr_h = utils._extract(h) arr_dT = utils._extract(DeltaTemp) arr_h_b, arr_dT_b = utils._broadcast(arr_h, arr_dT) core = _delta_core(arr_h_b, arr_dT_b) return utils._wrap(core, original=h)
def _sigma_core(arr_theta, arr_delta): """ Core normalized density computation from precomputed arrays """ return ( (arr_delta * const.p_0) / (arr_theta * const.temp_0 * const.R) / const.rho_0 )
[docs] def sigma(h=None, DeltaTemp=None, theta=None, delta=None): """Normalized density according to ISA, vectorized for xarray.DataArray, pandas Series/DataFrame, and numpy arrays/scalars. You can either provide: - `h` (pressure altitude) and `DeltaTemp` (temperature deviation) to compute normalized temperature and pressure internally; - Or precomputed `theta` (normalized temperature) and `delta` (normalized pressure) directly to avoid recomputation. :param h: pressure altitude AMSL [m], required if `theta` is None. :type h: float or array-like or xarray.DataArray or pandas Series/DataFrame :param DeltaTemp: Temperature deviation from ISA at sea level [K], required if `theta` is None. :type DeltaTemp: float or array-like or xarray.DataArray or pandas Series/DataFrame :param theta: Precomputed normalized temperature [-], optional. :type theta: float or array-like or xarray.DataArray or pandas Series/DataFrame :param delta: Precomputed normalized pressure [-], optional. :type delta: float or array-like or xarray.DataArray or pandas Series/DataFrame :returns: Normalized air density [-] """ if theta is not None and delta is not None: arr_t = utils._extract(theta) arr_d = utils._extract(delta) arr_t_b, arr_d_b = utils._broadcast(arr_t, arr_d) core = _sigma_core(arr_t_b, arr_d_b) return utils._wrap(core, original=theta) elif h is not None or DeltaTemp is not None: arr_h = utils._extract(h) arr_dT = utils._extract(DeltaTemp) arr_h_b, arr_dT_b = utils._broadcast(arr_h, arr_dT) arr_t = _theta_core(arr_h_b, arr_dT_b) arr_d = _delta_core(arr_h_b, arr_dT_b) core = _sigma_core(arr_t, arr_d) return utils._wrap(core, original=h) else: raise ValueError( "Either provide both h & DeltaTemp, or theta & delta." )
def _aSound_core(arr_theta): """ Core speed-of-sound computation from normalized temperature arrays """ return np.sqrt(const.Agamma * const.R * arr_theta * const.temp_0)
[docs] def aSound(theta): """Calculates the speed of sound based on the normalized air temperature. :param theta: Normalized temperature [-]. Can be scalar, numpy array, pandas Series/DataFrame, or xarray.DataArray. :returns: Speed of sound in meters per second (m/s) """ arr = utils._extract(theta) core = _aSound_core(arr) return utils._wrap(core, original=theta)
def _mach2Tas_core(arr_mach, arr_theta): """ Core true airspeed computation """ return arr_mach * _aSound_core(arr_theta) def _tas2Mach_core(arr_v, arr_theta): """ Core Mach number computation from true airspeed and normalized temperature """ return arr_v / _aSound_core(arr_theta)
[docs] def mach2Tas(Mach, theta): """Converts Mach number to true airspeed (TAS), vectorized for xarray.DataArray, pandas Series/DataFrame, and numpy arrays/scalars. :param Mach: Mach number [-]. Can be scalar, numpy array, pandas Series/DataFrame, or xarray.DataArray. :param theta: Normalized air temperature [-]. Same type as Mach. :type Mach: float or array-like or xarray.DataArray or pandas Series/DataFrame :type theta: float or array-like or xarray.DataArray or pandas Series/DataFrame :returns: True airspeed in meters per second (m/s). """ Mach_b, theta_b = utils._broadcast(Mach, theta) return utils._vectorized_wrapper(_mach2Tas_core, Mach_b, theta_b)
[docs] def tas2Mach(v, theta): """Converts true airspeed (TAS) to Mach number, vectorized for xarray.DataArray, pandas Series/DataFrame, and numpy arrays/scalars. :param v: True airspeed in meters per second (m/s). Can be scalar, numpy array, pandas Series/DataFrame, or xarray.DataArray. :param theta: Normalized air temperature [-]. Same type as v. :type v: float or array-like or xarray.DataArray or pandas Series/DataFrame :type theta: float or array-like or xarray.DataArray or pandas Series/DataFrame :returns: Mach number [-]. """ v_b, theta_b = utils._broadcast(v, theta) return utils._vectorized_wrapper(_tas2Mach_core, v_b, theta_b)
def _tas2Cas_core(arr_tas, arr_delta, arr_sigma): """ Core calibrated airspeed computation: Uses compressibility correction formula. """ rho = arr_sigma * const.rho_0 p = arr_delta * const.p_0 A = np.power(1 + const.Amu * rho * arr_tas**2 / (2 * p), 1 / const.Amu) - 1 B = np.power(1 + arr_delta * A, const.Amu) - 1 return np.sqrt(2 * const.p_0 * B / (const.Amu * const.rho_0)) def _cas2Tas_core(arr_cas, arr_delta, arr_sigma): """ Core true airspeed from calibrated airspeed: Inverts compressibility corrections. """ rho = arr_sigma * const.rho_0 p = arr_delta * const.p_0 A = ( np.power( 1 + const.Amu * const.rho_0 * arr_cas**2 / (2 * const.p_0), 1 / const.Amu, ) - 1 ) B = np.power(1 + (1 / arr_delta) * A, const.Amu) - 1 return np.sqrt(2 * p * B / (const.Amu * rho))
[docs] def tas2Cas(tas, delta, sigma): """Converts true airspeed (TAS) to calibrated airspeed (CAS), vectorized for xarray.DataArray, pandas Series/DataFrame, and numpy arrays/scalars. :param tas: True airspeed in meters per second (m/s). Can be scalar, numpy array, pandas Series/DataFrame, or xarray.DataArray. :param delta: Normalized air pressure [-]. Same type as tas. :param sigma: Normalized air density [-]. Same type as tas. :type tas: float or array-like or xarray.DataArray or pandas Series/DataFrame :type delta: float or array-like or xarray.DataArray or pandas Series/DataFrame :type sigma: float or array-like or xarray.DataArray or pandas Series/DataFrame :returns: Calibrated airspeed in meters per second (m/s) """ tas_b, delta_b, sigma_b = utils._broadcast(tas, delta, sigma) return utils._vectorized_wrapper(_tas2Cas_core, tas_b, delta_b, sigma_b)
[docs] def cas2Tas(cas, delta, sigma): """Converts calibrated airspeed (CAS) to true airspeed (TAS), vectorized for xarray.DataArray, pandas Series/DataFrame, and numpy arrays/scalars. :param cas: Calibrated airspeed in meters per second (m/s). Can be scalar, numpy array, pandas Series/DataFrame, or xarray.DataArray. :param delta: Normalized air pressure [-]. Same type as cas. :param sigma: Normalized air density [-]. Same type as cas. :type cas: float or array-like or xarray.DataArray or pandas Series/DataFrame :type delta: float or array-like or xarray.DataArray or pandas Series/DataFrame :type sigma: float or array-like or xarray.DataArray or pandas Series/DataFrame :returns: True airspeed in meters per second (m/s). """ cas_b, delta_b, sigma_b = utils._broadcast(cas, delta, sigma) return utils._vectorized_wrapper(_cas2Tas_core, cas_b, delta_b, sigma_b)
def _mach2Cas_core(arr_mach, arr_theta, arr_delta, arr_sigma): """ Core conversion from Mach to calibrated airspeed: Compute TAS then CAS core. """ tas = _mach2Tas_core(arr_mach, arr_theta) return _tas2Cas_core(tas, arr_delta, arr_sigma) def _cas2Mach_core(arr_cas, arr_theta, arr_delta, arr_sigma): """ Core conversion from calibrated airspeed to Mach: Compute TAS then Mach core. """ tas = _cas2Tas_core(arr_cas, arr_delta, arr_sigma) return _tas2Mach_core(tas, arr_theta)
[docs] def mach2Cas(Mach, theta, delta, sigma): """Converts Mach number to calibrated airspeed (CAS), vectorized for xarray.DataArray, pandas Series/DataFrame, and numpy arrays/scalars. :param Mach: Mach number [-]. Can be scalar, numpy array, pandas Series/DataFrame, or xarray.DataArray. :param theta: Normalized air temperature [-]. Same type as Mach. :param delta: Normalized air pressure [-]. Same type as Mach. :param sigma: Normalized air density [-]. Same type as Mach. :type Mach: float or array-like or xarray.DataArray or pandas Series/DataFrame :type theta: float or array-like or xarray.DataArray or pandas Series/DataFrame :type delta: float or array-like or xarray.DataArray or pandas Series/DataFrame :type sigma: float or array-like or xarray.DataArray or pandas Series/DataFrame :returns: Calibrated airspeed in meters per second (m/s). """ Mach_b, theta_b, delta_b, sigma_b = utils._broadcast( Mach, theta, delta, sigma ) return utils._vectorized_wrapper( _mach2Cas_core, Mach_b, theta_b, delta_b, sigma_b )
[docs] def cas2Mach(cas, theta, delta, sigma): """Converts calibrated airspeed (CAS) to Mach number, vectorized for xarray.DataArray, pandas Series/DataFrame, and numpy arrays/scalars. :param cas: Calibrated airspeed in meters per second (m/s). Can be scalar, numpy array, pandas Series/DataFrame, or xarray.DataArray. :param theta: Normalized air temperature [-]. Same type as cas. :param delta: Normalized air pressure [-]. Same type as cas. :param sigma: Normalized air density [-]. Same type as cas. :type cas: float or array-like or xarray.DataArray or pandas Series/DataFrame :type theta: float or array-like or xarray.DataArray or pandas Series/DataFrame :type delta: float or array-like or xarray.DataArray or pandas Series/DataFrame :type sigma: float or array-like or xarray.DataArray or pandas Series/DataFrame :returns: Mach number [-]. """ cas_b, theta_b, delta_b, sigma_b = utils._broadcast( cas, theta, delta, sigma ) return utils._vectorized_wrapper( _cas2Mach_core, cas_b, theta_b, delta_b, sigma_b )
def _pressureAltitude_core(arr_p, QNH): """ Core pressure altitude computation using ISA: """ part1 = (const.temp_0 / const.temp_h) * ( 1 - np.power(arr_p / QNH, const.R * const.temp_h / const.g) ) part2 = const.h_11 + (const.R * const.temp_11 / const.g) * np.log( const.p_11 / arr_p ) return np.where(arr_p > const.p_11, part1, part2)
[docs] def pressureAltitude(pressure, QNH=101325.0): """Calculates pressure altitude based on air pressure and reference pressure (QNH), vectorized for xarray.DataArray, pandas Series/DataFrame, and numpy arrays/scalars. :param pressure: Air pressure in Pascals (Pa). Can be scalar, numpy array, pandas Series/DataFrame, or xarray.DataArray. :param QNH: Reference sea-level pressure in Pascals (Pa). Default 101325 Pa. :type pressure: float or array-like or xarray.DataArray or pandas Series/DataFrame :type QNH: float :returns: Pressure altitude in meters (m). """ arr_pressure = utils._extract(pressure) arr_QNH = utils._extract(QNH) arr_pressure_b, arr_QNH_b = utils._broadcast(arr_pressure, arr_QNH) core = _pressureAltitude_core(arr_pressure_b, arr_QNH_b) return utils._wrap(core, original=pressure)
def _isaTempDev_core(arr_temp, arr_h): """ Core ISA temperature deviation computation, with scalar passthrough for Python numbers and array broadcasting for others. """ return arr_temp - _theta_core(arr_h, np.zeros_like(arr_h)) * const.temp_0
[docs] def ISATemperatureDeviation(temperature, pressureAltitude): """Calculates ISA temperature deviation at a given pressure altitude. Supports Python scalars, numpy arrays, pandas Series/DataFrame, xarray DataArray; handles broadcasting between inputs. :param temperature: Air temperature in Kelvin. :param pressureAltitude: Pressure altitude in meters. :returns: ISA temperature deviation in Kelvin, wrapped to original type. """ h = utils._extract(pressureAltitude) T = utils._extract(temperature) h_b, T_b = utils._broadcast(h, T) core = _isaTempDev_core(T_b, h_b) return utils._wrap(core, original=temperature)
def _crossOver_core(arr_cas, arr_mach): """ Core cross-over altitude computation: Calculates the altitude where CAS and Mach yield the same TAS. """ p_trans = const.p_0 * ( ( np.power( 1 + ((const.Agamma - 1.0) / 2.0) * ((arr_cas / const.a_0) ** 2), 1 / const.Amu, ) - 1.0 ) / ( np.power( 1 + ((const.Agamma - 1.0) / 2.0) * (arr_mach**2), 1 / const.Amu ) - 1.0 ) ) theta_trans = np.power( p_trans / const.p_0, (const.temp_h * const.R) / const.g ) h = np.where( p_trans < const.p_11, const.h_11 - (const.R * const.temp_11 / const.g) * np.log(p_trans / const.p_11), (const.temp_0 / -const.temp_h) * (theta_trans - 1), ) return h
[docs] def crossOver(cas, Mach): """Calculates the cross-over altitude where calibrated airspeed (CAS) and Mach number intersect, vectorized for xarray.DataArray, pandas Series/DataFrame, and numpy arrays/scalars. :param cas: Calibrated airspeed in meters per second (m/s). Can be scalar, numpy array, pandas Series/DataFrame, or xarray.DataArray. :param Mach: Mach number [-]. Same type as cas. :type cas: float or array-like or xarray.DataArray or pandas Series/DataFrame :type Mach: float or array-like or xarray.DataArray or pandas Series/DataFrame :returns: Cross-over altitude in meters (m). """ cas_b, Mach_b = utils._broadcast(cas, Mach) return utils._vectorized_wrapper(_crossOver_core, cas_b, Mach_b)
[docs] def atmosphereProperties(h, DeltaTemp): """ Calculates atmospheric properties: normalized temperature, pressure, and density ratios based on altitude and temperature deviation from ISA. Vectorized for xarray.DataArray, pandas Series/DataFrame, and numpy arrays/scalars. :param h: Altitude in meters (m). Can be scalar, numpy array, pandas Series/DataFrame, or xarray.DataArray. :param DeltaTemp: Deviation from ISA temperature in Kelvin (K). Same type as h. :type h: float or array-like or xarray.DataArray or pandas Series/DataFrame :type DeltaTemp: float or array-like or xarray.DataArray or pandas Series/DataFrame :returns: List of [theta_norm, delta_norm, sigma_norm], each matching the type of input. """ arr_h = utils._extract(h) arr_dT = utils._extract(DeltaTemp) arr_h_b, arr_dT_b = utils._broadcast(arr_h, arr_dT) arr_theta = _theta_core(arr_h_b, arr_dT_b) arr_delta = _delta_core(arr_h_b, arr_dT_b) arr_sigma = _sigma_core(arr_theta, arr_delta) return [ utils._wrap(arr_theta, original=h), utils._wrap(arr_delta, original=h), utils._wrap(arr_sigma, original=h), ]
[docs] def convertSpeed(v, speedType, theta, delta, sigma): """ Calculates Mach, true airspeed (TAS), and calibrated airspeed (CAS) based on input speed and its type. Vectorized for xarray.DataArray, pandas Series/DataFrame, and numpy arrays/scalars. :param v: Airspeed value, depending on the type provided (M, CAS, TAS) [-, kt, kt]. Can be scalar, numpy array, pandas Series/DataFrame, or xarray.DataArray. :param speedType: Type of input speed: "M" (Mach), "CAS", or "TAS". :param theta: Normalized air temperature [-]. Same type as v. :param delta: Normalized air pressure [-]. Same type as v. :param sigma: Normalized air density [-]. Same type as v. :returns: [Mach number, CAS (m/s), TAS (m/s)] each matching the type of input. """ arr_v = utils._extract(v) arr_theta = utils._extract(theta) arr_delta = utils._extract(delta) arr_sigma = utils._extract(sigma) arr_v_b, arr_theta_b, arr_delta_b, arr_sigma_b = utils._broadcast( arr_v, arr_theta, arr_delta, arr_sigma ) if speedType.upper() == "TAS": arr_TAS = conv.kt2ms(arr_v_b) arr_CAS = _tas2Cas_core(arr_TAS, arr_delta_b, arr_sigma_b) arr_M = _tas2Mach_core(arr_TAS, arr_theta_b) elif speedType.upper() == "CAS": arr_CAS = conv.kt2ms(arr_v_b) arr_TAS = _cas2Tas_core(arr_CAS, arr_delta_b, arr_sigma_b) arr_M = _tas2Mach_core(arr_TAS, arr_theta_b) elif speedType.upper() == "M" or speedType.upper() == "MACH": arr_M = arr_v_b arr_CAS = _mach2Cas_core(arr_M, arr_theta_b, arr_delta_b, arr_sigma_b) arr_TAS = _cas2Tas_core(arr_CAS, arr_delta_b, arr_sigma_b) else: raise ValueError( f"Expected speedType 'TAS', 'CAS' or 'M', got: {speedType!r}" ) return [ utils._wrap(arr_M, original=v), utils._wrap(arr_CAS, original=v), utils._wrap(arr_TAS, original=v), ]