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,157 @@
1
+ """Isostasy and flexure: Airy roots and the bending of an elastic lithosphere under loads.
2
+
3
+ SI units unless stated otherwise.
4
+ """
5
+
6
+ import numpy as np
7
+
8
+ from earthsciencekit._validation import model, quantity, require
9
+
10
+ __all__ = [
11
+ "airy_root",
12
+ "flexural_rigidity",
13
+ "flexural_parameter",
14
+ "flexural_deflection",
15
+ ]
16
+
17
+
18
+ @model
19
+ def airy_root(elevation, crust_density, mantle_density):
20
+ """Airy crustal compensation.
21
+
22
+ Crustal buoyancy provides a baseline for mountain-root support.
23
+
24
+ Parameters
25
+ ----------
26
+ elevation : float or array_like
27
+ m; constraint: nonnegative.
28
+ crust_density : float or array_like
29
+ kg/m^3; constraint: positive.
30
+ mantle_density : float or array_like
31
+ kg/m^3; constraint: positive.
32
+
33
+ Returns
34
+ -------
35
+ numpy scalar or ndarray
36
+ Model value [rad/s]. Scalar input returns a NumPy scalar; arrays broadcast.
37
+
38
+ Notes
39
+ -----
40
+ Local compensation without plate flexure; mantle must be denser than crust.
41
+ Equation: ``elevation * crust_density / (mantle_density - crust_density)``.
42
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
43
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
44
+ """
45
+ elevation = quantity("elevation", elevation, lower=0)
46
+ crust_density = quantity("crust_density", crust_density, lower=0, strict_lower=True)
47
+ mantle_density = quantity("mantle_density", mantle_density, lower=0, strict_lower=True)
48
+ require(mantle_density > crust_density, "mantle must be denser than crust")
49
+ return elevation * crust_density / (mantle_density - crust_density)
50
+
51
+
52
+ @model
53
+ def flexural_rigidity(young_modulus, elastic_thickness, poisson_ratio):
54
+ """Elastic lithosphere rigidity.
55
+
56
+ Elastic thickness turns lithospheric strength into a bending resistance.
57
+
58
+ Parameters
59
+ ----------
60
+ young_modulus : float or array_like
61
+ Pa; constraint: positive.
62
+ elastic_thickness : float or array_like
63
+ m; constraint: positive.
64
+ poisson_ratio : float or array_like
65
+ 1; constraint: poisson.
66
+
67
+ Returns
68
+ -------
69
+ numpy scalar or ndarray
70
+ Model value [rad/s]. Scalar input returns a NumPy scalar; arrays broadcast.
71
+
72
+ Notes
73
+ -----
74
+ Thin isotropic elastic plate with stable Poisson ratio, not a yielding viscoelastic lithosphere.
75
+ Equation: ``young_modulus * elastic_thickness**3 / (12 * (1 - poisson_ratio**2))``.
76
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
77
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
78
+ """
79
+ young_modulus = quantity("young_modulus", young_modulus, lower=0, strict_lower=True)
80
+ elastic_thickness = quantity("elastic_thickness", elastic_thickness, lower=0, strict_lower=True)
81
+ poisson_ratio = quantity("poisson_ratio", poisson_ratio, lower=-1, upper=0.5, strict_lower=True, strict_upper=True)
82
+ return young_modulus * elastic_thickness**3 / (12 * (1 - poisson_ratio**2))
83
+
84
+
85
+ @model
86
+ def flexural_parameter(rigidity, density_contrast, gravity):
87
+ """Lithosphere flexural length.
88
+
89
+ Bending and buoyancy jointly set the scale of load compensation.
90
+
91
+ Parameters
92
+ ----------
93
+ rigidity : float or array_like
94
+ N m; constraint: positive.
95
+ density_contrast : float or array_like
96
+ kg/m^3; constraint: positive.
97
+ gravity : float or array_like
98
+ m/s^2; constraint: positive.
99
+
100
+ Returns
101
+ -------
102
+ numpy scalar or ndarray
103
+ Model value [rad/s]. Scalar input returns a NumPy scalar; arrays broadcast.
104
+
105
+ Notes
106
+ -----
107
+ Infinite beam-like plate convention alpha to the fourth equals 4D over density contrast times g.
108
+ Equation: ``(4 * rigidity / (density_contrast * gravity))**0.25``.
109
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
110
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
111
+ """
112
+ rigidity = quantity("rigidity", rigidity, lower=0, strict_lower=True)
113
+ density_contrast = quantity("density_contrast", density_contrast, lower=0, strict_lower=True)
114
+ gravity = quantity("gravity", gravity, lower=0, strict_lower=True)
115
+ return (4 * rigidity / (density_contrast * gravity)) ** 0.25
116
+
117
+
118
+ @model
119
+ def flexural_deflection(distance, line_load, rigidity, density_contrast, gravity):
120
+ """Elastic plate deflection under a line load.
121
+
122
+ A load bends the lithosphere into a moat flanked by a peripheral bulge.
123
+
124
+ Parameters
125
+ ----------
126
+ distance : float or array_like
127
+ m; constraint: real.
128
+ line_load : float or array_like
129
+ N/m; constraint: nonnegative.
130
+ rigidity : float or array_like
131
+ N m; constraint: positive.
132
+ density_contrast : float or array_like
133
+ kg/m^3; constraint: positive.
134
+ gravity : float or array_like
135
+ m/s^2; constraint: positive.
136
+
137
+ Returns
138
+ -------
139
+ numpy scalar or ndarray
140
+ Model value [m]. Scalar input returns a NumPy scalar; arrays broadcast.
141
+
142
+ Notes
143
+ -----
144
+ Unbroken infinite thin elastic plate, deflection positive downward; density contrast is mantle minus infill.
145
+ Equation: ``line_load * alpha**3 / (8 * rigidity) * np.exp(-x) * (np.cos(x) + np.sin(x))``
146
+ with ``x = abs(distance) / alpha`` and ``alpha = (4 * rigidity / (density_contrast * gravity))**0.25``.
147
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
148
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
149
+ """
150
+ distance = quantity("distance", distance)
151
+ line_load = quantity("line_load", line_load, lower=0)
152
+ rigidity = quantity("rigidity", rigidity, lower=0, strict_lower=True)
153
+ density_contrast = quantity("density_contrast", density_contrast, lower=0, strict_lower=True)
154
+ gravity = quantity("gravity", gravity, lower=0, strict_lower=True)
155
+ alpha = (4 * rigidity / (density_contrast * gravity)) ** 0.25
156
+ scaled = np.abs(distance) / alpha
157
+ return line_load * alpha**3 / (8 * rigidity) * np.exp(-scaled) * (np.cos(scaled) + np.sin(scaled))
@@ -0,0 +1,290 @@
1
+ """Rigid-plate kinematics: Euler rotation, plate speed and displacement, relative Euler vectors and hotspot tracks.
2
+
3
+ SI units unless stated otherwise.
4
+ """
5
+
6
+ import numpy as np
7
+
8
+ from earthsciencekit._validation import model, quantity, require
9
+
10
+ __all__ = [
11
+ "angular_speed",
12
+ "plate_speed",
13
+ "euler_velocity_x",
14
+ "euler_velocity_y",
15
+ "euler_velocity_z",
16
+ "plate_displacement",
17
+ "hotspot_age",
18
+ "compose_euler_vectors",
19
+ ]
20
+
21
+
22
+ @model
23
+ def angular_speed(rotation_angle, time):
24
+ """Euler-pole angular speed.
25
+
26
+ Rigid plate motion is rotation about an Euler pole.
27
+
28
+ Parameters
29
+ ----------
30
+ rotation_angle : float or array_like
31
+ rad; constraint: real.
32
+ time : float or array_like
33
+ s; constraint: positive.
34
+
35
+ Returns
36
+ -------
37
+ numpy scalar or ndarray
38
+ Model value [rad/s]. Scalar input returns a NumPy scalar; arrays broadcast.
39
+
40
+ Notes
41
+ -----
42
+ Constant angular rate; angular input is radians and output radians per second.
43
+ Equation: ``rotation_angle / time``.
44
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
45
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
46
+ """
47
+ rotation_angle = quantity("rotation_angle", rotation_angle)
48
+ time = quantity("time", time, lower=0, strict_lower=True)
49
+ return rotation_angle / time
50
+
51
+
52
+ @model
53
+ def plate_speed(angular_speed, radius, angular_distance):
54
+ """Rigid plate speed about an Euler pole.
55
+
56
+ Distance from the rotation axis controls surface plate speed.
57
+
58
+ Parameters
59
+ ----------
60
+ angular_speed : float or array_like
61
+ rad/s; constraint: nonnegative.
62
+ radius : float or array_like
63
+ m; constraint: positive.
64
+ angular_distance : float or array_like
65
+ rad; constraint: width.
66
+
67
+ Returns
68
+ -------
69
+ numpy scalar or ndarray
70
+ Model value [rad/s]. Scalar input returns a NumPy scalar; arrays broadcast.
71
+
72
+ Notes
73
+ -----
74
+ Spherical rigid plate, magnitude only; angular distance must be at most pi.
75
+ Equation: ``angular_speed * radius * np.sin(angular_distance)``.
76
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
77
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
78
+ """
79
+ angular_speed = quantity("angular_speed", angular_speed, lower=0)
80
+ radius = quantity("radius", radius, lower=0, strict_lower=True)
81
+ angular_distance = quantity("angular_distance", angular_distance, lower=0, upper=2 * np.pi)
82
+ require(angular_distance <= np.pi, "angular distance exceeds pi")
83
+ return angular_speed * radius * np.sin(angular_distance)
84
+
85
+
86
+ @model
87
+ def euler_velocity_x(omega_y, omega_z, position_y, position_z):
88
+ """Euler rotation east-fixed x velocity.
89
+
90
+ The cross product of rotation and position gives Cartesian plate velocity.
91
+
92
+ Parameters
93
+ ----------
94
+ omega_y : float or array_like
95
+ rad/s; constraint: real.
96
+ omega_z : float or array_like
97
+ rad/s; constraint: real.
98
+ position_y : float or array_like
99
+ m; constraint: real.
100
+ position_z : float or array_like
101
+ m; constraint: real.
102
+
103
+ Returns
104
+ -------
105
+ numpy scalar or ndarray
106
+ Model value [rad/s]. Scalar input returns a NumPy scalar; arrays broadcast.
107
+
108
+ Notes
109
+ -----
110
+ Earth-fixed Cartesian axes; x is not the local east component.
111
+ Equation: ``omega_y * position_z - omega_z * position_y``.
112
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
113
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
114
+ """
115
+ omega_y = quantity("omega_y", omega_y)
116
+ omega_z = quantity("omega_z", omega_z)
117
+ position_y = quantity("position_y", position_y)
118
+ position_z = quantity("position_z", position_z)
119
+ return omega_y * position_z - omega_z * position_y
120
+
121
+
122
+ @model
123
+ def euler_velocity_y(omega_z, omega_x, position_z, position_x):
124
+ """Euler rotation y velocity.
125
+
126
+ A rigid plate velocity field is tangent to its spherical path.
127
+
128
+ Parameters
129
+ ----------
130
+ omega_z : float or array_like
131
+ rad/s; constraint: real.
132
+ omega_x : float or array_like
133
+ rad/s; constraint: real.
134
+ position_z : float or array_like
135
+ m; constraint: real.
136
+ position_x : float or array_like
137
+ m; constraint: real.
138
+
139
+ Returns
140
+ -------
141
+ numpy scalar or ndarray
142
+ Model value [rad/s]. Scalar input returns a NumPy scalar; arrays broadcast.
143
+
144
+ Notes
145
+ -----
146
+ Same Earth-fixed Cartesian convention as euler_velocity_x.
147
+ Equation: ``omega_z * position_x - omega_x * position_z``.
148
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
149
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
150
+ """
151
+ omega_z = quantity("omega_z", omega_z)
152
+ omega_x = quantity("omega_x", omega_x)
153
+ position_z = quantity("position_z", position_z)
154
+ position_x = quantity("position_x", position_x)
155
+ return omega_z * position_x - omega_x * position_z
156
+
157
+
158
+ @model
159
+ def euler_velocity_z(omega_x, omega_y, position_x, position_y):
160
+ """Euler rotation polar velocity.
161
+
162
+ The polar velocity component completes the Euler-vector cross product.
163
+
164
+ Parameters
165
+ ----------
166
+ omega_x : float or array_like
167
+ rad/s; constraint: real.
168
+ omega_y : float or array_like
169
+ rad/s; constraint: real.
170
+ position_x : float or array_like
171
+ m; constraint: real.
172
+ position_y : float or array_like
173
+ m; constraint: real.
174
+
175
+ Returns
176
+ -------
177
+ numpy scalar or ndarray
178
+ Model value [rad/s]. Scalar input returns a NumPy scalar; arrays broadcast.
179
+
180
+ Notes
181
+ -----
182
+ Earth-fixed components, not local vertical motion or deformation.
183
+ Equation: ``omega_x * position_y - omega_y * position_x``.
184
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
185
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
186
+ """
187
+ omega_x = quantity("omega_x", omega_x)
188
+ omega_y = quantity("omega_y", omega_y)
189
+ position_x = quantity("position_x", position_x)
190
+ position_y = quantity("position_y", position_y)
191
+ return omega_x * position_y - omega_y * position_x
192
+
193
+
194
+ @model
195
+ def plate_displacement(speed, time):
196
+ """Plate kinematic displacement.
197
+
198
+ Velocity connects short instrumental records with geological displacement.
199
+
200
+ Parameters
201
+ ----------
202
+ speed : float or array_like
203
+ m/s; constraint: real.
204
+ time : float or array_like
205
+ s; constraint: nonnegative.
206
+
207
+ Returns
208
+ -------
209
+ numpy scalar or ndarray
210
+ Model value [rad/s]. Scalar input returns a NumPy scalar; arrays broadcast.
211
+
212
+ Notes
213
+ -----
214
+ Constant signed speed along a local coordinate; not a finite Euler rotation.
215
+ Equation: ``speed * time``.
216
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
217
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
218
+ """
219
+ speed = quantity("speed", speed)
220
+ time = quantity("time", time, lower=0)
221
+ return speed * time
222
+
223
+
224
+ @model
225
+ def compose_euler_vectors(omega_ab, omega_bc):
226
+ """Composition of relative Euler vectors around a plate circuit.
227
+
228
+ Angular velocities of rigid plates add, so two relative motions predict the third.
229
+
230
+ Parameters
231
+ ----------
232
+ omega_ab : array_like, shape (..., 3)
233
+ rad/s; constraint: real. Earth-fixed Cartesian Euler vector of plate A relative to plate B.
234
+ omega_bc : array_like, shape (..., 3)
235
+ rad/s; constraint: real. Euler vector of plate B relative to plate C.
236
+
237
+ Returns
238
+ -------
239
+ ndarray, shape (..., 3)
240
+ Model value [rad/s]: the Euler vector of plate A relative to plate C. Leading axes broadcast.
241
+
242
+ Notes
243
+ -----
244
+ Instantaneous (infinitesimal) rotations only; finite rotations do not commute and do not add.
245
+ Closure of the circuit A-B-C means ``omega_ab + omega_bc + omega_ca = 0``.
246
+ Equation: ``omega_ab + omega_bc``.
247
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
248
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
249
+ """
250
+ omega_ab = quantity("omega_ab", omega_ab)
251
+ omega_bc = quantity("omega_bc", omega_bc)
252
+ require(omega_ab.ndim >= 1 and omega_ab.shape[-1] == 3, "omega_ab must have three Cartesian components on its last axis")
253
+ require(omega_bc.ndim >= 1 and omega_bc.shape[-1] == 3, "omega_bc must have three Cartesian components on its last axis")
254
+ return omega_ab + omega_bc
255
+
256
+
257
+ @model
258
+ def hotspot_age(track_length, angular_speed, radius, pole_distance):
259
+ """Age progression along a hotspot track.
260
+
261
+ A plate moving over a fixed plume leaves volcanoes that age with distance along a small circle.
262
+
263
+ Parameters
264
+ ----------
265
+ track_length : float or array_like
266
+ m; constraint: nonnegative.
267
+ angular_speed : float or array_like
268
+ rad/s; constraint: positive.
269
+ radius : float or array_like
270
+ m; constraint: positive.
271
+ pole_distance : float or array_like
272
+ rad; constraint: open interval (0, pi).
273
+
274
+ Returns
275
+ -------
276
+ numpy scalar or ndarray
277
+ Model value [s]. Scalar input returns a NumPy scalar; arrays broadcast.
278
+
279
+ Notes
280
+ -----
281
+ Fixed hotspot and constant Euler rotation of the plate over it; track length is measured along the small circle.
282
+ Equation: ``track_length / (angular_speed * radius * np.sin(pole_distance))``.
283
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
284
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
285
+ """
286
+ track_length = quantity("track_length", track_length, lower=0)
287
+ angular_speed = quantity("angular_speed", angular_speed, lower=0, strict_lower=True)
288
+ radius = quantity("radius", radius, lower=0, strict_lower=True)
289
+ pole_distance = quantity("pole_distance", pole_distance, lower=0, upper=np.pi, strict_lower=True, strict_upper=True)
290
+ return track_length / (angular_speed * radius * np.sin(pole_distance))
File without changes