earthsciencekit 0.2.0__py3-none-any.whl

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.
Files changed (102) hide show
  1. earthsciencekit/__init__.py +6 -0
  2. earthsciencekit/_validation.py +62 -0
  3. earthsciencekit/atmosphere/__init__.py +57 -0
  4. earthsciencekit/atmosphere/core/__init__.py +5 -0
  5. earthsciencekit/atmosphere/core/base.py +10 -0
  6. earthsciencekit/atmosphere/systems/__init__.py +1 -0
  7. earthsciencekit/atmosphere/systems/dynamics.py +208 -0
  8. earthsciencekit/atmosphere/systems/hydrostatics.py +214 -0
  9. earthsciencekit/atmosphere/systems/moisture.py +162 -0
  10. earthsciencekit/atmosphere/systems/thermodynamics.py +222 -0
  11. earthsciencekit/atmosphere/tests/__init__.py +0 -0
  12. earthsciencekit/atmosphere/tests/test_models.py +741 -0
  13. earthsciencekit/atmosphere/tests/test_physics.py +81 -0
  14. earthsciencekit/atmosphere/visualizers/__init__.py +9 -0
  15. earthsciencekit/climate/__init__.py +63 -0
  16. earthsciencekit/climate/core/__init__.py +5 -0
  17. earthsciencekit/climate/core/base.py +10 -0
  18. earthsciencekit/climate/systems/__init__.py +1 -0
  19. earthsciencekit/climate/systems/carbon.py +76 -0
  20. earthsciencekit/climate/systems/forcing.py +124 -0
  21. earthsciencekit/climate/systems/radiation.py +214 -0
  22. earthsciencekit/climate/systems/response.py +402 -0
  23. earthsciencekit/climate/systems/sea_level.py +77 -0
  24. earthsciencekit/climate/tests/__init__.py +0 -0
  25. earthsciencekit/climate/tests/test_models.py +1015 -0
  26. earthsciencekit/climate/tests/test_physics.py +16 -0
  27. earthsciencekit/climate/visualizers/__init__.py +9 -0
  28. earthsciencekit/constants/__init__.py +23 -0
  29. earthsciencekit/geophysics/__init__.py +77 -0
  30. earthsciencekit/geophysics/core/__init__.py +5 -0
  31. earthsciencekit/geophysics/core/base.py +10 -0
  32. earthsciencekit/geophysics/systems/__init__.py +1 -0
  33. earthsciencekit/geophysics/systems/deformation.py +161 -0
  34. earthsciencekit/geophysics/systems/dynamo.py +159 -0
  35. earthsciencekit/geophysics/systems/figure.py +292 -0
  36. earthsciencekit/geophysics/systems/gravity.py +235 -0
  37. earthsciencekit/geophysics/systems/magnetism.py +354 -0
  38. earthsciencekit/geophysics/tests/__init__.py +0 -0
  39. earthsciencekit/geophysics/tests/test_models.py +898 -0
  40. earthsciencekit/geophysics/tests/test_physics.py +171 -0
  41. earthsciencekit/geophysics/visualizers/__init__.py +9 -0
  42. earthsciencekit/integrators/__init__.py +62 -0
  43. earthsciencekit/oceanography/__init__.py +61 -0
  44. earthsciencekit/oceanography/core/__init__.py +5 -0
  45. earthsciencekit/oceanography/core/base.py +10 -0
  46. earthsciencekit/oceanography/systems/__init__.py +1 -0
  47. earthsciencekit/oceanography/systems/circulation.py +388 -0
  48. earthsciencekit/oceanography/systems/hydrography.py +212 -0
  49. earthsciencekit/oceanography/systems/inertial.py +86 -0
  50. earthsciencekit/oceanography/systems/waves.py +281 -0
  51. earthsciencekit/oceanography/tests/__init__.py +0 -0
  52. earthsciencekit/oceanography/tests/test_models.py +916 -0
  53. earthsciencekit/oceanography/tests/test_physics.py +102 -0
  54. earthsciencekit/oceanography/visualizers/__init__.py +9 -0
  55. earthsciencekit/plate_tectonics/__init__.py +59 -0
  56. earthsciencekit/plate_tectonics/core/__init__.py +5 -0
  57. earthsciencekit/plate_tectonics/core/base.py +10 -0
  58. earthsciencekit/plate_tectonics/systems/__init__.py +1 -0
  59. earthsciencekit/plate_tectonics/systems/boundaries.py +201 -0
  60. earthsciencekit/plate_tectonics/systems/cooling.py +238 -0
  61. earthsciencekit/plate_tectonics/systems/isostasy.py +157 -0
  62. earthsciencekit/plate_tectonics/systems/kinematics.py +290 -0
  63. earthsciencekit/plate_tectonics/tests/__init__.py +0 -0
  64. earthsciencekit/plate_tectonics/tests/test_models.py +920 -0
  65. earthsciencekit/plate_tectonics/tests/test_physics.py +94 -0
  66. earthsciencekit/plate_tectonics/visualizers/__init__.py +9 -0
  67. earthsciencekit/py.typed +0 -0
  68. earthsciencekit/results.py +73 -0
  69. earthsciencekit/seismology/__init__.py +59 -0
  70. earthsciencekit/seismology/core/__init__.py +5 -0
  71. earthsciencekit/seismology/core/base.py +10 -0
  72. earthsciencekit/seismology/systems/__init__.py +1 -0
  73. earthsciencekit/seismology/systems/interfaces.py +102 -0
  74. earthsciencekit/seismology/systems/rays.py +221 -0
  75. earthsciencekit/seismology/systems/sources.py +249 -0
  76. earthsciencekit/seismology/systems/waves.py +268 -0
  77. earthsciencekit/seismology/tests/__init__.py +0 -0
  78. earthsciencekit/seismology/tests/test_models.py +852 -0
  79. earthsciencekit/seismology/tests/test_physics.py +100 -0
  80. earthsciencekit/seismology/visualizers/__init__.py +9 -0
  81. earthsciencekit/tests/__init__.py +0 -0
  82. earthsciencekit/tests/test_core.py +181 -0
  83. earthsciencekit/tests/test_relations.py +36 -0
  84. earthsciencekit/units/__init__.py +66 -0
  85. earthsciencekit/visualizers/__init__.py +45 -0
  86. earthsciencekit/volcanology/__init__.py +61 -0
  87. earthsciencekit/volcanology/core/__init__.py +5 -0
  88. earthsciencekit/volcanology/core/base.py +10 -0
  89. earthsciencekit/volcanology/systems/__init__.py +1 -0
  90. earthsciencekit/volcanology/systems/eruptions.py +161 -0
  91. earthsciencekit/volcanology/systems/geochemistry.py +226 -0
  92. earthsciencekit/volcanology/systems/geochronology.py +304 -0
  93. earthsciencekit/volcanology/systems/magma.py +190 -0
  94. earthsciencekit/volcanology/tests/__init__.py +0 -0
  95. earthsciencekit/volcanology/tests/test_models.py +485 -0
  96. earthsciencekit/volcanology/tests/test_physics.py +176 -0
  97. earthsciencekit/volcanology/visualizers/__init__.py +9 -0
  98. earthsciencekit-0.2.0.dist-info/METADATA +127 -0
  99. earthsciencekit-0.2.0.dist-info/RECORD +102 -0
  100. earthsciencekit-0.2.0.dist-info/WHEEL +5 -0
  101. earthsciencekit-0.2.0.dist-info/licenses/LICENSE +15 -0
  102. earthsciencekit-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,162 @@
1
+ """Moisture: saturation vapour pressure, mixing ratio, specific and relative humidity and virtual temperature.
2
+
3
+ SI units unless stated otherwise.
4
+ """
5
+
6
+ import numpy as np
7
+
8
+ from earthsciencekit._validation import model, quantity, require
9
+ from earthsciencekit.constants import EPSILON_WATER, R_VAPOR
10
+
11
+ __all__ = [
12
+ "saturation_vapor_pressure",
13
+ "mixing_ratio",
14
+ "specific_humidity",
15
+ "virtual_temperature",
16
+ "relative_humidity",
17
+ ]
18
+
19
+
20
+ @model
21
+ def saturation_vapor_pressure(temperature):
22
+ """Integrated Clausius-Clapeyron approximation.
23
+
24
+ Phase equilibrium makes saturation vapor pressure strongly temperature dependent.
25
+
26
+ Parameters
27
+ ----------
28
+ temperature : float or array_like
29
+ K; constraint: temperature.
30
+
31
+ Returns
32
+ -------
33
+ numpy scalar or ndarray
34
+ Model value [kg/m^3]. Scalar input returns a NumPy scalar; arrays broadcast.
35
+
36
+ Notes
37
+ -----
38
+ Liquid-water reference with constant latent heat; restricted to 180-330 K, not an ice saturation formula.
39
+ Equation: ``611.2 * np.exp((2.5e6 / R_VAPOR) * (1 / 273.15 - 1 / temperature))``.
40
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
41
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
42
+ """
43
+ temperature = quantity("temperature", temperature, lower=180, upper=330)
44
+ return 611.2 * np.exp((2.5e6 / R_VAPOR) * (1 / 273.15 - 1 / temperature))
45
+
46
+
47
+ @model
48
+ def mixing_ratio(vapor_pressure, total_pressure):
49
+ """Water-vapor mass mixing ratio.
50
+
51
+ Partial pressure determines water mass per unit dry-air mass.
52
+
53
+ Parameters
54
+ ----------
55
+ vapor_pressure : float or array_like
56
+ Pa; constraint: nonnegative.
57
+ total_pressure : float or array_like
58
+ Pa; constraint: positive.
59
+
60
+ Returns
61
+ -------
62
+ numpy scalar or ndarray
63
+ Model value [kg/m^3]. Scalar input returns a NumPy scalar; arrays broadcast.
64
+
65
+ Notes
66
+ -----
67
+ Ideal vapor mixture; vapor pressure must be below total pressure.
68
+ Equation: ``EPSILON_WATER * vapor_pressure / (total_pressure - vapor_pressure)``.
69
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
70
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
71
+ """
72
+ vapor_pressure = quantity("vapor_pressure", vapor_pressure, lower=0)
73
+ total_pressure = quantity("total_pressure", total_pressure, lower=0, strict_lower=True)
74
+ require(vapor_pressure < total_pressure, "vapor pressure must be below total pressure")
75
+ return EPSILON_WATER * vapor_pressure / (total_pressure - vapor_pressure)
76
+
77
+
78
+ @model
79
+ def specific_humidity(mixing_ratio):
80
+ """Specific humidity from mixing ratio.
81
+
82
+ Changing the denominator connects two moisture conventions.
83
+
84
+ Parameters
85
+ ----------
86
+ mixing_ratio : float or array_like
87
+ kg/kg dry air; constraint: nonnegative.
88
+
89
+ Returns
90
+ -------
91
+ numpy scalar or ndarray
92
+ Model value [kg/m^3]. Scalar input returns a NumPy scalar; arrays broadcast.
93
+
94
+ Notes
95
+ -----
96
+ Output is water mass per total moist-air mass.
97
+ Equation: ``mixing_ratio / (1 + mixing_ratio)``.
98
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
99
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
100
+ """
101
+ mixing_ratio = quantity("mixing_ratio", mixing_ratio, lower=0)
102
+ return mixing_ratio / (1 + mixing_ratio)
103
+
104
+
105
+ @model
106
+ def virtual_temperature(temperature, specific_humidity):
107
+ """Virtual-temperature buoyancy correction.
108
+
109
+ Moist air is less dense than dry air at the same pressure and temperature.
110
+
111
+ Parameters
112
+ ----------
113
+ temperature : float or array_like
114
+ K; constraint: positive.
115
+ specific_humidity : float or array_like
116
+ kg/kg moist air; constraint: fraction.
117
+
118
+ Returns
119
+ -------
120
+ numpy scalar or ndarray
121
+ Model value [kg/m^3]. Scalar input returns a NumPy scalar; arrays broadcast.
122
+
123
+ Notes
124
+ -----
125
+ Ideal vapor mixture without suspended liquid or ice.
126
+ Equation: ``temperature * (1 + (1 / EPSILON_WATER - 1) * specific_humidity)``.
127
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
128
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
129
+ """
130
+ temperature = quantity("temperature", temperature, lower=0, strict_lower=True)
131
+ specific_humidity = quantity("specific_humidity", specific_humidity, lower=0, upper=1)
132
+ return temperature * (1 + (1 / EPSILON_WATER - 1) * specific_humidity)
133
+
134
+
135
+ @model
136
+ def relative_humidity(vapor_pressure, saturation_pressure):
137
+ """Relative humidity fraction.
138
+
139
+ Humidity compares actual vapor pressure to a saturation reference.
140
+
141
+ Parameters
142
+ ----------
143
+ vapor_pressure : float or array_like
144
+ Pa; constraint: nonnegative.
145
+ saturation_pressure : float or array_like
146
+ Pa; constraint: positive.
147
+
148
+ Returns
149
+ -------
150
+ numpy scalar or ndarray
151
+ Model value [kg/m^3]. Scalar input returns a NumPy scalar; arrays broadcast.
152
+
153
+ Notes
154
+ -----
155
+ Returned fraction may exceed one during supersaturation; no clipping.
156
+ Equation: ``vapor_pressure / saturation_pressure``.
157
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
158
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
159
+ """
160
+ vapor_pressure = quantity("vapor_pressure", vapor_pressure, lower=0)
161
+ saturation_pressure = quantity("saturation_pressure", saturation_pressure, lower=0, strict_lower=True)
162
+ return vapor_pressure / saturation_pressure
@@ -0,0 +1,222 @@
1
+ """Dry thermodynamics: potential temperature, the dry adiabat, static stability, the Richardson number and parcel ascent.
2
+
3
+ SI units unless stated otherwise.
4
+ """
5
+
6
+ from earthsciencekit._validation import model, quantity, require, safe_arithmetic
7
+ from earthsciencekit.constants import CP_DRY_AIR, R_DRY_AIR
8
+
9
+ __all__ = [
10
+ "potential_temperature",
11
+ "temperature_from_potential",
12
+ "dry_adiabatic_lapse_rate",
13
+ "brunt_vaisala_squared",
14
+ "richardson_number",
15
+ "simulate_dry_ascent",
16
+ ]
17
+
18
+
19
+ @model
20
+ def potential_temperature(temperature, pressure, reference_pressure):
21
+ """Poisson potential temperature.
22
+
23
+ Adiabatic temperature adjustment permits comparisons across pressure levels.
24
+
25
+ Parameters
26
+ ----------
27
+ temperature : float or array_like
28
+ K; constraint: positive.
29
+ pressure : float or array_like
30
+ Pa; constraint: positive.
31
+ reference_pressure : float or array_like
32
+ Pa; constraint: positive.
33
+
34
+ Returns
35
+ -------
36
+ numpy scalar or ndarray
37
+ Model value [kg/m^3]. Scalar input returns a NumPy scalar; arrays broadcast.
38
+
39
+ Notes
40
+ -----
41
+ Dry ideal gas with constant heat capacity; not equivalent potential temperature.
42
+ Equation: ``temperature * (reference_pressure / pressure)**(R_DRY_AIR / CP_DRY_AIR)``.
43
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
44
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
45
+ """
46
+ temperature = quantity("temperature", temperature, lower=0, strict_lower=True)
47
+ pressure = quantity("pressure", pressure, lower=0, strict_lower=True)
48
+ reference_pressure = quantity("reference_pressure", reference_pressure, lower=0, strict_lower=True)
49
+ return temperature * (reference_pressure / pressure) ** (R_DRY_AIR / CP_DRY_AIR)
50
+
51
+
52
+ @model
53
+ def temperature_from_potential(potential_temperature, pressure, reference_pressure):
54
+ """Inverse potential temperature.
55
+
56
+ Potential-temperature inversion recovers the local dry-air temperature.
57
+
58
+ Parameters
59
+ ----------
60
+ potential_temperature : float or array_like
61
+ K; constraint: positive.
62
+ pressure : float or array_like
63
+ Pa; constraint: positive.
64
+ reference_pressure : float or array_like
65
+ Pa; constraint: positive.
66
+
67
+ Returns
68
+ -------
69
+ numpy scalar or ndarray
70
+ Model value [kg/m^3]. Scalar input returns a NumPy scalar; arrays broadcast.
71
+
72
+ Notes
73
+ -----
74
+ Uses the same dry-air heat capacity and reference pressure convention.
75
+ Equation: ``potential_temperature * (pressure / reference_pressure)**(R_DRY_AIR / CP_DRY_AIR)``.
76
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
77
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
78
+ """
79
+ potential_temperature = quantity("potential_temperature", potential_temperature, lower=0, strict_lower=True)
80
+ pressure = quantity("pressure", pressure, lower=0, strict_lower=True)
81
+ reference_pressure = quantity("reference_pressure", reference_pressure, lower=0, strict_lower=True)
82
+ return potential_temperature * (pressure / reference_pressure) ** (R_DRY_AIR / CP_DRY_AIR)
83
+
84
+
85
+ @model
86
+ def dry_adiabatic_lapse_rate(gravity, heat_capacity):
87
+ """Dry adiabatic lapse rate.
88
+
89
+ Rising dry parcels cool as they perform expansion work.
90
+
91
+ Parameters
92
+ ----------
93
+ gravity : float or array_like
94
+ m/s^2; constraint: positive.
95
+ heat_capacity : float or array_like
96
+ J/(kg K); constraint: positive.
97
+
98
+ Returns
99
+ -------
100
+ numpy scalar or ndarray
101
+ Model value [kg/m^3]. Scalar input returns a NumPy scalar; arrays broadcast.
102
+
103
+ Notes
104
+ -----
105
+ Returned positive lapse magnitude in K per metre; temperature decreases upward.
106
+ Equation: ``gravity / heat_capacity``.
107
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
108
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
109
+ """
110
+ gravity = quantity("gravity", gravity, lower=0, strict_lower=True)
111
+ heat_capacity = quantity("heat_capacity", heat_capacity, lower=0, strict_lower=True)
112
+ return gravity / heat_capacity
113
+
114
+
115
+ @model
116
+ def brunt_vaisala_squared(potential_temperature, vertical_gradient, gravity):
117
+ """Dry static stability.
118
+
119
+ A potential-temperature gradient determines dry stratification stability.
120
+
121
+ Parameters
122
+ ----------
123
+ potential_temperature : float or array_like
124
+ K; constraint: positive.
125
+ vertical_gradient : float or array_like
126
+ K/m; constraint: real.
127
+ gravity : float or array_like
128
+ m/s^2; constraint: positive.
129
+
130
+ Returns
131
+ -------
132
+ numpy scalar or ndarray
133
+ Model value [kg/m^3]. Scalar input returns a NumPy scalar; arrays broadcast.
134
+
135
+ Notes
136
+ -----
137
+ Returns signed N squared; negative values represent instability rather than an imaginary frequency.
138
+ Equation: ``gravity * vertical_gradient / potential_temperature``.
139
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
140
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
141
+ """
142
+ potential_temperature = quantity("potential_temperature", potential_temperature, lower=0, strict_lower=True)
143
+ vertical_gradient = quantity("vertical_gradient", vertical_gradient)
144
+ gravity = quantity("gravity", gravity, lower=0, strict_lower=True)
145
+ return gravity * vertical_gradient / potential_temperature
146
+
147
+
148
+ @model
149
+ def richardson_number(brunt_vaisala_squared, shear):
150
+ """Gradient Richardson number.
151
+
152
+ Stratification suppresses the turbulence that vertical wind shear generates.
153
+
154
+ Parameters
155
+ ----------
156
+ brunt_vaisala_squared : float or array_like
157
+ 1/s^2; constraint: real.
158
+ shear : float or array_like
159
+ 1/s; constraint: nonzero.
160
+
161
+ Returns
162
+ -------
163
+ numpy scalar or ndarray
164
+ Model value [1]. Scalar input returns a NumPy scalar; arrays broadcast.
165
+
166
+ Notes
167
+ -----
168
+ Signed; stratified shear flow can become unstable only where it falls below 1/4 (Miles-Howard).
169
+ Equation: ``brunt_vaisala_squared / shear**2``.
170
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
171
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
172
+ """
173
+ brunt_vaisala_squared = quantity("brunt_vaisala_squared", brunt_vaisala_squared)
174
+ shear = quantity("shear", shear)
175
+ require(shear != 0, "shear must be nonzero")
176
+ return brunt_vaisala_squared / shear**2
177
+
178
+
179
+ @safe_arithmetic
180
+ def simulate_dry_ascent(initial_temperature, initial_pressure, vertical_speed, time):
181
+ """Follow an ideal dry parcel along a hydrostatic constant-speed ascent.
182
+
183
+ Parameters
184
+ ----------
185
+ initial_temperature : float
186
+ Positive scalar initial temperature [K].
187
+ initial_pressure : float
188
+ Positive scalar initial pressure [Pa].
189
+ vertical_speed : float
190
+ Finite scalar vertical velocity [m/s], positive upward.
191
+ time : array_like
192
+ Nonnegative strictly increasing sampled times [s].
193
+
194
+ Returns
195
+ -------
196
+ TimeSeries
197
+ Parcel temperature [K] and pressure [Pa].
198
+
199
+ Notes
200
+ -----
201
+ Dry lapse and hydrostatic pressure equations use constant standard gravity
202
+ and dry-air heat capacity. Potential temperature should remain constant.
203
+ The parcel is unsaturated; condensation and environmental entrainment are
204
+ excluded. Reject trajectories whose predicted temperature reaches zero.
205
+ """
206
+ from earthsciencekit._validation import time_grid
207
+ from earthsciencekit.constants import GRAVITY
208
+ from earthsciencekit.integrators import integrate_ode
209
+
210
+ temperature = quantity("initial_temperature", initial_temperature, lower=0, strict_lower=True)
211
+ pressure = quantity("initial_pressure", initial_pressure, lower=0, strict_lower=True)
212
+ speed = quantity("vertical_speed", vertical_speed)
213
+ require(temperature.ndim == pressure.ndim == speed.ndim == 0, "parcel parameters must be scalars")
214
+ time = time_grid(time)
215
+ require(temperature - GRAVITY / CP_DRY_AIR * speed * (time[-1] - time[0]) > 0, "trajectory reaches nonpositive temperature")
216
+ return integrate_ode(
217
+ lambda t, y: [-GRAVITY / CP_DRY_AIR * speed, -GRAVITY * y[1] * speed / (R_DRY_AIR * y[0])],
218
+ [temperature, pressure],
219
+ time,
220
+ names=("temperature", "pressure"),
221
+ units=("K", "Pa"),
222
+ )
File without changes