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,402 @@
|
|
|
1
|
+
"""Climate response: feedbacks, heat capacity, transient and stochastic one-box response, ice-albedo equilibria and the Gregory regression.
|
|
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 SIGMA_SB
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"planck_feedback",
|
|
13
|
+
"equilibrium_warming",
|
|
14
|
+
"heat_capacity_column",
|
|
15
|
+
"response_timescale",
|
|
16
|
+
"step_temperature",
|
|
17
|
+
"heat_uptake",
|
|
18
|
+
"ice_albedo",
|
|
19
|
+
"equilibrium_solar_constant",
|
|
20
|
+
"red_noise_spectrum",
|
|
21
|
+
"gregory_regression",
|
|
22
|
+
"simulate_one_box",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@model
|
|
27
|
+
def planck_feedback(temperature, emissivity):
|
|
28
|
+
"""Blackbody restoring derivative.
|
|
29
|
+
|
|
30
|
+
The temperature derivative of emission supplies a radiative restoring coefficient.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
temperature : float or array_like
|
|
35
|
+
K; constraint: positive.
|
|
36
|
+
emissivity : float or array_like
|
|
37
|
+
1; constraint: open fraction.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
numpy scalar or ndarray
|
|
42
|
+
Model value [W/m^2]. Scalar input returns a NumPy scalar; arrays broadcast.
|
|
43
|
+
|
|
44
|
+
Notes
|
|
45
|
+
-----
|
|
46
|
+
Positive restoring magnitude in W per square metre per kelvin; other feedbacks are separate.
|
|
47
|
+
Equation: ``4 * emissivity * SIGMA_SB * temperature**3``.
|
|
48
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
49
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
50
|
+
"""
|
|
51
|
+
temperature = quantity("temperature", temperature, lower=0, strict_lower=True)
|
|
52
|
+
emissivity = quantity("emissivity", emissivity, lower=0, upper=1, strict_lower=True)
|
|
53
|
+
return 4 * emissivity * SIGMA_SB * temperature**3
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@model
|
|
57
|
+
def equilibrium_warming(forcing, restoring):
|
|
58
|
+
"""Linear climate response.
|
|
59
|
+
|
|
60
|
+
A restoring feedback maps steady forcing to equilibrium temperature change.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
forcing : float or array_like
|
|
65
|
+
W/m^2; constraint: real.
|
|
66
|
+
restoring : float or array_like
|
|
67
|
+
W/(m^2 K); constraint: positive.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
numpy scalar or ndarray
|
|
72
|
+
Model value [W/m^2]. Scalar input returns a NumPy scalar; arrays broadcast.
|
|
73
|
+
|
|
74
|
+
Notes
|
|
75
|
+
-----
|
|
76
|
+
Stable linear response with a positive net restoring coefficient.
|
|
77
|
+
Equation: ``forcing / restoring``.
|
|
78
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
79
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
80
|
+
"""
|
|
81
|
+
forcing = quantity("forcing", forcing)
|
|
82
|
+
restoring = quantity("restoring", restoring, lower=0, strict_lower=True)
|
|
83
|
+
return forcing / restoring
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@model
|
|
87
|
+
def heat_capacity_column(density, heat_capacity, depth):
|
|
88
|
+
"""Mixed-layer areal heat capacity.
|
|
89
|
+
|
|
90
|
+
Mixed-layer depth determines how much energy is needed for warming.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
density : float or array_like
|
|
95
|
+
kg/m^3; constraint: positive.
|
|
96
|
+
heat_capacity : float or array_like
|
|
97
|
+
J/(kg K); constraint: positive.
|
|
98
|
+
depth : float or array_like
|
|
99
|
+
m; constraint: positive.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
numpy scalar or ndarray
|
|
104
|
+
Model value [W/m^2]. Scalar input returns a NumPy scalar; arrays broadcast.
|
|
105
|
+
|
|
106
|
+
Notes
|
|
107
|
+
-----
|
|
108
|
+
Uniform slab with constant material properties.
|
|
109
|
+
Equation: ``density * heat_capacity * depth``.
|
|
110
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
111
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
112
|
+
"""
|
|
113
|
+
density = quantity("density", density, lower=0, strict_lower=True)
|
|
114
|
+
heat_capacity = quantity("heat_capacity", heat_capacity, lower=0, strict_lower=True)
|
|
115
|
+
depth = quantity("depth", depth, lower=0, strict_lower=True)
|
|
116
|
+
return density * heat_capacity * depth
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@model
|
|
120
|
+
def response_timescale(areal_heat_capacity, restoring):
|
|
121
|
+
"""Climate relaxation time.
|
|
122
|
+
|
|
123
|
+
Heat storage delays the equilibrium response to forcing.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
areal_heat_capacity : float or array_like
|
|
128
|
+
J/(m^2 K); constraint: positive.
|
|
129
|
+
restoring : float or array_like
|
|
130
|
+
W/(m^2 K); constraint: positive.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
numpy scalar or ndarray
|
|
135
|
+
Model value [W/m^2]. Scalar input returns a NumPy scalar; arrays broadcast.
|
|
136
|
+
|
|
137
|
+
Notes
|
|
138
|
+
-----
|
|
139
|
+
One-box linear model; output is seconds.
|
|
140
|
+
Equation: ``areal_heat_capacity / restoring``.
|
|
141
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
142
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
143
|
+
"""
|
|
144
|
+
areal_heat_capacity = quantity("areal_heat_capacity", areal_heat_capacity, lower=0, strict_lower=True)
|
|
145
|
+
restoring = quantity("restoring", restoring, lower=0, strict_lower=True)
|
|
146
|
+
return areal_heat_capacity / restoring
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@model
|
|
150
|
+
def step_temperature(forcing, restoring, areal_heat_capacity, time):
|
|
151
|
+
"""Transient one-box warming.
|
|
152
|
+
|
|
153
|
+
A step forcing exposes the lag between forcing and temperature adjustment.
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
forcing : float or array_like
|
|
158
|
+
W/m^2; constraint: real.
|
|
159
|
+
restoring : float or array_like
|
|
160
|
+
W/(m^2 K); constraint: positive.
|
|
161
|
+
areal_heat_capacity : float or array_like
|
|
162
|
+
J/(m^2 K); constraint: positive.
|
|
163
|
+
time : float or array_like
|
|
164
|
+
s; constraint: nonnegative.
|
|
165
|
+
|
|
166
|
+
Returns
|
|
167
|
+
-------
|
|
168
|
+
numpy scalar or ndarray
|
|
169
|
+
Model value [W/m^2]. Scalar input returns a NumPy scalar; arrays broadcast.
|
|
170
|
+
|
|
171
|
+
Notes
|
|
172
|
+
-----
|
|
173
|
+
Zero initial anomaly and constant parameters; no deep-ocean exchange.
|
|
174
|
+
Equation: ``forcing / restoring * (-np.expm1(-restoring * time / areal_heat_capacity))``.
|
|
175
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
176
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
177
|
+
"""
|
|
178
|
+
forcing = quantity("forcing", forcing)
|
|
179
|
+
restoring = quantity("restoring", restoring, lower=0, strict_lower=True)
|
|
180
|
+
areal_heat_capacity = quantity("areal_heat_capacity", areal_heat_capacity, lower=0, strict_lower=True)
|
|
181
|
+
time = quantity("time", time, lower=0)
|
|
182
|
+
return forcing / restoring * (-np.expm1(-restoring * time / areal_heat_capacity))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@model
|
|
186
|
+
def heat_uptake(areal_heat_capacity, temperature_change):
|
|
187
|
+
"""Areal heat-content change.
|
|
188
|
+
|
|
189
|
+
Energy accounting connects warming to ocean heat storage.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
areal_heat_capacity : float or array_like
|
|
194
|
+
J/(m^2 K); constraint: positive.
|
|
195
|
+
temperature_change : float or array_like
|
|
196
|
+
K; constraint: real.
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
numpy scalar or ndarray
|
|
201
|
+
Model value [W/m^2]. Scalar input returns a NumPy scalar; arrays broadcast.
|
|
202
|
+
|
|
203
|
+
Notes
|
|
204
|
+
-----
|
|
205
|
+
One uniform layer; temperature_change is an anomaly.
|
|
206
|
+
Equation: ``areal_heat_capacity * temperature_change``.
|
|
207
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
208
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
209
|
+
"""
|
|
210
|
+
areal_heat_capacity = quantity("areal_heat_capacity", areal_heat_capacity, lower=0, strict_lower=True)
|
|
211
|
+
temperature_change = quantity("temperature_change", temperature_change)
|
|
212
|
+
return areal_heat_capacity * temperature_change
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@model
|
|
216
|
+
def ice_albedo(temperature, cold_albedo, warm_albedo, transition_temperature, width):
|
|
217
|
+
"""Smooth illustrative ice-albedo transition.
|
|
218
|
+
|
|
219
|
+
Temperature-dependent reflection illustrates ice-albedo feedback.
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
temperature : float or array_like
|
|
224
|
+
K; constraint: positive.
|
|
225
|
+
cold_albedo : float or array_like
|
|
226
|
+
1; constraint: fraction.
|
|
227
|
+
warm_albedo : float or array_like
|
|
228
|
+
1; constraint: fraction.
|
|
229
|
+
transition_temperature : float or array_like
|
|
230
|
+
K; constraint: positive.
|
|
231
|
+
width : float or array_like
|
|
232
|
+
K; constraint: positive.
|
|
233
|
+
|
|
234
|
+
Returns
|
|
235
|
+
-------
|
|
236
|
+
numpy scalar or ndarray
|
|
237
|
+
Model value [W/m^2]. Scalar input returns a NumPy scalar; arrays broadcast.
|
|
238
|
+
|
|
239
|
+
Notes
|
|
240
|
+
-----
|
|
241
|
+
Illustrative smooth closure, not a calibrated satellite albedo retrieval.
|
|
242
|
+
Equation: ``warm_albedo + (cold_albedo - warm_albedo) * (1 - np.tanh((temperature - transition_temperature) / width)) / 2``.
|
|
243
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
244
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
245
|
+
"""
|
|
246
|
+
temperature = quantity("temperature", temperature, lower=0, strict_lower=True)
|
|
247
|
+
cold_albedo = quantity("cold_albedo", cold_albedo, lower=0, upper=1)
|
|
248
|
+
warm_albedo = quantity("warm_albedo", warm_albedo, lower=0, upper=1)
|
|
249
|
+
transition_temperature = quantity("transition_temperature", transition_temperature, lower=0, strict_lower=True)
|
|
250
|
+
width = quantity("width", width, lower=0, strict_lower=True)
|
|
251
|
+
require(cold_albedo >= warm_albedo, "cold albedo must be at least warm albedo")
|
|
252
|
+
return warm_albedo + (cold_albedo - warm_albedo) * (1 - np.tanh((temperature - transition_temperature) / width)) / 2
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@model
|
|
256
|
+
def equilibrium_solar_constant(temperature, emissivity, cold_albedo, warm_albedo, transition_temperature, width):
|
|
257
|
+
"""Solar constant that holds a planet with ice-albedo feedback at a given temperature.
|
|
258
|
+
|
|
259
|
+
Inverting the energy balance traces every equilibrium, including the unstable branch between a warm and an ice-covered state.
|
|
260
|
+
|
|
261
|
+
Parameters
|
|
262
|
+
----------
|
|
263
|
+
temperature : float or array_like
|
|
264
|
+
K; constraint: positive.
|
|
265
|
+
emissivity : float or array_like
|
|
266
|
+
1; constraint: open fraction.
|
|
267
|
+
cold_albedo : float or array_like
|
|
268
|
+
1; constraint: fraction below one.
|
|
269
|
+
warm_albedo : float or array_like
|
|
270
|
+
1; constraint: fraction.
|
|
271
|
+
transition_temperature : float or array_like
|
|
272
|
+
K; constraint: positive.
|
|
273
|
+
width : float or array_like
|
|
274
|
+
K; constraint: positive.
|
|
275
|
+
|
|
276
|
+
Returns
|
|
277
|
+
-------
|
|
278
|
+
numpy scalar or ndarray
|
|
279
|
+
Model value [W/m^2]. Scalar input returns a NumPy scalar; arrays broadcast.
|
|
280
|
+
|
|
281
|
+
Notes
|
|
282
|
+
-----
|
|
283
|
+
Zero-dimensional greybody balance with the ice_albedo closure; equilibria with a negative slope dS/dT are unstable.
|
|
284
|
+
Equation: ``4 * emissivity * SIGMA_SB * temperature**4 / (1 - albedo)``, with ``albedo`` from ice_albedo.
|
|
285
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
286
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
287
|
+
"""
|
|
288
|
+
emissivity = quantity("emissivity", emissivity, lower=0, upper=1, strict_lower=True)
|
|
289
|
+
require(quantity("cold_albedo", cold_albedo) < 1, "cold albedo must be below one")
|
|
290
|
+
albedo = ice_albedo(temperature, cold_albedo, warm_albedo, transition_temperature, width)
|
|
291
|
+
return 4 * emissivity * SIGMA_SB * quantity("temperature", temperature) ** 4 / (1 - albedo)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@model
|
|
295
|
+
def red_noise_spectrum(frequency, forcing_spectrum, restoring, areal_heat_capacity):
|
|
296
|
+
"""Temperature spectrum of a one-box climate driven by white noise.
|
|
297
|
+
|
|
298
|
+
Heat capacity integrates random weather forcing into slow red-noise climate variability.
|
|
299
|
+
|
|
300
|
+
Parameters
|
|
301
|
+
----------
|
|
302
|
+
frequency : float or array_like
|
|
303
|
+
Hz; constraint: real.
|
|
304
|
+
forcing_spectrum : float or array_like
|
|
305
|
+
W^2/(m^4 Hz), two-sided; constraint: nonnegative.
|
|
306
|
+
restoring : float or array_like
|
|
307
|
+
W/(m^2 K); constraint: positive.
|
|
308
|
+
areal_heat_capacity : float or array_like
|
|
309
|
+
J/(m^2 K); constraint: positive.
|
|
310
|
+
|
|
311
|
+
Returns
|
|
312
|
+
-------
|
|
313
|
+
numpy scalar or ndarray
|
|
314
|
+
Model value [K^2/Hz], two-sided. Scalar input returns a NumPy scalar; arrays broadcast.
|
|
315
|
+
|
|
316
|
+
Notes
|
|
317
|
+
-----
|
|
318
|
+
Linear C dT/dt = F - lambda T with white-noise F; integrating over all frequencies gives the
|
|
319
|
+
variance forcing_spectrum / (2 * restoring * areal_heat_capacity).
|
|
320
|
+
Equation: ``forcing_spectrum / (restoring**2 + (2 * np.pi * frequency * areal_heat_capacity)**2)``.
|
|
321
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
322
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
323
|
+
"""
|
|
324
|
+
frequency = quantity("frequency", frequency)
|
|
325
|
+
forcing_spectrum = quantity("forcing_spectrum", forcing_spectrum, lower=0)
|
|
326
|
+
restoring = quantity("restoring", restoring, lower=0, strict_lower=True)
|
|
327
|
+
areal_heat_capacity = quantity("areal_heat_capacity", areal_heat_capacity, lower=0, strict_lower=True)
|
|
328
|
+
return forcing_spectrum / (restoring**2 + (2 * np.pi * frequency * areal_heat_capacity) ** 2)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@model
|
|
332
|
+
def gregory_regression(temperature_change, imbalance):
|
|
333
|
+
"""Forcing and feedback from a regression of imbalance on warming.
|
|
334
|
+
|
|
335
|
+
In a run with constant forcing, the top-of-atmosphere imbalance falls linearly with warming, so a straight-line fit gives both.
|
|
336
|
+
|
|
337
|
+
Parameters
|
|
338
|
+
----------
|
|
339
|
+
temperature_change : array_like, shape (n,), n >= 2
|
|
340
|
+
K; constraint: real, not all equal.
|
|
341
|
+
imbalance : array_like, shape (n,)
|
|
342
|
+
W/m^2, net downward top-of-atmosphere flux; constraint: real.
|
|
343
|
+
|
|
344
|
+
Returns
|
|
345
|
+
-------
|
|
346
|
+
numpy.ndarray, shape (2,)
|
|
347
|
+
``[forcing, restoring]``: the intercept [W/m^2] and minus the slope [W/(m^2 K)] of the least-squares line.
|
|
348
|
+
|
|
349
|
+
Notes
|
|
350
|
+
-----
|
|
351
|
+
Assumes N = F - lambda T with constant F and lambda; equilibrium warming is forcing / restoring.
|
|
352
|
+
Equation: ``imbalance = forcing - restoring * temperature_change`` (ordinary least squares).
|
|
353
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
354
|
+
"""
|
|
355
|
+
temperature_change = quantity("temperature_change", temperature_change)
|
|
356
|
+
imbalance = quantity("imbalance", imbalance)
|
|
357
|
+
require(temperature_change.ndim == 1 and temperature_change.size >= 2, "temperature_change must be a vector of at least two samples")
|
|
358
|
+
require(imbalance.shape == temperature_change.shape, "imbalance must match temperature_change")
|
|
359
|
+
anomaly = temperature_change - temperature_change.mean()
|
|
360
|
+
require(np.any(anomaly != 0), "temperature_change must vary")
|
|
361
|
+
slope = np.dot(anomaly, imbalance - imbalance.mean()) / np.dot(anomaly, anomaly)
|
|
362
|
+
return np.array([imbalance.mean() - slope * temperature_change.mean(), -slope])
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
@safe_arithmetic
|
|
366
|
+
def simulate_one_box(forcing, restoring, areal_heat_capacity, time, *, initial_anomaly=0.0):
|
|
367
|
+
"""Integrate a linear one-box climate energy balance.
|
|
368
|
+
|
|
369
|
+
Parameters
|
|
370
|
+
----------
|
|
371
|
+
forcing : float
|
|
372
|
+
Constant signed radiative perturbation [W/m^2].
|
|
373
|
+
restoring : float
|
|
374
|
+
Positive scalar net restoring coefficient [W/(m^2 K)].
|
|
375
|
+
areal_heat_capacity : float
|
|
376
|
+
Positive scalar effective heat capacity [J/(m^2 K)].
|
|
377
|
+
time : array_like
|
|
378
|
+
Nonnegative strictly increasing sampled times [s].
|
|
379
|
+
initial_anomaly : float
|
|
380
|
+
Finite scalar temperature anomaly [K] at the first supplied time.
|
|
381
|
+
|
|
382
|
+
Returns
|
|
383
|
+
-------
|
|
384
|
+
TimeSeries
|
|
385
|
+
Temperature anomaly [K].
|
|
386
|
+
|
|
387
|
+
Notes
|
|
388
|
+
-----
|
|
389
|
+
C dT/dt = F - lambda T. Compare with step_temperature for zero initial
|
|
390
|
+
anomaly, using elapsed time since the first sample. This educational model
|
|
391
|
+
omits deep-ocean exchange, nonlinear feedbacks and geographic structure.
|
|
392
|
+
"""
|
|
393
|
+
from earthsciencekit._validation import time_grid
|
|
394
|
+
from earthsciencekit.integrators import integrate_ode
|
|
395
|
+
|
|
396
|
+
forcing = quantity("forcing", forcing)
|
|
397
|
+
restoring = quantity("restoring", restoring, lower=0, strict_lower=True)
|
|
398
|
+
capacity = quantity("areal_heat_capacity", areal_heat_capacity, lower=0, strict_lower=True)
|
|
399
|
+
initial = quantity("initial_anomaly", initial_anomaly)
|
|
400
|
+
require(all(v.ndim == 0 for v in (forcing, restoring, capacity, initial)), "climate parameters must be scalars")
|
|
401
|
+
time = time_grid(time)
|
|
402
|
+
return integrate_ode(lambda t, y: (forcing - restoring * y) / capacity, [initial], time, names=("temperature_anomaly",), units=("K",))
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Sea level: thermal expansion and the mass equivalent of added water.
|
|
2
|
+
|
|
3
|
+
SI units unless stated otherwise.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from earthsciencekit._validation import model, quantity
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"thermal_expansion_sea_level",
|
|
10
|
+
"water_equivalent_sea_level",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@model
|
|
15
|
+
def thermal_expansion_sea_level(expansion_coefficient, depth, temperature_change):
|
|
16
|
+
"""Steric sea-level slab approximation.
|
|
17
|
+
|
|
18
|
+
Ocean expansion connects heat uptake to a sea-level contribution.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
expansion_coefficient : float or array_like
|
|
23
|
+
1/K; constraint: positive.
|
|
24
|
+
depth : float or array_like
|
|
25
|
+
m; constraint: positive.
|
|
26
|
+
temperature_change : float or array_like
|
|
27
|
+
K; constraint: real.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
numpy scalar or ndarray
|
|
32
|
+
Model value [W/m^2]. Scalar input returns a NumPy scalar; arrays broadcast.
|
|
33
|
+
|
|
34
|
+
Notes
|
|
35
|
+
-----
|
|
36
|
+
Constant coefficient and uniform warming; salinity and pressure dependence are omitted.
|
|
37
|
+
Equation: ``expansion_coefficient * depth * temperature_change``.
|
|
38
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
39
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
40
|
+
"""
|
|
41
|
+
expansion_coefficient = quantity("expansion_coefficient", expansion_coefficient, lower=0, strict_lower=True)
|
|
42
|
+
depth = quantity("depth", depth, lower=0, strict_lower=True)
|
|
43
|
+
temperature_change = quantity("temperature_change", temperature_change)
|
|
44
|
+
return expansion_coefficient * depth * temperature_change
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@model
|
|
48
|
+
def water_equivalent_sea_level(water_mass, ocean_area, water_density):
|
|
49
|
+
"""Mass-equivalent sea-level contribution.
|
|
50
|
+
|
|
51
|
+
Added ocean water changes mean level through mass conservation.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
water_mass : float or array_like
|
|
56
|
+
kg; constraint: nonnegative.
|
|
57
|
+
ocean_area : float or array_like
|
|
58
|
+
m^2; constraint: positive.
|
|
59
|
+
water_density : float or array_like
|
|
60
|
+
kg/m^3; constraint: positive.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
numpy scalar or ndarray
|
|
65
|
+
Model value [W/m^2]. Scalar input returns a NumPy scalar; arrays broadcast.
|
|
66
|
+
|
|
67
|
+
Notes
|
|
68
|
+
-----
|
|
69
|
+
Uniform ocean area and density; gravitational fingerprints and land motion are omitted.
|
|
70
|
+
Equation: ``water_mass / (ocean_area * water_density)``.
|
|
71
|
+
Invalid, empty, nonreal or nonfinite inputs raise ValueError.
|
|
72
|
+
NumPy broadcasting applies; singular and overflowing outputs raise ValueError.
|
|
73
|
+
"""
|
|
74
|
+
water_mass = quantity("water_mass", water_mass, lower=0)
|
|
75
|
+
ocean_area = quantity("ocean_area", ocean_area, lower=0, strict_lower=True)
|
|
76
|
+
water_density = quantity("water_density", water_density, lower=0, strict_lower=True)
|
|
77
|
+
return water_mass / (ocean_area * water_density)
|
|
File without changes
|