atmospheric 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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