Skip to content

Core

geo

G_0

Standard gravitational acceleration, sea level

RADIUS_EARTH_EQUATORIAL

RADIUS_EARTH_EQUATORIAL: LengthM[float] = 6378137.0

Semi-major axis, Earth, WGS84

RADIUS_EARTH_POLAR

RADIUS_EARTH_POLAR: LengthM[float] = 6356752.3

Semi-minor axis, Earth, WGS84

RADIUS_EARTH_MEAN

RADIUS_EARTH_MEAN: LengthM[float] = 6371008.7714

Mean radius of semi-axes, Earth, WGS84

F_INV

F_INV = 298.257223563

Inverse flattening \(f = \frac{R_\text{equator} - R_\text{pole}}{R_\text{equator}}\)

F

F = 1 / F_INV

Flattening

E2

E2 = 1 - (1 - F) * (1 - F)

Eccentricity, squared

distance

distance(
    lon0: AngleRad,
    lat0: AngleRad,
    lon1: AngleRad,
    lat1: AngleRad,
    *,
    xp: ArrayApiNamespace | None,
) -> LengthM

Returns the Haversine great circle distance between two coordinates.

Source code in src/aerocore/geo.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def distance(
    lon0: t.AngleRad,
    lat0: t.AngleRad,
    lon1: t.AngleRad,
    lat1: t.AngleRad,
    *,
    xp: ArrayApiNamespace | None,
) -> t.LengthM:
    """
    Returns the [Haversine great circle distance](https://en.wikipedia.org/wiki/Haversine_formula)
    between two coordinates.
    """
    d_lon = lon1 - lon0
    d_lat = lat1 - lat0

    xpr = math if xp is None else xp
    a = (xpr.sin(d_lat / 2)) ** 2 + (
        xpr.cos(lat0) * xpr.cos(lat1) * xpr.sin(d_lon / 2)
    ) ** 2
    c = 2 * xpr.asin(xpr.sqrt(a))

    return RADIUS_EARTH_MEAN * c

bearing

bearing(
    lon0: AngleRad,
    lat0: AngleRad,
    lon1: AngleRad,
    lat1: AngleRad,
    *,
    xp: ArrayApiNamespace | None,
) -> AngleRad

Returns the initial bearing (from origin to destination) along a great-circle.

Returns:

Type Description
AngleRad

initial bearing, radians, [\(-\pi\), \(\pi\)], clockwise from north

Source code in src/aerocore/geo.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def bearing(
    lon0: t.AngleRad,
    lat0: t.AngleRad,
    lon1: t.AngleRad,
    lat1: t.AngleRad,
    *,
    xp: ArrayApiNamespace | None,
) -> t.AngleRad:
    """
    Returns the initial bearing (from origin to destination) along a
    [great-circle](https://en.wikipedia.org/wiki/Great_circle).

    :return: initial bearing, radians, [$-\\pi$, $\\pi$], clockwise from north
    """
    d_lon = lon1 - lon0

    xpr = math if xp is None else xp
    y = xpr.sin(d_lon) * xpr.cos(lat1)
    x = xpr.cos(lat0) * xpr.sin(lat1) - (
        xpr.sin(lat0) * xpr.cos(lat1) * xpr.cos(d_lon)
    )

    return xpr.atan2(y, x)

T

T = TypeVar('T')

Point2D

Bases: NamedTuple, Generic[T]

A point in 2D space

x

x: T

y

y: T

Point3D

Bases: NamedTuple, Generic[T]

A point in 3D space

x

x: T

y

y: T

z

z: T

lla_to_ecef

lla_to_ecef(
    lon: AngleRad,
    lat: AngleRad,
    alt: GeometricAltitudeM,
    *,
    xp: ArrayApiNamespace | None,
) -> Point3D[LengthM]

Converts geodetic coordinates to Earth-centered, Earth-fixed coordinates. Equivalent to epsg:4979 +proj=cart +ellps=WGS84 in PROJ.

Returns:

Type Description
Point3D[LengthM]

(x, y, z) coordinates

Source code in src/aerocore/geo.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
def lla_to_ecef(
    lon: t.AngleRad,
    lat: t.AngleRad,
    alt: t.GeometricAltitudeM,
    *,
    xp: ArrayApiNamespace | None,
) -> Point3D[t.LengthM]:
    """
    Converts geodetic coordinates to Earth-centered, Earth-fixed coordinates.
    Equivalent to `epsg:4979 +proj=cart +ellps=WGS84` in PROJ.

    :return: (x, y, z) coordinates
    """
    xpr = math if xp is None else xp
    v = RADIUS_EARTH_MEAN / xpr.sqrt(1 - E2 * xpr.sin(lat) * xpr.sin(lat))

    x = (v + alt) * xpr.cos(lat) * xpr.cos(lon)
    y = (v + alt) * xpr.cos(lat) * xpr.sin(lon)
    z = (v * (1 - E2) + alt) * xpr.sin(lat)

    return Point3D(x, y, z)

ecef_to_enu

ecef_to_enu(
    dx: DeltaLengthM,
    dy: DeltaLengthM,
    dz: DeltaLengthM,
    lon_ref: AngleRad,
    lat_ref: AngleRad,
    *,
    xp: ArrayApiNamespace | None,
) -> Point3D[LengthM]

Converts Earth-centered, Earth-fixed coordinates (x, y, z with respect to a reference point) to East-North-Up coordinates.

Returns:

Type Description
Point3D[LengthM]

(east, north, up) coordinates

Source code in src/aerocore/geo.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def ecef_to_enu(
    dx: t.DeltaLengthM,
    dy: t.DeltaLengthM,
    dz: t.DeltaLengthM,
    lon_ref: t.AngleRad,
    lat_ref: t.AngleRad,
    *,
    xp: ArrayApiNamespace | None,
) -> Point3D[t.LengthM]:
    """
    Converts Earth-centered, Earth-fixed coordinates
    (x, y, z with respect to a reference point)
    to East-North-Up coordinates.

    :return: (east, north, up) coordinates
    """
    xpr = math if xp is None else xp
    s_lat = xpr.sin(lat_ref)
    c_lat = xpr.cos(lat_ref)
    s_lon = xpr.sin(lon_ref)
    c_lon = xpr.cos(lon_ref)

    east = -s_lon * dx + c_lon * dy
    north = -s_lat * c_lon * dx - s_lat * s_lon * dy + c_lat * dz
    up = c_lat * c_lon * dx + c_lat * s_lon * dy + s_lat * dz

    return Point3D(east, north, up)

airspeed

Implements common airspeed conversions.

        _________________
        v               v
IAS    CAS --> EAS <-> TAS    GS
            ^           |
            |           |------> q
            |           |
            |           |------> p_t ----> q_c
            |                               |
            |                               v
            |                    compressibility factor f
            |                               |
            |-------------------------------|

The equivalent airspeed \(V_e\) is such that the dynamic pressure at some altitude \(q = \frac{1}{2} \rho V^2\) is the same as the dynamic pressure at sea level ISA conditions. \(q = \frac{1}{2} \rho_0 V_e^2\). EAS and TAS is related through aerocore.airspeed.eas_from_tas.

impact_pressure_from_cas

impact_pressure_from_cas(
    cas: CasMPS, gamma: RatioOfSpecificHeats = GAMMA_DRY_AIR
) -> ImpactPressurePA

Impact pressure, compressible flow

Source code in src/aerocore/airspeed.py
41
42
43
44
45
46
def impact_pressure_from_cas(
    cas: t.CasMPS,
    gamma: t.RatioOfSpecificHeats = GAMMA_DRY_AIR,
) -> t.ImpactPressurePA:
    """Impact pressure, compressible flow"""
    return impact_pressure(cas, RHO_0, P_0, gamma)

impact_pressure_from_cas_behind_normal_shock

impact_pressure_from_cas_behind_normal_shock(
    cas: CasMPS, gamma: RatioOfSpecificHeats = GAMMA_DRY_AIR
) -> ImpactPressurePA

Impact pressure, behind normal shock wave, supersonic flow

Source code in src/aerocore/airspeed.py
49
50
51
52
53
54
def impact_pressure_from_cas_behind_normal_shock(
    cas: t.CasMPS,
    gamma: t.RatioOfSpecificHeats = GAMMA_DRY_AIR,
) -> t.ImpactPressurePA:
    """Impact pressure, behind normal shock wave, supersonic flow"""
    return impact_pressure_behind_normal_shock(cas, A_0, P_0, gamma)

density_factor

density_factor(rho: DensityKGM3) -> DensityFactor
Source code in src/aerocore/airspeed.py
57
58
59
60
def density_factor(
    rho: t.DensityKGM3,
) -> t.DensityFactor:
    return (rho / RHO_0) ** 0.5

eas_from_tas

eas_from_tas(tas: TasMPS, rho: DensityKGM3) -> EasMPS

Converts TAS to EAS

Source code in src/aerocore/airspeed.py
63
64
65
66
67
68
def eas_from_tas(
    tas: t.TasMPS,
    rho: t.DensityKGM3,
) -> t.EasMPS:
    """Converts TAS to EAS"""
    return tas * density_factor(rho)

tas_from_eas

tas_from_eas(eas: EasMPS, rho: DensityKGM3) -> TasMPS

Converts EAS to TAS

Source code in src/aerocore/airspeed.py
71
72
73
74
75
76
def tas_from_eas(
    eas: t.EasMPS,
    rho: t.DensityKGM3,
) -> t.TasMPS:
    """Converts EAS to TAS"""
    return eas / density_factor(rho)

compressibility_factor

Assumption: subsonic speeds

Source code in src/aerocore/airspeed.py
79
80
81
82
83
84
85
86
87
def compressibility_factor(
    qc: t.ImpactPressurePA,
    p: t.StaticPressurePA,
    gamma: t.RatioOfSpecificHeats = GAMMA_DRY_AIR,
) -> t.CompressibilityFactor:
    """Assumption: subsonic speeds"""
    exponent = (gamma - 1) / gamma
    inner = (qc / p + 1) ** exponent - 1
    return (exponent * p / qc * inner) ** 0.5

eas_from_cas

eas_from_cas(
    cas: CasMPS,
    p: StaticPressurePA,
    gamma: RatioOfSpecificHeats = GAMMA_DRY_AIR,
) -> EasMPS

Assumption: subsonic speeds

Source code in src/aerocore/airspeed.py
90
91
92
93
94
95
96
97
98
99
def eas_from_cas(
    cas: t.CasMPS,
    p: t.StaticPressurePA,
    gamma: t.RatioOfSpecificHeats = GAMMA_DRY_AIR,
) -> t.EasMPS:
    """Assumption: subsonic speeds"""
    qc = impact_pressure_from_cas(cas)
    f = compressibility_factor(qc, p, gamma)
    f0 = compressibility_factor(qc, P_0, gamma)
    return cas * f / f0

tas_from_cas

tas_from_cas(
    cas: CasMPS, rho: DensityKGM3, p: StaticPressurePA
) -> TasMPS

Assumption: subsonic speeds

Source code in src/aerocore/airspeed.py
102
103
104
105
106
107
108
109
def tas_from_cas(
    cas: t.CasMPS,
    rho: t.DensityKGM3,
    p: t.StaticPressurePA,
) -> t.TasMPS:
    """Assumption: subsonic speeds"""
    eas = eas_from_cas(cas, p)
    return tas_from_eas(eas, rho)

cas_from_tas

cas_from_tas(
    tas: TasMPS,
    rho: DensityKGM3,
    p: StaticPressurePA,
    gamma: RatioOfSpecificHeats = GAMMA_DRY_AIR,
) -> CasMPS

Assumption: subsonic speeds

Source code in src/aerocore/airspeed.py
112
113
114
115
116
117
118
119
120
121
122
123
def cas_from_tas(
    tas: t.TasMPS,
    rho: t.DensityKGM3,
    p: t.StaticPressurePA,
    gamma: t.RatioOfSpecificHeats = GAMMA_DRY_AIR,
) -> t.CasMPS:
    """Assumption: subsonic speeds"""
    eas = eas_from_tas(tas, rho)
    qc = impact_pressure(tas, rho, p, gamma)
    f = compressibility_factor(qc, p, gamma)
    f0 = compressibility_factor(qc, P_0, gamma)
    return eas * f0 / f

bada3

Implementation of the Base of aircraft data (BADA) Family 3.

Revision: 3.12 (No. 14/04/24-44)

Contents:

  • 3 Operational Performance Models
    • 3.1 Atmosphere Model
    • 3.2 Total-Energy Model
    • 3.3 Aircraft Type
    • 3.4 Mass
    • 3.5 Flight Envelope
    • 3.6 Aerodynamics
    • 3.7 Engine Thrust
    • 3.8 Reduced Climb Power
    • 3.9 Fuel Consumption
    • 3.10 Ground Movement
    • 3.11 Summary of Operations Performance Parameters
  • 4 Airline Procedure Models
  • 5 Global Aircraft Parameters
  • 6 File Structure
    • 6.1 File Types
    • 6.2 File Configuration Management
    • 6.3 Synonym File Format
    • 6.4 OPF File Format
    • 6.5 APF File Format
    • 6.6 PTF File Format
    • 6.7 PTD File Format
    • 6.8 BADA.GPF File Format

The following functions are defined elsewhere:

temperature_below_tropopause

temperature_below_tropopause(
    altitude: GeopotentialAltitudeM,
    delta_temperature: DeltaTemperatureK = 0.0,
) -> StaticTemperatureKBelowTropo

Temperature below the tropopause, lapsing linearly with altitude (3.1-13)

Source code in src/aerocore/bada3.py
82
83
84
85
86
87
88
89
def temperature_below_tropopause(
    altitude: t.GeopotentialAltitudeM,
    delta_temperature: t.DeltaTemperatureK = 0.0,
) -> t.StaticTemperatureKBelowTropo:
    """
    Temperature below the tropopause, lapsing linearly with altitude (3.1-13)
    """
    return T_0 + delta_temperature + altitude * BETA_BELOW_TROP

temperature_above_tropopause

temperature_above_tropopause(
    delta_temperature: DeltaTemperatureK,
) -> StaticPressurePABelowTropo

Temperature above the tropopause, isothermal (3.1-15, 3.1-16).

Source code in src/aerocore/bada3.py
92
93
94
95
96
def temperature_above_tropopause(
    delta_temperature: t.DeltaTemperatureK,
) -> t.StaticPressurePABelowTropo:
    """Temperature above the tropopause, isothermal (3.1-15, 3.1-16)."""
    return T_11 + delta_temperature

pressure_below_tropopause

pressure_below_tropopause(
    temperature_below_trop: StaticTemperatureK,
    delta_temperature: DeltaTemperatureK = 0.0,
) -> StaticPressurePA

Pressure below tropopause (3.1-18)

Source code in src/aerocore/bada3.py
 99
100
101
102
103
104
105
106
107
def pressure_below_tropopause(
    temperature_below_trop: t.StaticTemperatureK,
    delta_temperature: t.DeltaTemperatureK = 0.0,
) -> t.StaticPressurePA:
    """Pressure below tropopause (3.1-18)"""
    return P_0 * (
        ((temperature_below_trop - delta_temperature) / T_0)
        ** (-G_0 / (R_SPECIFIC_DRY_AIR * BETA_BELOW_TROP))
    )

pressure_above_tropopause

pressure_above_tropopause(
    altitude: GeopotentialAltitudeM,
    *,
    xp: ArrayApiNamespace | None,
) -> StaticPressurePA

Pressure above tropopause (3.1-20)

Source code in src/aerocore/bada3.py
110
111
112
113
114
115
116
117
118
119
def pressure_above_tropopause(
    altitude: t.GeopotentialAltitudeM, *, xp: ArrayApiNamespace | None
) -> t.StaticPressurePA:
    """Pressure above tropopause (3.1-20)"""
    xpr = math if xp is None else xp
    return P_11 * (
        xpr.exp(
            (altitude - H_BELOW_TROP) * (-G_0 / (R_SPECIFIC_DRY_AIR * T_11))
        )
    )

atmosphere

atmosphere(
    altitude: GeopotentialAltitudeM,
    delta_temperature: DeltaTemperatureK,
    *,
    xp: ArrayApiNamespace | None,
) -> GasState

BADA3 atmospheric model.

Non-standard atmosphere conditions are modelled with the ISA temperature deviation.

Note that this is only valid for 0-20km.

Source code in src/aerocore/bada3.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def atmosphere(
    altitude: t.GeopotentialAltitudeM,
    delta_temperature: t.DeltaTemperatureK,
    *,
    xp: ArrayApiNamespace | None,
) -> GasState:
    """
    BADA3 atmospheric model.

    Non-standard atmosphere conditions are modelled with the
    ISA temperature deviation.

    Note that this is only valid for 0-20km.
    """
    tropo_mask = altitude <= H_BELOW_TROP
    temperature = where(
        tropo_mask,
        temperature_below_tropopause(altitude, delta_temperature),
        lambda: temperature_above_tropopause(delta_temperature),
        xp=xp,
    )
    return GasState(
        temperature=temperature,
        pressure=(
            where(
                tropo_mask,
                pressure_below_tropopause(temperature, delta_temperature),
                lambda: pressure_above_tropopause(altitude, xp=xp),
                xp=xp,
            )
        ),
    )

mach_number

mach_number(
    tas: TasMPS, temperature: StaticTemperatureK
) -> MachNumber

Mach/TAS conversion (3.1-26)

Source code in src/aerocore/bada3.py
156
157
158
159
160
161
162
def mach_number(
    tas: t.TasMPS,
    temperature: t.StaticTemperatureK,
) -> t.MachNumber:
    """Mach/TAS conversion (3.1-26)"""
    a = speed_of_sound(temperature, KAPPA, R_SPECIFIC_DRY_AIR)
    return tas / a

thermo

R

R: GasConstantJMolK[float] = 8.31446261815324

Universal gas constant

M_DRY_AIR

M_DRY_AIR: MolarMassKGMol[float] = 0.028964917

R_SPECIFIC_DRY_AIR

R_SPECIFIC_DRY_AIR: SpecificGasConstantJKGK[float] = (
    287.052874
)

GAMMA_DRY_AIR

GAMMA_DRY_AIR: RatioOfSpecificHeats[float] = 1.4

specific_gas_constant

specific_gas_constant(
    molar_mass: MolarMassKGMol,
) -> SpecificGasConstantJKGK
Source code in src/aerocore/thermo.py
18
19
20
21
def specific_gas_constant(
    molar_mass: t.MolarMassKGMol,
) -> t.SpecificGasConstantJKGK:
    return R / molar_mass

GasState

Bases: NamedTuple

temperature

temperature: StaticTemperatureK

pressure

pressure: StaticPressurePA

density

density(
    specific_gas_constant: SpecificGasConstantJKGK,
) -> DensityKGM3

Density, perfect gas

Source code in src/aerocore/thermo.py
28
29
30
31
32
33
def density(
    self,
    specific_gas_constant: t.SpecificGasConstantJKGK,
) -> t.DensityKGM3:
    """Density, perfect gas"""
    return density(self.temperature, self.pressure, specific_gas_constant)

density

density(
    temperature: StaticTemperatureK,
    pressure: StaticPressurePA,
    specific_gas_constant: SpecificGasConstantJKGK,
) -> DensityKGM3

Density, perfect gas

Source code in src/aerocore/thermo.py
36
37
38
39
40
41
42
def density(
    temperature: t.StaticTemperatureK,
    pressure: t.StaticPressurePA,
    specific_gas_constant: t.SpecificGasConstantJKGK,
) -> t.DensityKGM3:
    """Density, perfect gas"""
    return pressure / (specific_gas_constant * temperature)

speed_of_sound

speed_of_sound(
    temperature: StaticTemperatureK,
    adiabatic_index: RatioOfSpecificHeats,
    specific_gas_constant: SpecificGasConstantJKGK,
) -> SpeedOfSoundMPS

Speed of sound, perfect gas

Source code in src/aerocore/thermo.py
45
46
47
48
49
50
51
def speed_of_sound(
    temperature: t.StaticTemperatureK,
    adiabatic_index: t.RatioOfSpecificHeats,
    specific_gas_constant: t.SpecificGasConstantJKGK,
) -> t.SpeedOfSoundMPS:
    """Speed of sound, perfect gas"""
    return (adiabatic_index * specific_gas_constant * temperature) ** 0.5

dynamic_pressure

dynamic_pressure(
    rho: DensityKGM3, tas: TasMPS
) -> DynamicPressurePA

Dynamic pressure, incompressible flow

Source code in src/aerocore/thermo.py
54
55
56
57
58
59
def dynamic_pressure(
    rho: t.DensityKGM3,
    tas: t.TasMPS,
) -> t.DynamicPressurePA:
    """Dynamic pressure, incompressible flow"""
    return 0.5 * rho * tas**2

total_pressure

Total pressure, compressible flow

Source code in src/aerocore/thermo.py
62
63
64
65
66
67
68
69
70
71
def total_pressure(
    tas: t.TasMPS,
    rho: t.DensityKGM3,
    p: t.StaticPressurePA,
    gamma: t.RatioOfSpecificHeats = GAMMA_DRY_AIR,
) -> t.TotalPressurePA:
    """Total pressure, compressible flow"""
    # NOTE: from bernoulli's formula
    inner = 1 + (gamma - 1) / (2 * gamma) * rho / p * tas**2
    return p * inner ** (gamma / (gamma - 1))

total_pressure_behind_normal_shock

total_pressure_behind_normal_shock(
    tas: TasMPS,
    rho: DensityKGM3,
    p: StaticPressurePA,
    gamma: RatioOfSpecificHeats = GAMMA_DRY_AIR,
) -> TotalPressurePA

Total pressure, behind normal shock wave, supersonic flow

Source code in src/aerocore/thermo.py
74
75
76
77
78
79
80
81
82
83
def total_pressure_behind_normal_shock(
    tas: t.TasMPS,
    rho: t.DensityKGM3,
    p: t.StaticPressurePA,
    gamma: t.RatioOfSpecificHeats = GAMMA_DRY_AIR,
) -> t.TotalPressurePA:
    """Total pressure, behind normal shock wave, supersonic flow"""
    common = rho / p * tas**2
    inner = ((gamma + 1) ** 2 / gamma * common) / (4 * common - 2 * (gamma - 1))
    return (1 + gamma) / (2 * gamma) * rho * tas**2 * inner ** (1 / (gamma - 1))

impact_pressure

Impact pressure, compressible flow

Source code in src/aerocore/thermo.py
86
87
88
89
90
91
92
93
def impact_pressure(
    tas: t.TasMPS,
    rho: t.DensityKGM3,
    p: t.StaticPressurePA,
    gamma: t.RatioOfSpecificHeats = GAMMA_DRY_AIR,
) -> t.ImpactPressurePA:
    """Impact pressure, compressible flow"""
    return total_pressure(tas, rho, p, gamma) - p

impact_pressure_behind_normal_shock

impact_pressure_behind_normal_shock(
    tas: TasMPS,
    a: SpeedOfSoundMPS,
    p: StaticPressurePA,
    gamma: RatioOfSpecificHeats = GAMMA_DRY_AIR,
) -> TotalPressurePA

Impact pressure, behind normal shock wave, supersonic flow

Source code in src/aerocore/thermo.py
 96
 97
 98
 99
100
101
102
103
104
105
106
def impact_pressure_behind_normal_shock(
    tas: t.TasMPS,
    a: t.SpeedOfSoundMPS,
    p: t.StaticPressurePA,
    gamma: t.RatioOfSpecificHeats = GAMMA_DRY_AIR,
) -> t.TotalPressurePA:
    """Impact pressure, behind normal shock wave, supersonic flow"""
    inner = (gamma + 1) ** 2 / (4 * gamma - 2 * (gamma - 1) * (a / tas) ** 2)
    return (
        (1 + gamma) / 2 * (tas / a) ** 2 * p * (inner ** (1 / (gamma - 1)) - 1)
    )