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,1015 @@
|
|
|
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 climate as domain
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_absorbed_solar_reference():
|
|
10
|
+
result = domain.absorbed_solar(*[1360, 0.3])
|
|
11
|
+
np.testing.assert_allclose(result, 238, rtol=1e-11, atol=1e-12)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
15
|
+
def test_absorbed_solar_rejects_invalid(bad):
|
|
16
|
+
args = [1360, 0.3]
|
|
17
|
+
args[0] = bad
|
|
18
|
+
with pytest.raises(ValueError):
|
|
19
|
+
domain.absorbed_solar(*args)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_effective_temperature_reference():
|
|
23
|
+
result = domain.effective_temperature(*[459.300327939, 1])
|
|
24
|
+
np.testing.assert_allclose(result, 300, rtol=1e-11, atol=1e-12)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
28
|
+
def test_effective_temperature_rejects_invalid(bad):
|
|
29
|
+
args = [459.300327939, 1]
|
|
30
|
+
args[0] = bad
|
|
31
|
+
with pytest.raises(ValueError):
|
|
32
|
+
domain.effective_temperature(*args)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_outgoing_longwave_reference():
|
|
36
|
+
result = domain.outgoing_longwave(*[300, 1])
|
|
37
|
+
np.testing.assert_allclose(result, 459.300327939, rtol=1e-11, atol=1e-12)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
41
|
+
def test_outgoing_longwave_rejects_invalid(bad):
|
|
42
|
+
args = [300, 1]
|
|
43
|
+
args[0] = bad
|
|
44
|
+
with pytest.raises(ValueError):
|
|
45
|
+
domain.outgoing_longwave(*args)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_planck_feedback_reference():
|
|
49
|
+
result = domain.planck_feedback(*[300, 1])
|
|
50
|
+
np.testing.assert_allclose(result, 6.12400437252, rtol=1e-11, atol=1e-12)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
54
|
+
def test_planck_feedback_rejects_invalid(bad):
|
|
55
|
+
args = [300, 1]
|
|
56
|
+
args[0] = bad
|
|
57
|
+
with pytest.raises(ValueError):
|
|
58
|
+
domain.planck_feedback(*args)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_co2_forcing_reference():
|
|
62
|
+
result = domain.co2_forcing(*[560, 280, 5.35])
|
|
63
|
+
np.testing.assert_allclose(result, 5.35 * np.log(2), rtol=1e-11, atol=1e-12)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
67
|
+
def test_co2_forcing_rejects_invalid(bad):
|
|
68
|
+
args = [560, 280, 5.35]
|
|
69
|
+
args[0] = bad
|
|
70
|
+
with pytest.raises(ValueError):
|
|
71
|
+
domain.co2_forcing(*args)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_equilibrium_warming_reference():
|
|
75
|
+
result = domain.equilibrium_warming(*[4, 2])
|
|
76
|
+
np.testing.assert_allclose(result, 2, rtol=1e-11, atol=1e-12)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
80
|
+
def test_equilibrium_warming_rejects_invalid(bad):
|
|
81
|
+
args = [4, 2]
|
|
82
|
+
args[0] = bad
|
|
83
|
+
with pytest.raises(ValueError):
|
|
84
|
+
domain.equilibrium_warming(*args)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_heat_capacity_column_reference():
|
|
88
|
+
result = domain.heat_capacity_column(*[1000, 4000, 50])
|
|
89
|
+
np.testing.assert_allclose(result, 2e8, rtol=1e-11, atol=1e-12)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
93
|
+
def test_heat_capacity_column_rejects_invalid(bad):
|
|
94
|
+
args = [1000, 4000, 50]
|
|
95
|
+
args[0] = bad
|
|
96
|
+
with pytest.raises(ValueError):
|
|
97
|
+
domain.heat_capacity_column(*args)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_response_timescale_reference():
|
|
101
|
+
result = domain.response_timescale(*[200000000.0, 2])
|
|
102
|
+
np.testing.assert_allclose(result, 1e8, rtol=1e-11, atol=1e-12)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
106
|
+
def test_response_timescale_rejects_invalid(bad):
|
|
107
|
+
args = [200000000.0, 2]
|
|
108
|
+
args[0] = bad
|
|
109
|
+
with pytest.raises(ValueError):
|
|
110
|
+
domain.response_timescale(*args)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_step_temperature_reference():
|
|
114
|
+
result = domain.step_temperature(*[4, 2, 2, 0])
|
|
115
|
+
np.testing.assert_allclose(result, 0, rtol=1e-11, atol=1e-12)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
119
|
+
def test_step_temperature_rejects_invalid(bad):
|
|
120
|
+
args = [4, 2, 2, 0]
|
|
121
|
+
args[0] = bad
|
|
122
|
+
with pytest.raises(ValueError):
|
|
123
|
+
domain.step_temperature(*args)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_heat_uptake_reference():
|
|
127
|
+
result = domain.heat_uptake(*[200000000.0, 2])
|
|
128
|
+
np.testing.assert_allclose(result, 4e8, rtol=1e-11, atol=1e-12)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
132
|
+
def test_heat_uptake_rejects_invalid(bad):
|
|
133
|
+
args = [200000000.0, 2]
|
|
134
|
+
args[0] = bad
|
|
135
|
+
with pytest.raises(ValueError):
|
|
136
|
+
domain.heat_uptake(*args)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_ice_albedo_reference():
|
|
140
|
+
result = domain.ice_albedo(*[273, 0.6, 0.2, 273, 5])
|
|
141
|
+
np.testing.assert_allclose(result, 0.4, rtol=1e-11, atol=1e-12)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
145
|
+
def test_ice_albedo_rejects_invalid(bad):
|
|
146
|
+
args = [273, 0.6, 0.2, 273, 5]
|
|
147
|
+
args[0] = bad
|
|
148
|
+
with pytest.raises(ValueError):
|
|
149
|
+
domain.ice_albedo(*args)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_carbon_airborne_fraction_reference():
|
|
153
|
+
result = domain.carbon_airborne_fraction(*[4, 10])
|
|
154
|
+
np.testing.assert_allclose(result, 0.4, rtol=1e-11, atol=1e-12)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
158
|
+
def test_carbon_airborne_fraction_rejects_invalid(bad):
|
|
159
|
+
args = [4, 10]
|
|
160
|
+
args[0] = bad
|
|
161
|
+
with pytest.raises(ValueError):
|
|
162
|
+
domain.carbon_airborne_fraction(*args)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_carbon_decay_reference():
|
|
166
|
+
result = domain.carbon_decay(*[2, 1, np.float64(0.6931471805599453)])
|
|
167
|
+
np.testing.assert_allclose(result, 1, rtol=1e-11, atol=1e-12)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
171
|
+
def test_carbon_decay_rejects_invalid(bad):
|
|
172
|
+
args = [2, 1, np.float64(0.6931471805599453)]
|
|
173
|
+
args[0] = bad
|
|
174
|
+
with pytest.raises(ValueError):
|
|
175
|
+
domain.carbon_decay(*args)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_thermal_expansion_sea_level_reference():
|
|
179
|
+
result = domain.thermal_expansion_sea_level(*[0.0002, 1000, 1])
|
|
180
|
+
np.testing.assert_allclose(result, 0.2, rtol=1e-11, atol=1e-12)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
184
|
+
def test_thermal_expansion_sea_level_rejects_invalid(bad):
|
|
185
|
+
args = [0.0002, 1000, 1]
|
|
186
|
+
args[0] = bad
|
|
187
|
+
with pytest.raises(ValueError):
|
|
188
|
+
domain.thermal_expansion_sea_level(*args)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_water_equivalent_sea_level_reference():
|
|
192
|
+
result = domain.water_equivalent_sea_level(*[1000000000000.0, 1000000000000.0, 1000])
|
|
193
|
+
np.testing.assert_allclose(result, 0.001, rtol=1e-11, atol=1e-12)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
197
|
+
def test_water_equivalent_sea_level_rejects_invalid(bad):
|
|
198
|
+
args = [1000000000000.0, 1000000000000.0, 1000]
|
|
199
|
+
args[0] = bad
|
|
200
|
+
with pytest.raises(ValueError):
|
|
201
|
+
domain.water_equivalent_sea_level(*args)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_absorbed_solar_broadcasting():
|
|
205
|
+
args = [1360, 0.3]
|
|
206
|
+
reference = domain.absorbed_solar(*args)
|
|
207
|
+
args[0] = np.full((2, 1), args[0])
|
|
208
|
+
result = domain.absorbed_solar(*args)
|
|
209
|
+
assert result.shape == (2, 1)
|
|
210
|
+
np.testing.assert_allclose(result, reference)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_absorbed_solar_solar_constant_physical_bound():
|
|
214
|
+
args = [1360, 0.3]
|
|
215
|
+
args[0] = -1
|
|
216
|
+
with pytest.raises(ValueError):
|
|
217
|
+
domain.absorbed_solar(*args)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def test_absorbed_solar_albedo_physical_bound():
|
|
221
|
+
args = [1360, 0.3]
|
|
222
|
+
args[1] = 1.1
|
|
223
|
+
with pytest.raises(ValueError):
|
|
224
|
+
domain.absorbed_solar(*args)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def test_effective_temperature_broadcasting():
|
|
228
|
+
args = [459.300327939, 1]
|
|
229
|
+
reference = domain.effective_temperature(*args)
|
|
230
|
+
args[0] = np.full((2, 1), args[0])
|
|
231
|
+
result = domain.effective_temperature(*args)
|
|
232
|
+
assert result.shape == (2, 1)
|
|
233
|
+
np.testing.assert_allclose(result, reference)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def test_effective_temperature_absorbed_flux_physical_bound():
|
|
237
|
+
args = [459.300327939, 1]
|
|
238
|
+
args[0] = -1
|
|
239
|
+
with pytest.raises(ValueError):
|
|
240
|
+
domain.effective_temperature(*args)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def test_effective_temperature_emissivity_physical_bound():
|
|
244
|
+
args = [459.300327939, 1]
|
|
245
|
+
args[1] = 0
|
|
246
|
+
with pytest.raises(ValueError):
|
|
247
|
+
domain.effective_temperature(*args)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_outgoing_longwave_broadcasting():
|
|
251
|
+
args = [300, 1]
|
|
252
|
+
reference = domain.outgoing_longwave(*args)
|
|
253
|
+
args[0] = np.full((2, 1), args[0])
|
|
254
|
+
result = domain.outgoing_longwave(*args)
|
|
255
|
+
assert result.shape == (2, 1)
|
|
256
|
+
np.testing.assert_allclose(result, reference)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_outgoing_longwave_temperature_physical_bound():
|
|
260
|
+
args = [300, 1]
|
|
261
|
+
args[0] = 0
|
|
262
|
+
with pytest.raises(ValueError):
|
|
263
|
+
domain.outgoing_longwave(*args)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_outgoing_longwave_emissivity_physical_bound():
|
|
267
|
+
args = [300, 1]
|
|
268
|
+
args[1] = 1.1
|
|
269
|
+
with pytest.raises(ValueError):
|
|
270
|
+
domain.outgoing_longwave(*args)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def test_planck_feedback_broadcasting():
|
|
274
|
+
args = [300, 1]
|
|
275
|
+
reference = domain.planck_feedback(*args)
|
|
276
|
+
args[0] = np.full((2, 1), args[0])
|
|
277
|
+
result = domain.planck_feedback(*args)
|
|
278
|
+
assert result.shape == (2, 1)
|
|
279
|
+
np.testing.assert_allclose(result, reference)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def test_planck_feedback_temperature_physical_bound():
|
|
283
|
+
args = [300, 1]
|
|
284
|
+
args[0] = 0
|
|
285
|
+
with pytest.raises(ValueError):
|
|
286
|
+
domain.planck_feedback(*args)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def test_planck_feedback_emissivity_physical_bound():
|
|
290
|
+
args = [300, 1]
|
|
291
|
+
args[1] = 0
|
|
292
|
+
with pytest.raises(ValueError):
|
|
293
|
+
domain.planck_feedback(*args)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def test_co2_forcing_broadcasting():
|
|
297
|
+
args = [560, 280, 5.35]
|
|
298
|
+
reference = domain.co2_forcing(*args)
|
|
299
|
+
args[0] = np.full((2, 1), args[0])
|
|
300
|
+
result = domain.co2_forcing(*args)
|
|
301
|
+
assert result.shape == (2, 1)
|
|
302
|
+
np.testing.assert_allclose(result, reference)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def test_co2_forcing_concentration_physical_bound():
|
|
306
|
+
args = [560, 280, 5.35]
|
|
307
|
+
args[0] = 0
|
|
308
|
+
with pytest.raises(ValueError):
|
|
309
|
+
domain.co2_forcing(*args)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def test_co2_forcing_reference_concentration_physical_bound():
|
|
313
|
+
args = [560, 280, 5.35]
|
|
314
|
+
args[1] = 0
|
|
315
|
+
with pytest.raises(ValueError):
|
|
316
|
+
domain.co2_forcing(*args)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def test_co2_forcing_coefficient_physical_bound():
|
|
320
|
+
args = [560, 280, 5.35]
|
|
321
|
+
args[2] = 0
|
|
322
|
+
with pytest.raises(ValueError):
|
|
323
|
+
domain.co2_forcing(*args)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def test_equilibrium_warming_broadcasting():
|
|
327
|
+
args = [4, 2]
|
|
328
|
+
reference = domain.equilibrium_warming(*args)
|
|
329
|
+
args[0] = np.full((2, 1), args[0])
|
|
330
|
+
result = domain.equilibrium_warming(*args)
|
|
331
|
+
assert result.shape == (2, 1)
|
|
332
|
+
np.testing.assert_allclose(result, reference)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def test_equilibrium_warming_restoring_physical_bound():
|
|
336
|
+
args = [4, 2]
|
|
337
|
+
args[1] = 0
|
|
338
|
+
with pytest.raises(ValueError):
|
|
339
|
+
domain.equilibrium_warming(*args)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def test_heat_capacity_column_broadcasting():
|
|
343
|
+
args = [1000, 4000, 50]
|
|
344
|
+
reference = domain.heat_capacity_column(*args)
|
|
345
|
+
args[0] = np.full((2, 1), args[0])
|
|
346
|
+
result = domain.heat_capacity_column(*args)
|
|
347
|
+
assert result.shape == (2, 1)
|
|
348
|
+
np.testing.assert_allclose(result, reference)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def test_heat_capacity_column_density_physical_bound():
|
|
352
|
+
args = [1000, 4000, 50]
|
|
353
|
+
args[0] = 0
|
|
354
|
+
with pytest.raises(ValueError):
|
|
355
|
+
domain.heat_capacity_column(*args)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def test_heat_capacity_column_heat_capacity_physical_bound():
|
|
359
|
+
args = [1000, 4000, 50]
|
|
360
|
+
args[1] = 0
|
|
361
|
+
with pytest.raises(ValueError):
|
|
362
|
+
domain.heat_capacity_column(*args)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def test_heat_capacity_column_depth_physical_bound():
|
|
366
|
+
args = [1000, 4000, 50]
|
|
367
|
+
args[2] = 0
|
|
368
|
+
with pytest.raises(ValueError):
|
|
369
|
+
domain.heat_capacity_column(*args)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def test_response_timescale_broadcasting():
|
|
373
|
+
args = [200000000.0, 2]
|
|
374
|
+
reference = domain.response_timescale(*args)
|
|
375
|
+
args[0] = np.full((2, 1), args[0])
|
|
376
|
+
result = domain.response_timescale(*args)
|
|
377
|
+
assert result.shape == (2, 1)
|
|
378
|
+
np.testing.assert_allclose(result, reference)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def test_response_timescale_areal_heat_capacity_physical_bound():
|
|
382
|
+
args = [200000000.0, 2]
|
|
383
|
+
args[0] = 0
|
|
384
|
+
with pytest.raises(ValueError):
|
|
385
|
+
domain.response_timescale(*args)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def test_response_timescale_restoring_physical_bound():
|
|
389
|
+
args = [200000000.0, 2]
|
|
390
|
+
args[1] = 0
|
|
391
|
+
with pytest.raises(ValueError):
|
|
392
|
+
domain.response_timescale(*args)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def test_step_temperature_broadcasting():
|
|
396
|
+
args = [4, 2, 2, 0]
|
|
397
|
+
reference = domain.step_temperature(*args)
|
|
398
|
+
args[0] = np.full((2, 1), args[0])
|
|
399
|
+
result = domain.step_temperature(*args)
|
|
400
|
+
assert result.shape == (2, 1)
|
|
401
|
+
np.testing.assert_allclose(result, reference)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def test_step_temperature_restoring_physical_bound():
|
|
405
|
+
args = [4, 2, 2, 0]
|
|
406
|
+
args[1] = 0
|
|
407
|
+
with pytest.raises(ValueError):
|
|
408
|
+
domain.step_temperature(*args)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def test_step_temperature_areal_heat_capacity_physical_bound():
|
|
412
|
+
args = [4, 2, 2, 0]
|
|
413
|
+
args[2] = 0
|
|
414
|
+
with pytest.raises(ValueError):
|
|
415
|
+
domain.step_temperature(*args)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def test_step_temperature_time_physical_bound():
|
|
419
|
+
args = [4, 2, 2, 0]
|
|
420
|
+
args[3] = -1
|
|
421
|
+
with pytest.raises(ValueError):
|
|
422
|
+
domain.step_temperature(*args)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def test_heat_uptake_broadcasting():
|
|
426
|
+
args = [200000000.0, 2]
|
|
427
|
+
reference = domain.heat_uptake(*args)
|
|
428
|
+
args[0] = np.full((2, 1), args[0])
|
|
429
|
+
result = domain.heat_uptake(*args)
|
|
430
|
+
assert result.shape == (2, 1)
|
|
431
|
+
np.testing.assert_allclose(result, reference)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def test_heat_uptake_areal_heat_capacity_physical_bound():
|
|
435
|
+
args = [200000000.0, 2]
|
|
436
|
+
args[0] = 0
|
|
437
|
+
with pytest.raises(ValueError):
|
|
438
|
+
domain.heat_uptake(*args)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def test_ice_albedo_broadcasting():
|
|
442
|
+
args = [273, 0.6, 0.2, 273, 5]
|
|
443
|
+
reference = domain.ice_albedo(*args)
|
|
444
|
+
args[0] = np.full((2, 1), args[0])
|
|
445
|
+
result = domain.ice_albedo(*args)
|
|
446
|
+
assert result.shape == (2, 1)
|
|
447
|
+
np.testing.assert_allclose(result, reference)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def test_ice_albedo_temperature_physical_bound():
|
|
451
|
+
args = [273, 0.6, 0.2, 273, 5]
|
|
452
|
+
args[0] = 0
|
|
453
|
+
with pytest.raises(ValueError):
|
|
454
|
+
domain.ice_albedo(*args)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def test_ice_albedo_cold_albedo_physical_bound():
|
|
458
|
+
args = [273, 0.6, 0.2, 273, 5]
|
|
459
|
+
args[1] = 1.1
|
|
460
|
+
with pytest.raises(ValueError):
|
|
461
|
+
domain.ice_albedo(*args)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def test_ice_albedo_warm_albedo_physical_bound():
|
|
465
|
+
args = [273, 0.6, 0.2, 273, 5]
|
|
466
|
+
args[2] = 1.1
|
|
467
|
+
with pytest.raises(ValueError):
|
|
468
|
+
domain.ice_albedo(*args)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def test_ice_albedo_transition_temperature_physical_bound():
|
|
472
|
+
args = [273, 0.6, 0.2, 273, 5]
|
|
473
|
+
args[3] = 0
|
|
474
|
+
with pytest.raises(ValueError):
|
|
475
|
+
domain.ice_albedo(*args)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def test_ice_albedo_width_physical_bound():
|
|
479
|
+
args = [273, 0.6, 0.2, 273, 5]
|
|
480
|
+
args[4] = 0
|
|
481
|
+
with pytest.raises(ValueError):
|
|
482
|
+
domain.ice_albedo(*args)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def test_carbon_airborne_fraction_broadcasting():
|
|
486
|
+
args = [4, 10]
|
|
487
|
+
reference = domain.carbon_airborne_fraction(*args)
|
|
488
|
+
args[0] = np.full((2, 1), args[0])
|
|
489
|
+
result = domain.carbon_airborne_fraction(*args)
|
|
490
|
+
assert result.shape == (2, 1)
|
|
491
|
+
np.testing.assert_allclose(result, reference)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def test_carbon_airborne_fraction_atmospheric_increase_physical_bound():
|
|
495
|
+
args = [4, 10]
|
|
496
|
+
args[0] = -1
|
|
497
|
+
with pytest.raises(ValueError):
|
|
498
|
+
domain.carbon_airborne_fraction(*args)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def test_carbon_airborne_fraction_emissions_physical_bound():
|
|
502
|
+
args = [4, 10]
|
|
503
|
+
args[1] = 0
|
|
504
|
+
with pytest.raises(ValueError):
|
|
505
|
+
domain.carbon_airborne_fraction(*args)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def test_carbon_decay_broadcasting():
|
|
509
|
+
args = [2, 1, 0.6931471805599453]
|
|
510
|
+
reference = domain.carbon_decay(*args)
|
|
511
|
+
args[0] = np.full((2, 1), args[0])
|
|
512
|
+
result = domain.carbon_decay(*args)
|
|
513
|
+
assert result.shape == (2, 1)
|
|
514
|
+
np.testing.assert_allclose(result, reference)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def test_carbon_decay_initial_excess_physical_bound():
|
|
518
|
+
args = [2, 1, 0.6931471805599453]
|
|
519
|
+
args[0] = -1
|
|
520
|
+
with pytest.raises(ValueError):
|
|
521
|
+
domain.carbon_decay(*args)
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def test_carbon_decay_timescale_physical_bound():
|
|
525
|
+
args = [2, 1, 0.6931471805599453]
|
|
526
|
+
args[1] = 0
|
|
527
|
+
with pytest.raises(ValueError):
|
|
528
|
+
domain.carbon_decay(*args)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def test_carbon_decay_time_physical_bound():
|
|
532
|
+
args = [2, 1, 0.6931471805599453]
|
|
533
|
+
args[2] = -1
|
|
534
|
+
with pytest.raises(ValueError):
|
|
535
|
+
domain.carbon_decay(*args)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def test_thermal_expansion_sea_level_broadcasting():
|
|
539
|
+
args = [0.0002, 1000, 1]
|
|
540
|
+
reference = domain.thermal_expansion_sea_level(*args)
|
|
541
|
+
args[0] = np.full((2, 1), args[0])
|
|
542
|
+
result = domain.thermal_expansion_sea_level(*args)
|
|
543
|
+
assert result.shape == (2, 1)
|
|
544
|
+
np.testing.assert_allclose(result, reference)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def test_thermal_expansion_sea_level_expansion_coefficient_physical_bound():
|
|
548
|
+
args = [0.0002, 1000, 1]
|
|
549
|
+
args[0] = 0
|
|
550
|
+
with pytest.raises(ValueError):
|
|
551
|
+
domain.thermal_expansion_sea_level(*args)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def test_thermal_expansion_sea_level_depth_physical_bound():
|
|
555
|
+
args = [0.0002, 1000, 1]
|
|
556
|
+
args[1] = 0
|
|
557
|
+
with pytest.raises(ValueError):
|
|
558
|
+
domain.thermal_expansion_sea_level(*args)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def test_water_equivalent_sea_level_broadcasting():
|
|
562
|
+
args = [1000000000000.0, 1000000000000.0, 1000]
|
|
563
|
+
reference = domain.water_equivalent_sea_level(*args)
|
|
564
|
+
args[0] = np.full((2, 1), args[0])
|
|
565
|
+
result = domain.water_equivalent_sea_level(*args)
|
|
566
|
+
assert result.shape == (2, 1)
|
|
567
|
+
np.testing.assert_allclose(result, reference)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def test_water_equivalent_sea_level_water_mass_physical_bound():
|
|
571
|
+
args = [1000000000000.0, 1000000000000.0, 1000]
|
|
572
|
+
args[0] = -1
|
|
573
|
+
with pytest.raises(ValueError):
|
|
574
|
+
domain.water_equivalent_sea_level(*args)
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def test_water_equivalent_sea_level_ocean_area_physical_bound():
|
|
578
|
+
args = [1000000000000.0, 1000000000000.0, 1000]
|
|
579
|
+
args[1] = 0
|
|
580
|
+
with pytest.raises(ValueError):
|
|
581
|
+
domain.water_equivalent_sea_level(*args)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def test_water_equivalent_sea_level_water_density_physical_bound():
|
|
585
|
+
args = [1000000000000.0, 1000000000000.0, 1000]
|
|
586
|
+
args[2] = 0
|
|
587
|
+
with pytest.raises(ValueError):
|
|
588
|
+
domain.water_equivalent_sea_level(*args)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def test_greenhouse_surface_temperature_reference():
|
|
592
|
+
result = domain.greenhouse_surface_temperature(*[459.300327939, 0])
|
|
593
|
+
np.testing.assert_allclose(result, 300, rtol=1e-11, atol=1e-12)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
597
|
+
def test_greenhouse_surface_temperature_rejects_invalid(bad):
|
|
598
|
+
args = [459.300327939, 0]
|
|
599
|
+
args[0] = bad
|
|
600
|
+
with pytest.raises(ValueError):
|
|
601
|
+
domain.greenhouse_surface_temperature(*args)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def test_greenhouse_surface_temperature_broadcasting():
|
|
605
|
+
args = [459.300327939, 0]
|
|
606
|
+
reference = domain.greenhouse_surface_temperature(*args)
|
|
607
|
+
args[0] = np.full((2, 1), args[0])
|
|
608
|
+
result = domain.greenhouse_surface_temperature(*args)
|
|
609
|
+
assert result.shape == (2, 1)
|
|
610
|
+
np.testing.assert_allclose(result, reference)
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def test_greenhouse_surface_temperature_absorbed_flux_physical_bound():
|
|
614
|
+
args = [459.300327939, 0]
|
|
615
|
+
args[0] = -1
|
|
616
|
+
with pytest.raises(ValueError):
|
|
617
|
+
domain.greenhouse_surface_temperature(*args)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def test_greenhouse_surface_temperature_layer_emissivity_physical_bound():
|
|
621
|
+
args = [459.300327939, 0]
|
|
622
|
+
args[1] = 1.1
|
|
623
|
+
with pytest.raises(ValueError):
|
|
624
|
+
domain.greenhouse_surface_temperature(*args)
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def test_daily_insolation_reference():
|
|
628
|
+
result = domain.daily_insolation(*[1361, 0, 0, 1])
|
|
629
|
+
np.testing.assert_allclose(result, 1361 / np.pi, rtol=1e-11, atol=1e-12)
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
633
|
+
def test_daily_insolation_rejects_invalid(bad):
|
|
634
|
+
args = [1361, 0, 0, 1]
|
|
635
|
+
args[0] = bad
|
|
636
|
+
with pytest.raises(ValueError):
|
|
637
|
+
domain.daily_insolation(*args)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def test_daily_insolation_broadcasting():
|
|
641
|
+
args = [1361, 0, 0, 1]
|
|
642
|
+
reference = domain.daily_insolation(*args)
|
|
643
|
+
args[0] = np.full((2, 1), args[0])
|
|
644
|
+
result = domain.daily_insolation(*args)
|
|
645
|
+
assert result.shape == (2, 1)
|
|
646
|
+
np.testing.assert_allclose(result, reference)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def test_daily_insolation_solar_constant_physical_bound():
|
|
650
|
+
args = [1361, 0, 0, 1]
|
|
651
|
+
args[0] = -1
|
|
652
|
+
with pytest.raises(ValueError):
|
|
653
|
+
domain.daily_insolation(*args)
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def test_daily_insolation_latitude_physical_bound():
|
|
657
|
+
args = [1361, 0, 0, 1]
|
|
658
|
+
args[1] = 2
|
|
659
|
+
with pytest.raises(ValueError):
|
|
660
|
+
domain.daily_insolation(*args)
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def test_daily_insolation_declination_physical_bound():
|
|
664
|
+
args = [1361, 0, 0, 1]
|
|
665
|
+
args[2] = 2
|
|
666
|
+
with pytest.raises(ValueError):
|
|
667
|
+
domain.daily_insolation(*args)
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def test_daily_insolation_distance_ratio_physical_bound():
|
|
671
|
+
args = [1361, 0, 0, 1]
|
|
672
|
+
args[3] = 0
|
|
673
|
+
with pytest.raises(ValueError):
|
|
674
|
+
domain.daily_insolation(*args)
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def test_radiative_convective_temperature_reference():
|
|
678
|
+
result = domain.radiative_convective_temperature(*[0, 255, 0.0065, 5000])
|
|
679
|
+
np.testing.assert_allclose(result, 287.5, rtol=1e-11, atol=1e-12)
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
683
|
+
def test_radiative_convective_temperature_rejects_invalid(bad):
|
|
684
|
+
args = [0, 255, 0.0065, 5000]
|
|
685
|
+
args[0] = bad
|
|
686
|
+
with pytest.raises(ValueError):
|
|
687
|
+
domain.radiative_convective_temperature(*args)
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def test_radiative_convective_temperature_broadcasting():
|
|
691
|
+
args = [0, 255, 0.0065, 5000]
|
|
692
|
+
reference = domain.radiative_convective_temperature(*args)
|
|
693
|
+
args[0] = np.full((2, 1), args[0])
|
|
694
|
+
result = domain.radiative_convective_temperature(*args)
|
|
695
|
+
assert result.shape == (2, 1)
|
|
696
|
+
np.testing.assert_allclose(result, reference)
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def test_radiative_convective_temperature_height_physical_bound():
|
|
700
|
+
args = [0, 255, 0.0065, 5000]
|
|
701
|
+
args[0] = -1
|
|
702
|
+
with pytest.raises(ValueError):
|
|
703
|
+
domain.radiative_convective_temperature(*args)
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def test_radiative_convective_temperature_effective_temperature_physical_bound():
|
|
707
|
+
args = [0, 255, 0.0065, 5000]
|
|
708
|
+
args[1] = 0
|
|
709
|
+
with pytest.raises(ValueError):
|
|
710
|
+
domain.radiative_convective_temperature(*args)
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def test_radiative_convective_temperature_lapse_rate_physical_bound():
|
|
714
|
+
args = [0, 255, 0.0065, 5000]
|
|
715
|
+
args[2] = 0
|
|
716
|
+
with pytest.raises(ValueError):
|
|
717
|
+
domain.radiative_convective_temperature(*args)
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def test_radiative_convective_temperature_emission_height_physical_bound():
|
|
721
|
+
args = [0, 255, 0.0065, 5000]
|
|
722
|
+
args[3] = -1
|
|
723
|
+
with pytest.raises(ValueError):
|
|
724
|
+
domain.radiative_convective_temperature(*args)
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def test_equilibrium_solar_constant_reference():
|
|
728
|
+
result = domain.equilibrium_solar_constant(*[288, 1, 0.6, 0.2, 288, 5])
|
|
729
|
+
np.testing.assert_allclose(result, 4 * 5.670374419e-8 * 288**4 / 0.6, rtol=1e-11, atol=1e-12)
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
733
|
+
def test_equilibrium_solar_constant_rejects_invalid(bad):
|
|
734
|
+
args = [288, 1, 0.6, 0.2, 288, 5]
|
|
735
|
+
args[0] = bad
|
|
736
|
+
with pytest.raises(ValueError):
|
|
737
|
+
domain.equilibrium_solar_constant(*args)
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
def test_equilibrium_solar_constant_broadcasting():
|
|
741
|
+
args = [288, 1, 0.6, 0.2, 288, 5]
|
|
742
|
+
reference = domain.equilibrium_solar_constant(*args)
|
|
743
|
+
args[0] = np.full((2, 1), args[0])
|
|
744
|
+
result = domain.equilibrium_solar_constant(*args)
|
|
745
|
+
assert result.shape == (2, 1)
|
|
746
|
+
np.testing.assert_allclose(result, reference)
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
def test_equilibrium_solar_constant_temperature_physical_bound():
|
|
750
|
+
args = [288, 1, 0.6, 0.2, 288, 5]
|
|
751
|
+
args[0] = 0
|
|
752
|
+
with pytest.raises(ValueError):
|
|
753
|
+
domain.equilibrium_solar_constant(*args)
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def test_equilibrium_solar_constant_emissivity_physical_bound():
|
|
757
|
+
args = [288, 1, 0.6, 0.2, 288, 5]
|
|
758
|
+
args[1] = 0
|
|
759
|
+
with pytest.raises(ValueError):
|
|
760
|
+
domain.equilibrium_solar_constant(*args)
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
def test_equilibrium_solar_constant_cold_albedo_physical_bound():
|
|
764
|
+
args = [288, 1, 0.6, 0.2, 288, 5]
|
|
765
|
+
args[2] = 1
|
|
766
|
+
with pytest.raises(ValueError):
|
|
767
|
+
domain.equilibrium_solar_constant(*args)
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
def test_equilibrium_solar_constant_warm_albedo_physical_bound():
|
|
771
|
+
args = [288, 1, 0.6, 0.2, 288, 5]
|
|
772
|
+
args[3] = -0.1
|
|
773
|
+
with pytest.raises(ValueError):
|
|
774
|
+
domain.equilibrium_solar_constant(*args)
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
def test_equilibrium_solar_constant_transition_temperature_physical_bound():
|
|
778
|
+
args = [288, 1, 0.6, 0.2, 288, 5]
|
|
779
|
+
args[4] = 0
|
|
780
|
+
with pytest.raises(ValueError):
|
|
781
|
+
domain.equilibrium_solar_constant(*args)
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
def test_equilibrium_solar_constant_width_physical_bound():
|
|
785
|
+
args = [288, 1, 0.6, 0.2, 288, 5]
|
|
786
|
+
args[5] = 0
|
|
787
|
+
with pytest.raises(ValueError):
|
|
788
|
+
domain.equilibrium_solar_constant(*args)
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def test_red_noise_spectrum_reference():
|
|
792
|
+
result = domain.red_noise_spectrum(*[0, 4, 2, 1])
|
|
793
|
+
np.testing.assert_allclose(result, 1, rtol=1e-11, atol=1e-12)
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
797
|
+
def test_red_noise_spectrum_rejects_invalid(bad):
|
|
798
|
+
args = [0, 4, 2, 1]
|
|
799
|
+
args[0] = bad
|
|
800
|
+
with pytest.raises(ValueError):
|
|
801
|
+
domain.red_noise_spectrum(*args)
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
def test_red_noise_spectrum_broadcasting():
|
|
805
|
+
args = [0, 4, 2, 1]
|
|
806
|
+
reference = domain.red_noise_spectrum(*args)
|
|
807
|
+
args[0] = np.full((2, 1), args[0])
|
|
808
|
+
result = domain.red_noise_spectrum(*args)
|
|
809
|
+
assert result.shape == (2, 1)
|
|
810
|
+
np.testing.assert_allclose(result, reference)
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
def test_red_noise_spectrum_forcing_spectrum_physical_bound():
|
|
814
|
+
args = [0, 4, 2, 1]
|
|
815
|
+
args[1] = -1
|
|
816
|
+
with pytest.raises(ValueError):
|
|
817
|
+
domain.red_noise_spectrum(*args)
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
def test_red_noise_spectrum_restoring_physical_bound():
|
|
821
|
+
args = [0, 4, 2, 1]
|
|
822
|
+
args[2] = 0
|
|
823
|
+
with pytest.raises(ValueError):
|
|
824
|
+
domain.red_noise_spectrum(*args)
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
def test_red_noise_spectrum_areal_heat_capacity_physical_bound():
|
|
828
|
+
args = [0, 4, 2, 1]
|
|
829
|
+
args[3] = 0
|
|
830
|
+
with pytest.raises(ValueError):
|
|
831
|
+
domain.red_noise_spectrum(*args)
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def test_methane_forcing_reference():
|
|
835
|
+
result = domain.methane_forcing(*[900, 400, 1e-16])
|
|
836
|
+
np.testing.assert_allclose(result, 0.36, rtol=1e-11, atol=1e-12)
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
840
|
+
def test_methane_forcing_rejects_invalid(bad):
|
|
841
|
+
args = [900, 400, 1e-16]
|
|
842
|
+
args[0] = bad
|
|
843
|
+
with pytest.raises(ValueError):
|
|
844
|
+
domain.methane_forcing(*args)
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
def test_methane_forcing_broadcasting():
|
|
848
|
+
args = [900, 400, 1e-16]
|
|
849
|
+
reference = domain.methane_forcing(*args)
|
|
850
|
+
args[0] = np.full((2, 1), args[0])
|
|
851
|
+
result = domain.methane_forcing(*args)
|
|
852
|
+
assert result.shape == (2, 1)
|
|
853
|
+
np.testing.assert_allclose(result, reference)
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
def test_methane_forcing_concentration_physical_bound():
|
|
857
|
+
args = [900, 400, 1e-16]
|
|
858
|
+
args[0] = 0
|
|
859
|
+
with pytest.raises(ValueError):
|
|
860
|
+
domain.methane_forcing(*args)
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def test_methane_forcing_reference_concentration_physical_bound():
|
|
864
|
+
args = [900, 400, 1e-16]
|
|
865
|
+
args[1] = 0
|
|
866
|
+
with pytest.raises(ValueError):
|
|
867
|
+
domain.methane_forcing(*args)
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
def test_methane_forcing_nitrous_oxide_physical_bound():
|
|
871
|
+
args = [900, 400, 1e-16]
|
|
872
|
+
args[2] = 0
|
|
873
|
+
with pytest.raises(ValueError):
|
|
874
|
+
domain.methane_forcing(*args)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
def test_nitrous_oxide_forcing_reference():
|
|
878
|
+
result = domain.nitrous_oxide_forcing(*[400, 100, 1e-16])
|
|
879
|
+
np.testing.assert_allclose(result, 1.2, rtol=1e-11, atol=1e-12)
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
883
|
+
def test_nitrous_oxide_forcing_rejects_invalid(bad):
|
|
884
|
+
args = [400, 100, 1e-16]
|
|
885
|
+
args[0] = bad
|
|
886
|
+
with pytest.raises(ValueError):
|
|
887
|
+
domain.nitrous_oxide_forcing(*args)
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def test_nitrous_oxide_forcing_broadcasting():
|
|
891
|
+
args = [400, 100, 1e-16]
|
|
892
|
+
reference = domain.nitrous_oxide_forcing(*args)
|
|
893
|
+
args[0] = np.full((2, 1), args[0])
|
|
894
|
+
result = domain.nitrous_oxide_forcing(*args)
|
|
895
|
+
assert result.shape == (2, 1)
|
|
896
|
+
np.testing.assert_allclose(result, reference)
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def test_nitrous_oxide_forcing_concentration_physical_bound():
|
|
900
|
+
args = [400, 100, 1e-16]
|
|
901
|
+
args[0] = 0
|
|
902
|
+
with pytest.raises(ValueError):
|
|
903
|
+
domain.nitrous_oxide_forcing(*args)
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
def test_nitrous_oxide_forcing_reference_concentration_physical_bound():
|
|
907
|
+
args = [400, 100, 1e-16]
|
|
908
|
+
args[1] = 0
|
|
909
|
+
with pytest.raises(ValueError):
|
|
910
|
+
domain.nitrous_oxide_forcing(*args)
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
def test_nitrous_oxide_forcing_methane_physical_bound():
|
|
914
|
+
args = [400, 100, 1e-16]
|
|
915
|
+
args[2] = 0
|
|
916
|
+
with pytest.raises(ValueError):
|
|
917
|
+
domain.nitrous_oxide_forcing(*args)
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
def test_gregory_regression_reference():
|
|
921
|
+
result = domain.gregory_regression([0, 1, 2], [4, 2.8, 1.6])
|
|
922
|
+
np.testing.assert_allclose(result, [4, 1.2], rtol=1e-11, atol=1e-12)
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
@pytest.mark.parametrize("bad", [np.nan, np.inf, -np.inf, [], "bad", True, 1j])
|
|
926
|
+
def test_gregory_regression_rejects_invalid(bad):
|
|
927
|
+
with pytest.raises(ValueError):
|
|
928
|
+
domain.gregory_regression(bad, [4, 2.8, 1.6])
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
@pytest.mark.parametrize(
|
|
932
|
+
"temperature, imbalance",
|
|
933
|
+
[(1.0, 1.0), ([1], [1]), ([0, 1], [1, 2, 3]), ([[0, 1]], [[1, 2]]), ([2, 2, 2], [1, 2, 3])],
|
|
934
|
+
)
|
|
935
|
+
def test_gregory_regression_rejects_bad_shapes(temperature, imbalance):
|
|
936
|
+
with pytest.raises(ValueError):
|
|
937
|
+
domain.gregory_regression(temperature, imbalance)
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
def test_greenhouse_layer_balances_top_of_atmosphere():
|
|
941
|
+
absorbed, emissivity = 240.0, np.linspace(0, 1, 11)
|
|
942
|
+
surface = domain.greenhouse_surface_temperature(absorbed, emissivity)
|
|
943
|
+
layer = surface / 2**0.25
|
|
944
|
+
outgoing = (1 - emissivity) * domain.outgoing_longwave(surface, 1) + domain.outgoing_longwave(layer, emissivity)
|
|
945
|
+
np.testing.assert_allclose(outgoing, absorbed)
|
|
946
|
+
np.testing.assert_allclose(surface[-1], 2**0.25 * domain.effective_temperature(absorbed, 1))
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
@pytest.mark.parametrize("declination", [0.0, 0.2, -0.41])
|
|
950
|
+
def test_daily_insolation_global_mean_is_quarter_solar_constant(declination):
|
|
951
|
+
latitude = np.linspace(-np.pi / 2, np.pi / 2, 40001)
|
|
952
|
+
flux = domain.daily_insolation(1361, latitude, declination, 1)
|
|
953
|
+
assert np.isclose(np.trapezoid(flux * np.cos(latitude), latitude) / 2, 1361 / 4, rtol=1e-7)
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
def test_daily_insolation_polar_day_night_and_distance():
|
|
957
|
+
assert np.isclose(domain.daily_insolation(1361, np.pi / 2, 0.4, 1), 1361 * np.sin(0.4))
|
|
958
|
+
assert domain.daily_insolation(1361, -np.pi / 2, 0.4, 1) == 0
|
|
959
|
+
assert np.isclose(domain.daily_insolation(1361, 0.3, 0.1, 1.02), 1.02**2 * domain.daily_insolation(1361, 0.3, 0.1, 1))
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
@pytest.mark.parametrize("position, value", [(1, 2), (2, -2)])
|
|
963
|
+
def test_daily_insolation_angle_upper_bounds(position, value):
|
|
964
|
+
args = [1361, 0, 0, 1]
|
|
965
|
+
args[position] = value
|
|
966
|
+
with pytest.raises(ValueError):
|
|
967
|
+
domain.daily_insolation(*args)
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
def test_radiative_convective_profile_structure():
|
|
971
|
+
height = np.linspace(0, 30e3, 3001)
|
|
972
|
+
profile = domain.radiative_convective_temperature(height, 255, 6.5e-3, 5e3)
|
|
973
|
+
assert np.isclose(domain.radiative_convective_temperature(5e3, 255, 6.5e-3, 5e3), 255)
|
|
974
|
+
assert np.isclose(profile[0], 255 + 6.5e-3 * 5e3)
|
|
975
|
+
np.testing.assert_allclose(profile[-1], 255 / 2**0.25)
|
|
976
|
+
tropopause = (profile[0] - 255 / 2**0.25) / 6.5e-3
|
|
977
|
+
np.testing.assert_allclose(profile[height < tropopause], profile[0] - 6.5e-3 * height[height < tropopause])
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
def test_equilibrium_solar_constant_closes_energy_balance():
|
|
981
|
+
temperature = np.linspace(220, 320, 101)
|
|
982
|
+
solar = domain.equilibrium_solar_constant(temperature, 0.62, 0.62, 0.3, 265, 8)
|
|
983
|
+
albedo = domain.ice_albedo(temperature, 0.62, 0.3, 265, 8)
|
|
984
|
+
np.testing.assert_allclose(domain.absorbed_solar(solar, albedo), domain.outgoing_longwave(temperature, 0.62))
|
|
985
|
+
assert np.any(np.diff(solar) < 0), "the ice-albedo feedback must create an unstable branch"
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
@pytest.mark.parametrize("cold, warm", [(1, 0.3), (0.2, 0.3)])
|
|
989
|
+
def test_equilibrium_solar_constant_rejects_albedos(cold, warm):
|
|
990
|
+
with pytest.raises(ValueError):
|
|
991
|
+
domain.equilibrium_solar_constant(288, 1, cold, warm, 288, 5)
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
def test_red_noise_spectrum_integrates_to_variance():
|
|
995
|
+
from scipy.integrate import quad
|
|
996
|
+
|
|
997
|
+
forcing, restoring, capacity = 3.0, 1.2, 2e8
|
|
998
|
+
corner = restoring / (2 * np.pi * capacity)
|
|
999
|
+
integral, _ = quad(lambda f: domain.red_noise_spectrum(f, forcing, restoring, capacity), -np.inf, np.inf, points=None, limit=200)
|
|
1000
|
+
assert np.isclose(integral, forcing / (2 * restoring * capacity), rtol=1e-8)
|
|
1001
|
+
assert np.isclose(domain.red_noise_spectrum(corner, forcing, restoring, capacity), forcing / (2 * restoring**2))
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
def test_myhre_forcings_match_ipcc_tar():
|
|
1005
|
+
assert np.isclose(domain.methane_forcing(1745, 700, 270), 0.48, atol=0.005)
|
|
1006
|
+
assert np.isclose(domain.nitrous_oxide_forcing(314, 270, 700), 0.15, atol=0.005)
|
|
1007
|
+
assert domain.methane_forcing(1745, 1745, 314) == 0
|
|
1008
|
+
assert domain.nitrous_oxide_forcing(314, 314, 1745) == 0
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
def test_gregory_regression_recovers_one_box_parameters():
|
|
1012
|
+
time = np.linspace(0, 150, 151) * 365.25 * 86400
|
|
1013
|
+
temperature = domain.simulate_one_box(7.4, 1.1, 3e8, time).values[:, 0]
|
|
1014
|
+
forcing, restoring = domain.gregory_regression(temperature, 7.4 - 1.1 * temperature)
|
|
1015
|
+
np.testing.assert_allclose([forcing, restoring], [7.4, 1.1])
|