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,190 @@
1
+ """Magma: viscosity, water solubility, ascent in dikes and ground deformation above a magma chamber.
2
+
3
+ SI units unless stated otherwise.
4
+ """
5
+
6
+ import numpy as np
7
+
8
+ from earthsciencekit._validation import model, quantity
9
+ from earthsciencekit.constants import R_GAS
10
+
11
+ __all__ = [
12
+ "arrhenius_viscosity",
13
+ "shaw_viscosity",
14
+ "water_solubility",
15
+ "dike_ascent_velocity",
16
+ "mogi_displacement",
17
+ ]
18
+
19
+
20
+ @model
21
+ def arrhenius_viscosity(temperature, reference_viscosity, activation_energy, reference_temperature):
22
+ """Arrhenius temperature dependence of melt viscosity.
23
+
24
+ Silicate melts stiffen by orders of magnitude as they cool, which controls how lava flows and how magma erupts.
25
+
26
+ Parameters
27
+ ----------
28
+ temperature : float or array_like
29
+ K; constraint: positive.
30
+ reference_viscosity : float or array_like
31
+ Pa s, viscosity at the reference temperature; constraint: positive.
32
+ activation_energy : float or array_like
33
+ J/mol; constraint: nonnegative.
34
+ reference_temperature : float or array_like
35
+ K; constraint: positive.
36
+
37
+ Returns
38
+ -------
39
+ numpy scalar or ndarray
40
+ Model value [Pa s]. Scalar input returns a NumPy scalar; arrays broadcast.
41
+
42
+ Notes
43
+ -----
44
+ Constant activation energy over the range of interest; crystal- and bubble-free Newtonian melt.
45
+ Equation: ``reference_viscosity * np.exp(activation_energy / R_GAS * (1 / temperature - 1 / reference_temperature))``.
46
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
47
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
48
+ """
49
+ temperature = quantity("temperature", temperature, lower=0, strict_lower=True)
50
+ reference_viscosity = quantity("reference_viscosity", reference_viscosity, lower=0, strict_lower=True)
51
+ activation_energy = quantity("activation_energy", activation_energy, lower=0)
52
+ reference_temperature = quantity("reference_temperature", reference_temperature, lower=0, strict_lower=True)
53
+ return reference_viscosity * np.exp(activation_energy / R_GAS * (1 / temperature - 1 / reference_temperature))
54
+
55
+
56
+ @model
57
+ def shaw_viscosity(temperature, slope):
58
+ """Shaw's empirical viscosity of an anhydrous or hydrous silicate melt.
59
+
60
+ One composition-dependent slope predicts the viscosity of melts from basalt to rhyolite.
61
+
62
+ Parameters
63
+ ----------
64
+ temperature : float or array_like
65
+ K; constraint: positive.
66
+ slope : float or array_like
67
+ 1, Shaw's s from the molar composition (about 2-3 for basalt, 3.5-4 for rhyolite); constraint: positive.
68
+
69
+ Returns
70
+ -------
71
+ numpy scalar or ndarray
72
+ Model value [Pa s]. Scalar input returns a NumPy scalar; arrays broadcast.
73
+
74
+ Notes
75
+ -----
76
+ Shaw (1972), Am. J. Sci. 272: 870-893; all lines pass near ln(eta [poise]) = -6.4 at 1e4/T = 1.5. Crystal- and
77
+ bubble-free Newtonian melt; about 0.2 log units of accuracy.
78
+ Equation: ``0.1 * np.exp(slope * (1e4 / temperature) - 1.5 * slope - 6.4)``.
79
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
80
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
81
+ """
82
+ temperature = quantity("temperature", temperature, lower=0, strict_lower=True)
83
+ slope = quantity("slope", slope, lower=0, strict_lower=True)
84
+ return 0.1 * np.exp(slope * (1e4 / temperature) - 1.5 * slope - 6.4)
85
+
86
+
87
+ @model
88
+ def water_solubility(pressure, solubility_constant):
89
+ """Dissolved water in a melt saturated at a given pressure.
90
+
91
+ Water dissolves as hydroxyl, so its solubility grows as the square root of pressure and ascending magma exsolves
92
+ bubbles.
93
+
94
+ Parameters
95
+ ----------
96
+ pressure : float or array_like
97
+ Pa; constraint: nonnegative.
98
+ solubility_constant : float or array_like
99
+ 1/sqrt(Pa), mass fraction per root pascal (about 4.1e-6 for rhyolite); constraint: positive.
100
+
101
+ Returns
102
+ -------
103
+ numpy scalar or ndarray
104
+ Model value [1], mass fraction. Scalar input returns a NumPy scalar; arrays broadcast.
105
+
106
+ Notes
107
+ -----
108
+ Square-root (Henry-type) law for pure water vapour, valid up to a few hundred MPa; ignores CO2 and temperature.
109
+ Equation: ``solubility_constant * np.sqrt(pressure)``.
110
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
111
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
112
+ """
113
+ pressure = quantity("pressure", pressure, lower=0)
114
+ solubility_constant = quantity("solubility_constant", solubility_constant, lower=0, strict_lower=True)
115
+ return solubility_constant * np.sqrt(pressure)
116
+
117
+
118
+ @model
119
+ def dike_ascent_velocity(width, density_contrast, viscosity, gravity):
120
+ """Mean laminar velocity of buoyant magma in a dike.
121
+
122
+ Buoyancy drives magma up a planar crack, and the viscous drag on its walls sets how fast it rises.
123
+
124
+ Parameters
125
+ ----------
126
+ width : float or array_like
127
+ m, full dike width; constraint: positive.
128
+ density_contrast : float or array_like
129
+ kg/m^3, host rock minus magma; constraint: real.
130
+ viscosity : float or array_like
131
+ Pa s; constraint: positive.
132
+ gravity : float or array_like
133
+ m/s^2; constraint: positive.
134
+
135
+ Returns
136
+ -------
137
+ numpy scalar or ndarray
138
+ Model value [m/s], positive upward. Scalar input returns a NumPy scalar; arrays broadcast.
139
+
140
+ Notes
141
+ -----
142
+ Wilson and Head (1981): plane Poiseuille flow between parallel walls driven by buoyancy alone; check that the Reynolds
143
+ number ``magma_density * velocity * width / viscosity`` stays below about 2000.
144
+ Equation: ``width**2 * density_contrast * gravity / (12 * viscosity)``.
145
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
146
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
147
+ """
148
+ width = quantity("width", width, lower=0, strict_lower=True)
149
+ density_contrast = quantity("density_contrast", density_contrast)
150
+ viscosity = quantity("viscosity", viscosity, lower=0, strict_lower=True)
151
+ gravity = quantity("gravity", gravity, lower=0, strict_lower=True)
152
+ return width**2 * density_contrast * gravity / (12 * viscosity)
153
+
154
+
155
+ @model
156
+ def mogi_displacement(radial_distance, depth, volume_change, poisson_ratio):
157
+ """Surface displacement above an inflating point source.
158
+
159
+ Magma entering a chamber lifts the ground in a bell-shaped dome whose width reveals the chamber depth.
160
+
161
+ Parameters
162
+ ----------
163
+ radial_distance : float or array_like
164
+ m, horizontal distance from the point above the source; constraint: nonnegative.
165
+ depth : float or array_like
166
+ m; constraint: positive.
167
+ volume_change : float or array_like
168
+ m^3, signed change of the source volume; constraint: real.
169
+ poisson_ratio : float or array_like
170
+ 1; constraint: between 0 and 0.5.
171
+
172
+ Returns
173
+ -------
174
+ ndarray, shape (..., 2)
175
+ Model value [m]: ``[radial, vertical]`` displacement, outward and upward positive.
176
+
177
+ Notes
178
+ -----
179
+ Mogi (1958) point source in an elastic half-space; the chamber radius must be small compared with its depth. The
180
+ uplift halves at ``radial_distance = 0.766 * depth``.
181
+ Equation: ``(1 - poisson_ratio) * volume_change / np.pi * [radial_distance, depth] / (radial_distance**2 + depth**2)**1.5``.
182
+ Invalid, empty, nonreal or nonfinite inputs raise ValueError.
183
+ NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
184
+ """
185
+ radial_distance = quantity("radial_distance", radial_distance, lower=0)
186
+ depth = quantity("depth", depth, lower=0, strict_lower=True)
187
+ volume_change = quantity("volume_change", volume_change)
188
+ poisson_ratio = quantity("poisson_ratio", poisson_ratio, lower=0, upper=0.5)
189
+ scale = (1 - poisson_ratio) * volume_change / np.pi / (radial_distance**2 + depth**2) ** 1.5
190
+ return np.stack(np.broadcast_arrays(scale * radial_distance, scale * depth), axis=-1)
File without changes
@@ -0,0 +1,485 @@
1
+ """Golden checks and invalid-input contracts for every public model."""
2
+
3
+ import numpy as np
4
+ import pytest
5
+
6
+ from earthsciencekit import volcanology as domain
7
+ from earthsciencekit.constants import AVOGADRO, LAMBDA_U238, R_GAS
8
+
9
+ HALF_LIFE_U238 = np.log(2) / LAMBDA_U238
10
+ BAD = [np.nan, np.inf, -np.inf, [], "bad", True, 1j]
11
+
12
+
13
+ def test_decay_constant_reference():
14
+ result = domain.decay_constant(*[2])
15
+ np.testing.assert_allclose(result, np.log(2) / 2, rtol=1e-11, atol=1e-12)
16
+
17
+
18
+ @pytest.mark.parametrize("bad", BAD)
19
+ def test_decay_constant_rejects_invalid(bad):
20
+ args = [2]
21
+ args[0] = bad
22
+ with pytest.raises(ValueError):
23
+ domain.decay_constant(*args)
24
+
25
+
26
+ @pytest.mark.parametrize("index,value", [(0, 0)])
27
+ def test_decay_constant_physical_bound(index, value):
28
+ args = [2]
29
+ args[index] = value
30
+ with pytest.raises(ValueError):
31
+ domain.decay_constant(*args)
32
+
33
+
34
+ def test_parent_fraction_reference():
35
+ result = domain.parent_fraction(*[np.log(2), 1])
36
+ np.testing.assert_allclose(result, 0.5, rtol=1e-11, atol=1e-12)
37
+
38
+
39
+ @pytest.mark.parametrize("bad", BAD)
40
+ def test_parent_fraction_rejects_invalid(bad):
41
+ args = [np.log(2), 1]
42
+ args[0] = bad
43
+ with pytest.raises(ValueError):
44
+ domain.parent_fraction(*args)
45
+
46
+
47
+ @pytest.mark.parametrize("index,value", [(0, 0), (1, -1)])
48
+ def test_parent_fraction_physical_bound(index, value):
49
+ args = [np.log(2), 1]
50
+ args[index] = value
51
+ with pytest.raises(ValueError):
52
+ domain.parent_fraction(*args)
53
+
54
+
55
+ def test_radiometric_age_reference():
56
+ result = domain.radiometric_age(*[np.e - 1, 2])
57
+ np.testing.assert_allclose(result, 0.5, rtol=1e-11, atol=1e-12)
58
+
59
+
60
+ @pytest.mark.parametrize("bad", BAD)
61
+ def test_radiometric_age_rejects_invalid(bad):
62
+ args = [np.e - 1, 2]
63
+ args[0] = bad
64
+ with pytest.raises(ValueError):
65
+ domain.radiometric_age(*args)
66
+
67
+
68
+ @pytest.mark.parametrize("index,value", [(0, -1), (1, 0)])
69
+ def test_radiometric_age_physical_bound(index, value):
70
+ args = [np.e - 1, 2]
71
+ args[index] = value
72
+ with pytest.raises(ValueError):
73
+ domain.radiometric_age(*args)
74
+
75
+
76
+ def test_radiogenic_daughter_reference():
77
+ result = domain.radiogenic_daughter(*[3, np.log(2), 1])
78
+ np.testing.assert_allclose(result, 3, rtol=1e-11, atol=1e-12)
79
+
80
+
81
+ @pytest.mark.parametrize("bad", BAD)
82
+ def test_radiogenic_daughter_rejects_invalid(bad):
83
+ args = [3, np.log(2), 1]
84
+ args[0] = bad
85
+ with pytest.raises(ValueError):
86
+ domain.radiogenic_daughter(*args)
87
+
88
+
89
+ @pytest.mark.parametrize("index,value", [(0, -1), (1, 0), (2, -1)])
90
+ def test_radiogenic_daughter_physical_bound(index, value):
91
+ args = [3, np.log(2), 1]
92
+ args[index] = value
93
+ with pytest.raises(ValueError):
94
+ domain.radiogenic_daughter(*args)
95
+
96
+
97
+ def test_radiocarbon_age_reference():
98
+ result = domain.radiocarbon_age(*[np.exp(-1)])
99
+ np.testing.assert_allclose(result, 8033 * 365.25 * 86400, rtol=1e-11, atol=1e-12)
100
+
101
+
102
+ @pytest.mark.parametrize("bad", BAD)
103
+ def test_radiocarbon_age_rejects_invalid(bad):
104
+ args = [np.exp(-1)]
105
+ args[0] = bad
106
+ with pytest.raises(ValueError):
107
+ domain.radiocarbon_age(*args)
108
+
109
+
110
+ @pytest.mark.parametrize("index,value", [(0, 0)])
111
+ def test_radiocarbon_age_physical_bound(index, value):
112
+ args = [np.exp(-1)]
113
+ args[index] = value
114
+ with pytest.raises(ValueError):
115
+ domain.radiocarbon_age(*args)
116
+
117
+
118
+ def test_radiogenic_lead_ratio_reference():
119
+ result = domain.radiogenic_lead_ratio(*[HALF_LIFE_U238])
120
+ np.testing.assert_allclose(result, (2 ** (9.8485 / 1.55125) - 1) / 137.88, rtol=1e-11, atol=1e-12)
121
+
122
+
123
+ @pytest.mark.parametrize("bad", BAD)
124
+ def test_radiogenic_lead_ratio_rejects_invalid(bad):
125
+ args = [HALF_LIFE_U238]
126
+ args[0] = bad
127
+ with pytest.raises(ValueError):
128
+ domain.radiogenic_lead_ratio(*args)
129
+
130
+
131
+ @pytest.mark.parametrize("index,value", [(0, 0)])
132
+ def test_radiogenic_lead_ratio_physical_bound(index, value):
133
+ args = [HALF_LIFE_U238]
134
+ args[index] = value
135
+ with pytest.raises(ValueError):
136
+ domain.radiogenic_lead_ratio(*args)
137
+
138
+
139
+ def test_lead_lead_age_reference():
140
+ result = domain.lead_lead_age(*[(2 ** (9.8485 / 1.55125) - 1) / 137.88])
141
+ np.testing.assert_allclose(result, HALF_LIFE_U238, rtol=1e-11, atol=1e-12)
142
+
143
+
144
+ @pytest.mark.parametrize("bad", BAD)
145
+ def test_lead_lead_age_rejects_invalid(bad):
146
+ args = [(2 ** (9.8485 / 1.55125) - 1) / 137.88]
147
+ args[0] = bad
148
+ with pytest.raises(ValueError):
149
+ domain.lead_lead_age(*args)
150
+
151
+
152
+ @pytest.mark.parametrize("index,value", [(0, 0.04), (0, 50.0)])
153
+ def test_lead_lead_age_physical_bound(index, value):
154
+ args = [(2 ** (9.8485 / 1.55125) - 1) / 137.88]
155
+ args[index] = value
156
+ with pytest.raises(ValueError):
157
+ domain.lead_lead_age(*args)
158
+
159
+
160
+ def test_concordia_ratios_reference():
161
+ result = domain.concordia_ratios(*[HALF_LIFE_U238])
162
+ np.testing.assert_allclose(result, [2 ** (9.8485 / 1.55125) - 1, 1], rtol=1e-11, atol=1e-12)
163
+
164
+
165
+ @pytest.mark.parametrize("bad", BAD)
166
+ def test_concordia_ratios_rejects_invalid(bad):
167
+ args = [HALF_LIFE_U238]
168
+ args[0] = bad
169
+ with pytest.raises(ValueError):
170
+ domain.concordia_ratios(*args)
171
+
172
+
173
+ @pytest.mark.parametrize("index,value", [(0, -1)])
174
+ def test_concordia_ratios_physical_bound(index, value):
175
+ args = [HALF_LIFE_U238]
176
+ args[index] = value
177
+ with pytest.raises(ValueError):
178
+ domain.concordia_ratios(*args)
179
+
180
+
181
+ def test_isochron_fit_reference():
182
+ result = domain.isochron_fit(*[[0, 1, 2], [0.7, 0.7 + np.e - 1, 0.7 + 2 * (np.e - 1)], 1])
183
+ np.testing.assert_allclose(result, [1, 0.7], rtol=1e-11, atol=1e-12)
184
+
185
+
186
+ @pytest.mark.parametrize("bad", BAD)
187
+ def test_isochron_fit_rejects_invalid(bad):
188
+ args = [[0, 1, 2], [0.7, 0.7 + np.e - 1, 0.7 + 2 * (np.e - 1)], 1]
189
+ args[0] = bad
190
+ with pytest.raises(ValueError):
191
+ domain.isochron_fit(*args)
192
+
193
+
194
+ @pytest.mark.parametrize("index,value", [(0, [-1, 1, 2]), (1, [-1, 1, 2]), (2, 0)])
195
+ def test_isochron_fit_physical_bound(index, value):
196
+ args = [[0, 1, 2], [0.7, 0.7 + np.e - 1, 0.7 + 2 * (np.e - 1)], 1]
197
+ args[index] = value
198
+ with pytest.raises(ValueError):
199
+ domain.isochron_fit(*args)
200
+
201
+
202
+ def test_rayleigh_fractionation_reference():
203
+ result = domain.rayleigh_fractionation(*[2, 0.25, 0.5])
204
+ np.testing.assert_allclose(result, 4, rtol=1e-11, atol=1e-12)
205
+
206
+
207
+ @pytest.mark.parametrize("bad", BAD)
208
+ def test_rayleigh_fractionation_rejects_invalid(bad):
209
+ args = [2, 0.25, 0.5]
210
+ args[0] = bad
211
+ with pytest.raises(ValueError):
212
+ domain.rayleigh_fractionation(*args)
213
+
214
+
215
+ @pytest.mark.parametrize("index,value", [(0, 0), (1, 0), (1, 1.5), (2, 0)])
216
+ def test_rayleigh_fractionation_physical_bound(index, value):
217
+ args = [2, 0.25, 0.5]
218
+ args[index] = value
219
+ with pytest.raises(ValueError):
220
+ domain.rayleigh_fractionation(*args)
221
+
222
+
223
+ def test_delta_value_reference():
224
+ result = domain.delta_value(*[1.01, 1])
225
+ np.testing.assert_allclose(result, 10, rtol=1e-11, atol=1e-12)
226
+
227
+
228
+ @pytest.mark.parametrize("bad", BAD)
229
+ def test_delta_value_rejects_invalid(bad):
230
+ args = [1.01, 1]
231
+ args[0] = bad
232
+ with pytest.raises(ValueError):
233
+ domain.delta_value(*args)
234
+
235
+
236
+ @pytest.mark.parametrize("index,value", [(0, 0), (1, 0)])
237
+ def test_delta_value_physical_bound(index, value):
238
+ args = [1.01, 1]
239
+ args[index] = value
240
+ with pytest.raises(ValueError):
241
+ domain.delta_value(*args)
242
+
243
+
244
+ def test_fractional_crystallization_melt_reference():
245
+ result = domain.fractional_crystallization_melt(*[10, 0.25, 0.5])
246
+ np.testing.assert_allclose(result, 20, rtol=1e-11, atol=1e-12)
247
+
248
+
249
+ @pytest.mark.parametrize("bad", BAD)
250
+ def test_fractional_crystallization_melt_rejects_invalid(bad):
251
+ args = [10, 0.25, 0.5]
252
+ args[0] = bad
253
+ with pytest.raises(ValueError):
254
+ domain.fractional_crystallization_melt(*args)
255
+
256
+
257
+ @pytest.mark.parametrize("index,value", [(0, -1), (1, 0), (1, 1.5), (2, -1)])
258
+ def test_fractional_crystallization_melt_physical_bound(index, value):
259
+ args = [10, 0.25, 0.5]
260
+ args[index] = value
261
+ with pytest.raises(ValueError):
262
+ domain.fractional_crystallization_melt(*args)
263
+
264
+
265
+ def test_equilibrium_crystallization_melt_reference():
266
+ result = domain.equilibrium_crystallization_melt(*[10, 0.5, 0.5])
267
+ np.testing.assert_allclose(result, 40 / 3, rtol=1e-11, atol=1e-12)
268
+
269
+
270
+ @pytest.mark.parametrize("bad", BAD)
271
+ def test_equilibrium_crystallization_melt_rejects_invalid(bad):
272
+ args = [10, 0.5, 0.5]
273
+ args[0] = bad
274
+ with pytest.raises(ValueError):
275
+ domain.equilibrium_crystallization_melt(*args)
276
+
277
+
278
+ @pytest.mark.parametrize("index,value", [(0, -1), (1, -0.5), (1, 1.5), (2, 0)])
279
+ def test_equilibrium_crystallization_melt_physical_bound(index, value):
280
+ args = [10, 0.5, 0.5]
281
+ args[index] = value
282
+ with pytest.raises(ValueError):
283
+ domain.equilibrium_crystallization_melt(*args)
284
+
285
+
286
+ def test_lattice_strain_partition_reference():
287
+ result = domain.lattice_strain_partition(*[2e-10, 1e-10, 3, 1.2 * R_GAS * 1000 / (4 * np.pi * AVOGADRO * 1e-30), 1000])
288
+ np.testing.assert_allclose(result, 3 * np.exp(-1), rtol=1e-11, atol=1e-12)
289
+
290
+
291
+ @pytest.mark.parametrize("bad", BAD)
292
+ def test_lattice_strain_partition_rejects_invalid(bad):
293
+ args = [2e-10, 1e-10, 3, 1.2 * R_GAS * 1000 / (4 * np.pi * AVOGADRO * 1e-30), 1000]
294
+ args[0] = bad
295
+ with pytest.raises(ValueError):
296
+ domain.lattice_strain_partition(*args)
297
+
298
+
299
+ @pytest.mark.parametrize("index,value", [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0)])
300
+ def test_lattice_strain_partition_physical_bound(index, value):
301
+ args = [2e-10, 1e-10, 3, 1.2 * R_GAS * 1000 / (4 * np.pi * AVOGADRO * 1e-30), 1000]
302
+ args[index] = value
303
+ with pytest.raises(ValueError):
304
+ domain.lattice_strain_partition(*args)
305
+
306
+
307
+ def test_isotope_temperature_reference():
308
+ result = domain.isotope_temperature(*[1, 0])
309
+ np.testing.assert_allclose(result, 273.15 + 16.5 - 4.3 + 0.14, rtol=1e-11, atol=1e-12)
310
+
311
+
312
+ @pytest.mark.parametrize("bad", BAD)
313
+ def test_isotope_temperature_rejects_invalid(bad):
314
+ args = [1, 0]
315
+ args[0] = bad
316
+ with pytest.raises(ValueError):
317
+ domain.isotope_temperature(*args)
318
+
319
+
320
+ def test_arrhenius_viscosity_reference():
321
+ result = domain.arrhenius_viscosity(*[1000, 10, R_GAS * 1000, 500])
322
+ np.testing.assert_allclose(result, 10 * np.exp(-1), rtol=1e-11, atol=1e-12)
323
+
324
+
325
+ @pytest.mark.parametrize("bad", BAD)
326
+ def test_arrhenius_viscosity_rejects_invalid(bad):
327
+ args = [1000, 10, R_GAS * 1000, 500]
328
+ args[0] = bad
329
+ with pytest.raises(ValueError):
330
+ domain.arrhenius_viscosity(*args)
331
+
332
+
333
+ @pytest.mark.parametrize("index,value", [(0, 0), (1, 0), (2, -1), (3, 0)])
334
+ def test_arrhenius_viscosity_physical_bound(index, value):
335
+ args = [1000, 10, R_GAS * 1000, 500]
336
+ args[index] = value
337
+ with pytest.raises(ValueError):
338
+ domain.arrhenius_viscosity(*args)
339
+
340
+
341
+ def test_shaw_viscosity_reference():
342
+ result = domain.shaw_viscosity(*[1e4 / 1.5, 2])
343
+ np.testing.assert_allclose(result, 0.1 * np.exp(-6.4), rtol=1e-11, atol=1e-12)
344
+
345
+
346
+ @pytest.mark.parametrize("bad", BAD)
347
+ def test_shaw_viscosity_rejects_invalid(bad):
348
+ args = [1e4 / 1.5, 2]
349
+ args[0] = bad
350
+ with pytest.raises(ValueError):
351
+ domain.shaw_viscosity(*args)
352
+
353
+
354
+ @pytest.mark.parametrize("index,value", [(0, 0), (1, 0)])
355
+ def test_shaw_viscosity_physical_bound(index, value):
356
+ args = [1e4 / 1.5, 2]
357
+ args[index] = value
358
+ with pytest.raises(ValueError):
359
+ domain.shaw_viscosity(*args)
360
+
361
+
362
+ def test_water_solubility_reference():
363
+ result = domain.water_solubility(*[4e8, 4e-6])
364
+ np.testing.assert_allclose(result, 0.08, rtol=1e-11, atol=1e-12)
365
+
366
+
367
+ @pytest.mark.parametrize("bad", BAD)
368
+ def test_water_solubility_rejects_invalid(bad):
369
+ args = [4e8, 4e-6]
370
+ args[0] = bad
371
+ with pytest.raises(ValueError):
372
+ domain.water_solubility(*args)
373
+
374
+
375
+ @pytest.mark.parametrize("index,value", [(0, -1), (1, 0)])
376
+ def test_water_solubility_physical_bound(index, value):
377
+ args = [4e8, 4e-6]
378
+ args[index] = value
379
+ with pytest.raises(ValueError):
380
+ domain.water_solubility(*args)
381
+
382
+
383
+ def test_dike_ascent_velocity_reference():
384
+ result = domain.dike_ascent_velocity(*[2, 300, 100, 10])
385
+ np.testing.assert_allclose(result, 10, rtol=1e-11, atol=1e-12)
386
+
387
+
388
+ @pytest.mark.parametrize("bad", BAD)
389
+ def test_dike_ascent_velocity_rejects_invalid(bad):
390
+ args = [2, 300, 100, 10]
391
+ args[0] = bad
392
+ with pytest.raises(ValueError):
393
+ domain.dike_ascent_velocity(*args)
394
+
395
+
396
+ @pytest.mark.parametrize("index,value", [(0, 0), (2, 0), (3, 0)])
397
+ def test_dike_ascent_velocity_physical_bound(index, value):
398
+ args = [2, 300, 100, 10]
399
+ args[index] = value
400
+ with pytest.raises(ValueError):
401
+ domain.dike_ascent_velocity(*args)
402
+
403
+
404
+ def test_mogi_displacement_reference():
405
+ result = domain.mogi_displacement(*[0, 1000, np.pi, 0.5])
406
+ np.testing.assert_allclose(result, [0, 5e-7], rtol=1e-11, atol=1e-12)
407
+
408
+
409
+ @pytest.mark.parametrize("bad", BAD)
410
+ def test_mogi_displacement_rejects_invalid(bad):
411
+ args = [0, 1000, np.pi, 0.5]
412
+ args[0] = bad
413
+ with pytest.raises(ValueError):
414
+ domain.mogi_displacement(*args)
415
+
416
+
417
+ @pytest.mark.parametrize("index,value", [(0, -1), (1, 0), (3, -0.1), (3, 0.6)])
418
+ def test_mogi_displacement_physical_bound(index, value):
419
+ args = [0, 1000, np.pi, 0.5]
420
+ args[index] = value
421
+ with pytest.raises(ValueError):
422
+ domain.mogi_displacement(*args)
423
+
424
+
425
+ def test_plume_rise_height_reference():
426
+ result = domain.plume_rise_height(*[16, 1 / 16])
427
+ np.testing.assert_allclose(result, 80, rtol=1e-11, atol=1e-12)
428
+
429
+
430
+ @pytest.mark.parametrize("bad", BAD)
431
+ def test_plume_rise_height_rejects_invalid(bad):
432
+ args = [16, 1 / 16]
433
+ args[0] = bad
434
+ with pytest.raises(ValueError):
435
+ domain.plume_rise_height(*args)
436
+
437
+
438
+ @pytest.mark.parametrize("index,value", [(0, 0), (1, 0)])
439
+ def test_plume_rise_height_physical_bound(index, value):
440
+ args = [16, 1 / 16]
441
+ args[index] = value
442
+ with pytest.raises(ValueError):
443
+ domain.plume_rise_height(*args)
444
+
445
+
446
+ def test_column_height_reference():
447
+ result = domain.column_height(*[1e12])
448
+ np.testing.assert_allclose(result, 8200, rtol=1e-11, atol=1e-12)
449
+
450
+
451
+ @pytest.mark.parametrize("bad", BAD)
452
+ def test_column_height_rejects_invalid(bad):
453
+ args = [1e12]
454
+ args[0] = bad
455
+ with pytest.raises(ValueError):
456
+ domain.column_height(*args)
457
+
458
+
459
+ @pytest.mark.parametrize("index,value", [(0, 0)])
460
+ def test_column_height_physical_bound(index, value):
461
+ args = [1e12]
462
+ args[index] = value
463
+ with pytest.raises(ValueError):
464
+ domain.column_height(*args)
465
+
466
+
467
+ def test_volcanic_explosivity_index_reference():
468
+ result = domain.volcanic_explosivity_index(*[5e7])
469
+ np.testing.assert_allclose(result, 3, rtol=1e-11, atol=1e-12)
470
+
471
+
472
+ @pytest.mark.parametrize("bad", BAD)
473
+ def test_volcanic_explosivity_index_rejects_invalid(bad):
474
+ args = [5e7]
475
+ args[0] = bad
476
+ with pytest.raises(ValueError):
477
+ domain.volcanic_explosivity_index(*args)
478
+
479
+
480
+ @pytest.mark.parametrize("index,value", [(0, -1)])
481
+ def test_volcanic_explosivity_index_physical_bound(index, value):
482
+ args = [5e7]
483
+ args[index] = value
484
+ with pytest.raises(ValueError):
485
+ domain.volcanic_explosivity_index(*args)