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,171 @@
1
+ """Physical identities, limits and dynamic checks for earthsciencekit.geophysics."""
2
+
3
+ import numpy as np
4
+ import pytest
5
+ from scipy.integrate import quad
6
+ from scipy.special import eval_legendre
7
+
8
+ from earthsciencekit.constants import MU0
9
+ from earthsciencekit.geophysics.systems.deformation import equivalent_water_height, love_number_h2, love_number_k2
10
+ from earthsciencekit.geophysics.systems.dynamo import rikitake_fixed_point, simulate_rikitake_dynamo
11
+ from earthsciencekit.geophysics.systems.figure import (
12
+ centrifugal_ratio,
13
+ clairaut_gravity,
14
+ degree_geoid_height,
15
+ flattening_from_j2,
16
+ nodal_precession_rate,
17
+ normal_gravity,
18
+ stokes_function,
19
+ )
20
+ from earthsciencekit.geophysics.systems.gravity import bouguer_anomaly, bouguer_correction, free_air_correction, sphere_gravity
21
+ from earthsciencekit.geophysics.systems.magnetism import (
22
+ block_magnetic_anomaly,
23
+ dipole_field,
24
+ dipole_inclination,
25
+ direction_angle,
26
+ field_elements,
27
+ paleolatitude,
28
+ virtual_geomagnetic_pole,
29
+ zonal_field,
30
+ )
31
+
32
+
33
+ @pytest.mark.parametrize(
34
+ "function,args",
35
+ [
36
+ (sphere_gravity, [0.5, 0, 1, 1000]),
37
+ (nodal_precession_rate, [1.5, 0.5, 0, 1e-3, 1, 1]),
38
+ (field_elements, [0, 0, 0]),
39
+ (block_magnetic_anomaly, [0, 1, 1, 1, 2, 1]),
40
+ (block_magnetic_anomaly, [0, -1, 1, 2, 2, 1]),
41
+ ],
42
+ )
43
+ def test_cross_parameter_rejections(function, args):
44
+ with pytest.raises(ValueError):
45
+ function(*args)
46
+
47
+
48
+ def test_sphere_gravity_peak_and_half_width():
49
+ depth = 1000.0
50
+ field = sphere_gravity(np.array([-depth * 0.766, 0, depth * 0.766]), depth, 200, 500)
51
+ assert np.isclose(field[0, 1], field[1, 1] / 2, rtol=1e-3)
52
+ assert field[0, 0] > 0 > field[2, 0] and field[1, 0] == 0
53
+
54
+
55
+ def test_bouguer_anomaly_recovers_buried_body_on_topography():
56
+ height = np.array([0.0, 250.0, 800.0])
57
+ radius, density, buried = 6.371e6, 2670.0, 3e-5
58
+ normal = normal_gravity(0.7)
59
+ observed = normal - free_air_correction(height, normal, radius) + bouguer_correction(density, height) + buried
60
+ np.testing.assert_allclose(bouguer_anomaly(observed, normal, height, density, radius), buried, rtol=1e-9)
61
+
62
+
63
+ def test_clairaut_matches_somigliana_to_first_order():
64
+ latitude = np.linspace(0, np.pi / 2, 19)
65
+ flattening, ratio = 1 / 298.257222101, 0.00344978600308
66
+ np.testing.assert_allclose(clairaut_gravity(latitude, normal_gravity(0), flattening, ratio), normal_gravity(latitude), rtol=5e-5)
67
+
68
+
69
+ def test_satellite_precession_returns_flattening():
70
+ gm, radius, j2 = 3.986004418e14, 6378137.0, 1.08263e-3
71
+ rate = nodal_precession_rate(7.3e6, 0.1, np.radians(65.3), j2, radius, gm)
72
+ recovered = rate / nodal_precession_rate(7.3e6, 0.1, np.radians(65.3), 1, radius, gm)
73
+ assert np.isclose(recovered, j2)
74
+ assert nodal_precession_rate(7.3e6, 0, np.pi / 2, j2, radius, gm) == pytest.approx(0, abs=1e-20)
75
+ ratio = centrifugal_ratio(7.292115e-5, radius, 9.7803267715)
76
+ assert np.isclose(1 / flattening_from_j2(j2, ratio), 298.257, rtol=2e-3)
77
+
78
+
79
+ @pytest.mark.parametrize("degree", [2, 3, 5])
80
+ def test_stokes_integral_of_a_zonal_anomaly(degree):
81
+ def integrand(psi):
82
+ return eval_legendre(degree, np.cos(psi)) * stokes_function(psi) * np.sin(psi)
83
+
84
+ integral = quad(integrand, 0, np.pi, limit=200)[0] / 2
85
+ assert np.isclose(integral * 6.371e6 / 9.8, degree_geoid_height(1, degree, 6.371e6, 9.8), rtol=1e-6)
86
+
87
+
88
+ def test_dipole_directions_poles_and_paleolatitude():
89
+ latitude = np.linspace(-1.4, 1.4, 15)
90
+ field = dipole_field(latitude, 6.371e6, 8e22)
91
+ elements = field_elements(field[..., 0], 0, field[..., 1])
92
+ np.testing.assert_allclose(elements[..., 1], dipole_inclination(latitude))
93
+ np.testing.assert_allclose(paleolatitude(dipole_inclination(latitude)), latitude)
94
+ poles = dipole_field([np.pi / 2, 0], 6.371e6, 8e22)
95
+ assert np.isclose(poles[0, 1], 2 * poles[1, 0])
96
+ pole = virtual_geomagnetic_pole(latitude, 2.0, 0, dipole_inclination(latitude))
97
+ np.testing.assert_allclose(pole[..., 0], np.pi / 2)
98
+ reversed_pole = virtual_geomagnetic_pole(0.6, 2.0, np.pi, -dipole_inclination(0.6))
99
+ assert np.isclose(reversed_pole[0], -np.pi / 2)
100
+
101
+
102
+ def test_zonal_dipole_term_is_the_axial_dipole():
103
+ latitude, distance, g10, a = np.linspace(-1.5, 1.5, 7), 8e6, -29404.8e-9, 6371.2e3
104
+ moment = -4 * np.pi * a**3 * g10 / MU0
105
+ np.testing.assert_allclose(zonal_field(latitude, distance, 1, g10, a), dipole_field(latitude, distance, moment), rtol=1e-12)
106
+ far, near = zonal_field(0.4, [2 * a, a], 3, 1e-6, a)[:, 1]
107
+ assert np.isclose(far / near, 2.0**-5)
108
+
109
+
110
+ def test_vgp_of_a_tilted_dipole_recovers_its_pole():
111
+ pole_latitude, pole_longitude = np.radians(70), np.radians(300)
112
+ site_latitude, site_longitude = np.radians([10, 45, -30]), np.radians([20, 100, 250])
113
+ cos_p = np.sin(site_latitude) * np.sin(pole_latitude) + np.cos(site_latitude) * np.cos(pole_latitude) * np.cos(
114
+ pole_longitude - site_longitude
115
+ )
116
+ p = np.arccos(cos_p)
117
+ declination = np.arctan2(
118
+ np.cos(pole_latitude) * np.sin(pole_longitude - site_longitude),
119
+ np.cos(site_latitude) * np.sin(pole_latitude)
120
+ - np.sin(site_latitude) * np.cos(pole_latitude) * np.cos(pole_longitude - site_longitude),
121
+ )
122
+ inclination = np.arctan(2 / np.tan(p))
123
+ pole = virtual_geomagnetic_pole(site_latitude, site_longitude, declination, inclination)
124
+ np.testing.assert_allclose(pole, np.column_stack([np.full(3, pole_latitude), np.full(3, pole_longitude)]))
125
+
126
+
127
+ def test_direction_angle_of_a_reversal_is_pi():
128
+ inclination = dipole_inclination(0.6)
129
+ assert np.isclose(direction_angle(0, inclination, np.pi, -inclination), np.pi)
130
+ assert direction_angle(0.3, 0.2, 0.3, 0.2) == 0
131
+
132
+
133
+ def test_block_anomaly_superposes_and_cancels_for_a_layer():
134
+ x = np.linspace(-5e4, 5e4, 11)
135
+ whole = block_magnetic_anomaly(x, -1e4, 1e4, 2500, 3000, 5)
136
+ parts = block_magnetic_anomaly(x, -1e4, 0, 2500, 3000, 5) + block_magnetic_anomaly(x, 0, 1e4, 2500, 3000, 5)
137
+ np.testing.assert_allclose(whole, parts)
138
+ np.testing.assert_allclose(block_magnetic_anomaly(x, -1e12, 1e12, 2500, 3000, 5), 0, atol=1e-6 * np.abs(whole).max())
139
+ np.testing.assert_allclose(block_magnetic_anomaly(x, -1e4, 1e4, 2500, 3000, -5), -whole)
140
+
141
+
142
+ def test_love_numbers_fluid_and_rigid_limits():
143
+ assert love_number_k2(0, 5500, 9.8, 6.371e6) == 1.5
144
+ assert love_number_h2(0, 5500, 9.8, 6.371e6) == 2.5
145
+ stiff = love_number_k2(1e15, 5500, 9.8, 6.371e6)
146
+ assert 0 < stiff < 1e-3
147
+ assert np.isclose(love_number_h2(1e11, 5500, 9.8, 6.371e6), 5 / 3 * love_number_k2(1e11, 5500, 9.8, 6.371e6))
148
+
149
+
150
+ def test_equivalent_water_height_is_linear_and_rejects_noninteger_degree():
151
+ single = equivalent_water_height(1e-10, 10, -0.069, 6.371e6, 5514, 1000)
152
+ assert np.isclose(equivalent_water_height(3e-10, 10, -0.069, 6.371e6, 5514, 1000), 3 * single)
153
+ with pytest.raises(ValueError):
154
+ equivalent_water_height(1e-10, 10.5, -0.069, 6.371e6, 5514, 1000)
155
+
156
+
157
+ def test_rikitake_fixed_point_is_steady_and_flow_reverses():
158
+ mu, k = 1.0, 2.0
159
+ time = np.linspace(0, 20, 41)
160
+ steady = simulate_rikitake_dynamo(mu, k, 1.0, time, initial_state=rikitake_fixed_point(mu, k))
161
+ np.testing.assert_allclose(steady.values, np.broadcast_to(rikitake_fixed_point(mu, k), steady.values.shape), atol=1e-7)
162
+ mirrored = simulate_rikitake_dynamo(mu, k, 1.0, time, initial_state=-rikitake_fixed_point(mu, k) * [1, 1, -1])
163
+ np.testing.assert_allclose(mirrored.values[:, :2], -steady.values[:, :2], atol=1e-7)
164
+ chaotic = simulate_rikitake_dynamo(mu, k, 2.0, np.linspace(0, 400, 2001))
165
+ assert np.count_nonzero(np.diff(np.sign(chaotic.values[:, 0]))) > 4
166
+ assert chaotic.names == ("x", "y", "z") and chaotic.units == ("1", "1", "1")
167
+ for args in [(0, k, 1.0), (mu, k, [1.0, 2.0])]:
168
+ with pytest.raises(ValueError):
169
+ simulate_rikitake_dynamo(*args, time)
170
+ with pytest.raises(ValueError):
171
+ simulate_rikitake_dynamo(mu, k, 1.0, time, initial_state=[1, 0])
@@ -0,0 +1,9 @@
1
+ """Plots for earthsciencekit.geophysics.
2
+
3
+ The dynamic models return a TimeSeries, whose ``plot`` and ``animate`` methods
4
+ call these package-wide helpers.
5
+ """
6
+
7
+ from earthsciencekit.visualizers import animate_series, plot_series
8
+
9
+ __all__ = ["plot_series", "animate_series"]
@@ -0,0 +1,62 @@
1
+ """Validated access to SciPy initial-value integration."""
2
+
3
+ import numpy as np
4
+ from scipy.integrate import solve_ivp
5
+
6
+ from earthsciencekit._validation import quantity, require
7
+ from earthsciencekit.results import TimeSeries
8
+
9
+ __all__ = ["integrate_ode"]
10
+
11
+
12
+ def integrate_ode(rhs, initial, time, *, names=None, units=None, rtol=1e-8, atol=1e-10, method="RK45"):
13
+ """Integrate a real initial-value system and retain the requested samples.
14
+
15
+ Parameters
16
+ ----------
17
+ rhs : callable
18
+ ``rhs(t, state)`` returns derivatives with the same vector shape as state.
19
+ initial : array_like, shape (m,)
20
+ State at the first supplied time, in the units specified by ``units``.
21
+ time : array_like, shape (n,)
22
+ Strictly increasing output times in seconds, with at least two samples.
23
+ names, units : sequence of str or None
24
+ State labels; defaults are x0, x1, ... and dimensionless unit labels.
25
+ rtol, atol : float
26
+ Positive finite scalar solver tolerances; atol uses each state's units.
27
+ method : str
28
+ One of RK23, RK45, DOP853, Radau, BDF or LSODA.
29
+
30
+ Returns
31
+ -------
32
+ TimeSeries
33
+ Immutable sampled states. Numerical solver errors raise ValueError.
34
+
35
+ Notes
36
+ -----
37
+ SciPy adapts its internal steps; the output grid is not the integration step.
38
+ Solver tolerances do not validate a physical model or guarantee conservation.
39
+ """
40
+ require(callable(rhs), "rhs must be callable")
41
+ initial = quantity("initial", initial)
42
+ time = quantity("time", time)
43
+ require(initial.ndim == 1, "initial must be a nonempty state vector")
44
+ require(time.ndim == 1 and time.size >= 2, "time must have at least two samples")
45
+ require(np.all(np.diff(time) > 0), "time must strictly increase")
46
+ for name, value in [("rtol", rtol), ("atol", atol)]:
47
+ tolerance = quantity(name, value, lower=0, strict_lower=True)
48
+ require(tolerance.ndim == 0, "tolerances must be scalars")
49
+ require(method in ("RK23", "RK45", "DOP853", "Radau", "BDF", "LSODA"), "unsupported integration method")
50
+ names = tuple(f"x{i}" for i in range(initial.size)) if names is None else tuple(names)
51
+ units = tuple("1" for _ in initial) if units is None else tuple(units)
52
+ # Validate labels before invoking potentially expensive or stateful callbacks.
53
+ TimeSeries(time, np.broadcast_to(initial, (time.size, initial.size)), names, units)
54
+
55
+ def checked_rhs(t, state):
56
+ derivative = quantity("rhs derivative", rhs(t, state))
57
+ require(derivative.shape == initial.shape, "rhs must return a matching state vector")
58
+ return derivative
59
+
60
+ solution = solve_ivp(checked_rhs, (time[0], time[-1]), initial, t_eval=time, rtol=rtol, atol=atol, method=method)
61
+ require(solution.success, f"integration failed: {solution.message}")
62
+ return TimeSeries(solution.t, solution.y.T, names, units)
@@ -0,0 +1,61 @@
1
+ """Inspectable oceanography models; SI units unless stated otherwise."""
2
+
3
+ from earthsciencekit.oceanography.systems.circulation import (
4
+ ekman_spiral_velocity,
5
+ ekman_transport,
6
+ geostrophic_current,
7
+ rossby_radius,
8
+ simulate_thermohaline_boxes,
9
+ stommel_streamfunction,
10
+ sverdrup_transport,
11
+ thermohaline_forcing,
12
+ volume_transport,
13
+ )
14
+ from earthsciencekit.oceanography.systems.hydrography import (
15
+ abyssal_temperature,
16
+ buoyancy_frequency_squared,
17
+ hydrostatic_pressure,
18
+ linear_density,
19
+ simplified_seawater_density,
20
+ )
21
+ from earthsciencekit.oceanography.systems.inertial import (
22
+ inertial_period,
23
+ simulate_inertial_current,
24
+ )
25
+ from earthsciencekit.oceanography.systems.waves import (
26
+ deep_group_speed,
27
+ deep_wave_speed,
28
+ gerstner_orbit_radius,
29
+ internal_wave_speed,
30
+ reduced_gravity,
31
+ shallow_wave_speed,
32
+ tidal_elevation,
33
+ wave_angular_frequency,
34
+ )
35
+
36
+ __all__ = [
37
+ "hydrostatic_pressure",
38
+ "linear_density",
39
+ "buoyancy_frequency_squared",
40
+ "geostrophic_current",
41
+ "ekman_transport",
42
+ "inertial_period",
43
+ "shallow_wave_speed",
44
+ "deep_wave_speed",
45
+ "wave_angular_frequency",
46
+ "deep_group_speed",
47
+ "rossby_radius",
48
+ "reduced_gravity",
49
+ "internal_wave_speed",
50
+ "volume_transport",
51
+ "tidal_elevation",
52
+ "gerstner_orbit_radius",
53
+ "ekman_spiral_velocity",
54
+ "sverdrup_transport",
55
+ "stommel_streamfunction",
56
+ "thermohaline_forcing",
57
+ "abyssal_temperature",
58
+ "simplified_seawater_density",
59
+ "simulate_inertial_current",
60
+ "simulate_thermohaline_boxes",
61
+ ]
@@ -0,0 +1,5 @@
1
+ """Result containers for earthsciencekit.oceanography."""
2
+
3
+ from earthsciencekit.oceanography.core.base import TimeSeries
4
+
5
+ __all__ = ["TimeSeries"]
@@ -0,0 +1,10 @@
1
+ """Result containers for earthsciencekit.oceanography.
2
+
3
+ Algebraic models return NumPy scalars or arrays. The sampled dynamic models
4
+ return the package-wide immutable :class:`~earthsciencekit.results.TimeSeries`,
5
+ which is re-exported here as this domain's result type.
6
+ """
7
+
8
+ from earthsciencekit.results import TimeSeries
9
+
10
+ __all__ = ["TimeSeries"]
@@ -0,0 +1 @@
1
+ """Models of earthsciencekit.oceanography, grouped by topic."""