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,354 @@
1
+ """Geomagnetism and paleomagnetism: the dipole, Gauss coefficients, field directions, poles and crustal anomalies.
2
+
3
+ SI units unless stated otherwise; latitude, longitude, declination and inclination in radians.
4
+ """
5
+
6
+ import numpy as np
7
+ from scipy.special import lpmv
8
+
9
+ from earthsciencekit._validation import model, quantity, require
10
+ from earthsciencekit.constants import MU0
11
+
12
+ __all__ = [
13
+ "dipole_field",
14
+ "dipole_inclination",
15
+ "paleolatitude",
16
+ "zonal_field",
17
+ "gauss_dipole_moment",
18
+ "field_elements",
19
+ "direction_angle",
20
+ "virtual_geomagnetic_pole",
21
+ "block_magnetic_anomaly",
22
+ ]
23
+
24
+
25
+ def _latitude(name, value):
26
+ return quantity(name, value, lower=-np.pi / 2, upper=np.pi / 2)
27
+
28
+
29
+ @model
30
+ def dipole_field(latitude, distance, moment):
31
+ """Field of a geocentric axial dipole.
32
+
33
+ A uniformly magnetized Earth gives a field that dips more steeply toward the poles.
34
+
35
+ Parameters
36
+ ----------
37
+ latitude : float or array_like
38
+ rad; constraint: -pi/2 <= latitude <= pi/2. Geocentric latitude.
39
+ distance : float or array_like
40
+ m; constraint: positive. Distance from the Earth's centre.
41
+ moment : float or array_like
42
+ A m^2; constraint: real. Positive for today's polarity, whose field points down in the northern hemisphere.
43
+
44
+ Returns
45
+ -------
46
+ ndarray, shape (..., 2)
47
+ Model value [T]: the northward and the downward component.
48
+
49
+ Notes
50
+ -----
51
+ Pure axial dipole, outside its sources. The field at the poles is twice that at the equator.
52
+ Equation: ``MU0 * moment / (4 * np.pi * distance**3) * [np.cos(latitude), 2 * np.sin(latitude)]``.
53
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
54
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
55
+ """
56
+ latitude = _latitude("latitude", latitude)
57
+ distance = quantity("distance", distance, lower=0, strict_lower=True)
58
+ moment = quantity("moment", moment)
59
+ scale = MU0 * moment / (4 * np.pi * distance**3)
60
+ return np.stack(np.broadcast_arrays(scale * np.cos(latitude), 2 * scale * np.sin(latitude)), axis=-1)
61
+
62
+
63
+ @model
64
+ def dipole_inclination(latitude):
65
+ """Inclination of an axial dipole field.
66
+
67
+ The dip of a compass needle tells a navigator, or a rock, its magnetic latitude.
68
+
69
+ Parameters
70
+ ----------
71
+ latitude : float or array_like
72
+ rad; constraint: -pi/2 <= latitude <= pi/2.
73
+
74
+ Returns
75
+ -------
76
+ numpy scalar or ndarray
77
+ Model value [rad]: positive downward, for today's polarity.
78
+
79
+ Notes
80
+ -----
81
+ Geocentric axial dipole; ``tan(I) = 2 tan(latitude)``.
82
+ Equation: ``np.arctan2(2 * np.sin(latitude), np.cos(latitude))``.
83
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
84
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
85
+ """
86
+ latitude = _latitude("latitude", latitude)
87
+ return np.arctan2(2 * np.sin(latitude), np.cos(latitude))
88
+
89
+
90
+ @model
91
+ def paleolatitude(inclination):
92
+ """Paleolatitude from a remanent inclination.
93
+
94
+ A rock's frozen-in dip records how far from the pole it formed.
95
+
96
+ Parameters
97
+ ----------
98
+ inclination : float or array_like
99
+ rad; constraint: -pi/2 <= inclination <= pi/2. Positive downward.
100
+
101
+ Returns
102
+ -------
103
+ numpy scalar or ndarray
104
+ Model value [rad]: the magnetic latitude of the site when the rock was magnetized.
105
+
106
+ Notes
107
+ -----
108
+ Assumes a geocentric axial dipole averaged over secular variation; a reversed direction must be inverted first.
109
+ Equation: ``np.arctan2(np.sin(inclination), 2 * np.cos(inclination))``.
110
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
111
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
112
+ """
113
+ inclination = _latitude("inclination", inclination)
114
+ return np.arctan2(np.sin(inclination), 2 * np.cos(inclination))
115
+
116
+
117
+ @model
118
+ def zonal_field(latitude, distance, degree, coefficient, reference_radius):
119
+ """Field of one zonal Gauss coefficient.
120
+
121
+ Expanding the potential in spherical harmonics separates the dipole from smaller, faster-decaying parts.
122
+
123
+ Parameters
124
+ ----------
125
+ latitude : float or array_like
126
+ rad; constraint: -pi/2 <= latitude <= pi/2. Geocentric latitude.
127
+ distance : float or array_like
128
+ m; constraint: positive. Distance from the Earth's centre.
129
+ degree : float or array_like
130
+ 1; constraint: integer >= 1.
131
+ coefficient : float or array_like
132
+ T; constraint: real. Gauss coefficient ``g_n^0``; for zonal terms Schmidt and unnormalized Legendre functions agree.
133
+ reference_radius : float or array_like
134
+ m; constraint: positive. Usually 6371.2 km.
135
+
136
+ Returns
137
+ -------
138
+ ndarray, shape (..., 2)
139
+ Model value [T]: the northward and the downward component.
140
+
141
+ Notes
142
+ -----
143
+ Internal sources only, potential ``V = reference_radius * sum((a / r) ** (n + 1) g_n^0 P_n(sin latitude))``.
144
+ Sum several calls for a truncated zonal model; non-zonal terms (``m > 0``) are not included.
145
+ Equation: ``(a / r) ** (n + 2) * coefficient * [lpmv(1, n, x), -(n + 1) * lpmv(0, n, x)]`` with
146
+ ``x = np.sin(latitude)``, ``a = reference_radius``, ``r = distance`` and ``n = degree``.
147
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
148
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
149
+ """
150
+ latitude = _latitude("latitude", latitude)
151
+ distance = quantity("distance", distance, lower=0, strict_lower=True)
152
+ degree = quantity("degree", degree, lower=1)
153
+ require(degree == np.round(degree), "degree must be an integer")
154
+ coefficient = quantity("coefficient", coefficient)
155
+ reference_radius = quantity("reference_radius", reference_radius, lower=0, strict_lower=True)
156
+ x = np.sin(latitude)
157
+ scale = (reference_radius / distance) ** (degree + 2) * coefficient
158
+ north = scale * lpmv(1, degree, x)
159
+ down = -(degree + 1) * scale * lpmv(0, degree, x)
160
+ return np.stack(np.broadcast_arrays(north, down), axis=-1)
161
+
162
+
163
+ @model
164
+ def gauss_dipole_moment(g10, g11, h11, reference_radius):
165
+ """Dipole moment from the degree-1 Gauss coefficients.
166
+
167
+ Gauss's analysis weighed the Earth's magnet from surface measurements alone.
168
+
169
+ Parameters
170
+ ----------
171
+ g10, g11, h11 : float or array_like
172
+ T; constraint: real. Schmidt semi-normalized degree-1 coefficients.
173
+ reference_radius : float or array_like
174
+ m; constraint: positive.
175
+
176
+ Returns
177
+ -------
178
+ numpy scalar or ndarray
179
+ Model value [A m^2]. Scalar input returns a NumPy scalar; arrays broadcast.
180
+
181
+ Notes
182
+ -----
183
+ Magnitude of the best-fitting geocentric (tilted) dipole; the tilt is ``arccos(abs(g10) / B0)``.
184
+ Equation: ``4 * np.pi * reference_radius**3 / MU0 * np.sqrt(g10**2 + g11**2 + h11**2)``.
185
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
186
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
187
+ """
188
+ g10 = quantity("g10", g10)
189
+ g11 = quantity("g11", g11)
190
+ h11 = quantity("h11", h11)
191
+ reference_radius = quantity("reference_radius", reference_radius, lower=0, strict_lower=True)
192
+ return 4 * np.pi * reference_radius**3 / MU0 * np.sqrt(g10**2 + g11**2 + h11**2)
193
+
194
+
195
+ @model
196
+ def field_elements(north, east, down):
197
+ """Declination, inclination and intensity of a field vector.
198
+
199
+ Magnetic directions in rocks and observatories are reported as two angles and a strength.
200
+
201
+ Parameters
202
+ ----------
203
+ north, east, down : float or array_like
204
+ T; constraint: real, not all zero.
205
+
206
+ Returns
207
+ -------
208
+ ndarray, shape (..., 3)
209
+ Model value: declination [rad] east of north in (-pi, pi], inclination [rad] positive down, intensity [T].
210
+
211
+ Notes
212
+ -----
213
+ Local north-east-down frame. A reversed field has declination near pi and inclination of the opposite sign.
214
+ Equation: ``[np.arctan2(east, north), np.arctan2(down, np.hypot(north, east)), np.sqrt(north**2 + east**2 + down**2)]``.
215
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
216
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
217
+ """
218
+ north = quantity("north", north)
219
+ east = quantity("east", east)
220
+ down = quantity("down", down)
221
+ intensity = np.sqrt(north**2 + east**2 + down**2)
222
+ require(intensity > 0, "the field vector must be nonzero")
223
+ declination = np.arctan2(east, north)
224
+ inclination = np.arctan2(down, np.hypot(north, east))
225
+ return np.stack(np.broadcast_arrays(declination, inclination, intensity), axis=-1)
226
+
227
+
228
+ @model
229
+ def direction_angle(declination_a, inclination_a, declination_b, inclination_b):
230
+ """Angle between two magnetic directions.
231
+
232
+ A remanence more than 90 degrees from the present field records a reversed polarity.
233
+
234
+ Parameters
235
+ ----------
236
+ declination_a, declination_b : float or array_like
237
+ rad; constraint: real.
238
+ inclination_a, inclination_b : float or array_like
239
+ rad; constraint: -pi/2 <= inclination <= pi/2.
240
+
241
+ Returns
242
+ -------
243
+ numpy scalar or ndarray
244
+ Model value [rad] in [0, pi]. Scalar input returns a NumPy scalar; arrays broadcast.
245
+
246
+ Notes
247
+ -----
248
+ Great-circle distance between unit vectors; intensities are ignored.
249
+ Equation: ``np.arccos(np.sin(Ia) * np.sin(Ib) + np.cos(Ia) * np.cos(Ib) * np.cos(Da - Db))``.
250
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
251
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
252
+ """
253
+ declination_a = quantity("declination_a", declination_a)
254
+ inclination_a = _latitude("inclination_a", inclination_a)
255
+ declination_b = quantity("declination_b", declination_b)
256
+ inclination_b = _latitude("inclination_b", inclination_b)
257
+ cosine = np.sin(inclination_a) * np.sin(inclination_b) + np.cos(inclination_a) * np.cos(inclination_b) * np.cos(
258
+ declination_a - declination_b
259
+ )
260
+ return np.arccos(np.clip(cosine, -1, 1))
261
+
262
+
263
+ @model
264
+ def virtual_geomagnetic_pole(site_latitude, site_longitude, declination, inclination):
265
+ """Virtual geomagnetic pole of a site direction.
266
+
267
+ Poles from rocks of one age on one continent should agree; when they wander, the continent moved.
268
+
269
+ Parameters
270
+ ----------
271
+ site_latitude : float or array_like
272
+ rad; constraint: -pi/2 <= latitude <= pi/2.
273
+ site_longitude : float or array_like
274
+ rad; constraint: real.
275
+ declination : float or array_like
276
+ rad; constraint: real.
277
+ inclination : float or array_like
278
+ rad; constraint: -pi/2 <= inclination <= pi/2.
279
+
280
+ Returns
281
+ -------
282
+ ndarray, shape (..., 2)
283
+ Model value [rad]: pole latitude and pole longitude in [0, 2 pi).
284
+
285
+ Notes
286
+ -----
287
+ The pole of the geocentric dipole that gives the observed direction at the site; a reversed direction gives a
288
+ southern pole. Its angular distance from the site is ``p = np.arctan2(2 * np.cos(I), np.sin(I))``, along the
289
+ great circle leaving the site at azimuth ``D``.
290
+ Equation: ``pole = site * np.cos(p) + (north * np.cos(D) + east * np.sin(D)) * np.sin(p)`` with the site's unit
291
+ position vector and local north and east unit vectors; latitude ``np.arctan2(z, np.hypot(x, y))``.
292
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
293
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
294
+ """
295
+ site_latitude = _latitude("site_latitude", site_latitude)
296
+ site_longitude = quantity("site_longitude", site_longitude)
297
+ declination = quantity("declination", declination)
298
+ inclination = _latitude("inclination", inclination)
299
+ p = np.arctan2(2 * np.cos(inclination), np.sin(inclination))
300
+ sin_lat, cos_lat, sin_lon, cos_lon = np.sin(site_latitude), np.cos(site_latitude), np.sin(site_longitude), np.cos(site_longitude)
301
+ along = np.cos(p)
302
+ toward_north, toward_east = np.sin(p) * np.cos(declination), np.sin(p) * np.sin(declination)
303
+ x = cos_lat * cos_lon * along - sin_lat * cos_lon * toward_north - sin_lon * toward_east
304
+ y = cos_lat * sin_lon * along - sin_lat * sin_lon * toward_north + cos_lon * toward_east
305
+ z = sin_lat * along + cos_lat * toward_north
306
+ pole_latitude = np.arctan2(z, np.hypot(x, y))
307
+ pole_longitude = np.mod(np.arctan2(y, x), 2 * np.pi)
308
+ return np.stack(np.broadcast_arrays(pole_latitude, pole_longitude), axis=-1)
309
+
310
+
311
+ @model
312
+ def block_magnetic_anomaly(x, left, right, top, bottom, magnetization):
313
+ """Magnetic anomaly of a vertically magnetized two-dimensional block.
314
+
315
+ Strips of normally and reversely magnetized crust give the alternating anomalies over mid-ocean ridges.
316
+
317
+ Parameters
318
+ ----------
319
+ x : float or array_like
320
+ m; constraint: real. Horizontal position of the observer, perpendicular to the strike.
321
+ left, right : float or array_like
322
+ m; constraint: left < right. Edges of the block.
323
+ top, bottom : float or array_like
324
+ m; constraint: 0 < top < bottom. Depths below the observation level.
325
+ magnetization : float or array_like
326
+ A/m; constraint: real. Positive downward (normal polarity at the north magnetic pole).
327
+
328
+ Returns
329
+ -------
330
+ numpy scalar or ndarray
331
+ Model value [T]: vertical field anomaly, equal to the total-field anomaly reduced to the pole.
332
+
333
+ Notes
334
+ -----
335
+ Infinite strike length, uniform vertical magnetization and a vertical ambient field. A laterally infinite layer has
336
+ no external field.
337
+ Equation: ``MU0 * magnetization / (2 * np.pi) * (phi(top) - phi(bottom))`` with
338
+ ``phi(z) = np.arctan((right - x) / z) - np.arctan((left - x) / z)``.
339
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
340
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
341
+ """
342
+ x = quantity("x", x)
343
+ left = quantity("left", left)
344
+ right = quantity("right", right)
345
+ top = quantity("top", top, lower=0, strict_lower=True)
346
+ bottom = quantity("bottom", bottom, lower=0, strict_lower=True)
347
+ magnetization = quantity("magnetization", magnetization)
348
+ require(left < right, "left must lie west of right")
349
+ require(top < bottom, "top must be shallower than bottom")
350
+
351
+ def subtended(depth):
352
+ return np.arctan((right - x) / depth) - np.arctan((left - x) / depth)
353
+
354
+ return MU0 * magnetization / (2 * np.pi) * (subtended(top) - subtended(bottom))
File without changes