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.
- earthsciencekit/__init__.py +6 -0
- earthsciencekit/_validation.py +62 -0
- earthsciencekit/atmosphere/__init__.py +57 -0
- earthsciencekit/atmosphere/core/__init__.py +5 -0
- earthsciencekit/atmosphere/core/base.py +10 -0
- earthsciencekit/atmosphere/systems/__init__.py +1 -0
- earthsciencekit/atmosphere/systems/dynamics.py +208 -0
- earthsciencekit/atmosphere/systems/hydrostatics.py +214 -0
- earthsciencekit/atmosphere/systems/moisture.py +162 -0
- earthsciencekit/atmosphere/systems/thermodynamics.py +222 -0
- earthsciencekit/atmosphere/tests/__init__.py +0 -0
- earthsciencekit/atmosphere/tests/test_models.py +741 -0
- earthsciencekit/atmosphere/tests/test_physics.py +81 -0
- earthsciencekit/atmosphere/visualizers/__init__.py +9 -0
- earthsciencekit/climate/__init__.py +63 -0
- earthsciencekit/climate/core/__init__.py +5 -0
- earthsciencekit/climate/core/base.py +10 -0
- earthsciencekit/climate/systems/__init__.py +1 -0
- earthsciencekit/climate/systems/carbon.py +76 -0
- earthsciencekit/climate/systems/forcing.py +124 -0
- earthsciencekit/climate/systems/radiation.py +214 -0
- earthsciencekit/climate/systems/response.py +402 -0
- earthsciencekit/climate/systems/sea_level.py +77 -0
- earthsciencekit/climate/tests/__init__.py +0 -0
- earthsciencekit/climate/tests/test_models.py +1015 -0
- earthsciencekit/climate/tests/test_physics.py +16 -0
- earthsciencekit/climate/visualizers/__init__.py +9 -0
- earthsciencekit/constants/__init__.py +23 -0
- earthsciencekit/geophysics/__init__.py +77 -0
- earthsciencekit/geophysics/core/__init__.py +5 -0
- earthsciencekit/geophysics/core/base.py +10 -0
- earthsciencekit/geophysics/systems/__init__.py +1 -0
- earthsciencekit/geophysics/systems/deformation.py +161 -0
- earthsciencekit/geophysics/systems/dynamo.py +159 -0
- earthsciencekit/geophysics/systems/figure.py +292 -0
- earthsciencekit/geophysics/systems/gravity.py +235 -0
- earthsciencekit/geophysics/systems/magnetism.py +354 -0
- earthsciencekit/geophysics/tests/__init__.py +0 -0
- earthsciencekit/geophysics/tests/test_models.py +898 -0
- earthsciencekit/geophysics/tests/test_physics.py +171 -0
- earthsciencekit/geophysics/visualizers/__init__.py +9 -0
- earthsciencekit/integrators/__init__.py +62 -0
- earthsciencekit/oceanography/__init__.py +61 -0
- earthsciencekit/oceanography/core/__init__.py +5 -0
- earthsciencekit/oceanography/core/base.py +10 -0
- earthsciencekit/oceanography/systems/__init__.py +1 -0
- earthsciencekit/oceanography/systems/circulation.py +388 -0
- earthsciencekit/oceanography/systems/hydrography.py +212 -0
- earthsciencekit/oceanography/systems/inertial.py +86 -0
- earthsciencekit/oceanography/systems/waves.py +281 -0
- earthsciencekit/oceanography/tests/__init__.py +0 -0
- earthsciencekit/oceanography/tests/test_models.py +916 -0
- earthsciencekit/oceanography/tests/test_physics.py +102 -0
- earthsciencekit/oceanography/visualizers/__init__.py +9 -0
- earthsciencekit/plate_tectonics/__init__.py +59 -0
- earthsciencekit/plate_tectonics/core/__init__.py +5 -0
- earthsciencekit/plate_tectonics/core/base.py +10 -0
- earthsciencekit/plate_tectonics/systems/__init__.py +1 -0
- earthsciencekit/plate_tectonics/systems/boundaries.py +201 -0
- earthsciencekit/plate_tectonics/systems/cooling.py +238 -0
- earthsciencekit/plate_tectonics/systems/isostasy.py +157 -0
- earthsciencekit/plate_tectonics/systems/kinematics.py +290 -0
- earthsciencekit/plate_tectonics/tests/__init__.py +0 -0
- earthsciencekit/plate_tectonics/tests/test_models.py +920 -0
- earthsciencekit/plate_tectonics/tests/test_physics.py +94 -0
- earthsciencekit/plate_tectonics/visualizers/__init__.py +9 -0
- earthsciencekit/py.typed +0 -0
- earthsciencekit/results.py +73 -0
- earthsciencekit/seismology/__init__.py +59 -0
- earthsciencekit/seismology/core/__init__.py +5 -0
- earthsciencekit/seismology/core/base.py +10 -0
- earthsciencekit/seismology/systems/__init__.py +1 -0
- earthsciencekit/seismology/systems/interfaces.py +102 -0
- earthsciencekit/seismology/systems/rays.py +221 -0
- earthsciencekit/seismology/systems/sources.py +249 -0
- earthsciencekit/seismology/systems/waves.py +268 -0
- earthsciencekit/seismology/tests/__init__.py +0 -0
- earthsciencekit/seismology/tests/test_models.py +852 -0
- earthsciencekit/seismology/tests/test_physics.py +100 -0
- earthsciencekit/seismology/visualizers/__init__.py +9 -0
- earthsciencekit/tests/__init__.py +0 -0
- earthsciencekit/tests/test_core.py +181 -0
- earthsciencekit/tests/test_relations.py +36 -0
- earthsciencekit/units/__init__.py +66 -0
- earthsciencekit/visualizers/__init__.py +45 -0
- earthsciencekit/volcanology/__init__.py +61 -0
- earthsciencekit/volcanology/core/__init__.py +5 -0
- earthsciencekit/volcanology/core/base.py +10 -0
- earthsciencekit/volcanology/systems/__init__.py +1 -0
- earthsciencekit/volcanology/systems/eruptions.py +161 -0
- earthsciencekit/volcanology/systems/geochemistry.py +226 -0
- earthsciencekit/volcanology/systems/geochronology.py +304 -0
- earthsciencekit/volcanology/systems/magma.py +190 -0
- earthsciencekit/volcanology/tests/__init__.py +0 -0
- earthsciencekit/volcanology/tests/test_models.py +485 -0
- earthsciencekit/volcanology/tests/test_physics.py +176 -0
- earthsciencekit/volcanology/visualizers/__init__.py +9 -0
- earthsciencekit-0.2.0.dist-info/METADATA +127 -0
- earthsciencekit-0.2.0.dist-info/RECORD +102 -0
- earthsciencekit-0.2.0.dist-info/WHEEL +5 -0
- earthsciencekit-0.2.0.dist-info/licenses/LICENSE +15 -0
- earthsciencekit-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Physical identities, limits and dynamic checks for earthsciencekit.climate."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from earthsciencekit.climate.systems.response import simulate_one_box, step_temperature
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_one_box_exact_solution_and_nonzero_start():
|
|
10
|
+
time = np.linspace(10, 20, 51)
|
|
11
|
+
result = simulate_one_box(4, 2, 2, time, initial_anomaly=-1)
|
|
12
|
+
np.testing.assert_allclose(result.values[:, 0], 2 - 3 * np.exp(-(time - time[0])), atol=2e-8)
|
|
13
|
+
zero = simulate_one_box(4, 2, 2, time)
|
|
14
|
+
np.testing.assert_allclose(zero.values[:, 0], step_temperature(4, 2, 2, time - time[0]), atol=2e-8)
|
|
15
|
+
with pytest.raises(ValueError):
|
|
16
|
+
simulate_one_box(4, [2, 3], 2, time)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Plots for earthsciencekit.climate.
|
|
2
|
+
|
|
3
|
+
The dynamic models return a TimeSeries, whose ``plot`` and ``animate`` methods
|
|
4
|
+
call these package-wide helpers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from earthsciencekit.visualizers import animate_series, plot_series
|
|
8
|
+
|
|
9
|
+
__all__ = ["plot_series", "animate_series"]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Explicit reference constants, not an evolving observational dataset.
|
|
2
|
+
|
|
3
|
+
SI units apply. Earth angular speed and material properties are educational
|
|
4
|
+
reference values. EPSILON_WATER is the ratio of dry-air to vapor gas constants.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
G = 6.67430e-11 # m^3 kg^-1 s^-2
|
|
8
|
+
MU0 = 1.2566370614359173e-6 # H/m, conventional nonmagnetic approximation
|
|
9
|
+
R_GAS = 8.31446261815324 # J/(mol K)
|
|
10
|
+
AVOGADRO = 6.02214076e23 # 1/mol
|
|
11
|
+
R_DRY_AIR = 287.05 # J/(kg K)
|
|
12
|
+
CP_DRY_AIR = 1004.0 # J/(kg K)
|
|
13
|
+
R_VAPOR = 461.5 # J/(kg K)
|
|
14
|
+
EPSILON_WATER = R_DRY_AIR / R_VAPOR
|
|
15
|
+
OMEGA_EARTH = 7.2921150e-5 # rad/s
|
|
16
|
+
SIGMA_SB = 5.670374419e-8 # W/(m^2 K^4)
|
|
17
|
+
GRAVITY = 9.80665 # m/s^2, standard gravity
|
|
18
|
+
EARTH_RADIUS = 6371008.8 # m, mean radius reference
|
|
19
|
+
SECONDS_PER_YEAR = 365.25 * 86400 # s, Julian year
|
|
20
|
+
LAMBDA_U238 = 1.55125e-10 / SECONDS_PER_YEAR # 1/s, Jaffey et al. (1971)
|
|
21
|
+
LAMBDA_U235 = 9.8485e-10 / SECONDS_PER_YEAR # 1/s, Jaffey et al. (1971)
|
|
22
|
+
U238_U235 = 137.88 # 1, present-day atomic ratio, Steiger and Jaeger (1977)
|
|
23
|
+
LIBBY_MEAN_LIFE = 8033 * SECONDS_PER_YEAR # s, conventional radiocarbon mean life (5568-year half-life)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Inspectable geophysics models: gravity, the figure of the Earth and geomagnetism; SI units unless stated otherwise."""
|
|
2
|
+
|
|
3
|
+
from earthsciencekit.geophysics.systems.deformation import (
|
|
4
|
+
equivalent_water_height,
|
|
5
|
+
love_number_h2,
|
|
6
|
+
love_number_k2,
|
|
7
|
+
tidal_gravimetric_factor,
|
|
8
|
+
)
|
|
9
|
+
from earthsciencekit.geophysics.systems.dynamo import (
|
|
10
|
+
magnetic_decay_time,
|
|
11
|
+
magnetic_reynolds_number,
|
|
12
|
+
rikitake_fixed_point,
|
|
13
|
+
simulate_rikitake_dynamo,
|
|
14
|
+
)
|
|
15
|
+
from earthsciencekit.geophysics.systems.figure import (
|
|
16
|
+
centrifugal_ratio,
|
|
17
|
+
clairaut_gravity,
|
|
18
|
+
degree_geoid_height,
|
|
19
|
+
flattening_from_j2,
|
|
20
|
+
homogeneous_flattening,
|
|
21
|
+
nodal_precession_rate,
|
|
22
|
+
normal_gravity,
|
|
23
|
+
stokes_function,
|
|
24
|
+
)
|
|
25
|
+
from earthsciencekit.geophysics.systems.gravity import (
|
|
26
|
+
bouguer_anomaly,
|
|
27
|
+
bouguer_correction,
|
|
28
|
+
earth_mean_density,
|
|
29
|
+
free_air_correction,
|
|
30
|
+
sphere_gravity,
|
|
31
|
+
torsion_balance_gravitational_constant,
|
|
32
|
+
)
|
|
33
|
+
from earthsciencekit.geophysics.systems.magnetism import (
|
|
34
|
+
block_magnetic_anomaly,
|
|
35
|
+
dipole_field,
|
|
36
|
+
dipole_inclination,
|
|
37
|
+
direction_angle,
|
|
38
|
+
field_elements,
|
|
39
|
+
gauss_dipole_moment,
|
|
40
|
+
paleolatitude,
|
|
41
|
+
virtual_geomagnetic_pole,
|
|
42
|
+
zonal_field,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"sphere_gravity",
|
|
47
|
+
"bouguer_correction",
|
|
48
|
+
"free_air_correction",
|
|
49
|
+
"bouguer_anomaly",
|
|
50
|
+
"torsion_balance_gravitational_constant",
|
|
51
|
+
"earth_mean_density",
|
|
52
|
+
"centrifugal_ratio",
|
|
53
|
+
"homogeneous_flattening",
|
|
54
|
+
"clairaut_gravity",
|
|
55
|
+
"normal_gravity",
|
|
56
|
+
"flattening_from_j2",
|
|
57
|
+
"nodal_precession_rate",
|
|
58
|
+
"stokes_function",
|
|
59
|
+
"degree_geoid_height",
|
|
60
|
+
"dipole_field",
|
|
61
|
+
"dipole_inclination",
|
|
62
|
+
"paleolatitude",
|
|
63
|
+
"zonal_field",
|
|
64
|
+
"gauss_dipole_moment",
|
|
65
|
+
"field_elements",
|
|
66
|
+
"direction_angle",
|
|
67
|
+
"virtual_geomagnetic_pole",
|
|
68
|
+
"block_magnetic_anomaly",
|
|
69
|
+
"magnetic_reynolds_number",
|
|
70
|
+
"magnetic_decay_time",
|
|
71
|
+
"rikitake_fixed_point",
|
|
72
|
+
"love_number_h2",
|
|
73
|
+
"love_number_k2",
|
|
74
|
+
"tidal_gravimetric_factor",
|
|
75
|
+
"equivalent_water_height",
|
|
76
|
+
"simulate_rikitake_dynamo",
|
|
77
|
+
]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Result containers for earthsciencekit.geophysics.
|
|
2
|
+
|
|
3
|
+
Algebraic models return NumPy scalars or arrays. The sampled dynamic models
|
|
4
|
+
return the package-wide immutable :class:`~earthsciencekit.results.TimeSeries`,
|
|
5
|
+
which is re-exported here as this domain's result type.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from earthsciencekit.results import TimeSeries
|
|
9
|
+
|
|
10
|
+
__all__ = ["TimeSeries"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Models of earthsciencekit.geophysics, grouped by topic."""
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Deformation of the Earth: tidal Love numbers and mass change from time-variable gravity.
|
|
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
|
+
"love_number_h2",
|
|
12
|
+
"love_number_k2",
|
|
13
|
+
"tidal_gravimetric_factor",
|
|
14
|
+
"equivalent_water_height",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _rigidity_ratio(rigidity, density, gravity, radius):
|
|
19
|
+
rigidity = quantity("rigidity", rigidity, lower=0)
|
|
20
|
+
density = quantity("density", density, lower=0, strict_lower=True)
|
|
21
|
+
gravity = quantity("gravity", gravity, lower=0, strict_lower=True)
|
|
22
|
+
radius = quantity("radius", radius, lower=0, strict_lower=True)
|
|
23
|
+
return 19 * rigidity / (2 * density * gravity * radius)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@model
|
|
27
|
+
def love_number_h2(rigidity, density, gravity, radius):
|
|
28
|
+
"""Degree-2 displacement Love number of a homogeneous elastic Earth.
|
|
29
|
+
|
|
30
|
+
The solid Earth rises and falls with the tide by a fraction of the equilibrium tide that measures its rigidity.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
rigidity : float or array_like
|
|
35
|
+
Pa; constraint: nonnegative. Shear modulus; zero gives a fluid Earth.
|
|
36
|
+
density : float or array_like
|
|
37
|
+
kg/m^3; constraint: positive.
|
|
38
|
+
gravity : float or array_like
|
|
39
|
+
m/s^2; constraint: positive.
|
|
40
|
+
radius : float or array_like
|
|
41
|
+
m; constraint: positive.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
numpy scalar or ndarray
|
|
46
|
+
Model value [1]: radial displacement over the equilibrium tide height.
|
|
47
|
+
|
|
48
|
+
Notes
|
|
49
|
+
-----
|
|
50
|
+
Uniform, incompressible, self-gravitating elastic sphere (Kelvin's solution); ``h2 = 5 k2 / 3``.
|
|
51
|
+
Equation: ``2.5 / (1 + 19 * rigidity / (2 * density * gravity * radius))``.
|
|
52
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
53
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
54
|
+
"""
|
|
55
|
+
return 2.5 / (1 + _rigidity_ratio(rigidity, density, gravity, radius))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@model
|
|
59
|
+
def love_number_k2(rigidity, density, gravity, radius):
|
|
60
|
+
"""Degree-2 potential Love number of a homogeneous elastic Earth.
|
|
61
|
+
|
|
62
|
+
The tidal bulge adds its own gravitational potential, a fraction k2 of the tidal potential.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
rigidity : float or array_like
|
|
67
|
+
Pa; constraint: nonnegative. Shear modulus; zero gives a fluid Earth.
|
|
68
|
+
density : float or array_like
|
|
69
|
+
kg/m^3; constraint: positive.
|
|
70
|
+
gravity : float or array_like
|
|
71
|
+
m/s^2; constraint: positive.
|
|
72
|
+
radius : float or array_like
|
|
73
|
+
m; constraint: positive.
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
numpy scalar or ndarray
|
|
78
|
+
Model value [1]. Scalar input returns a NumPy scalar; arrays broadcast.
|
|
79
|
+
|
|
80
|
+
Notes
|
|
81
|
+
-----
|
|
82
|
+
Uniform, incompressible, self-gravitating elastic sphere (Kelvin's solution); the fluid limit is 3/2.
|
|
83
|
+
Equation: ``1.5 / (1 + 19 * rigidity / (2 * density * gravity * radius))``.
|
|
84
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
85
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
86
|
+
"""
|
|
87
|
+
return 1.5 / (1 + _rigidity_ratio(rigidity, density, gravity, radius))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@model
|
|
91
|
+
def tidal_gravimetric_factor(h2, k2):
|
|
92
|
+
"""Gravimetric factor of the body tide.
|
|
93
|
+
|
|
94
|
+
A gravimeter on a yielding Earth sees the tide amplified by its uplift and reduced by the bulge's attraction.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
h2 : float or array_like
|
|
99
|
+
1; constraint: nonnegative.
|
|
100
|
+
k2 : float or array_like
|
|
101
|
+
1; constraint: nonnegative.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
numpy scalar or ndarray
|
|
106
|
+
Model value [1]: observed over rigid-Earth tidal gravity change.
|
|
107
|
+
|
|
108
|
+
Notes
|
|
109
|
+
-----
|
|
110
|
+
Degree-2 tide on a spherically symmetric elastic Earth, without ocean loading. About 1.16 for the real Earth.
|
|
111
|
+
Equation: ``1 + h2 - 1.5 * k2``.
|
|
112
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
113
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
114
|
+
"""
|
|
115
|
+
h2 = quantity("h2", h2, lower=0)
|
|
116
|
+
k2 = quantity("k2", k2, lower=0)
|
|
117
|
+
return 1 + h2 - 1.5 * k2
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@model
|
|
121
|
+
def equivalent_water_height(coefficient_change, degree, load_love_number, radius, mean_density, water_density):
|
|
122
|
+
"""Water layer behind a change of one Stokes coefficient.
|
|
123
|
+
|
|
124
|
+
Monthly gravity fields from a satellite pair reveal the moving mass of water, ice and groundwater.
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
coefficient_change : float or array_like
|
|
129
|
+
1; constraint: real. Change of a fully normalized Stokes coefficient of degree ``n``.
|
|
130
|
+
degree : float or array_like
|
|
131
|
+
1; constraint: integer >= 0.
|
|
132
|
+
load_love_number : float or array_like
|
|
133
|
+
1; constraint: greater than -1. Elastic yielding ``k'_n`` of the Earth under the load.
|
|
134
|
+
radius : float or array_like
|
|
135
|
+
m; constraint: positive.
|
|
136
|
+
mean_density : float or array_like
|
|
137
|
+
kg/m^3; constraint: positive. Mean density of the Earth.
|
|
138
|
+
water_density : float or array_like
|
|
139
|
+
kg/m^3; constraint: positive.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
numpy scalar or ndarray
|
|
144
|
+
Model value [m]: coefficient of the same fully normalized harmonic in the water thickness.
|
|
145
|
+
|
|
146
|
+
Notes
|
|
147
|
+
-----
|
|
148
|
+
Thin surface layer on an elastic Earth (Wahr et al., 1998); valid while the layer is much thinner than the
|
|
149
|
+
resolution of the truncated expansion.
|
|
150
|
+
Equation: ``radius * mean_density * (2 * degree + 1) * coefficient_change / (3 * water_density * (1 + load_love_number))``.
|
|
151
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
152
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
153
|
+
"""
|
|
154
|
+
coefficient_change = quantity("coefficient_change", coefficient_change)
|
|
155
|
+
degree = quantity("degree", degree, lower=0)
|
|
156
|
+
require(degree == np.round(degree), "degree must be an integer")
|
|
157
|
+
load_love_number = quantity("load_love_number", load_love_number, lower=-1, strict_lower=True)
|
|
158
|
+
radius = quantity("radius", radius, lower=0, strict_lower=True)
|
|
159
|
+
mean_density = quantity("mean_density", mean_density, lower=0, strict_lower=True)
|
|
160
|
+
water_density = quantity("water_density", water_density, lower=0, strict_lower=True)
|
|
161
|
+
return radius * mean_density * (2 * degree + 1) * coefficient_change / (3 * water_density * (1 + load_love_number))
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Geodynamo: magnetic diffusion, the magnetic Reynolds number and Rikitake's two-disc dynamo.
|
|
2
|
+
|
|
3
|
+
SI units unless stated otherwise.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from earthsciencekit._validation import model, quantity, require, safe_arithmetic
|
|
9
|
+
from earthsciencekit.constants import MU0
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"magnetic_reynolds_number",
|
|
13
|
+
"magnetic_decay_time",
|
|
14
|
+
"rikitake_fixed_point",
|
|
15
|
+
"simulate_rikitake_dynamo",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@model
|
|
20
|
+
def magnetic_reynolds_number(speed, length, conductivity):
|
|
21
|
+
"""Magnetic Reynolds number.
|
|
22
|
+
|
|
23
|
+
A conducting fluid can stretch and regenerate a magnetic field only if advection beats diffusion.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
speed : float or array_like
|
|
28
|
+
m/s; constraint: nonnegative.
|
|
29
|
+
length : float or array_like
|
|
30
|
+
m; constraint: positive.
|
|
31
|
+
conductivity : float or array_like
|
|
32
|
+
S/m; constraint: positive.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
numpy scalar or ndarray
|
|
37
|
+
Model value [1]. Scalar input returns a NumPy scalar; arrays broadcast.
|
|
38
|
+
|
|
39
|
+
Notes
|
|
40
|
+
-----
|
|
41
|
+
Order-of-magnitude scale; dynamos need values above a geometry-dependent threshold of order 10 to 100.
|
|
42
|
+
Equation: ``MU0 * conductivity * speed * length``.
|
|
43
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
44
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
45
|
+
"""
|
|
46
|
+
speed = quantity("speed", speed, lower=0)
|
|
47
|
+
length = quantity("length", length, lower=0, strict_lower=True)
|
|
48
|
+
conductivity = quantity("conductivity", conductivity, lower=0, strict_lower=True)
|
|
49
|
+
return MU0 * conductivity * speed * length
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@model
|
|
53
|
+
def magnetic_decay_time(radius, conductivity):
|
|
54
|
+
"""Free decay time of the slowest magnetic mode of a conducting sphere.
|
|
55
|
+
|
|
56
|
+
Without a dynamo the Earth's field would die in tens of thousands of years, far less than its age.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
radius : float or array_like
|
|
61
|
+
m; constraint: positive.
|
|
62
|
+
conductivity : float or array_like
|
|
63
|
+
S/m; constraint: positive.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
numpy scalar or ndarray
|
|
68
|
+
Model value [s]: e-folding time of the field of the slowest dipole mode.
|
|
69
|
+
|
|
70
|
+
Notes
|
|
71
|
+
-----
|
|
72
|
+
Uniform stationary conductor surrounded by an insulator.
|
|
73
|
+
Equation: ``MU0 * conductivity * radius**2 / np.pi**2``.
|
|
74
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
75
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
76
|
+
"""
|
|
77
|
+
radius = quantity("radius", radius, lower=0, strict_lower=True)
|
|
78
|
+
conductivity = quantity("conductivity", conductivity, lower=0, strict_lower=True)
|
|
79
|
+
return MU0 * conductivity * radius**2 / np.pi**2
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@model
|
|
83
|
+
def rikitake_fixed_point(mu, k):
|
|
84
|
+
"""Steady state of Rikitake's two-disc dynamo.
|
|
85
|
+
|
|
86
|
+
The two coupled discs have a pair of steady states of opposite polarity, which the chaotic flow visits in turn.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
mu : float or array_like
|
|
91
|
+
1; constraint: positive. Scaled resistance.
|
|
92
|
+
k : float or array_like
|
|
93
|
+
1; constraint: positive. Sets ``A = mu * (k**2 - 1 / k**2)``.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
ndarray, shape (..., 3)
|
|
98
|
+
Model value [1]: the scaled currents ``x``, ``y`` and the scaled angular speed ``z`` of the positive state; the
|
|
99
|
+
other state is ``(-x, -y, z)``.
|
|
100
|
+
|
|
101
|
+
Notes
|
|
102
|
+
-----
|
|
103
|
+
Nondimensional system of :func:`simulate_rikitake_dynamo`. Both states are unstable for the usual parameters.
|
|
104
|
+
Equation: ``[k, 1 / k, mu * k**2]``.
|
|
105
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
106
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
107
|
+
"""
|
|
108
|
+
mu = quantity("mu", mu, lower=0, strict_lower=True)
|
|
109
|
+
k = quantity("k", k, lower=0, strict_lower=True)
|
|
110
|
+
return np.stack(np.broadcast_arrays(k, 1 / k, mu * k**2), axis=-1)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@safe_arithmetic
|
|
114
|
+
def simulate_rikitake_dynamo(mu, k, time_scale, time, *, initial_state=(1.0, 0.0, 0.0)):
|
|
115
|
+
"""Integrate Rikitake's coupled two-disc dynamo.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
mu : float
|
|
120
|
+
Positive scalar scaled resistance [1].
|
|
121
|
+
k : float
|
|
122
|
+
Positive scalar setting the torque asymmetry ``A = mu * (k**2 - 1 / k**2)`` [1].
|
|
123
|
+
time_scale : float
|
|
124
|
+
Positive scalar seconds per nondimensional time unit [s].
|
|
125
|
+
time : array_like
|
|
126
|
+
Nonnegative strictly increasing sampled times [s].
|
|
127
|
+
initial_state : sequence of float
|
|
128
|
+
Scaled currents ``x``, ``y`` and angular speed ``z`` at the first time [1].
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
TimeSeries
|
|
133
|
+
The scaled currents ``x`` and ``y`` (whose sign is the polarity) and the scaled angular speed ``z`` [1].
|
|
134
|
+
|
|
135
|
+
Notes
|
|
136
|
+
-----
|
|
137
|
+
``dx/dt = -mu * x + y * z``, ``dy/dt = -mu * y + (z - A) * x``, ``dz/dt = 1 - x * y`` in units of ``time_scale``.
|
|
138
|
+
The steady states are :func:`rikitake_fixed_point`. Trajectories oscillate about one and jump irregularly to the
|
|
139
|
+
other, reversing both currents: a chaotic analogue of geomagnetic reversals, not a model of the core.
|
|
140
|
+
"""
|
|
141
|
+
from earthsciencekit._validation import time_grid
|
|
142
|
+
from earthsciencekit.geophysics.core.base import TimeSeries
|
|
143
|
+
from earthsciencekit.integrators import integrate_ode
|
|
144
|
+
|
|
145
|
+
mu = quantity("mu", mu, lower=0, strict_lower=True)
|
|
146
|
+
k = quantity("k", k, lower=0, strict_lower=True)
|
|
147
|
+
time_scale = quantity("time_scale", time_scale, lower=0, strict_lower=True)
|
|
148
|
+
initial_state = quantity("initial_state", initial_state)
|
|
149
|
+
require(all(v.ndim == 0 for v in (mu, k, time_scale)), "dynamo parameters must be scalars")
|
|
150
|
+
require(initial_state.shape == (3,), "initial_state must hold x, y and z")
|
|
151
|
+
time = time_grid(time)
|
|
152
|
+
a = mu * (k**2 - 1 / k**2)
|
|
153
|
+
|
|
154
|
+
def rhs(t, state):
|
|
155
|
+
x, y, z = state
|
|
156
|
+
return [(-mu * x + y * z) / time_scale, (-mu * y + (z - a) * x) / time_scale, (1 - x * y) / time_scale]
|
|
157
|
+
|
|
158
|
+
result = integrate_ode(rhs, initial_state, time, names=("x", "y", "z"), units=("1", "1", "1"), method="DOP853", rtol=1e-10)
|
|
159
|
+
return TimeSeries(result.time, result.values, result.names, result.units)
|