atmospheric 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 91653cc446f211fbec67bb75c9ca04fb3135e24bb7d1e1e52707e865508285d5
4
+ data.tar.gz: c7cb3677f1d0d1c896815a6607241a58b2649bc43313efc270ce8d124ddf44e7
5
+ SHA512:
6
+ metadata.gz: f8233e299548737ac029e652c50effc3a94fccc83bf1038295429242f9554da545c0c9abdc8c57638da3abf5e91535ec29d01c9cb3e8d978c4d05411d9b30a6c
7
+ data.tar.gz: a8c034abf9efec7b006e9be2d22119083f2d46fbacda5b2592e6d0fcc1fd657e9d1894dcb5e0ec0b81ac07268be51baa29ee3d8e8d2c8f31a26bd0adb1b3330e
data/LICENSE.txt ADDED
@@ -0,0 +1,25 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2018, Ribose
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.adoc ADDED
@@ -0,0 +1,97 @@
1
+ = Atmospheric for Ruby (International Standard Atmosphere / ICAO Standard Atmosphere (ISA))
2
+
3
+ == Purpose
4
+
5
+ This repository provides Ruby code for calculating values defined in the
6
+ following documents:
7
+
8
+ * International Standard Atmosphere (ISA) from ISO 2533:1975
9
+ * ICAO Standard Atmosphere (ICAO Doc 7488/3, 1994)
10
+
11
+ Which are technically identical documents but different in presentation and
12
+ units (the ICAO document includes `ft` in addition to `m`).
13
+
14
+ == Usage
15
+
16
+ [source,ruby]
17
+ ----
18
+ > require 'atmospheric'
19
+ > instance = Atmospheric::Isa.method_name
20
+ > # method_name is one of the following
21
+ ----
22
+
23
+ The available methods are:
24
+
25
+ * `geometric_altitude_from_geopotential(geopotential_altitude)`
26
+ * `geopotential_altitude_from_geometric(geometric_altitude)`
27
+ * `temperature_at_layer_from_H(geopotential_altitude)`
28
+ * `temperature_at_layer_celcius(geopotential_altitude)`
29
+ * `pressure_from_H_mbar(geopotential_altitude)`
30
+ * `pressure_from_H_mmhg(geopotential_altitude)`
31
+ * `density_from_H(geopotential_altitude)`
32
+ * `gravity_at_geopotential(geopotential_altitude)`
33
+ * `p_p_n_from_H(geopotential_altitude)`
34
+ * `rho_rho_n_from_H(geopotential_altitude)`
35
+ * `root_rho_rho_n_from_H(geopotential_altitude)`
36
+ * `speed_of_sound_from_H(geopotential_altitude)`
37
+ * `dynamic_viscosity_from_H(geopotential_altitude)`
38
+ * `kinematic_viscosity_from_H(geopotential_altitude)`
39
+ * `thermal_conductivity_from_H(geopotential_altitude)`
40
+ * `pressure_scale_height_from_H(geopotential_altitude)`
41
+ * `specific_weight_from_H(geopotential_altitude)`
42
+ * `air_number_density_from_H(geopotential_altitude)`
43
+ * `mean_air_particle_speed_from_H(geopotential_altitude)`
44
+ * `air_particle_collision_frequency_from_H(geopotential_altitude)`
45
+ * `mean_free_path_of_air_particles_from_H(geopotential_altitude)`
46
+
47
+
48
+ == Testing
49
+
50
+ [source,sh]
51
+ ----
52
+ $ rspec
53
+ ----
54
+
55
+ Tests are encoded in `spec/fixtures/tests.yml` in the following format:
56
+
57
+ [source,yml]
58
+ ----
59
+ - H: -2000.0
60
+ h: -1999.0
61
+ TK: 301.15
62
+ TC: 28.0
63
+ p_mbar: 1277.74
64
+ p_mmhg: 958.382
65
+ rho: 1.47808
66
+ g: 9.8128
67
+ p_p_n: 1.26103
68
+ rho_rho_n: 1.20659
69
+ root_rho_rho_n: 1.09845
70
+ a: 347.886
71
+ mu: 1.8514e-05
72
+ v: 1.2526e-05
73
+ lambda: 0.026359
74
+ H_p: 8809.5
75
+ gamma: 14.504
76
+ n: 3.0734e+25
77
+ v_bar: 469.18
78
+ omega: 8535100000.0
79
+ l: 549710000.0
80
+ ----
81
+
82
+ Each of these values are associated with a cell in the tables of the source
83
+ documents.
84
+
85
+ The only defining value in a tests is `H` (geopotential altitude).
86
+ It is used to generate all the other values.
87
+
88
+
89
+ == License
90
+
91
+ Copyright Ribose and its respective owners.
92
+
93
+
94
+ == TODO
95
+
96
+ * make into module
97
+ * expose this as a plugin to LutaML / Metanorma YAML2text
@@ -0,0 +1,350 @@
1
+ module Atmospheric
2
+ module Isa
3
+ # International Standard Atmosphere (ISA) (ISO 2533:1975)
4
+ # ICAO Standard Atmosphere (ICAO Doc 7488/3, 1994)
5
+
6
+ # 2.1 Primary constants and characteristics
7
+ # Table 1 - Main constants and characteristics adopted for
8
+ # the calculation of the ISO Standard Atmosphere
9
+ CONST = {
10
+ g_n: 9.80665, # m.s-2
11
+ N_A: 602.257e21, # Avogadro constant, mol-1
12
+ p_n: 101325, # In Pascal
13
+ rho_n: 1.225, # rho_n standard air density
14
+ T_n: 288.15, # T_n standard thermodynamic air temperature at mean sea level
15
+ R_star: 8.31432, # universal gas constant
16
+
17
+ radius: 6356766, # radius of the Earth (m)
18
+ k: 1.4 # adiabatic index, dimensionless
19
+ }
20
+
21
+ # 2.2 The equation of the static atmosphere and the perfect gas law
22
+ # Formula (2)
23
+ # M: air molar mass at sea level, kg.kmol-1
24
+ # Value given in 2.1 as M: 28.964720
25
+ CONST[:M] = (CONST[:rho_n] * CONST[:R_star] * CONST[:T_n]) / CONST[:p_n]
26
+
27
+ # Formula (3)
28
+ # R: specific gas constant, J.K-1.kg-1.
29
+ # Value given in 2.1 as R: 287.05287
30
+ CONST[:R] = CONST[:R_star] / CONST[:M]
31
+
32
+
33
+ class << self
34
+ # 2.3 Geopotential and geometric altitides; acceleration of free fall
35
+
36
+ # 2.3 Formula (8)
37
+ # H to h
38
+ # h(m)
39
+ def geometric_altitude_from_geopotential(geopotential_alt)
40
+ CONST[:radius] * geopotential_alt / (CONST[:radius] - geopotential_alt)
41
+ end
42
+
43
+ # 2.3 Formula (9)
44
+ # h to H
45
+ # H(m)
46
+ def geopotential_altitude_from_geometric(geometric_alt)
47
+ CONST[:radius] * geometric_alt / (CONST[:radius] + geometric_alt)
48
+ end
49
+
50
+ # 2.3 Formula (7)
51
+ # g(h)
52
+ def gravity_at_geometric(geometric_alt)
53
+ temp = CONST[:radius] / (CONST[:radius] + geometric_alt)
54
+ CONST[:g_n] * temp * temp
55
+ end
56
+
57
+ def gravity_at_geopotential(geopotential_alt)
58
+ geometric_h = geometric_altitude_from_geopotential(geopotential_alt)
59
+ gravity_at_geometric(geometric_h)
60
+ end
61
+
62
+ # 2.4 Atmospheric composition and air molar mass
63
+
64
+ # 2.5 Physical characteristics of the atmosphere at mean sea level
65
+
66
+ # 2.6 Temperature and vertical temperature gradient
67
+
68
+ # Formula (11)
69
+ # T
70
+ def temperature_at_layer_from_H(geopotential_alt)
71
+ lower_layer_index = locate_lower_layer(geopotential_alt)
72
+ lower_layer = TEMPERATURE_LAYERS[lower_layer_index]
73
+ beta = lower_layer[:B]
74
+ capital_t_b = lower_layer[:T]
75
+ capital_h_b = lower_layer[:H]
76
+
77
+ capital_t_b + (beta * (geopotential_alt - capital_h_b))
78
+ end
79
+
80
+ def temperature_at_layer_celcius(geopotential_alt)
81
+ kelvin_to_celsius(temperature_at_layer_from_H(geopotential_alt))
82
+ end
83
+
84
+ def locate_lower_layer(geopotential_alt)
85
+ # Return first layer if lower than lowest
86
+ return 0 if geopotential_alt < TEMPERATURE_LAYERS[0][:H]
87
+
88
+ # Return second last layer if beyond last layer
89
+ i = TEMPERATURE_LAYERS.length - 1
90
+ return i - 1 if geopotential_alt >= TEMPERATURE_LAYERS[i][:H]
91
+
92
+ # find last layer with H larger than our H
93
+ TEMPERATURE_LAYERS.each_with_index do |layer, i|
94
+ return i if layer[:H] > geopotential_alt
95
+ end
96
+
97
+ nil
98
+ end
99
+
100
+ # Table 4 - Temperature and vertical temperature gradients
101
+ #
102
+ TEMPERATURE_LAYERS = [
103
+ # H is Geopotential altitude (base altitude) above mean sea level, m
104
+ # T is Temperature, K
105
+ # B is Temperature gradient, "beta", K m^-1
106
+
107
+ # This line is from ICAO 7488/3
108
+ # [H: -5000, T: 320.65, B: -0.0065 ],
109
+
110
+ # This line is from ISO 2533:1975
111
+ {H: -2000, T: 301.15, B: -0.0065 },
112
+ {H: 0, T: 288.15, B: -0.0065 },
113
+ {H: 11000, T: 216.65, B: 0 },
114
+ {H: 20000, T: 216.65, B: 0.001 },
115
+ {H: 32000, T: 228.65, B: 0.0028 },
116
+ {H: 47000, T: 270.65, B: 0 },
117
+ {H: 51000, T: 270.65, B: -0.0028 },
118
+ {H: 71000, T: 214.65, B: -0.002 },
119
+ {H: 80000, T: 196.65},
120
+ ]
121
+
122
+
123
+ # 2.7 Pressure
124
+
125
+ # Base pressure values given defined `TEMPERATURE_LAYERS` and constants
126
+ def pressure_layers
127
+ return @pressure_layers if @pressure_layers
128
+
129
+ # assuming TEMPERATURE_LAYERS index 1 base altitude is zero (mean sea level)
130
+ p = []
131
+
132
+ TEMPERATURE_LAYERS.each_with_index do |x, i|
133
+ last_i = (i == 0) ? 0 : i - 1
134
+ last_layer = TEMPERATURE_LAYERS[last_i]
135
+ beta = last_layer[:B]
136
+
137
+ if last_layer[:H] <= 0
138
+ pb = CONST[:p_n]
139
+ capital_h_b = 0
140
+ capital_t_b = CONST[:T_n]
141
+ else
142
+ pb = p[last_i]
143
+ capital_h_b = last_layer[:H]
144
+ capital_t_b = last_layer[:T]
145
+ end
146
+
147
+ current_layer = TEMPERATURE_LAYERS[i]
148
+ geopotential_alt = current_layer[:H]
149
+ temp = current_layer[:T]
150
+
151
+ p[i] = if beta != 0
152
+ # Formula (12)
153
+ pb * (1 + ((beta / capital_t_b) * (geopotential_alt - capital_h_b))) ** (-CONST[:g_n] / (beta * CONST[:R]))
154
+ else
155
+ # Formula (13)
156
+ pb * Math.exp(-(CONST[:g_n] / (CONST[:R] * temp)) * (geopotential_alt - capital_h_b))
157
+ end
158
+ end
159
+
160
+ @pressure_layers = p
161
+ end
162
+
163
+ # puts "PRE-CALCULATED PRESSURE LAYERS:"
164
+ # pp @pressure_layers
165
+
166
+ def pa_to_mmhg(pascal)
167
+ pascal * 0.007500616827
168
+ end
169
+
170
+ def pa_to_mbar(pascal)
171
+ pascal * 0.01
172
+ end
173
+
174
+ # Pressure for a given geopotential altitude `H` (m) above mean sea level
175
+ def pressure_from_H(geopotential_alt)
176
+ i = locate_lower_layer(geopotential_alt)
177
+ lower_temperature_layer = TEMPERATURE_LAYERS[i]
178
+ beta = lower_temperature_layer[:B]
179
+ capital_h_b = lower_temperature_layer[:H]
180
+ capital_t_b = lower_temperature_layer[:T]
181
+ temp = temperature_at_layer_from_H(geopotential_alt)
182
+ pb = pressure_layers[i]
183
+
184
+ if beta != 0
185
+ # Formula (12)
186
+ pb * (1 + ((beta / capital_t_b) * (geopotential_alt - capital_h_b))) ** (-CONST[:g_n] / (beta * CONST[:R]))
187
+ else
188
+ # Formula (13)
189
+ pb * Math.exp(-(CONST[:g_n] / (CONST[:R] * temp)) * (geopotential_alt - capital_h_b))
190
+ end
191
+ end
192
+
193
+ def pressure_from_H_mbar(geopotential_alt)
194
+ pa_to_mbar(pressure_from_H(geopotential_alt))
195
+ end
196
+
197
+ def pressure_from_H_mmhg(geopotential_alt)
198
+ pa_to_mmhg(pressure_from_H(geopotential_alt))
199
+ end
200
+
201
+ def p_p_n_from_H(geopotential_alt)
202
+ pressure_from_H(geopotential_alt) / CONST[:p_n]
203
+ end
204
+
205
+ # 2.8 Density and specific weight
206
+
207
+ # Calculate density for a given geopotential altitude `H` (m) above mean sea level
208
+ # Formula (14)
209
+ # rho
210
+ def density_from_H(geopotential_alt)
211
+ temp = temperature_at_layer_from_H(geopotential_alt)
212
+ p = pressure_from_H(geopotential_alt)
213
+
214
+ p / (CONST[:R] * temp)
215
+ end
216
+
217
+ def rho_rho_n_from_H(geopotential_alt)
218
+ density_from_H(geopotential_alt) / CONST[:rho_n]
219
+ end
220
+
221
+ def root_rho_rho_n_from_H(geopotential_alt)
222
+ Math.sqrt(rho_rho_n_from_H(geopotential_alt))
223
+ end
224
+
225
+
226
+ # Specific weight
227
+ # Formula (15)
228
+ # gamma
229
+ def specific_weight_from_H(geopotential_alt)
230
+ density_from_H(geopotential_alt) * gravity_at_geopotential(geopotential_alt)
231
+ end
232
+
233
+ # 2.9 Pressure scale height
234
+ # Formula (16)
235
+ # H_p
236
+ def pressure_scale_height_from_temp(temp)
237
+ (CONST[:R] * temp) / CONST[:g_n]
238
+ end
239
+
240
+ def pressure_scale_height_from_H(geopotential_alt)
241
+ temp = temperature_at_layer_from_H(geopotential_alt)
242
+ (CONST[:R] * temp) / gravity_at_geopotential(geopotential_alt)
243
+ end
244
+
245
+ # 2.10 Air number density
246
+ # Formula (17)
247
+ # n
248
+ def air_number_density_from_H(geopotential_alt)
249
+ temp = temperature_at_layer_from_H(geopotential_alt)
250
+ p = pressure_from_H(geopotential_alt)
251
+
252
+ CONST[:N_A] * p / (CONST[:R_star] * temp)
253
+ end
254
+
255
+ # 2.11 Mean air-particle speed
256
+ # Formula (18)
257
+ # v_bar
258
+ # CORRECT
259
+ def mean_air_particle_speed_from_temp(temp)
260
+ 1.595769 * Math.sqrt(CONST[:R] * temp)
261
+ end
262
+
263
+ def mean_air_particle_speed_from_H(geopotential_alt)
264
+ temp = temperature_at_layer_from_H(geopotential_alt)
265
+ mean_air_particle_speed_from_temp(temp)
266
+ end
267
+
268
+ # 2.12 Mean free path of air particles
269
+ # Formula (19)
270
+ # l
271
+ def mean_free_path_of_air_particles_from_H(geopotential_alt)
272
+ 1 / (1.414213562 * 3.141592654 * (0.365e-9 ** 2) * air_number_density_from_H(geopotential_alt))
273
+ end
274
+
275
+ # 2.13 Air-particle collision frequency
276
+ # Formula (20)
277
+ # omega
278
+ def air_particle_collision_frequency_from_temp(n, temp)
279
+ 4 * (0.365e-9 ** 2) * ((3.141592654 / (CONST[:R_star] * CONST[:M])) ** 0.5) * n * CONST[:R_star] * (temp ** 0.5)
280
+ end
281
+
282
+ def air_particle_collision_frequency_from_H(geopotential_alt)
283
+ temp = temperature_at_layer_from_H(geopotential_alt)
284
+ n = air_number_density_from_H(geopotential_alt)
285
+ air_particle_collision_frequency_from_temp(n, temp)
286
+ end
287
+
288
+ # 2.14 Speed of sound
289
+ # Formula (21)
290
+ # a (ms-1)
291
+ # CORRECT
292
+ def speed_of_sound_from_temp(temp)
293
+ # `kappa` (ratio of c_p / c_v) = 1.4 (see 2.14)
294
+ kappa = 1.4
295
+ Math.sqrt(kappa * CONST[:R] * temp)
296
+ end
297
+
298
+ def speed_of_sound_from_H(geopotential_alt)
299
+ temp = temperature_at_layer_from_H(geopotential_alt)
300
+ speed_of_sound_from_temp(temp)
301
+ end
302
+
303
+
304
+ # 2.15 Dynamic viscosity
305
+ # Formula (22)
306
+ # mu (Pa s)
307
+ def dynamic_viscosity(temp)
308
+ # Sutherland's empirical constants in the equation for dynamic viscosity
309
+ capital_b_s = 1.458e-6
310
+ capital_s = 110.4
311
+
312
+ (capital_b_s * (temp ** (1.5))) / (temp + capital_s)
313
+ end
314
+
315
+ def dynamic_viscosity_from_H(geopotential_alt)
316
+ temp = temperature_at_layer_from_H(geopotential_alt)
317
+ dynamic_viscosity(temp)
318
+ end
319
+
320
+ # 2.16 Kinematic viscosity
321
+ # Formula (23)
322
+ # v
323
+ def kinematic_viscosity(temp)
324
+ dynamic_viscosity(temp) / CONST[:rho_n]
325
+ end
326
+
327
+ def kinematic_viscosity_from_H(geopotential_alt)
328
+ temp = temperature_at_layer_from_H(geopotential_alt)
329
+ dynamic_viscosity(temp) / density_from_H(geopotential_alt)
330
+ end
331
+
332
+ # 2.17 Thermal conductivity
333
+ # Formula (24)
334
+ # lambda
335
+ def thermal_conductivity_from_temp(temp)
336
+ (2.648151e-3 * (temp ** (1.5))) / (temp + (245.4 * (10 ** (-12.0/temp))))
337
+ end
338
+
339
+ def thermal_conductivity_from_H(geopotential_alt)
340
+ temp = temperature_at_layer_from_H(geopotential_alt)
341
+ thermal_conductivity_from_temp(temp)
342
+ end
343
+
344
+ def kelvin_to_celsius(kelvin)
345
+ kelvin - 273.15
346
+ end
347
+
348
+ end
349
+ end
350
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atmospheric
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "atmospheric/version"
4
+ require_relative "atmospheric/isa"
5
+
6
+ module Atmospheric
7
+ class Error < StandardError; end
8
+ # Your code goes here...
9
+ end