Skip to content

Atmo

Atmo

Atmospheric conditions and density calculations

Properties

altitude: Altitude relative to sea level pressure: Unadjusted barometric pressure, a.k.a. station pressure temperature: Temperature humidity: Relative humidity [0% to 100%] powder_temp: Temperature of powder (if different from atmosphere). (Used when Ammo.use_powder_sensitivity is True) density_ratio: Ratio of current density to standard atmospheric density mach: Velocity of sound (Mach 1) for current atmosphere

Parameters:

Name Type Description Default
altitude Optional[Union[float, Distance]]

Altitude relative to sea level

None
pressure Optional[Union[float, Pressure]]

Atmospheric pressure

None
temperature Optional[Union[float, Temperature]]

Atmospheric temperature

None
humidity float

Atmospheric relative humidity [0% to 100%]

0.0
powder_t Optional[Union[float, Temperature]]

Custom temperature of powder different to atmospheric. Used when Ammo.use_powder_sensitivity is True

None
Example

This is how you can create an Atmo

from py_ballisticcalc import Atmo
atmo = Atmo(
    altitude=Unit.Meter(100),
    pressure=Unit.hPa(1000),
    temperature=Unit.Celsius(20),
    humidity=50,
    powder_t=Unit.Celsius(15)
)

Source code in py_ballisticcalc\conditions.py
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
def __init__(self,
             altitude: Optional[Union[float, Distance]] = None,
             pressure: Optional[Union[float, Pressure]] = None,
             temperature: Optional[Union[float, Temperature]] = None,
             humidity: float = 0.0,
             powder_t: Optional[Union[float, Temperature]] = None):
    """
    Create a new Atmo instance with given parameters

    Args:
        altitude: Altitude relative to sea level
        pressure: Atmospheric pressure
        temperature: Atmospheric temperature
        humidity: Atmospheric relative humidity [0% to 100%]
        powder_t: Custom temperature of powder different to atmospheric.
            Used when Ammo.use_powder_sensitivity is True

    Example:
        This is how you can create an Atmo
        ```python
        from py_ballisticcalc import Atmo
        atmo = Atmo(
            altitude=Unit.Meter(100),
            pressure=Unit.hPa(1000),
            temperature=Unit.Celsius(20),
            humidity=50,
            powder_t=Unit.Celsius(15)
        )
        ```
    """
    self._initializing = True
    self._altitude = PreferredUnits.distance(altitude or 0)
    self._pressure = PreferredUnits.pressure(pressure or Atmo.standard_pressure(self.altitude))
    self._temperature = PreferredUnits.temperature(temperature or Atmo.standard_temperature(self.altitude))
    # If powder_temperature not provided we use atmospheric temperature:
    self._powder_temp = PreferredUnits.temperature(powder_t or self.temperature)
    self._t0 = self.temperature >> Temperature.Celsius
    self._p0 = self.pressure >> Pressure.hPa
    self._a0 = self.altitude >> Distance.Foot
    self._mach = Atmo.machF(self._temperature >> Temperature.Fahrenheit)
    self.humidity = humidity
    self._initializing = False
    self.update_density_ratio()

altitude property

altitude: Distance

Altitude relative to sea level

pressure property

pressure: Pressure

Unadjusted barometric pressure, a.k.a. station pressure

temperature property

temperature: Temperature

Local air temperature

humidity property writable

humidity: float

Returns:

Type Description
float

Relative humidity [0% to 100%]

powder_temp property

powder_temp: Temperature

Powder temperature

density_ratio property

density_ratio: float

Ratio of current density to standard atmospheric density

mach property

mach: Velocity

Velocity of sound (Mach 1) for current atmosphere

density_metric property

density_metric: float

Returns:

Type Description
float

density in kg/m^3

density_imperial property

density_imperial: float

Returns:

Type Description
float

density in lb/ft^3

temperature_at_altitude

temperature_at_altitude(altitude: float) -> float

Temperature at altitude interpolated from zero conditions using lapse rate. Args: altitude: ASL in ft Returns: temperature in °C

Source code in py_ballisticcalc\conditions.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def temperature_at_altitude(self, altitude: float) -> float:
    """
    Temperature at altitude interpolated from zero conditions using lapse rate.
    Args:
        altitude: ASL in ft
    Returns:
        temperature in °C
    """
    t = (altitude - self._a0) * cLapseRateKperFoot + self._t0
    if t < Atmo.cLowestTempC:
        t = Atmo.cLowestTempC
        warnings.warn(f"Temperature interpolated from altitude fell below minimum temperature limit.  "
                      f"Model not accurate here.  Temperature bounded at cLowestTempF: {cLowestTempF}°F."
                      , RuntimeWarning)
    return t

update_density_ratio

update_density_ratio() -> None

Updates the density ratio based on current conditions

Source code in py_ballisticcalc\conditions.py
126
127
128
129
130
def update_density_ratio(self) -> None:
    """
    Updates the density ratio based on current conditions
    """
    self._density_ratio = Atmo.calculate_air_density(self._t0, self._p0, self.humidity) / cStandardDensityMetric

pressure_at_altitude

pressure_at_altitude(altitude: float) -> float

Pressure at altitude interpolated from zero conditions using lapse rate. Ref: https://en.wikipedia.org/wiki/Barometric_formula#Pressure_equations Args: altitude: ASL in ft Returns: pressure in hPa

Source code in py_ballisticcalc\conditions.py
164
165
166
167
168
169
170
171
172
173
174
175
def pressure_at_altitude(self, altitude: float) -> float:
    """
    Pressure at altitude interpolated from zero conditions using lapse rate.
    Ref: https://en.wikipedia.org/wiki/Barometric_formula#Pressure_equations
    Args:
        altitude: ASL in ft
    Returns:
        pressure in hPa
    """
    p = self._p0 * math.pow(1 + cLapseRateKperFoot * (altitude - self._a0) / (self._t0 + cDegreesCtoK),
                            cPressureExponent)
    return p

get_density_factor_and_mach_for_altitude

get_density_factor_and_mach_for_altitude(altitude: float) -> Tuple[float, float]

Ref: https://en.wikipedia.org/wiki/Barometric_formula#Density_equations Args: altitude: ASL in units of feet. Note: Altitude above 36,000 ft not modelled this way. Returns: density ratio and Mach 1 (fps) for the specified altitude

Source code in py_ballisticcalc\conditions.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def get_density_factor_and_mach_for_altitude(self, altitude: float) -> Tuple[float, float]:
    """
    Ref: https://en.wikipedia.org/wiki/Barometric_formula#Density_equations
    Args:
        altitude: ASL in units of feet.
            Note: Altitude above 36,000 ft not modelled this way.
    Returns:
        density ratio and Mach 1 (fps) for the specified altitude
    """
    # Within 30 ft of initial altitude use initial values to save compute
    if math.fabs(self._a0 - altitude) < 30:
        mach = self._mach
        density_ratio = self._density_ratio
    else:
        if altitude > 36089:
            warnings.warn("Density request for altitude above troposphere."
                           " Atmospheric model not valid here.", RuntimeWarning)
        t = self.temperature_at_altitude(altitude) + cDegreesCtoK
        mach = Velocity.MPS(Atmo.machK(t)) >> Velocity.FPS
        p = self.pressure_at_altitude(altitude)
        density_delta = ((self._t0 + cDegreesCtoK) * p) / (self._p0 * t)
        density_ratio = self._density_ratio * density_delta
        # # Alternative simplified model:
        # # Ref https://en.wikipedia.org/wiki/Density_of_air#Exponential_approximation
        # # see doc/'Air Density Models.svg' for comparison
        #density_ratio = self._density_ratio * math.exp(-(altitude - self._a0) / 34122)
    return density_ratio, mach

standard_temperature staticmethod

standard_temperature(altitude: Distance) -> Temperature

Note: This model only valid up to troposphere (~36,000 ft). Args: altitude: ASL in units of feet. Returns: ICAO standard temperature for altitude

Source code in py_ballisticcalc\conditions.py
212
213
214
215
216
217
218
219
220
221
222
@staticmethod
def standard_temperature(altitude: Distance) -> Temperature:
    """
    Note: This model only valid up to troposphere (~36,000 ft).
    Args:
        altitude: ASL in units of feet.
    Returns:
        ICAO standard temperature for altitude
    """
    return Temperature.Fahrenheit(cStandardTemperatureF
                                  + (altitude >> Distance.Foot) * cLapseRateImperial)

standard_pressure staticmethod

standard_pressure(altitude: Distance) -> Pressure
This model only valid up to troposphere (~36,000 ft).

Ref: https://en.wikipedia.org/wiki/Barometric_formula#Pressure_equations

Args: altitude: Distance above sea level (ASL) Returns: ICAO standard pressure for altitude

Source code in py_ballisticcalc\conditions.py
224
225
226
227
228
229
230
231
232
233
234
235
236
@staticmethod
def standard_pressure(altitude: Distance) -> Pressure:
    """
    Note: This model only valid up to troposphere (~36,000 ft).
        Ref: https://en.wikipedia.org/wiki/Barometric_formula#Pressure_equations
    Args:
        altitude: Distance above sea level (ASL)
    Returns:
        ICAO standard pressure for altitude
    """
    return Pressure.hPa(cStandardPressureMetric
        * math.pow(1 + cLapseRateMetric * (altitude >> Distance.Meter) / (cStandardTemperatureC + cDegreesCtoK),
                   cPressureExponent))

icao staticmethod

icao(altitude: Union[float, Distance] = 0, temperature: Optional[Temperature] = None, humidity: float = cStandardHumidity) -> Atmo

Note: This model only valid up to troposphere (~36,000 ft). Args: altitude: relative to sea level temperature: air temperature Returns: Atmo instance of standard ICAO atmosphere at given altitude. If temperature not specified uses standard temperature.

Source code in py_ballisticcalc\conditions.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
@staticmethod
def icao(altitude: Union[float, Distance] = 0, temperature: Optional[Temperature] = None, humidity: float = cStandardHumidity) -> 'Atmo':
    """
    Note: This model only valid up to troposphere (~36,000 ft).
    Args:
        altitude: relative to sea level
        temperature: air temperature
    Returns:
        Atmo instance of standard ICAO atmosphere at given altitude.
        If temperature not specified uses standard temperature.
    """
    altitude = PreferredUnits.distance(altitude)
    if temperature is None:
        temperature = Atmo.standard_temperature(altitude)
    pressure = Atmo.standard_pressure(altitude)

    return Atmo(altitude, pressure, temperature, humidity)

machF staticmethod

machF(fahrenheit: float) -> float

Parameters:

Name Type Description Default
fahrenheit float

Fahrenheit temperature

required

Returns: Mach 1 in fps for given temperature

Source code in py_ballisticcalc\conditions.py
258
259
260
261
262
263
264
265
266
267
268
269
270
@staticmethod
def machF(fahrenheit: float) -> float:
    """
    Args:
        fahrenheit: Fahrenheit temperature
    Returns:
        Mach 1 in fps for given temperature
    """
    if fahrenheit < -cDegreesFtoR:
        fahrenheit = cLowestTempF
        warnings.warn(f"Invalid temperature: {fahrenheit}°F. Adjusted to ({cLowestTempF}°F)."
                      , RuntimeWarning)
    return math.sqrt(fahrenheit + cDegreesFtoR) * cSpeedOfSoundImperial

machC staticmethod

machC(celsius: float) -> float

Parameters:

Name Type Description Default
celsius float

Celsius temperature

required

Returns: Mach 1 in m/s for Celsius temperature

Source code in py_ballisticcalc\conditions.py
272
273
274
275
276
277
278
279
280
281
282
283
284
285
@staticmethod
def machC(celsius: float) -> float:
    """
    Args:
        celsius: Celsius temperature
    Returns:
        Mach 1 in m/s for Celsius temperature
    """
    if celsius < -cDegreesCtoK:
        bad_temp = celsius
        celsius = Atmo.cLowestTempC
        warnings.warn(f"Invalid temperature: {bad_temp}°C. Adjusted to ({celsius}°C)."
                      , RuntimeWarning)
    return Atmo.machK(celsius + cDegreesCtoK)

machK staticmethod

machK(kelvin: float) -> float

Parameters:

Name Type Description Default
kelvin float

Kelvin temperature

required

Returns: Mach 1 in m/s for Kelvin temperature

Source code in py_ballisticcalc\conditions.py
287
288
289
290
291
292
293
294
295
@staticmethod
def machK(kelvin: float) -> float:
    """
    Args:
        kelvin: Kelvin temperature
    Returns:
        Mach 1 in m/s for Kelvin temperature
    """
    return math.sqrt(kelvin) * cSpeedOfSoundMetric

calculate_air_density staticmethod

calculate_air_density(t: float, p: float, humidity: float) -> float

Calculate the air density given temperature, pressure, and humidity.

Parameters: t (float): Temperature in degrees Celsius. p (float): Pressure in hPa. humidity (float): The relative humidity as a fraction of max [0%-100%]

Returns:

Name Type Description
float float

Air density in kg/m^3.

Notes: - Divide result by cDensityImperialToMetric to get density in lb/ft^3 - Source: https://www.nist.gov/system/files/documents/calibrations/CIPM-2007.pdf

Source code in py_ballisticcalc\conditions.py
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
@staticmethod
def calculate_air_density(t: float, p: float, humidity: float) -> float:
    """
    Calculate the air density given temperature, pressure, and humidity.

    Parameters:
    t (float): Temperature in degrees Celsius.
    p (float): Pressure in hPa.
    humidity (float): The relative humidity as a fraction of max [0%-100%]

    Returns:
        float: Air density in kg/m^3.

    Notes:
    - Divide result by cDensityImperialToMetric to get density in lb/ft^3
    - Source: https://www.nist.gov/system/files/documents/calibrations/CIPM-2007.pdf
    """
    R = 8.314472  # J/(mol·K), universal gas constant
    M_a = 28.96546e-3  # kg/mol, molar mass of dry air
    M_v = 18.01528e-3  # kg/mol, molar mass of water vapor

    def saturation_vapor_pressure(T):
        # Calculation of saturated vapor pressure according to CIPM 2007
        A = [1.2378847e-5, -1.9121316e-2, 33.93711047, -6.3431645e3]
        return math.exp(A[0] * T ** 2 + A[1] * T + A[2] + A[3] / T)

    def enhancement_factor(p, T):
        # Calculation of enhancement factor according to CIPM 2007
        alpha = 1.00062
        beta = 3.14e-8
        gamma = 5.6e-7
        return alpha + beta * p + gamma * T ** 2

    def compressibility_factor(p, T, x_v):
        # Calculation of compressibility factor according to CIPM 2007
        a0 = 1.58123e-6
        a1 = -2.9331e-8
        a2 = 1.1043e-10
        b0 = 5.707e-6
        b1 = -2.051e-8
        c0 = 1.9898e-4
        c1 = -2.376e-6
        d = 1.83e-11
        e = -0.765e-8

        t = T - cDegreesCtoK
        Z = 1 - (p / T) * (a0 + a1 * t + a2 * t ** 2 + (b0 + b1 * t) * x_v + (c0 + c1 * t) * x_v ** 2) \
            + (p / T) ** 2 * (d + e * x_v ** 2)
        return Z

    # Temperature in Kelvin
    T_K = t + cDegreesCtoK

    # Calculation of saturated vapor pressure and enhancement factor
    p_sv = saturation_vapor_pressure(T_K)
    f = enhancement_factor(p, t)

    # Calculation of partial pressure and mole fraction of water vapor
    p_v = humidity / 100 * f * p_sv
    x_v = p_v / p

    # Calculation of compressibility factor
    Z = compressibility_factor(p, T_K, x_v)

    density = (p * M_a) / (Z * R * T_K) * (1 - x_v * (1 - M_v / M_a))
    return 100 * density

Vacuum

Bases: Atmo

Vacuum atmosphere has zero drag

Source code in py_ballisticcalc\conditions.py
367
368
369
370
371
372
373
def __init__(self, 
             altitude: Optional[Union[float, Distance]] = None,
             temperature: Optional[Union[float, Temperature]] = None):
    super().__init__(altitude, 0, temperature, 0)
    self.cLowestTempC = cDegreesCtoK
    self._pressure = PreferredUnits.pressure(0)
    self._density_ratio = 0

update_density_ratio

update_density_ratio()
Source code in py_ballisticcalc\conditions.py
375
376
def update_density_ratio(self):
    pass