In astronomy air mass is a measure of the amount of atmosphere between the observer and the object being observed. It is a function of the zenith angle (the angle between the line of sight an vertical) and the altitude of the observer. It is defined as the integral of the atmospheric density along the line of sight and is usually expressed relative to the air mass at zenith. Thus, looking straight up gives an air mass of one (regardless of observer's altitude) and viewing at any zenith angle greater than zero gives higher values.

Air mass is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

You will need to integrate (h(a,z,x)) where (h) is the atmospheric density for a given height above sea level, and h(a,z,x) is the height above sea level for a point at distance x along the line of sight. Determining this last function requires some trigonometry.

For this task you can assume:

  •   The density of Earth's atmosphere is proportional to exp(-a/8500 metres)
  •   The Earth is a perfect sphere of radius 6371 km.


Task
  •   Write a function that calculates the air mass for an observer at a given altitude   a   above sea level and zenith angle   z.
  •   Show the air mass for zenith angles 0 to 90 in steps of 5 degrees for an observer at sea level.
  •   Do the same for the   NASA SOFIA infrared telescope,   which has a cruising altitude of 13,700 meters   (about 8.3 miles),
    it flies in a specially retrofitted Boeing 747 about four flights a week.



Translation of: Python
V DEG = 0.017453292519943295769236907684886127134
V RE = 6371000
V dd = 0.001
V FIN = 10000000

F rho(a)
   ‘ the density of air as a function of height above sea level ’
   R exp(-a / 8500.0)

F height(Float a; z, d)
   ‘
    a = altitude of observer
    z = zenith angle (in degrees)
    d = distance along line of sight
   ’
   R sqrt((:RE + a) ^ 2 + d ^ 2 - 2 * d * (:RE + a) * cos((180 - z) * :DEG)) - :RE

F column_density(a, z)
   ‘ integrates density along the line of sight ’
   V (dsum, d) = (0.0, 0.0)
   L d < :FIN
      V delta = max(:dd, (:dd) * d)
      dsum += rho(height(a, z, d + 0.5 * delta)) * delta
      d += delta
   R dsum

F airmass(a, z)
   R column_density(a, z) / column_density(a, 0)

print("Angle           0 m          13700 m\n "(‘-’ * 36))
L(z) (0.<91).step(5)
   print(f:‘{z:3}      {airmass(0, z):12.7}    {airmass(13700, z):12.7}’)
Output:
Angle           0 m          13700 m
 ------------------------------------
  0         1.0000000       1.0000000
  5         1.0038096       1.0038096
 10         1.0153847       1.0153848
 15         1.0351774       1.0351776
 20         1.0639905       1.0639909
 25         1.1030594       1.1030601
 30         1.1541897       1.1541908
 35         1.2199808       1.2199825
 40         1.3041893       1.3041919
 45         1.4123417       1.4123457
 50         1.5528040       1.5528102
 55         1.7387592       1.7387692
 60         1.9921200       1.9921366
 65         2.3519974       2.3520272
 70         2.8953137       2.8953729
 75         3.7958235       3.7959615
 80         5.5388581       5.5392811
 85        10.0789622      10.0811598
 90        34.3298114      34.3666656
Translation of: C
Works with: Ada 2012
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Numerics.Generic_Elementary_Functions;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure Main is
   subtype double is Long_Float;
   package double_io is new Ada.Text_IO.Float_IO (double);
   use double_io;
   package Elementary_Double is new Ada.Numerics.Generic_Elementary_Functions
     (Float_Type => double);
   use Elementary_Double;

   -- degrees to radians
   Deg : constant := 0.017_453_292_519_943_295_769_236_907_684_886_127_134;

   -- Earth radius in meters
   Re : constant := 6_371_000.0;

   -- integrate in this fraction of the distance already covered
   Dd : constant := 0.001;

   -- integrate only to a height of 10000km. effectively infinity
   Fin : constant := 10_000_000.0;

   function rho (a : double) return double is (Exp (-a / 8_500.0));

   function height (a : double; z : double; d : double) return double is
      aa : double := Re + a;
      hh : double :=
        Sqrt (aa * aa + d * d - 2.0 * d * aa * Cos ((180.0 - z) * Deg));
   begin
      return hh - Re;
   end height;

   function column_density (a : double; z : double) return double is
      sum     : double := 0.0;
      d       : double := 0.0;
      d_delta : double;
   begin
      while d < Fin loop
         -- adaptive step size to avoid it taking forever
         d_delta := Dd * d;
         if d_delta < Dd then
            d_delta := Dd;
         end if;
         sum := sum + rho (height (a, z, d + 0.5 * d_delta)) * d_delta;
         d   := d + d_delta;
      end loop;
      return sum;
   end column_density;

   function air_mass (a : double; z : double) return double is
     (column_density (a, z) / column_density (a, 0.0));

   z : double := 0.0;
begin
   Put_Line ("Angle     0 m              13700 m");
   Put_Line ("------------------------------------");
   while z <= 90.0 loop
      Put(Item => Integer(z), Width => 2);
      Put (Item => air_mass (0.0, z), Fore => 8, Aft => 8, Exp => 0);
      Put (Item => air_mass (13_700.0, z), Fore => 8, Aft => 8, Exp => 0);
      New_Line;
      z := z + 5.0;
   end loop;

end Main;
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
Translation of: Pluto – via Algol 68

Tested with Agena 5.0.3 Win32

scope # Air mass - translated from the Pluto sample

    local constant re  := 6371000;  # radius of earth in meters
    local constant dd  := 0.001;    # integrate in this fraction of the distance already covered
    local constant fin := 1e7;      # integrate only to a height of 10000km, effectively infinity

    # returns x degrees expressed in radians
    local constant toRadians := proc( x :: number ) :: number is return Pi * ( x / 180 ) end;

    # returns the maximum of a and b
    local constant rmax := proc( a :: number, b :: number ) :: number
                     is return if a >= b then a else b fi end;

    # The density of air as a function of height above sea level.
    local constant rho := proc( a :: number ) :: number
                    is return exp( -a / 8500 ) end;

    # a = altitude of observer
    # z = zenith angle (in degrees)
    # d = distance along line of sight
    local constant height := proc( a :: number, z :: number, d :: number ) :: number is
          local constant aa := re + a;
          local constant hh := sqrt( aa * aa + d * d - 2 * d * aa * cos( toRadians ( 180 - z ) ) );
    return hh - re
    end; # height

    # Integrates density along the line of sight.
    local constant column_density := proc( a :: number, z :: number  ) :: number is
          local sum := 0;
          local d   := 0;
          while d < fin do
             local constant delta
                  := rmax( dd, dd * d );  # adaptive step size to avoid it taking forever
             sum +:= rho( height( a, z, d + 0.5 * delta ) ) * delta;
             d   +:= delta
          od;
    return sum
    end; # column density

    local constant air_mass := proc( a :: number, z :: number ) :: number is
    return column_density( a, z ) / column_density( a, 0 )
    end; # air_mass

    scope
        print( "Angle     0 m              13700 m"   );
        print( "------------------------------------" );
        for z from 0 to 90 by 5 do
            printf( "%2d      %11.8f",   z, air_mass(     0, z ) );
            printf(    "      %11.8f\n",    air_mass( 13700, z ) )
        od
    end
end
Output:

Same as the Pluto and Algol 68 samples

Translation of: Pluto
BEGIN # Air mass - translated from the Pluto sample #

    # Constants. #
    INT  re  = 6371000;  # radius of earth in meters #
    REAL dd  = 0.001;    # integrate in this fraction of the distance already covered #
    REAL fin = 1e7;      # integrate only to a height of 10000km, effectively infinity #

    # returns x degrees expressed in radians #
    OP TORADIANS = ( REAL x )REAL: pi * ( x / 180 );

    # returns the maximum of a and b #
    PROC rmax = ( REAL a, b )REAL: IF a > b THEN a ELSE b FI;

    # The density of air as a function of height above sea level. #
    PROC rho = ( REAL a )REAL: exp( -a / 8500 );

    # a = altitude of observer         #
    # z = zenith angle (in degrees)    #
    # d = distance along line of sight #
    PROC height = ( REAL a, z, d )REAL:
         BEGIN
            REAL aa = re + a;
            REAL hh = sqrt( aa * aa + d * d - 2 * d * aa * cos( TORADIANS ( 180 - z ) ) );
            hh - re
         END # height # ;

    # Integrates density along the line of sight. #
    PROC column density = ( REAL a, z )REAL:
         BEGIN
            REAL sum := 0;
            REAL d   := 0;
            WHILE d < fin DO
                REAL delta = rmax( dd, dd * d );  # adaptive step size to avoid it taking forever #
                sum +:= rho( height( a, z, d + 0.5 * delta ) ) * delta;
                d   +:= delta
            OD;
            sum
         END # column density # ;

    PROC  air mass = ( REAL a, z )REAL: column density( a, z ) / column density( a, 0 );

    BEGIN
        print( ( "Angle     0 m              13700 m",   newline ) );
        print( ( "------------------------------------", newline ) );
        FOR z FROM 0 BY 5 TO 90 DO
            print( ( whole( z, -2 ), "      ", fixed( air mass(     0, z ), -11, 8 ) ) );
            print( (                 "      ", fixed( air mass( 13700, z ), -11, 8 ), newline ) )
        OD
    END
END
Output:

Same as the Pluto sample.

# syntax: GAWK -f AIR_MASS.AWK
# converted from FreeBASIC
BEGIN {
    dd = 0.001  # integrate in this fraction of the distance already covered
    DEG = 0.017453292519943295769236907684886127134 # degrees to radians
    RE = 6371000 # Earth radius in meters
    print("Angle          0 m      13700 m")
    for (z=0; z<=90; z+=5) {
      printf("%5d %12.8f %12.8f\n",z,am_airmass(0,z),am_airmass(13700,z))
    }
    exit(0)
}
function am_airmass(a,z) {
    return am_column_density(a,z) / am_column_density(a,0)
}
function am_column_density(a,z,  d,delta,sum) { # integrates density along the line of sight
    while (d < 10000000) { # integrate only to a height of 10000km, effectively infinity
      delta = max(dd,(dd)*d) # adaptive step size to avoid it taking forever
      sum += am_rho(am_height(a,z,d+0.5*delta))*delta
      d += delta
    }
    return(sum)
}
function am_height(a,z,d,  aa,hh) {
# a - altitude of observer
# z - zenith angle in degrees
# d - distance along line of sight
    aa = RE + a
    hh = sqrt(aa^2 + d^2 - 2*d*aa*cos((180-z)*DEG))
    return(hh-RE)
}
function am_rho(a) { # density of air as a function of height above sea level
    return exp(-a/8500.0)
}
function max(x,y) { return((x > y) ? x : y) }
Output:
Angle          0 m      13700 m
    0   1.00000000   1.00000000
    5   1.00380963   1.00380965
   10   1.01538466   1.01538475
   15   1.03517744   1.03517765
   20   1.06399053   1.06399093
   25   1.10305937   1.10306005
   30   1.15418974   1.15419083
   35   1.21998076   1.21998246
   40   1.30418931   1.30419190
   45   1.41234169   1.41234567
   50   1.55280404   1.55281025
   55   1.73875921   1.73876915
   60   1.99212000   1.99213665
   65   2.35199740   2.35202722
   70   2.89531368   2.89537287
   75   3.79582352   3.79596149
   80   5.53885809   5.53928113
   85  10.07896219  10.08115981
   90  34.32981136  34.36666557
Translation of: Wren
import ballerina/io;

// constants
const RE  = 6371000f; // radius of earth in meters
const DD  = 0.001;    // integrate in this fraction of the distance already covered
const FIN = 1e7;      // integrate only to a height of 10000km, effectively infinity

function rho(float a) returns float {
    return (-a / 8500.0).exp();
}

function radians(float degrees) returns float {
    return degrees * float:PI / 180.0;
}

// a = altitude of observer
// z = zenith angle (in degrees)
// d = distance along line of sight
function height(float a, float z, float d) returns float {
    float aa = RE + a;
    float hh = (aa * aa + d * d - 2.0 * d * aa * radians(180-z).cos()).sqrt();
    return hh - RE;
}

// Integrates density along the line of sight.
function columnDensity(float a, float z) returns float {
    float sum = 0;
    float d = 0;
    while d < FIN {
        float delta = DD.max(DD * d);  // adaptive step size to avoid it taking forever
        sum += rho(height(a, z, d + 0.5 * delta)) * delta;
        d += delta;
    }
    return sum;
}

function airmass(float a, float z) returns float {
    return columnDensity(a, z) / columnDensity(a, 0);
}

function F(float f, int size, int prec) returns string {
    string s = f.toFixedString(prec);
    return size >= 0 ? s.padStart(size) : s.padEnd(size);
}

public function main() {
    io:println("Angle     0 m              13700 m");
    io:println("------------------------------------");
    float z = 0.0;
    while z <= 90.0 {
        string s1 = F(z, 2, 0);
        string s2 = F(airmass(0, z), 11, 8);
        string s3 = F(airmass(13700, z), 11, 8);
        io:println(`${s1}      ${s2}      ${s3}`);
        z += 5.0;
    }
}
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
Translation of: FreeBASIC
global RE, dd, LIM
RE  = 6371000  #Earth radius in meters
dd  = 0.001    #integrate in this fraction of the distance already covered
LIM = 10000000 #integrate only to a height of 10000km, effectively inLIMity

print "Angle     0 m              13700 m"
print "------------------------------------"
for z = 0 to 90 step 5
    print rjust(z,2); "      "; ljust(airmass(0, z),13,"0"); "      "; ljust(airmass(13700, z),13,"0")
next z
end

function max(a, b)
    if a > b then return a else return b
end function

function rho(a)
    #the density of air as a function of height above sea level
    return exp(-a/8500.0)
end function

function height(a, z, d)
    #a = altitude of observer
    #z = zenith angle (in degrees)
    #d = distance along line of sight
    AA = RE + a
    HH = sqr(AA^2 + d^2 - 2*d*AA*cos(radians(180-z)))
    return HH - RE
end function

function column_density(a, z)
    #integrates density along the line of sight
    sum = 0.0
    d = 0.0
    while d < LIM
        delta = max(dd, (dd)*d)  #adaptive step size to avoid it taking forever:
        sum += rho(height(a, z, d+0.5*delta)) * delta
        d += delta
    end while
    return sum
end function

function airmass(a, z)
    return column_density(a, z) / column_density(a, 0)
end function
#define DEG 0.017453292519943295769236907684886127134  'degrees to radians
#define RE  6371000  'Earth radius in meters
#define dd  0.001     'integrate in this fraction of the distance already covered
#define FIN 10000000 'integrate only to a height of 10000km, effectively infinity
#define max(a, b) iif(a>b,a,b)

function rho(a as double) as double
    'the density of air as a function of height above sea level
    return exp(-a/8500.0)
end function

function height( a as double, z as double, d as double ) as double
    'a = altitude of observer
    'z = zenith angle (in degrees)
    'd = distance along line of sight
    dim as double AA = RE + a, HH
    HH = sqr( AA^2 + d^2 - 2*d*AA*cos((180-z)*DEG) )
    return HH - RE
end function

function column_density( a as double, z as double ) as double
    'integrates density along the line of sight
    dim as double sum = 0.0, d = 0.0, delta
    while d<FIN
        delta = max(dd, (dd)*d)  'adaptive step size to avoid it taking forever:
        sum += rho(height(a, z, d+0.5*delta))*delta
        d += delta
    wend
    return sum
end function

function airmass( a as double, z as double ) as double
    return column_density( a, z ) / column_density( a, 0 )
end function

print "Angle     0 m              13700 m"
print "------------------------------------"
for z as double = 0 to 90 step 5.0
    print using "##      ##.########      ##.########";z;airmass(0, z);airmass(13700, z)
next z
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
Translation of: FreeBASIC
FUNCTION max(a, b)
    IF a > b then LET max = a else LET max = b
END FUNCTION

FUNCTION rho(a)
    !the density of air as a function of height above sea level
    LET rho = exp(-a/8500)
END FUNCTION

FUNCTION height(a, z, d)
    !a = altitude of observer
    !z = zenith angle (in degrees)
    !d = distance along line of sight
    LET aa = re+a
    LET hh = sqr(aa^2+d^2-2*d*aa*cos((180-z)*deg))
    LET height = hh-re
END FUNCTION

FUNCTION columndensity(a, z)
    !integrates density along the line of sight
    LET sum = 0
    LET d = 0
    DO while d < lim
       LET delta = max(dd, (dd)*d)     !adaptive step size to avoid it taking forever:
       LET sum = sum+rho(height(a, z, d+.5*delta))*delta
       LET d = d+delta
    LOOP
    LET columndensity = sum
END FUNCTION

FUNCTION airmass(a, z)
    LET airmass = columndensity(a, z)/columndensity(a, 0)
END FUNCTION

LET deg = .0174532925199433       !degrees to radians
LET re = 6371000                  !Earth radius in meters
LET dd = .001                     !integrate in this fraction of the distance already covered
LET lim = 10000000                !integrate only to a height of 10000km, effectively infinity
PRINT "Angle     0 m              13700 m"
PRINT "------------------------------------"
FOR z = 0 to 90 step 5
    PRINT  using "##      ##.########      ##.########": z, airmass(0, z), airmass(13700, z)
NEXT z
END

C

Translation of: FreeBASIC
#include <math.h>
#include <stdio.h>

#define DEG 0.017453292519943295769236907684886127134  // degrees to radians
#define RE 6371000.0 // Earth radius in meters
#define DD 0.001 // integrate in this fraction of the distance already covered
#define FIN 10000000.0 // integrate only to a height of 10000km, effectively infinity

static double rho(double a) {
    // the density of air as a function of height above sea level
    return exp(-a / 8500.0);
}

static double height(double a, double z, double d) {
    // a = altitude of observer
    // z = zenith angle (in degrees)
    // d = distance along line of sight
    double aa = RE + a;
    double hh = sqrt(aa * aa + d * d - 2.0 * d * aa * cos((180 - z) * DEG));
    return hh - RE;
}

static double column_density(double a, double z) {
    // integrates density along the line of sight
    double sum = 0.0, d = 0.0;
    while (d < FIN) {
        // adaptive step size to avoid it taking forever
        double delta = DD * d;
        if (delta < DD)
            delta = DD;
        sum += rho(height(a, z, d + 0.5 * delta)) * delta;
        d += delta;
    }
    return sum;
}

static double airmass(double a, double z) {
    return column_density(a, z) / column_density(a, 0.0);
}

int main() {
    puts("Angle     0 m              13700 m");
    puts("------------------------------------");
    for (double z = 0; z <= 90; z+= 5) {
        printf("%2.0f      %11.8f      %11.8f\n",
               z, airmass(0.0, z), airmass(13700.0, z));
    }
}
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
#include <cstdint>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <numbers>

// Return the density of air at 'height' above sea level
double rho(const double& height) {
    return std::exp(-height / 8500.0);
}

// Return the height above sea level at a 'distance' along the line of sight of an observer
// at an 'altitude' viewing at a 'zenith_angle' in degrees from the vertical
double height(const double& altitude, const double& zenith_angle, const double& distance) {
	const double earth_radius = 6371000.0; // metres

	const double aa = earth_radius + altitude;
	const double height = std::sqrt(aa * aa + distance * distance - 2.0 * distance * aa
			                        * std::cos(( 180 - zenith_angle ) * std::numbers::pi / 180.0));
	return height - earth_radius;
}

// Return the integral of air density along the line of sight of an observer
// at an 'altitude' viewing at a 'zenith_angle' in degrees from the vertical
double column_density(const double& altitude, const double& zenith_angle) {
	const double limit_atmosphere = 10'000'000.0; // metres
	const double initial_step_size = 0.001;

	double sum = 0.0;
	double distance = 0.0;
	while ( distance < limit_atmosphere ) {
		// step size increases as the atmosphere becmes less dense
		const double step_size = std::max(distance * initial_step_size, initial_step_size);
		sum += rho(height(altitude, zenith_angle, distance + 0.5 * step_size)) * step_size;
		distance += step_size;
	}
	return sum;
}

// Return the air mass; a measure of the amount of atmosphere between the observer and the object being observed
double air_mass(const double& altitude, const double& zenith_angle) {
    return column_density(altitude, zenith_angle) / column_density(altitude, 0.0);
}

int main() {
	std::cout << "Angle     0 m               13700 m" << std::endl;
	std::cout << "--------------------------------------" << std::endl;
	for ( uint32_t zenith_angle = 0; zenith_angle <= 90; zenith_angle += 5 ) {
		std::cout << std::setw(2) << zenith_angle
				  << std::setw(18) << std::fixed <<std::setprecision(8) << air_mass(0.0, zenith_angle)
				  << std::setw(18) << air_mass(13'700.0, zenith_angle) << std::endl;
	}
}
Output:
Angle     0 m               13700 m
--------------------------------------
 0        1.00000000        1.00000000
 5        1.00380963        1.00380965
10        1.01538466        1.01538475
15        1.03517744        1.03517765
20        1.06399053        1.06399093
25        1.10305937        1.10306005
30        1.15418974        1.15419083
35        1.21998076        1.21998246
40        1.30418931        1.30419190
45        1.41234169        1.41234567
50        1.55280404        1.55281025
55        1.73875921        1.73876915
60        1.99212000        1.99213665
65        2.35199740        2.35202722
70        2.89531368        2.89537287
75        3.79582352        3.79596149
80        5.53885809        5.53928113
85       10.07896219       10.08115981
90       34.32981136       34.36666557

D

Translation of: C++
module air_mass_rosetta_code;

import std.math   : exp, sqrt, cos, PI;
import std.algorithm : max;
import std.stdio  : writeln, writef, writefln;
import std.format : format;

enum double EARTH_RADIUS      = 6_371_000.0;   // meters
enum double SCALE_HEIGHT      = 8_500.0;       // meters (atmospheric density scale height)
enum double LIMIT_ATMOSPHERE  = 10_000_000.0;  // meters (integration upper bound)
enum double INITIAL_STEP_SIZE = 0.001;         // meters

// Atmospheric density at `height` meters above sea level.
// @nogc because we don't use any GC features
// @safe because we don't do any unsafe operations
pure nothrow @nogc @safe 
double rho(double height)
{
    return exp(-height / SCALE_HEIGHT);
}

/**
 * Height above sea level (metres) for a point at `distance` along the
 * line of sight of an observer at `altitude`, viewing at `zenithAngle`
 * degrees from vertical.
 *
 * Derived from the law of cosines on the triangle formed by the Earth's
 * centre, the observer, and the point of interest.
 */
pure nothrow @nogc @safe
double heightAtDistance(double altitude, double zenithAngle, double distance)
{
    immutable double aa = EARTH_RADIUS + altitude;
    immutable double cosAngle = cos((180.0 - zenithAngle) * PI / 180.0);
    immutable double r = sqrt(aa * aa + distance * distance
                              - 2.0 * distance * aa * cosAngle);
    return r - EARTH_RADIUS;
}

/**
 * Column density: integral of atmospheric density along the line of sight
 * for an observer at `altitude` metres, viewing at `zenithAngle` degrees.
 *
 * Uses an adaptive step size that grows with distance, keeping the
 * integration efficient as density falls off exponentially.
 */
nothrow @nogc @safe
double columnDensity(double altitude, double zenithAngle)
{
    double sum      = 0.0;
    double distance = 0.0;

    while (distance < LIMIT_ATMOSPHERE)
    {
        // Step grows with distance so we don't over-sample the thin upper atmosphere.
        immutable double step = max(distance * INITIAL_STEP_SIZE, INITIAL_STEP_SIZE);

        // Midpoint rule: sample density at the centre of the current step.
        sum += rho(heightAtDistance(altitude, zenithAngle, distance + 0.5 * step)) * step;
        distance += step;
    }
    return sum;
}

/**
 * Air mass for an observer at `altitude` metres viewing at `zenithAngle`
 * degrees from vertical, normalised to the zenith column density.
 */
nothrow @nogc @safe
double airMass(double altitude, double zenithAngle)
{
    return columnDensity(altitude, zenithAngle) / columnDensity(altitude, 0.0);
}

void main()
{
    writeln("Angle       0 m                 13700 m");
    writeln("------------------------------------------");

    foreach (zenithAngle; 0 .. 19)   // 0..18 inclusive → 0, 5, 10, … 90
    {
        immutable int    angle     = zenithAngle * 5;
        immutable double seaLevel  = airMass(0.0,      angle);
        immutable double sofia     = airMass(13_700.0, angle);

        writefln("%2d  %18.8f  %18.8f", angle, seaLevel, sofia);
    }
}
Output:
Angle     0 m               13700 m
--------------------------------------
 0        1.00000000        1.00000000
 5        1.00380963        1.00380965
10        1.01538466        1.01538475
15        1.03517744        1.03517765
20        1.06399053        1.06399093
25        1.10305937        1.10306005
30        1.15418974        1.15419083
35        1.21998076        1.21998246
40        1.30418931        1.30419190
45        1.41234169        1.41234567
50        1.55280404        1.55281025
55        1.73875921        1.73876915
60        1.99212000        1.99213665
65        2.35199740        2.35202722
70        2.89531368        2.89537287
75        3.79582352        3.79596149
80        5.53885809        5.53928113
85       10.07896219       10.08115981
90       34.32981136       34.36666557
Works with: Delphi version 6.0
Translation of: Go
const RE  = 6371000; { radius of earth in meters}
const DD  = 0.001;   { integrate in this fraction of the distance already covered}
const FIN = 1e7;     { integrate only to a height of 10000km, effectively infinity}

function rho(a: double): double;
{ The density of air as a function of height above sea level.}
begin
Result:=Exp(-a / 8500);
end;

function Radians(degrees: double): double;
{ Converts degrees to radians}
begin
Result:= degrees * Pi / 180
end;

function Height(A, Z, D: double): double;
{ a = altitude of observer}
{ z = zenith angle (in degrees)}
{ d = distance along line of sight}
var AA,HH: double;
begin
AA := RE + A;
HH := Sqrt(AA*AA + D*D - 2*D*AA*Cos(Radians(180-z)));
Result:= HH - RE;
end;

function ColumnDensity(A, Z: double): double;
{ Integrates density along the line of sight.}
var Sum,D,Delta: double;
begin
Sum := 0.0;
D := 0.0;
while D < FIN do
	begin
	delta := Max(DD, DD*D); { adaptive step size to avoid it taking forever}
	Sum:=Sum + Rho(Height(A, Z, D+0.5*Delta)) * Delta;
	D:=D + delta;
	end;
Result:= Sum;
end;


function AirMass(A, Z: double): double;
begin
Result:= ColumnDensity(A, Z) / ColumnDensity(a, 0);
end;

procedure ShowAirMass(Memo: TMemo);
var Z: integer;
begin
Memo.Lines.Add('Angle     0 m              13700 m');
Memo.Lines.Add('------------------------------------');
Z:=0;
while Z<=90 do
	begin
        Memo.Lines.Add(Format('%2d      %11.8f      %11.8f', [z, airmass(0, Z), airmass(13700, Z)]));
        Z:=Z+5;
        end;
end;
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557

Elapsed Time: 189.304 ms.

Translation of: FreeBASIC
func rho a .
   return pow 2.718281828459 (-a / 8500)
.
func height a z d .
   AA = 6371000 + a
   HH = sqrt (AA * AA + d * d - 2 * d * AA * cos (180 - z))
   return HH - 6371000
.
func density a z .
   while d < 10000000
      delta = higher 0.001 (0.001 * d)
      sum += rho height a z (d + 0.5 * delta) * delta
      d += delta
   .
   return sum
.
func airmass a z .
   return density a z / density a 0
.
numfmt 2 8
print "Angle   0 m      13700 m"
print "------------------------"
for z = 0 step 5 to 90
   print z & "   " & airmass 0 z & " " & airmass 13700 z
.
Output:
Angle   0 m      13700 m
------------------------
 0    1  1
 5   1.00380963 1.00380965
10   1.01538466 1.01538475
15   1.03517744 1.03517765
20   1.06399053 1.06399093
25   1.10305937 1.10306005
30   1.15418974 1.15419083
35   1.21998076 1.21998246
40   1.30418931 1.30419190
45   1.41234169 1.41234567
50   1.55280404 1.55281025
55   1.73875921 1.73876915
60   1.99212000 1.99213665
65   2.35199740 2.35202722
70   2.89531368 2.89537287
75   3.79582352 3.79596149
80   5.53885809 5.53928113
85   10.07896219 10.08115981
90   34.32981136 34.36666557
Translation of: FreeBASIC
Works with: Factor version 0.99 2021-02-05
USING: formatting io kernel math math.functions math.order
math.ranges math.trig sequences ;

CONSTANT: RE 6,371,000     ! Earth's radius in meters
CONSTANT: dd 0.001         ! integrate in this fraction of the distance already covered
CONSTANT: FIN 10,000,000   ! integrate to a height of 10000km

! the density of air as a function of height above sea level
: rho ( a -- x ) neg 8500 / e^ ;

! z = zenith angle (in degrees)
! d = distance along line of sight
! a = altitude of observer
:: height ( a z d -- x )
    RE a + :> AA
    AA sq d sq + 180 z - deg>rad cos AA * d * 2 * - sqrt RE - ;

:: column-density ( a z -- x )
    ! integrates along the line of sight
    0 0 :> ( s! d! )
    [ d FIN < ] [
        dd dd d * max :> delta   ! adaptive step size to avoid taking it forever
        s a z d 0.5 delta * + height rho delta * + s!
        d delta + d!
    ] while s ;

: airmass ( a z -- x )
    [ column-density ] [ drop 0 column-density ] 2bi / ;

"Angle     0 m              13700 m" print
"------------------------------------" print
0 90 5 <range> [
    dup [ 0 swap airmass ] [ 13700 swap airmass ] bi
    "%2d %15.8f %17.8f\n" printf
] each
Output:
Angle     0 m              13700 m
------------------------------------
 0      1.00000000        1.00000000
 5      1.00380963        1.00380965
10      1.01538466        1.01538475
15      1.03517744        1.03517765
20      1.06399053        1.06399093
25      1.10305937        1.10306005
30      1.15418974        1.15419083
35      1.21998076        1.21998246
40      1.30418931        1.30419190
45      1.41234169        1.41234567
50      1.55280404        1.55281025
55      1.73875921        1.73876915
60      1.99212000        1.99213665
65      2.35199740        2.35202722
70      2.89531368        2.89537287
75      3.79582352        3.79596149
80      5.53885809        5.53928113
85     10.07896219       10.08115981
90     34.32981136       34.36666557


!! =============================================================================
! air_mass.f90  --  Astronomical Air Mass Calculation
! =============================================================================
!
! Air mass is a measure of the thickness of atmosphere a ray of light must
! traverse when arriving at an observer from a celestial object.  At the
! zenith (z = 0) the air mass is 1.0 by definition.  As the zenith angle
! increases the ray travels through progressively more atmosphere.
!
! MODEL
! -----
! We treat the atmosphere as a spherically-symmetric shell around a spherical
! Earth.  The density at height h (metres above sea level) follows an
! exponential law:
!
!   rho(h)  =  rho_0 * exp(-h / H)
!
! where H = 8 500 m is the pressure scale height and rho_0 is the sea-level
! density (it cancels out of the ratio).
!
! The line of sight from an observer at altitude a, looking at zenith angle z,
! is parameterised by the path length x measured along the ray from the
! observer.  The height h at path length x is given by the law of cosines
! applied to the triangle (centre of Earth, observer, point on ray):
!
!   r0  = R_EARTH + a                       (observer geocentric radius)
!   r(x) = sqrt( r0^2 + x^2 + 2*r0*x*cos(z) )
!
!   Note: cos(z) is positive for z < 90 (ray pointing upward),
!         zero for z = 90 (horizontal), and negative beyond.
!
!   h(x) = r(x) - R_EARTH                  (height above sea level)
!
! AIR MASS INTEGRAL
! -----------------
! The raw column density integral is:
!
!   I(a, z) = integral_0^{X_MAX} exp(-h(x) / H)  dx
!
! We normalise by the vertical (zenith) integral evaluated analytically:
!
!   I_zenith(a) = H * exp(-a / H)
!
! so that air_mass(a, 0) = 1.0 exactly and the result is dimensionless.
!
! NUMERICAL INTEGRATION
! ---------------------
! Composite Simpson's rule with N_STEPS (even) sub-intervals is used.
! X_MAX = 2 000 000 m (2 000 km) is far enough that exp(-h/H) is
! negligibly small beyond the upper boundary.
!
! FLAT-EARTH (PLANE-PARALLEL) APPROXIMATION
! ------------------------------------------
! The classic first-order approximation ignores Earth curvature:
!
!   air_mass_flat = 1 / cos(z)
!
! This is accurate for small z but diverges as z -> 90 degrees.
!
! OUTPUT
! ------
! Two tables are printed:
!   1. Sea-level observer  (a = 0 m)
!   2. NASA SOFIA aircraft (a = 13 700 m)
!
! Each row shows z, spherical air mass, and the flat-Earth value.
! =============================================================================
program air_mass_program
  use iso_fortran_env, only: real64
  implicit none

  ! --------------------------------------------------------------------------
  ! Physical constants and integration parameters
  ! --------------------------------------------------------------------------
  real(kind=real64), parameter :: R_EARTH  = 6371000.0_real64  ! Earth radius (m)
  real(kind=real64), parameter :: H_SCALE  = 8500.0_real64     ! pressure scale height (m)
  real(kind=real64), parameter :: X_MAX    = 2000000.0_real64  ! path length limit (m)
  integer,           parameter :: N_STEPS  = 2**17             ! Simpson sub-intervals (131072, must be even)

  ! Conversion factor
  real(kind=real64), parameter :: DEG2RAD  = acos(-1.0_real64) / 180.0_real64

  ! Observer altitudes (m)
  real(kind=real64), parameter :: ALT_SEA   =     0.0_real64   ! sea level
  real(kind=real64), parameter :: ALT_SOFIA = 13700.0_real64   ! SOFIA cruising altitude

  ! --------------------------------------------------------------------------
  ! Local variables
  ! --------------------------------------------------------------------------
  integer           :: iz
  real(kind=real64) :: z_deg, z_rad
  real(kind=real64) :: am_sea, am_sofia, am_flat

  ! --------------------------------------------------------------------------
  ! Table: sea-level observer
  ! --------------------------------------------------------------------------
  write(*, '(a)') '============================================================='
  write(*, '(a)') ' Air Mass Table -- Sea-level observer  (a = 0 m)'
  write(*, '(a)') '============================================================='
  write(*, '(a)') '  z (deg)   Spherical    Flat (1/cos z)'
  write(*, '(a)') ' --------  ----------   ---------------'

  do iz = 0, 18
    z_deg = real(iz * 5, real64)
    z_rad = z_deg * DEG2RAD

    am_sea  = air_mass(ALT_SEA, z_rad)

    if (iz < 18) then
      am_flat = 1.0_real64 / cos(z_rad)
      write(*, '(f9.1, f13.5, f16.5)') z_deg, am_sea, am_flat
    else
      ! z = 90 deg: flat-Earth value is infinite
      write(*, '(f9.1, f13.5, a)') z_deg, am_sea, '          Inf'
    end if
  end do

  write(*, *)

  ! --------------------------------------------------------------------------
  ! Table: SOFIA observer
  ! --------------------------------------------------------------------------
  write(*, '(a)') '============================================================='
  write(*, '(a)') ' Air Mass Table -- NASA SOFIA  (a = 13 700 m)'
  write(*, '(a)') '============================================================='
  write(*, '(a)') '  z (deg)   Spherical    Flat (1/cos z)'
  write(*, '(a)') ' --------  ----------   ---------------'

  do iz = 0, 18
    z_deg = real(iz * 5, real64)
    z_rad = z_deg * DEG2RAD

    am_sofia = air_mass(ALT_SOFIA, z_rad)

    if (iz < 18) then
      am_flat = 1.0_real64 / cos(z_rad)
      write(*, '(f9.1, f13.5, f16.5)') z_deg, am_sofia, am_flat
    else
      write(*, '(f9.1, f13.5, a)') z_deg, am_sofia, '          Inf'
    end if
  end do

  write(*, *)

  ! --------------------------------------------------------------------------
  ! Side-by-side comparison at selected angles
  ! --------------------------------------------------------------------------
  write(*, '(a)') '============================================================='
  write(*, '(a)') ' Comparison: Sea level vs SOFIA vs Flat-Earth'
  write(*, '(a)') '============================================================='
  write(*, '(a)') '  z (deg)   Sea level    SOFIA      Flat (1/cos z)'
  write(*, '(a)') ' --------  ----------  ----------  ---------------'

  do iz = 0, 18
    z_deg = real(iz * 5, real64)
    z_rad = z_deg * DEG2RAD

    am_sea  = air_mass(ALT_SEA,   z_rad)
    am_sofia = air_mass(ALT_SOFIA, z_rad)

    if (iz < 18) then
      am_flat = 1.0_real64 / cos(z_rad)
      write(*, '(f9.1, f12.5, f12.5, f16.5)') z_deg, am_sea, am_sofia, am_flat
    else
      write(*, '(f9.1, f12.5, f12.5, a)') z_deg, am_sea, am_sofia, '          Inf'
    end if
  end do

  write(*, *)
  write(*, '(a)') 'Notes:'
  write(*, '(a)') '  Spherical model: composite Simpsons rule, N = 131 072 intervals'
  write(*, '(a)') '  Scale height H = 8 500 m,  R_Earth = 6 371 000 m'
  write(*, '(a)') '  SOFIA cruising altitude = 13 700 m  (~45 000 ft)'
  write(*, '(a)') '  Air mass is normalised so that the zenith value = 1.0'

contains

  ! ==========================================================================
  ! air_mass(a, z)  --  compute air mass for observer at altitude a (m)
  !                     looking at zenith angle z (radians).
  !
  ! Uses composite Simpson's rule to evaluate:
  !
  !   I(a,z) = integral_0^{X_MAX} exp(-h(x)/H_SCALE) dx
  !
  ! then returns  I(a,z) / (H_SCALE * exp(-a/H_SCALE))
  !
  ! so that the zenith air mass is 1.0 by definition.
  ! ==========================================================================
  function air_mass(a, z) result(am)
    real(kind=real64), intent(in) :: a   ! observer altitude above sea level (m)
    real(kind=real64), intent(in) :: z   ! zenith angle (radians)
    real(kind=real64) :: am

    real(kind=real64) :: r0       ! geocentric radius of observer (m)
    real(kind=real64) :: dx       ! step size along ray (m)
    real(kind=real64) :: cz       ! cos(z), precomputed for efficiency
    real(kind=real64) :: integral ! accumulated Simpson sum
    real(kind=real64) :: x        ! current path length (m)
    real(kind=real64) :: h        ! height above sea level at x (m)
    real(kind=real64) :: w        ! Simpson weight (1, 4, or 2)
    real(kind=real64) :: zenith_I ! analytic zenith column: H * exp(-a/H)
    integer :: i

    r0 = R_EARTH + a
    dx = X_MAX / real(N_STEPS, real64)
    cz = cos(z)

    ! Simpson's rule: sum weights 1, 4, 2, 4, 2, ..., 4, 1
    integral = 0.0_real64
    do i = 0, N_STEPS
      x = real(i, real64) * dx

      ! Height at this point via law of cosines
      h = sqrt(r0*r0 + x*x + 2.0_real64*r0*x*cz) - R_EARTH

      ! Clamp h to zero to avoid negative heights at extreme angles
      if (h < 0.0_real64) h = 0.0_real64

      ! Simpson weights
      if (i == 0 .or. i == N_STEPS) then
        w = 1.0_real64
      else if (mod(i, 2) == 1) then
        w = 4.0_real64
      else
        w = 2.0_real64
      end if

      integral = integral + w * exp(-h / H_SCALE)
    end do
    integral = integral * dx / 3.0_real64

    ! Normalise by the analytic vertical column density
    zenith_I = H_SCALE * exp(-a / H_SCALE)
    am = integral / zenith_I
  end function air_mass

end program air_mass_program
=============================================================
 Air Mass Table -- Sea-level observer  (a = 0 m)
=============================================================
  z (deg)   Spherical    Flat (1/cos z)
 --------  ----------   ---------------
      0.0      1.00000         1.00000
      5.0      1.00381         1.00382
     10.0      1.01538         1.01543
     15.0      1.03518         1.03528
     20.0      1.06399         1.06418
     25.0      1.10306         1.10338
     30.0      1.15419         1.15470
     35.0      1.21998         1.22077
     40.0      1.30419         1.30541
     45.0      1.41234         1.41421
     50.0      1.55280         1.55572
     55.0      1.73876         1.74345
     60.0      1.99212         2.00000
     65.0      2.35200         2.36620
     70.0      2.89531         2.92380
     75.0      3.79582         3.86370
     80.0      5.53886         5.75877
     85.0     10.07896        11.47371
     90.0     34.32981          Inf

=============================================================
 Air Mass Table -- NASA SOFIA  (a = 13 700 m)
=============================================================
  z (deg)   Spherical    Flat (1/cos z)
 --------  ----------   ---------------
      0.0      1.00000         1.00000
      5.0      1.00381         1.00382
     10.0      1.01538         1.01543
     15.0      1.03518         1.03528
     20.0      1.06399         1.06418
     25.0      1.10306         1.10338
     30.0      1.15419         1.15470
     35.0      1.21998         1.22077
     40.0      1.30419         1.30541
     45.0      1.41235         1.41421
     50.0      1.55281         1.55572
     55.0      1.73877         1.74345
     60.0      1.99214         2.00000
     65.0      2.35203         2.36620
     70.0      2.89537         2.92380
     75.0      3.79596         3.86370
     80.0      5.53928         5.75877
     85.0     10.08116        11.47371
     90.0     34.36667          Inf

=============================================================
 Comparison: Sea level vs SOFIA vs Flat-Earth
=============================================================
  z (deg)   Sea level    SOFIA      Flat (1/cos z)
 --------  ----------  ----------  ---------------
      0.0     1.00000     1.00000         1.00000
      5.0     1.00381     1.00381         1.00382
     10.0     1.01538     1.01538         1.01543
     15.0     1.03518     1.03518         1.03528
     20.0     1.06399     1.06399         1.06418
     25.0     1.10306     1.10306         1.10338
     30.0     1.15419     1.15419         1.15470
     35.0     1.21998     1.21998         1.22077
     40.0     1.30419     1.30419         1.30541
     45.0     1.41234     1.41235         1.41421
     50.0     1.55280     1.55281         1.55572
     55.0     1.73876     1.73877         1.74345
     60.0     1.99212     1.99214         2.00000
     65.0     2.35200     2.35203         2.36620
     70.0     2.89531     2.89537         2.92380
     75.0     3.79582     3.79596         3.86370
     80.0     5.53886     5.53928         5.75877
     85.0    10.07896    10.08116        11.47371
     90.0    34.32981    34.36667          Inf

Notes:
  Spherical model: composite Simpsons rule, N = 131 072 intervals
  Scale height H = 8 500 m,  R_Earth = 6 371 000 m
  SOFIA cruising altitude = 13 700 m  (~45 000 ft)
  Air mass is normalised so that the zenith value = 1.0

Translation of: FreeBASIC
package main

import (
    "fmt"
    "math"
)

const (
    RE  = 6371000 // radius of earth in meters
    DD  = 0.001   // integrate in this fraction of the distance already covered
    FIN = 1e7     // integrate only to a height of 10000km, effectively infinity
)

// The density of air as a function of height above sea level.
func rho(a float64) float64 { return math.Exp(-a / 8500) }

// Converts degrees to radians
func radians(degrees float64) float64 { return degrees * math.Pi / 180 }

// a = altitude of observer
// z = zenith angle (in degrees)
// d = distance along line of sight
func height(a, z, d float64) float64 {
    aa := RE + a
    hh := math.Sqrt(aa*aa + d*d - 2*d*aa*math.Cos(radians(180-z)))
    return hh - RE
}

// Integrates density along the line of sight.
func columnDensity(a, z float64) float64 {
    sum := 0.0
    d := 0.0
    for d < FIN {
        delta := math.Max(DD, DD*d) // adaptive step size to avoid it taking forever
        sum += rho(height(a, z, d+0.5*delta)) * delta
        d += delta
    }
    return sum
}

func airmass(a, z float64) float64 {
    return columnDensity(a, z) / columnDensity(a, 0)
}

func main() {
    fmt.Println("Angle     0 m              13700 m")
    fmt.Println("------------------------------------")
    for z := 0; z <= 90; z += 5 {
        fz := float64(z)
        fmt.Printf("%2d      %11.8f      %11.8f\n", z, airmass(0, fz), airmass(13700, fz))
    }
}
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
Translation of: FreeBASIC
public class AirMass {
    public static void main(String[] args) {
        System.out.println("Angle     0 m              13700 m");
        System.out.println("------------------------------------");
        for (double z = 0; z <= 90; z+= 5) {
            System.out.printf("%2.0f      %11.8f      %11.8f\n",
                            z, airmass(0.0, z), airmass(13700.0, z));
        }
    }

    private static double rho(double a) {
        // the density of air as a function of height above sea level
        return Math.exp(-a / 8500.0);
    }

    private static double height(double a, double z, double d) {
        // a = altitude of observer
        // z = zenith angle (in degrees)
        // d = distance along line of sight
        double aa = RE + a;
        double hh = Math.sqrt(aa * aa + d * d - 2.0 * d * aa * Math.cos(Math.toRadians(180 - z)));
        return hh - RE;
    }

    private static double columnDensity(double a, double z) {
        // integrates density along the line of sight
        double sum = 0.0, d = 0.0;
        while (d < FIN) {
            // adaptive step size to avoid it taking forever
            double delta = Math.max(DD * d, DD);
            sum += rho(height(a, z, d + 0.5 * delta)) * delta;
            d += delta;
        }
        return sum;
    }
     
    private static double airmass(double a, double z) {
        return columnDensity(a, z) / columnDensity(a, 0.0);
    }

    private static final double RE = 6371000.0; // Earth radius in meters
    private static final double DD = 0.001; // integrate in this fraction of the distance already covered
    private static final double FIN = 10000000.0; // integrate only to a height of 10000km, effectively infinity
}
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557

Adapted from Wren

Works with: jq

Works with gojq, the Go implementation of jq

Preliminaries

def pi: 4 * (1|atan);

def radians: . * pi / 180;

def lpad($len): tostring | ($len - length) as $l | (" " * $l)[:$l] + .;

# Input: a number
# Output: a string with $digits fractional decimal digits, with proper rounding
def fmt($width; $digits):
  . as $in
  | tostring
  | index(".") as $ix
  | if test("[eE]") then .
    elif $ix
    then pow(10; $digits) as $p
    | ($in * $p | round | tostring) as $s
    | if test("[eE]") then $s
      else ($s | index(".")) as $ix
      | if $ix then $s[:$ix + 1] + $s[$ix+1: $ix+1+$digits]
        else $s[:-$digits] + "." + $s[-$digits:]
        end
      end
    else . + "." + "0" * digits
    end
  | lpad($width);

Physics

# constants
def RE: 6371000;   # radius of earth in meters
def DD:  0.001;    # integrate in this fraction of the distance already covered
def FIN: 1e7;      # integrate only to a height of 10000km, effectively infinity
 
# The density of air as a function of height above sea level.
def rho: (-./8500) | exp;

# a = altitude of observer (in m)
# z = zenith angle (in degrees)
# d = distance along line of sight (in m)
def height($a; $z; $d):
   (RE + $a) as $aa
   | (($aa * $aa + $d * $d - 2 * $d * $aa * ((180-$z)|radians|cos) )|sqrt ) - RE;
 
# Integrates density along the line of sight.
def columnDensity($a; $z):
  { sum: 0, d: 0 }
  | until (.d >= FIN; 
      ([DD, DD * .d] | max) as $delta  # adaptive step size to avoid it taking forever
      | .sum = .sum + ((height($a; $z; .d + 0.5 * $delta))|rho) * $delta
      | .d += $delta )
  | .sum ;
 
def airmass(a; z): columnDensity(a; z) / columnDensity(a; 0);

"Angle     0 m              13700 m",
"------------------------------------",
( range(0; 91; 5)
  |  "\(lpad(2))      \(airmass(0; .)|fmt(11;8))      \(airmass(13700; .)|fmt(11;8))" )
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557


Translation of: FreeBASIC
using Printf

const DEG = 0.017453292519943295769236907684886127134  # degrees to radians
const RE = 6371000                                     # Earth radius in meters
const dd = 0.001      # integrate in this fraction of the distance already covered
const FIN = 10000000  # integrate only to a height of 10000km, effectively infinity
 
""" the density of air as a function of height above sea level """
rho(a::Float64)::Float64 = exp(-a / 8500.0)

""" a = altitude of observer
    z = zenith angle (in degrees)
    d = distance along line of sight """ 
height(a, z, d) = sqrt((RE + a)^2 + d^2 - 2 * d * (RE + a) * cosd(180 - z)) - RE

""" integrates density along the line of sight """
function column_density(a, z)
    dsum, d = 0.0, 0.0
    while d < FIN
        delta = max(dd, (dd)*d)  # adaptive step size to avoid it taking forever:
        dsum += rho(height(a, z, d + 0.5 * delta)) * delta
        d += delta
    end
    return dsum
end

airmass(a, z) = column_density(a, z) / column_density(a, 0)
 
println("Angle           0 m          13700 m\n", "-"^36)
for z in 0:5:90
    @printf("%2d      %11.8f      %11.8f\n", z, airmass(0, z), airmass(13700, z))
end
Output:
Angle           0 m          13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
Translation of: Pluto

Tested with Lua 5.4.6

-- Constants.
local RE  <const> = 6371000  -- radius of earth in meters
local DD  <const> = 0.001    -- integrate in this fraction of the distance already covered
local FIN <const> = 1e7      -- integrate only to a height of 10000km, effectively infinity

-- The density of air as a function of height above sea level.
local function rho(a) return math.exp(-a / 8500) end

-- a = altitude of observer
-- z = zenith angle (in degrees)
-- d = distance along line of sight
local function height(a, z, d)
    local aa = RE + a
    local hh = math.sqrt(aa * aa + d * d - 2 * d * aa * math.cos(math.rad(180 - z)))
    return hh - RE
end

-- Integrates density along the line of sight.
local function column_density(a, z)
    local sum = 0
    local d = 0
    while d < FIN do
        local delta = math.max(DD, DD * d)  -- adaptive step size to avoid it taking forever
        sum = sum + rho(height(a, z, d + 0.5 * delta)) * delta
        d   = d + delta
    end
    return sum
end

local function air_mass(a, z) return column_density(a, z) / column_density(a, 0) end

print("Angle     0 m              13700 m")
print("------------------------------------")
for z = 0, 90, 5 do
    local f = "%2d      %11.8f      %11.8f"
    print(string.format(f, z, air_mass(0, z), air_mass(13700, z)))
end
Output:

Same as the Pluto sample.

R = 6371;
rho[a_] := Exp[-a / 8.5];
height[a_, z_, x_] := Sqrt[(R + a)^2 + x^2 - 2 x (R + a) Cos[(180 - z) °]] - R;
columnDensity[a_, z_] := NIntegrate[rho[height[a, z, x]], {x, 0, Infinity}];
airMass[a_, z_] := columnDensity[a, z] / columnDensity[a, 0];

Table[PaddedForm[airMass[a, z], {10, 8}], {z, 0, 90, 5}, {a, {0, 13.7}}] // 
  TableForm[#, TableHeadings -> {Range[0, 90, 5], {"  0 m", "  13700 m"}}] &
Output:
       0 m            13700 m
0      1.00000000     1.00000000
5      1.00380963     1.00380965
10     1.01538466     1.01538475
15     1.03517744     1.03517765
20     1.06399053     1.06399093
25     1.10305937     1.10306005
30     1.15418974     1.15419083
35     1.21998076     1.21998246
40     1.30418931     1.30419190
45     1.41234169     1.41234567
50     1.55280404     1.55281025
55     1.73875921     1.73876915
60     1.99212000     1.99213665
65     2.35199740     2.35202722
70     2.89531368     2.89537287
75     3.79582352     3.79596149
80     5.53885809     5.53928113
85    10.07896219    10.08115981
90    34.32981136    34.36666557
Translation of: Wren
Library: Mini-num
Library: Mini-fmt
import "mathUtil"
import "num"
import "fmt"

mu = mathUtil

// constants
RE  = 6371000  // radius of earth in meters
DD  = 0.001    // integrate in this fraction of the distance already covered
FIN = 1e7      // integrate only to a height of 10000km, effectively infinity

// The density of air as a function of height above sea level.
rho = function(a); return num.exp(-a / 8500); end function

// a = altitude of observer
// z = zenith angle (in degrees)
// d = distance along line of sight
height = function(a, z, d)
    aa = RE + a
    c  = cos(mu.degToRad(180 - z)) 
    hh = sqrt(aa * aa + d * d - 2 * d * aa * c)
    return hh - RE
end function

// Integrates density along the line of sight.
columnDensity = function(a, z)
    sum = 0
    d = 0
    while d < FIN
        delta = mu.max(DD, DD * d)   // adaptive step size to avoid it taking forever
        sum += rho(height(a, z, d + 0.5 * delta)) * delta
        d += delta
    end while
    return sum
end function

airmass = function(a, z)
    return columnDensity(a, z) / columnDensity(a, 0)
end function

print "Angle     0 m              13700 m"
print "------------------------------------"
z = 0
while z <= 90
    zs  = fmt.int(z, 2)
    am1 = fmt.num(airmass(0, z), 11, 8)
    am2 = fmt.num(airmass(13700, z), 11, 8)
    fmt.print "{}      {}      {}", zs, am1, am2
    z += 5
end while
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
Translation of: Wren
import math, strformat

const
  Re = 6371000  # Radius of earth in meters.
  Dd= 0.001     # Integrate in this fraction of the distance already covered.
  Fin = 1e7     # Integrate only to a height of 10000km, effectively infinity.


func rho(a: float): float =
  ## The density of air as a function of height above sea level.
  exp(-a / 8500)


func height(a, z, d: float): float =
  ## Height as a function of altitude (a), zenith angle (z)
  ## in degrees and distance along line of sight (d).
  let aa = Re + a
  let hh = sqrt(aa * aa + d * d - 2 * d * aa * cos(degToRad(180-z)))
  result = hh - Re


func columnDensity(a, z: float): float =
  ## Integrates density along the line of sight.
  var d = 0.0
  while d < Fin:
    let delta = max(Dd, Dd * d)   # Adaptive step size to avoid it taking forever.
    result += rho(height(a, z, d + 0.5 * delta)) * delta
    d += delta


func airmass(a, z: float): float =
  columnDensity(a, z) / columnDensity(a, 0)


echo "Angle     0 m              13700 m"
echo "------------------------------------"
var z = 0.0
while z <= 90:
  echo &"{z:2}      {airmass(0, z):11.8f}      {airmass(13700, z):11.8f}"
  z += 5
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
Translation of: Raku
use strict;
use warnings;
use feature <say signatures>;
no warnings 'experimental::signatures';
use List::Util 'max';

use constant PI  => 2*atan2(1,0);   # π
use constant DEG => PI/180;         # degrees to radians
use constant RE  => 6371000;        # Earth radius in meters
use constant dd  => 0.001;          # integrate in this fraction of the distance already covered
use constant FIN => 10000000;       # integrate only to a height of 10000km, effectively infinity

# Density of air as a function of height above sea level
sub rho ( $a ) {
    exp( -$a / 8500 );
}

sub height ( $a, $z, $d ) {
    # a = altitude of observer
    # z = zenith angle (in degrees)
    # d = distance along line of sight
    my $AA = RE + $a;
    my $HH = sqrt $AA**2 + $d**2 - 2 * $d * $AA * cos( (180-$z)*DEG );
    $HH - RE;
}

# Integrates density along the line of sight
sub column_density ( $a, $z ) {
    my $sum = 0;
    my $d   = 0;
    while ($d < FIN) {
        my $delta = max(dd, dd * $d);  # Adaptive step size to avoid it taking forever
        $sum += rho(height($a, $z, $d + $delta/2))*$delta;
        $d   += $delta;
    }
    $sum;
}

sub airmass ( $a, $z ) {
    column_density($a, $z) / column_density($a, 0);
}

say 'Angle     0 m              13700 m';
say '------------------------------------';
for my $z (map{ 5*$_ } 0..18) {
    printf "%2d      %11.8f      %11.8f\n", $z, airmass(0, $z), airmass(13700, $z);
}
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
constant RE = 6371000,  // radius of earth in meters
         DD = 0.001,    // integrate in this fraction of the distance already covered
         FIN = 1e7      // integrate only to a height of 10000km, effectively infinity
 
// The density of air as a function of height above sea level.
function rho(atom a) return exp(-a/8500) end function
 
// a = altitude of observer
// z = zenith angle (in degrees)
// d = distance along line of sight
function height(atom a, z, d)
    atom aa = RE + a,
         hh = sqrt(aa*aa + d*d - 2*d*aa*cos((180-z)*PI/180))
    return hh - RE
end function
 
// Integrates density along the line of sight.
function columnDensity(atom a, z)
    atom res = 0,
         d = 0
    while d<FIN do
        atom delta = max(DD, DD*d) // adaptive step size to avoid it taking forever
        res += rho(height(a, z, d + 0.5*delta))*delta
        d += delta
    end while
    return res
end function
 
function airmass(atom a, z) return columnDensity(a,z)/columnDensity(a,0) end function
 
printf(1,"Angle     0 m              13700 m\n")
printf(1,"------------------------------------\n")
for z=0 to 90 by 5 do
    printf(1,"%2d      %11.8f      %11.8f\n", {z, airmass(0,z), airmass(13700,z)})
end for
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
Translation of: Wren
-- Constants.
$define RE  = 6371000  -- radius of earth in meters
$define DD  = 0.001    -- integrate in this fraction of the distance already covered
$define FIN = 1e7      -- integrate only to a height of 10000km, effectively infinity

-- The density of air as a function of height above sea level.
local function rho(a) return math.exp(-a / 8500) end

-- a = altitude of observer
-- z = zenith angle (in degrees)
-- d = distance along line of sight
local function height(a, z, d)
    local aa = RE + a
    local hh = math.sqrt(aa * aa + d * d - 2 * d * aa * math.cos(math.rad(180 - z)))
    return hh - RE
end

-- Integrates density along the line of sight.
local function column_density(a, z)
    local sum = 0
    local d = 0
    while d < FIN do
        local delta = math.max(DD, DD * d)  -- adaptive step size to avoid it taking forever
        sum += rho(height(a, z, d + 0.5 * delta)) * delta
        d += delta
    end
    return sum
end

local function air_mass(a, z) return column_density(a, z) / column_density(a, 0) end

print("Angle     0 m              13700 m")
print("------------------------------------")
for z = 0, 90, 5 do
    local f = "%2d      %11.8f      %11.8f"
    print(string.format(f, z, air_mass(0, z), air_mass(13700, z)))
end
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
""" Rosetta Code task: Air_mass """

from math import sqrt, cos, exp

DEG = 0.017453292519943295769236907684886127134  # degrees to radians
RE = 6371000                                     # Earth radius in meters
dd = 0.001      # integrate in this fraction of the distance already covered
FIN = 10000000  # integrate only to a height of 10000km, effectively infinity
 
def rho(a):
    """ the density of air as a function of height above sea level """
    return exp(-a / 8500.0)
 
def height(a, z, d):
    """
    a = altitude of observer
    z = zenith angle (in degrees)
    d = distance along line of sight
    """ 
    return sqrt((RE + a)**2 + d**2 - 2 * d * (RE + a) * cos((180 - z) * DEG)) - RE
 
def column_density(a, z):
    """ integrates density along the line of sight """
    dsum, d = 0.0, 0.0
    while d < FIN:
        delta = max(dd, (dd)*d)  # adaptive step size to avoid it taking forever:
        dsum += rho(height(a, z, d + 0.5 * delta)) * delta
        d += delta
    return dsum

def airmass(a, z):
    return column_density(a, z) / column_density(a, 0)

print('Angle           0 m          13700 m\n', '-' * 36)
for z in range(0, 91, 5):
    print(f"{z: 3d}      {airmass(0, z): 12.7f}    {airmass(13700, z): 12.7f}")
Output:
Angle           0 m          13700 m
 ------------------------------------
  0         1.0000000       1.0000000
  5         1.0038096       1.0038096
 10         1.0153847       1.0153848
 15         1.0351774       1.0351776
 20         1.0639905       1.0639909
 25         1.1030594       1.1030601
 30         1.1541897       1.1541908
 35         1.2199808       1.2199825
 40         1.3041893       1.3041919
 45         1.4123417       1.4123457
 50         1.5528040       1.5528102
 55         1.7387592       1.7387692
 60         1.9921200       1.9921366
 65         2.3519974       2.3520272
 70         2.8953137       2.8953729
 75         3.7958235       3.7959615
 80         5.5388581       5.5392811
 85        10.0789622      10.0811598
 90        34.3298114      34.3666656

R

#Equation for height was worked out by hand using the cosine rule and quadratic formula
h <- function(a, z, x, r) -r + sqrt((a+r)^2 + x^2 + 2*x*(a+r)*cos(z))
rho <- function(a) exp(-a/8.5)

airmass_absolute <- function(a, z) {
  integrate(function(x) rho(h(a, z, x, 6371)), 0, 10000)$value
}
airmass_relative <- function(a) function(z) {
  airmass_absolute(a, z)/airmass_absolute(a, 0)
}

angles <- seq(from = 0, to = 90, by = 5)
call_angles <- function(f) sapply(angles*pi/180, f)

airmasses <- data.frame(
  "Angle" = angles,
  "a_0m" = call_angles(airmass_relative(0)),
  "a_13700m" = call_angles(airmass_relative(13.7))
) |> print(row.names = FALSE)
Output:
 Angle      a_0m  a_13700m
     0  1.000000  1.000000
     5  1.003810  1.003810
    10  1.015385  1.015385
    15  1.035177  1.035178
    20  1.063991  1.063991
    25  1.103059  1.103060
    30  1.154190  1.154191
    35  1.219981  1.219982
    40  1.304189  1.304192
    45  1.412342  1.412346
    50  1.552804  1.552810
    55  1.738759  1.738769
    60  1.992120  1.992137
    65  2.351997  2.352027
    70  2.895314  2.895373
    75  3.795824  3.795961
    80  5.538858  5.539281
    85 10.078962 10.081160
    90 34.329811 34.366666
Translation of: FreeBASIC
constant DEG = pi/180;    # degrees to radians
constant RE  = 6371000;   # Earth radius in meters
constant dd  = 0.001;     # integrate in this fraction of the distance already covered
constant FIN = 10000000;  # integrate only to a height of 10000km, effectively infinity

#| Density of air as a function of height above sea level
sub rho ( \a ) { exp( -a / 8500 ) }

sub height ( \a, \z, \d ) {
    # a = altitude of observer
    # z = zenith angle (in degrees)
    # d = distance along line of sight
    my \AA = RE + a;
    sqrt( AA² +  - 2*d*AA*cos((180-z)*DEG) ) - AA;
}

#| Integrates density along the line of sight
sub column_density ( \a, \z ) {
    my $sum = 0;
    my $d   = 0;
    while $d < FIN {
        my \delta = max(dd, (dd)*$d);  # Adaptive step size to avoid it taking forever
        $sum += rho(height(a, z, $d + delta/2))*delta;
        $d   += delta;
    }
    $sum;
}

sub airmass ( \a, \z ) {
    column_density( a, z ) /
    column_density( a, 0 )
}

say 'Angle     0 m              13700 m';
say '------------------------------------';
say join "\n", (0, 5 ... 90).hyper(:3batch).map: -> \z {
    sprintf "%2d      %11.8f      %11.8f", z, airmass(    0, z), airmass(13700, z);
}
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
Translation of: FreeBASIC
/*REXX pgm calculates the  air mass  above an observer and an object for various angles.*/
numeric digits (length(pi()) - length(.)) % 4    /*calculate the number of digits to use*/
parse arg aLO aHI aBY oHT .                      /*obtain optional arguments from the CL*/
if aLO=='' | aLO==","  then aLO=     0           /*not specified?  Then use the default.*/
if aHI=='' | aHI==","  then aHI=    90           /* "      "         "   "   "     "    */
if aBY=='' | aBY==","  then aBY=     5           /* "      "         "   "   "     "    */
if oHT=='' | oHT==","  then oHT= 13700           /* "      "         "   "   "     "    */
w= 30;             @ama= 'air mass at'           /*column width for the two air_masses. */
say 'angle|'center(@ama  "sea level", w)  center(@ama  commas(oHT)  'meters', w) /*title*/
say "─────┼"copies(center('', w, "─"), 2)'─'     /*display the title sep for the output.*/
y= left('', w-20)                                /*Y:  for alignment of the output cols.*/

      do j=aLO  to aHI  by aBY;        am0= airM(0, j);                 amht= airM(oHT, j)
      say center(j, 5)'│'right( format(am0, , 8), w-10)y  right( format(amht, , 8), w-10)y
      end   /*j*/

say "─────┴"copies(center('', w, "─"), 2)'─'     /*display the foot separator for output*/
exit 0                                           /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
airM: procedure; parse arg a,z;    if z==0  then return 1;  return colD(a, z) / colD(a, 0)
d2r:  return r2r( arg(1) * pi() / 180)           /*convert degrees   ──► radians.       */
pi:   pi= 3.1415926535897932384626433832795028841971693993751058209749445923078; return pi
rho:  procedure; parse arg a;            return exp(-a / 8500)
r2r:  return arg(1)  //  (pi() * 2)              /*normalize radians ──► a unit circle. */
e:    e= 2.718281828459045235360287471352662497757247093699959574966967627724;   return e
commas: parse arg ?; do jc=length(?)-3  to 1  by -3; ?=insert(',', ?, jc); end;  return ?
/*──────────────────────────────────────────────────────────────────────────────────────*/
cos:  procedure; parse arg x;    x= r2r(x);  a= abs(x);  numeric fuzz min(6, digits() - 3)
      hpi= pi*.5;  if a=pi    then return -1;   if a=hpi | a=hpi*3  then return 0;    z= 1
                   if a=pi/3  then return .5;   if a=pi*2/3         then return -.5;  _= 1
      x= x*x;  p= z;      do k=2  by 2;  _= -_ * x / (k*(k-1));     z= z + _
                          if z=p  then leave;   p= z;   end;                    return z
/*──────────────────────────────────────────────────────────────────────────────────────*/
exp:  procedure; parse arg x;  ix= x%1;  if abs(x-ix)>.5  then ix= ix + sign(x);   x= x-ix
      z=1;  _=1;   w=z;     do j=1; _= _*x/j;  z=(z+_)/1;  if z==w  then leave;  w=z;  end
      if z\==0  then z= z * e() ** ix;                                          return z/1
/*──────────────────────────────────────────────────────────────────────────────────────*/
sqrt: procedure; parse arg x; if x=0  then return 0;  d= digits();  numeric digits; h= d+6
      numeric form; parse value format(x,2,1,,0) 'E0'  with  g 'E' _ .;  g= g * .5'e'_ % 2
      m.=9;     do j=0  while h>9;       m.j= h;               h= h%2 + 1;      end  /*j*/
                do k=j+5  to 0  by -1;   numeric digits m.k;   g= (g+x/g)*.5;   end  /*k*/
      numeric digits d;                  return g/1
/*──────────────────────────────────────────────────────────────────────────────────────*/
elev: procedure; parse arg a,z,d;        earthRad= 6371000     /*earth radius in meters.*/
      aa= earthRad + a;  return sqrt(aa**2 + d**2 - 2*d*aa*cos( d2r(180-z) ) )  - earthRad
/*──────────────────────────────────────────────────────────────────────────────────────*/
colD: procedure; parse arg a,z;          sum= 0;   d= 0;    dd= .001;   infinity= 10000000
                   do while d<infinity;  delta= max(dd, dd*d)
                   sum= sum  +  rho( elev(a, z, d + 0.5*delta) ) * delta;     d= d + delta
                   end   /*while*/
      return sum
output   when using the default inputs:
angle|    air mass at sea level        air mass at 13,700 meters
─────┼─────────────────────────────────────────────────────────────
  0  │          1.00000000                     1.00000000
  5  │          1.00380963                     1.00380965
 10  │          1.01538466                     1.01538475
 15  │          1.03517744                     1.03517765
 20  │          1.06399053                     1.06399093
 25  │          1.10305937                     1.10306005
 30  │          1.15418974                     1.15419083
 35  │          1.21998076                     1.21998246
 40  │          1.30418931                     1.30419190
 45  │          1.41234169                     1.41234567
 50  │          1.55280404                     1.55281025
 55  │          1.73875921                     1.73876915
 60  │          1.99212000                     1.99213665
 65  │          2.35199740                     2.35202722
 70  │          2.89531368                     2.89537287
 75  │          3.79582352                     3.79596149
 80  │          5.53885809                     5.53928113
 85  │         10.07896219                    10.08115981
 90  │         34.32981136                    34.36666557
─────┴─────────────────────────────────────────────────────────────
Translation of: FreeBASIC
≪ → a ≪ a NEG 8500 / EXP ≫ ≫ ‘RHO’ STO

≪ 'RHO(√(aa^2+D^2-2*aa*D*COS(180-Z))-re)' EVAL 
≫ ‘COLD’ STO

≪ 
 6371000 3 PICK OVER + → a z re aa
  ≪ DEG 
    z 'Z' STO 'COLD' { D 0 1E7 } 1E-7 ∫ DROP
    0 'Z' STO 'COLD' { D 0 1E7 } 1E-7 ∫ DROP /
≫ ≫ ‘AM’ STO
≪ { } 0 90 FOR z z + z AM + 5 STEP
Output:
1: { 0 1 
     5 1.00380686363 
    10 1.01537368745 
    15 1.03515302646 
    20 1.06394782383 
    25 1.10299392042 
    30 1.15409753978 
    35 1.21985818096 
    40 1.30403285254 
    45 1.41214767977 
    50 1.55256798138 
    55 1.73847475415 
    60 1.99177718552 
    65 2.35157928673 
    70 2.89478919419 
    75 3.79512945489 
    80 5.53784169364 
    85 10.0771111633 
    90 34.3235064081 }
Translation of: FreeBASIC
const RE: f64 = 6371000.0; // Earth radius in meters
const DD: f64 = 0.001; // integrate in this fraction of the distance already covered
const FIN: f64 = 10000000.0; // integrate only to a height of 10000km, effectively infinity

fn rho(a: f64) -> f64 {
    // the density of air as a function of height above sea level
    (-a / 8500.0).exp()
}

fn height(a: f64, z: f64, d: f64) -> f64 {
    // a = altitude of observer
    // z = zenith angle (in degrees)
    // d = distance along line of sight
    let aa = RE + a;
    let hh = (aa * aa + d * d - 2.0 * d * aa * (180.0 - z).to_radians().cos()).sqrt();
    hh - RE
}

fn column_density(a: f64, z: f64) -> f64 {
    // integrates density along the line of sight
    let mut sum = 0.0;
    let mut d = 0.0;
    while d < FIN {
        // adaptive step size to avoid it taking forever
        let mut delta = DD * d;
        if delta < DD {
            delta = DD;
        }
        sum += rho(height(a, z, d + 0.5 * delta)) * delta;
        d += delta;
    }
    sum
}

fn airmass(a: f64, z: f64) -> f64 {
    column_density(a, z) / column_density(a, 0.0)
}

fn main() {
    println!("Angle     0 m              13700 m");
    println!("------------------------------------");
    for a in (0..=90).step_by(5) {
        let z = a as f64;
        println!(
            "{:2}      {:11.8}      {:11.8}",
            z,
            airmass(0.0, z),
            airmass(13700.0, z)
        );
    }
}
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
Translation of: FreeBASIC
$ include "seed7_05.s7i";
  include "float.s7i";
  include "math.s7i";

const float: DEG is 0.017453292519943295769236907684886127134; #degrees to radians
const float: RE is 6371000.0;   #Earth radius in meters
const float: dd is 0.001;       #integrate in this fraction of the distance already covered
const float: FIN is 10000000.0; #integrate only to a height of 10000km, effectively infinity

const func float: rho (in float: a) is
  #the density of air as a function of height above sea level
  return exp(-a / 8500.0);

const func float: height (in float: a, in float: z, in float: d) is func
  #a is altitude of observer
  #z is zenith angle (in degrees)
  #d is distance along line of sight
  result
    var float: r is 0.0;
  local
    var float: AA is 0.0;
    var float: HH is 0.0;
  begin
    AA := RE + a;
    HH := sqrt( AA ** 2.0 + d ** 2.0 - 2.0 * d * AA * cos((180.0 - z) * DEG) );
    r := HH - RE;
  end func;

const func float: columnDensity (in float: a, in float: z) is func
  #integrates density along line of sight
  result
    var float: sum is 0.0;
  local
    var float: d is 0.0;
    var float: delta is 0.0;
  begin
    while d < FIN do
      delta := max(dd, dd * d); #adaptive step size to avoid taking it forever
      sum +:= rho(height(a, z, d + 0.5 * delta)) * delta;
      d +:= delta;
    end while;
  end func;

const func float: airmass (in float: a, in float: z) is
  return columnDensity(a, z) / columnDensity(a, 0.0);

const proc: main is func
  local
    var integer: zz is 0;
    var float: z is 0.0;
  begin
    writeln("Angle     0 m              13700 m");
    writeln("------------------------------------");
    for zz range 0 to 90 step 5 do
      z := flt(zz);
      write(z lpad 4);
      write(airmass(0.0, z) digits 8 lpad 15);
      writeln(airmass(13700.0, z) digits 8 lpad 17);
    end for;
  end func;
Output:
Angle     0 m              13700 m
------------------------------------
 0.0     1.00000000       1.00000000
 5.0     1.00380963       1.00380965
10.0     1.01538466       1.01538475
15.0     1.03517744       1.03517765
20.0     1.06399053       1.06399093
25.0     1.10305937       1.10306005
30.0     1.15418974       1.15419083
35.0     1.21998076       1.21998246
40.0     1.30418931       1.30419190
45.0     1.41234169       1.41234567
50.0     1.55280404       1.55281025
55.0     1.73875921       1.73876915
60.0     1.99212000       1.99213665
65.0     2.35199740       2.35202722
70.0     2.89531368       2.89537287
75.0     3.79582352       3.79596149
80.0     5.53885809       5.53928113
85.0    10.07896219      10.08115981
90.0    34.32981136      34.36666557
Translation of: Go
import Foundation

extension Double {
  var radians: Double { self * .pi / 180 }
}

func columnDensity(_ a: Double, _ z: Double) -> Double {
  func rho(_ a: Double) -> Double {
    exp(-a / 8500)
  }

  func height(_ d: Double) -> Double {
    let aa = 6_371_000 + a
    let hh = aa * aa + d * d - 2 * d * aa * cos((180 - z).radians)

    return hh.squareRoot() - 6_371_000
  }

  var sum = 0.0
  var d = 0.0

  while d < 1e7 {
    let delta = max(0.001, 0.001 * d)

    sum += rho(height(d + 0.5 * delta)) * delta
    d += delta
  }

  return sum
}

func airMass(altitude: Double, zenith: Double) -> Double {
  return columnDensity(altitude, zenith) / columnDensity(altitude, 0)
}

print("Angle     0 m              13700 m")
print("------------------------------------")

for z in stride(from: 0.0, through: 90.0, by: 5.0) {
  let air = String(
    format: "%2.0f      %11.8f      %11.8f",
    z,
    airMass(altitude: 0, zenith: z),
    airMass(altitude: 13700, zenith: z)
  )

  print(air)
}
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
Translation of: FreeBASIC
Library: Wren-math
Library: Wren-fmt
import "./math" for Math
import "./fmt" for Fmt

// constants
var RE  = 6371000  // radius of earth in meters
var DD  = 0.001    // integrate in this fraction of the distance already covered
var FIN = 1e7      // integrate only to a height of 10000km, effectively infinity

// The density of air as a function of height above sea level.
var rho = Fn.new { |a| (-a/8500).exp }

// a = altitude of observer
// z = zenith angle (in degrees)
// d = distance along line of sight
var height = Fn.new { |a, z, d|
    var aa = RE + a
    var hh = (aa * aa + d * d - 2 * d * aa * (Math.radians(180-z).cos)).sqrt
    return hh - RE
}

// Integrates density along the line of sight.
var columnDensity = Fn.new { |a, z|
    var sum = 0
    var d = 0
    while (d < FIN) {
        var delta = DD.max(DD * d) // adaptive step size to avoid it taking forever
        sum = sum + rho.call(height.call(a, z, d + 0.5 * delta)) * delta
        d = d + delta
    }
    return sum
}

var airmass = Fn.new { |a, z| columnDensity.call(a, z) / columnDensity.call(a, 0) }

System.print("Angle     0 m              13700 m")
System.print("------------------------------------")
var z = 0
while (z <= 90) {
    Fmt.print("$2d      $11.8f      $11.8f", z, airmass.call(0, z), airmass.call(13700, z))
    z = z + 5
}
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557
Translation of: FreeBASIC
define DEG = 0.017453292519943295769236907684886127134;  \degrees to radians
define RE = 6371000.;   \Earth radius in meters
define DD = 0.001;      \integrate in this fraction of the distance already covered
define FIN = 10000000.; \integrate only to a height of 10000km, effectively infinity
 
function real Max(A, B);
real A, B;
    return (if A>B then A else B);

function real Rho(A);
real A;
[   \the density of air as a function of height above sea level
    return Exp(-A/8500.0)
end; \function
 
function real Height( A, Z, D );
real A, \= altitude of observer
     Z, \= zenith angle (in degrees)
     D; \= distance along line of sight
real AA, HH;
[   AA:= RE + A;
    HH:= sqrt( AA*AA + D*D - 2.*D*AA*Cos((180.-Z)*DEG) );
    return HH - RE;
end; \function
 
function real Column_density( A, Z );
real A, Z;   \integrates density along the line of sight
real Sum, D, Delta;
[   Sum:= 0.0; D:= 0.0;
    while D<FIN do
        [Delta:= Max(DD, (DD)*D); \adaptive step size to avoid it taking forever:
        Sum:= Sum + Rho(Height(A, Z, D+0.5*Delta))*Delta;
        D:= D + Delta;
        ];
    return Sum;
end; \function
 
function real Airmass( A, Z );
real A, Z;
[   return Column_density( A, Z ) / Column_density( A, 0. );
end; \function
 
real Z;
[Text(0, "Angle     0 m              13700 m^M^J");
 Text(0, "------------------------------------^M^J");
Z:= 0.;
while Z<=90. do
    [Format(2, 0);  RlOut(0, Z);
    Format(8, 8);   RlOut(0, Airmass(0., Z));
    RlOut(0, Airmass(13700., Z));  CrLf(0);
    Z:= Z + 5.;
    ]
]
Output:
Angle     0 m              13700 m
------------------------------------
 0       1.00000000       1.00000000
 5       1.00380963       1.00380965
10       1.01538466       1.01538475
15       1.03517744       1.03517765
20       1.06399053       1.06399093
25       1.10305937       1.10306005
30       1.15418974       1.15419083
35       1.21998076       1.21998246
40       1.30418931       1.30419190
45       1.41234169       1.41234567
50       1.55280404       1.55281025
55       1.73875921       1.73876915
60       1.99212000       1.99213665
65       2.35199740       2.35202722
70       2.89531368       2.89537287
75       3.79582352       3.79596149
80       5.53885809       5.53928113
85      10.07896219      10.08115981
90      34.32981136      34.36666557