dewi-lanjar 0.0.1__tar.gz
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.
- dewi_lanjar-0.0.1/LICENSE +21 -0
- dewi_lanjar-0.0.1/PKG-INFO +243 -0
- dewi_lanjar-0.0.1/README.md +206 -0
- dewi_lanjar-0.0.1/pyproject.toml +59 -0
- dewi_lanjar-0.0.1/setup.cfg +4 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/__init__.py +52 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/__main__.py +3 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/_version.py +1 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/analytic.py +285 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/cases/asymmetric_double_gyre.toml +31 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/cases/beta_emergence.toml +30 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/cases/double_gyre_eddies.toml +39 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/cases/f_plane_control.toml +29 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/cases/inertial_gyre.toml +34 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/cases/inertial_runaway.toml +35 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/cases/munk_1950.toml +42 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/cases/munk_narrowing.toml +32 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/cases/pulsating_wind.toml +36 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/cases/southern_hemisphere.toml +32 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/cases/stommel_1948.toml +39 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/cases/stommel_munk_spinup.toml +33 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/cases.py +422 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/cli.py +321 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/diagnostics.py +327 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/explain.py +345 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/floats.py +95 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/forcing.py +153 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/grid.py +85 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/operators.py +137 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/params.py +320 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/plotting.py +518 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/steady.py +100 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar/transient.py +300 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar.egg-info/PKG-INFO +243 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar.egg-info/SOURCES.txt +42 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar.egg-info/dependency_links.txt +1 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar.egg-info/entry_points.txt +2 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar.egg-info/requires.txt +17 -0
- dewi_lanjar-0.0.1/src/dewi_lanjar.egg-info/top_level.txt +1 -0
- dewi_lanjar-0.0.1/tests/test_analytic.py +134 -0
- dewi_lanjar-0.0.1/tests/test_cli_cases.py +85 -0
- dewi_lanjar-0.0.1/tests/test_operators.py +109 -0
- dewi_lanjar-0.0.1/tests/test_params_diagnostics.py +109 -0
- dewi_lanjar-0.0.1/tests/test_solvers.py +147 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Applied Geology Research Group, Bandung Institute of Technology
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dewi-lanjar
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Western boundary intensification of wind-driven ocean gyres: Stommel, Munk and inertial boundary currents with rigorous numerics, physical diagnostics and GIF animations
|
|
5
|
+
Author: Rima Rachmayani, Rusmawan Suwarman, Dasapta E. Irawan
|
|
6
|
+
Author-email: "Sandy H. S. Herho" <sandyherho@itb.ac.id>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/sandyherho/dewi-lanjar
|
|
9
|
+
Project-URL: Documentation, https://dewi-lanjar.readthedocs.io
|
|
10
|
+
Project-URL: Source, https://github.com/sandyherho/dewi-lanjar
|
|
11
|
+
Keywords: oceanography,western boundary current,Stommel,Munk,Sverdrup,barotropic vorticity equation,geophysical fluid dynamics,education
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Intended Audience :: Education
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: numpy>=1.23
|
|
24
|
+
Requires-Dist: scipy>=1.9
|
|
25
|
+
Requires-Dist: matplotlib>=3.6
|
|
26
|
+
Requires-Dist: pillow>=9.1
|
|
27
|
+
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
30
|
+
Requires-Dist: hypothesis>=6; extra == "test"
|
|
31
|
+
Requires-Dist: mpmath>=1.3; extra == "test"
|
|
32
|
+
Provides-Extra: docs
|
|
33
|
+
Requires-Dist: sphinx>=7; extra == "docs"
|
|
34
|
+
Requires-Dist: furo>=2023.9.10; extra == "docs"
|
|
35
|
+
Requires-Dist: myst-parser>=2; extra == "docs"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# dewi-lanjar
|
|
39
|
+
|
|
40
|
+
**Western boundary intensification of wind-driven ocean gyres in Python: Stommel, Munk and inertial boundary currents with rigorous numerics, physical diagnostics and GIF animations.**
|
|
41
|
+
|
|
42
|
+
[](https://github.com/sandyherho/dewi-lanjar)
|
|
43
|
+
[](https://www.python.org/)
|
|
44
|
+
[](LICENSE)
|
|
45
|
+
[](https://dewi-lanjar.readthedocs.io)
|
|
46
|
+
[](https://pytest.org)
|
|
47
|
+
[](https://hypothesis.readthedocs.io)
|
|
48
|
+
[](https://www.sphinx-doc.org)
|
|
49
|
+
[](#use)
|
|
50
|
+
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
Wind drives every ocean basin into a gyre, but the gyre is lopsided: the return
|
|
54
|
+
flow is squeezed against the western coast into a narrow, fast current (Gulf
|
|
55
|
+
Stream, Kuroshio, Agulhas, Brazil, East Australian Current) while the eastern
|
|
56
|
+
side drifts slowly. dewi-lanjar solves the barotropic vorticity equation on a
|
|
57
|
+
beta plane in a closed basin,
|
|
58
|
+
|
|
59
|
+
$$\partial_t\zeta + \delta_I^2 J(\psi,\zeta) + b\,\partial_x\psi = F - \delta_S\zeta + \delta_M^3\nabla^2\zeta,\qquad \zeta=\nabla^2\psi,$$
|
|
60
|
+
|
|
61
|
+
from the linear problems of Stommel (1948) and Munk (1950) to inertial
|
|
62
|
+
recirculations and eddy-shedding double-gyre jets, and explains every step.
|
|
63
|
+
Here $\delta_S$, $\delta_M$ and $\delta_I$ are the Stommel, Munk and inertial
|
|
64
|
+
boundary-layer widths divided by the basin width, and $b$ switches the
|
|
65
|
+
planetary vorticity gradient on (1) or off (0, the f-plane control).
|
|
66
|
+
|
|
67
|
+
Named after Dewi Lanjar, in Javanese folklore the spirit queen of the sea off
|
|
68
|
+
the northern coast of Java.
|
|
69
|
+
|
|
70
|
+
## What makes it rigorous
|
|
71
|
+
|
|
72
|
+
| Component | How dewi-lanjar does it |
|
|
73
|
+
|---|---|
|
|
74
|
+
| Reference solution | Exact separable solution of the steady linear problem for any sine-series wind, any mix of drag and viscosity, free- or no-slip western/eastern walls (characteristic quartic, overflow-free exponential basis). Checked against a 50-digit evaluation of Stommel's closed form to 1e-11. |
|
|
75
|
+
| Steady solver | One sparse direct solve with exactly the operators of the time stepper. Second-order convergence verified in every configuration (`dewi-lanjar validate`). |
|
|
76
|
+
| Advection | Arakawa (1966) Jacobian: discrete energy (and enstrophy) conservation verified to round-off on random fields, so no nonlinear instability. |
|
|
77
|
+
| Poisson problem | Exact inversion of the five-point Laplacian by the type-I discrete sine transform. |
|
|
78
|
+
| Time stepping | SSP-RK3 with an adaptive step from advective, Rossby-wave, viscous and drag stability limits; output times hit exactly. A long linear integration reproduces the steady solver to 1e-13. |
|
|
79
|
+
| Wall conditions | Free slip (zeta = 0) or no slip (Thom formula), set separately for western/eastern and southern/northern walls. |
|
|
80
|
+
| Budgets | Energy and vorticity budgets evaluated with the solver's own inner product: they close to round-off (1e-12 to 1e-15 in steady states). |
|
|
81
|
+
| Theory comparison | Boundary-layer widths from the local layer equation `dM^3 m^3 - dS m - b = 0`, measured on the theory profile and the model with the same algorithm. Reproduces delta_S, 2 pi delta_M / sqrt(3) and 4 pi delta_M / (3 sqrt(3)) to 1e-8. |
|
|
82
|
+
| Honesty about resolution | Points per boundary layer and the grid-scale fraction of vorticity are reported with warnings, because a bounded eddying run is not necessarily a resolved one. |
|
|
83
|
+
|
|
84
|
+
## A few numbers worth knowing
|
|
85
|
+
|
|
86
|
+
From `python tools/validation_report.py`:
|
|
87
|
+
|
|
88
|
+
- **Second order everywhere.** Doubling the grid from 128 to 256 cells cuts
|
|
89
|
+
the error against the exact solution by a factor of 4.00 for Stommel, Munk
|
|
90
|
+
(free and no slip), mixed Stommel-Munk double gyres and the f plane.
|
|
91
|
+
- **Beta is the whole story.** With beta switched off the Stommel gyre is
|
|
92
|
+
exactly symmetric (intensification index 1.000, kinetic-energy centroid
|
|
93
|
+
0.500). With beta on, the same wind and drag give an intensification index
|
|
94
|
+
of about 20 and put the energy centroid at one tenth of the basin width.
|
|
95
|
+
- **Two balances, one gyre.** In the steady Stommel-Munk double gyre
|
|
96
|
+
(delta_S = delta_M = 0.02) friction in the western strip (+1.35) offsets the beta
|
|
97
|
+
term (-1.18) and the local wind (-0.17), while in the interior beta takes up
|
|
98
|
+
two thirds of the wind input and friction the rest; each regional budget closes to 1e-12.
|
|
99
|
+
- **Munk's counter-current.** For delta_M = 0.03 with no slip, the numerical
|
|
100
|
+
counter-current begins at x = 0.103, about 5 % inside the asymptotic
|
|
101
|
+
2 pi delta_M / sqrt(3) = 0.109; the difference is the interior's own
|
|
102
|
+
southward flow, which the boundary-layer asymptotics neglect.
|
|
103
|
+
- **Inertia adds transport.** In the `inertial_gyre` case the boundary current
|
|
104
|
+
carries about 1.6 times the Sverdrup transport because of the northwest
|
|
105
|
+
recirculation.
|
|
106
|
+
|
|
107
|
+
## Install
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install dewi-lanjar
|
|
111
|
+
# or from source
|
|
112
|
+
git clone https://github.com/sandyherho/dewi-lanjar && cd dewi-lanjar && pip install .
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Python 3.10 or newer; depends on NumPy, SciPy, Matplotlib and Pillow. No GUI and
|
|
116
|
+
no display needed (Agg backend).
|
|
117
|
+
|
|
118
|
+
## Use
|
|
119
|
+
|
|
120
|
+
### Command line
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
dewi-lanjar list # twelve built-in cases
|
|
124
|
+
dewi-lanjar run stommel_1948 # -> outputs/stommel_1948/{gif,png,summary.txt,...}
|
|
125
|
+
dewi-lanjar run inertial_gyre --set physics.delta_i=0.05 --set grid.nx=256 --set grid.ny=256
|
|
126
|
+
dewi-lanjar init my_gyre.toml --template double_gyre_eddies && dewi-lanjar run my_gyre.toml
|
|
127
|
+
dewi-lanjar steady --delta-s 0.02 --delta-m 0.03 --bc no-slip --bc-ns free-slip --why
|
|
128
|
+
dewi-lanjar scales --L 6000 --lat 35 --r 1e-6 --A-h 2000 # ocean -> widths, with numbers substituted
|
|
129
|
+
dewi-lanjar widths --delta-m 0.03 --bc no-slip # boundary-layer roots and widths
|
|
130
|
+
dewi-lanjar validate # convergence table
|
|
131
|
+
dewi-lanjar explain munk # derivation in the terminal
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Every run writes the animation, a figure, `summary.txt` with all diagnostics
|
|
135
|
+
(in sverdrups, kilometers and meters per second when dimensional scales are
|
|
136
|
+
given), `diagnostics.json`, `fields.npz` and the resolved `case.toml`.
|
|
137
|
+
|
|
138
|
+
### Python
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from dewi_lanjar import Grid, Params, WindForcing, solve_steady, exact_linear, diagnostics
|
|
142
|
+
|
|
143
|
+
grid = Grid(192, 192)
|
|
144
|
+
p = Params(delta_s=0.02, delta_m=0.03, bc="no-slip", bc_ns="free-slip")
|
|
145
|
+
wind = WindForcing("double-gyre")
|
|
146
|
+
|
|
147
|
+
sol = solve_steady(grid, p, wind)
|
|
148
|
+
exact = exact_linear(grid.X, grid.Y, p, wind)["psi"]
|
|
149
|
+
d = diagnostics.compute(sol.psi, sol.zeta, grid, p, wind)
|
|
150
|
+
print(diagnostics.describe(d))
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from dewi_lanjar import TransientSolver, RunConfig
|
|
155
|
+
|
|
156
|
+
p = Params(delta_s=0.02, delta_m=0.02, delta_i=0.035, bc="no-slip")
|
|
157
|
+
run = TransientSolver(Grid(192, 192), p, WindForcing()).run(
|
|
158
|
+
RunConfig(t_end=200, n_frames=100, n_floats=500))
|
|
159
|
+
run.series["KE"], run.final["transport_ratio"]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Cases
|
|
163
|
+
|
|
164
|
+
| case | kind | what it shows |
|
|
165
|
+
|---|---|---|
|
|
166
|
+
| `stommel_1948` | steady | drag-closed gyre, dimensional values from a 5000 km basin |
|
|
167
|
+
| `munk_1950` | steady | viscous layer with its offshore counter-current |
|
|
168
|
+
| `f_plane_control` | steady | no beta, no intensification |
|
|
169
|
+
| `southern_hemisphere` | steady | anticlockwise gyre, still western-intensified |
|
|
170
|
+
| `asymmetric_double_gyre` | steady | unequal gyres, each with its own boundary current |
|
|
171
|
+
| `beta_emergence` | sweep | the boundary current forms as beta grows from zero |
|
|
172
|
+
| `munk_narrowing` | sweep | sharper current as viscosity falls |
|
|
173
|
+
| `stommel_munk_spinup` | transient | Rossby waves build the boundary current from rest |
|
|
174
|
+
| `pulsating_wind` | transient | periodic wind, basin Rossby waves, lagging transport |
|
|
175
|
+
| `inertial_gyre` | transient | overshoot and northwest recirculation |
|
|
176
|
+
| `inertial_runaway` | transient | free-slip walls: a basin-filling inertial mode |
|
|
177
|
+
| `double_gyre_eddies` | transient | separating jet that meanders and sheds eddies |
|
|
178
|
+
|
|
179
|
+
| | |
|
|
180
|
+
|---|---|
|
|
181
|
+
|  |  |
|
|
182
|
+
|  |  |
|
|
183
|
+
|
|
184
|
+
## Testing
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
pip install -e ".[test]"
|
|
188
|
+
pytest # operators, exact solution, solvers, diagnostics, CLI
|
|
189
|
+
pytest -m "not slow"
|
|
190
|
+
python tools/validation_report.py
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
What is tested, among others: Arakawa conservation and antisymmetry on random
|
|
194
|
+
grids (Hypothesis); exactness of the sine-transform Poisson solver; the exact
|
|
195
|
+
solution against 50-digit arithmetic and against its own ODE and wall
|
|
196
|
+
conditions for random parameters; second-order convergence of the steady
|
|
197
|
+
solver; convergence of the time integration to the steady solution; energy
|
|
198
|
+
conservation without forcing and friction; closure of the energy budget in
|
|
199
|
+
time; f-plane symmetry; hemispheric mirror symmetry; Munk widths against
|
|
200
|
+
theory; floats following streamlines; every built-in case and CLI command.
|
|
201
|
+
|
|
202
|
+
## Scope and limitations
|
|
203
|
+
|
|
204
|
+
- One homogeneous layer: no stratification, topography, realistic coastlines
|
|
205
|
+
or observed winds. The results are those of the idealized problem.
|
|
206
|
+
- Uniform grid; keep three or more points per boundary-layer decay scale and
|
|
207
|
+
watch the grid-scale fraction in eddying runs.
|
|
208
|
+
- The exact solution needs free-slip southern and northern walls when lateral
|
|
209
|
+
friction is present (set `bc_ns = "free-slip"`).
|
|
210
|
+
- The steady solver is linear; nonlinear states come from time integration.
|
|
211
|
+
|
|
212
|
+
## Documentation
|
|
213
|
+
|
|
214
|
+
Full documentation (installation, quick start, case gallery, CLI, case-file
|
|
215
|
+
reference, theory with every derivation, numerical methods, diagnostics,
|
|
216
|
+
validation, API) is built with Sphinx for Read the Docs from `docs/`:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
pip install -r docs/requirements.txt && sphinx-build -b html docs docs/_build/html
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Authors
|
|
223
|
+
|
|
224
|
+
Sandy H. S. Herho, Rima Rachmayani, Rusmawan Suwarman, Dasapta E. Irawan
|
|
225
|
+
|
|
226
|
+
## Citing
|
|
227
|
+
|
|
228
|
+
See [`CITATION.cff`](CITATION.cff).
|
|
229
|
+
|
|
230
|
+
## References
|
|
231
|
+
|
|
232
|
+
- Arakawa, A. (1966). Computational design for long-term numerical integration of the equations of fluid motion: Two-dimensional incompressible flow. Part I. *J. Comput. Phys.* 1(1), 119–143. https://doi.org/10.1016/0021-9991(66)90015-5
|
|
233
|
+
- Gottlieb, S., Shu, C.-W., Tadmor, E. (2001). Strong stability-preserving high-order time discretization methods. *SIAM Rev.* 43(1), 89–112. https://doi.org/10.1137/S003614450036757X
|
|
234
|
+
- Munk, W. H. (1950). On the wind-driven ocean circulation. *J. Meteorol.* 7(2), 80–93. https://doi.org/10.1175/1520-0469(1950)007<0080:OTWDOC>2.0.CO;2
|
|
235
|
+
- Pedlosky, J. (1996). *Ocean Circulation Theory*. Springer. https://doi.org/10.1007/978-3-662-03204-6
|
|
236
|
+
- Shu, C.-W., Osher, S. (1988). Efficient implementation of essentially non-oscillatory shock-capturing schemes. *J. Comput. Phys.* 77(2), 439–471. https://doi.org/10.1016/0021-9991(88)90177-5
|
|
237
|
+
- Stommel, H. (1948). The westward intensification of wind-driven ocean currents. *Trans. AGU* 29(2), 202–206. https://doi.org/10.1029/TR029i002p00202
|
|
238
|
+
- Sverdrup, H. U. (1947). Wind-driven currents in a baroclinic ocean. *PNAS* 33(11), 318–326. https://doi.org/10.1073/pnas.33.11.318
|
|
239
|
+
- Vallis, G. K. (2017). *Atmospheric and Oceanic Fluid Dynamics*, 2nd ed. Cambridge University Press. https://doi.org/10.1017/9781107588417
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
MIT. Copyright © 2026 Applied Geology Research Group, Bandung Institute of Technology.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# dewi-lanjar
|
|
2
|
+
|
|
3
|
+
**Western boundary intensification of wind-driven ocean gyres in Python: Stommel, Munk and inertial boundary currents with rigorous numerics, physical diagnostics and GIF animations.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/sandyherho/dewi-lanjar)
|
|
6
|
+
[](https://www.python.org/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://dewi-lanjar.readthedocs.io)
|
|
9
|
+
[](https://pytest.org)
|
|
10
|
+
[](https://hypothesis.readthedocs.io)
|
|
11
|
+
[](https://www.sphinx-doc.org)
|
|
12
|
+
[](#use)
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
Wind drives every ocean basin into a gyre, but the gyre is lopsided: the return
|
|
17
|
+
flow is squeezed against the western coast into a narrow, fast current (Gulf
|
|
18
|
+
Stream, Kuroshio, Agulhas, Brazil, East Australian Current) while the eastern
|
|
19
|
+
side drifts slowly. dewi-lanjar solves the barotropic vorticity equation on a
|
|
20
|
+
beta plane in a closed basin,
|
|
21
|
+
|
|
22
|
+
$$\partial_t\zeta + \delta_I^2 J(\psi,\zeta) + b\,\partial_x\psi = F - \delta_S\zeta + \delta_M^3\nabla^2\zeta,\qquad \zeta=\nabla^2\psi,$$
|
|
23
|
+
|
|
24
|
+
from the linear problems of Stommel (1948) and Munk (1950) to inertial
|
|
25
|
+
recirculations and eddy-shedding double-gyre jets, and explains every step.
|
|
26
|
+
Here $\delta_S$, $\delta_M$ and $\delta_I$ are the Stommel, Munk and inertial
|
|
27
|
+
boundary-layer widths divided by the basin width, and $b$ switches the
|
|
28
|
+
planetary vorticity gradient on (1) or off (0, the f-plane control).
|
|
29
|
+
|
|
30
|
+
Named after Dewi Lanjar, in Javanese folklore the spirit queen of the sea off
|
|
31
|
+
the northern coast of Java.
|
|
32
|
+
|
|
33
|
+
## What makes it rigorous
|
|
34
|
+
|
|
35
|
+
| Component | How dewi-lanjar does it |
|
|
36
|
+
|---|---|
|
|
37
|
+
| Reference solution | Exact separable solution of the steady linear problem for any sine-series wind, any mix of drag and viscosity, free- or no-slip western/eastern walls (characteristic quartic, overflow-free exponential basis). Checked against a 50-digit evaluation of Stommel's closed form to 1e-11. |
|
|
38
|
+
| Steady solver | One sparse direct solve with exactly the operators of the time stepper. Second-order convergence verified in every configuration (`dewi-lanjar validate`). |
|
|
39
|
+
| Advection | Arakawa (1966) Jacobian: discrete energy (and enstrophy) conservation verified to round-off on random fields, so no nonlinear instability. |
|
|
40
|
+
| Poisson problem | Exact inversion of the five-point Laplacian by the type-I discrete sine transform. |
|
|
41
|
+
| Time stepping | SSP-RK3 with an adaptive step from advective, Rossby-wave, viscous and drag stability limits; output times hit exactly. A long linear integration reproduces the steady solver to 1e-13. |
|
|
42
|
+
| Wall conditions | Free slip (zeta = 0) or no slip (Thom formula), set separately for western/eastern and southern/northern walls. |
|
|
43
|
+
| Budgets | Energy and vorticity budgets evaluated with the solver's own inner product: they close to round-off (1e-12 to 1e-15 in steady states). |
|
|
44
|
+
| Theory comparison | Boundary-layer widths from the local layer equation `dM^3 m^3 - dS m - b = 0`, measured on the theory profile and the model with the same algorithm. Reproduces delta_S, 2 pi delta_M / sqrt(3) and 4 pi delta_M / (3 sqrt(3)) to 1e-8. |
|
|
45
|
+
| Honesty about resolution | Points per boundary layer and the grid-scale fraction of vorticity are reported with warnings, because a bounded eddying run is not necessarily a resolved one. |
|
|
46
|
+
|
|
47
|
+
## A few numbers worth knowing
|
|
48
|
+
|
|
49
|
+
From `python tools/validation_report.py`:
|
|
50
|
+
|
|
51
|
+
- **Second order everywhere.** Doubling the grid from 128 to 256 cells cuts
|
|
52
|
+
the error against the exact solution by a factor of 4.00 for Stommel, Munk
|
|
53
|
+
(free and no slip), mixed Stommel-Munk double gyres and the f plane.
|
|
54
|
+
- **Beta is the whole story.** With beta switched off the Stommel gyre is
|
|
55
|
+
exactly symmetric (intensification index 1.000, kinetic-energy centroid
|
|
56
|
+
0.500). With beta on, the same wind and drag give an intensification index
|
|
57
|
+
of about 20 and put the energy centroid at one tenth of the basin width.
|
|
58
|
+
- **Two balances, one gyre.** In the steady Stommel-Munk double gyre
|
|
59
|
+
(delta_S = delta_M = 0.02) friction in the western strip (+1.35) offsets the beta
|
|
60
|
+
term (-1.18) and the local wind (-0.17), while in the interior beta takes up
|
|
61
|
+
two thirds of the wind input and friction the rest; each regional budget closes to 1e-12.
|
|
62
|
+
- **Munk's counter-current.** For delta_M = 0.03 with no slip, the numerical
|
|
63
|
+
counter-current begins at x = 0.103, about 5 % inside the asymptotic
|
|
64
|
+
2 pi delta_M / sqrt(3) = 0.109; the difference is the interior's own
|
|
65
|
+
southward flow, which the boundary-layer asymptotics neglect.
|
|
66
|
+
- **Inertia adds transport.** In the `inertial_gyre` case the boundary current
|
|
67
|
+
carries about 1.6 times the Sverdrup transport because of the northwest
|
|
68
|
+
recirculation.
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install dewi-lanjar
|
|
74
|
+
# or from source
|
|
75
|
+
git clone https://github.com/sandyherho/dewi-lanjar && cd dewi-lanjar && pip install .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Python 3.10 or newer; depends on NumPy, SciPy, Matplotlib and Pillow. No GUI and
|
|
79
|
+
no display needed (Agg backend).
|
|
80
|
+
|
|
81
|
+
## Use
|
|
82
|
+
|
|
83
|
+
### Command line
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
dewi-lanjar list # twelve built-in cases
|
|
87
|
+
dewi-lanjar run stommel_1948 # -> outputs/stommel_1948/{gif,png,summary.txt,...}
|
|
88
|
+
dewi-lanjar run inertial_gyre --set physics.delta_i=0.05 --set grid.nx=256 --set grid.ny=256
|
|
89
|
+
dewi-lanjar init my_gyre.toml --template double_gyre_eddies && dewi-lanjar run my_gyre.toml
|
|
90
|
+
dewi-lanjar steady --delta-s 0.02 --delta-m 0.03 --bc no-slip --bc-ns free-slip --why
|
|
91
|
+
dewi-lanjar scales --L 6000 --lat 35 --r 1e-6 --A-h 2000 # ocean -> widths, with numbers substituted
|
|
92
|
+
dewi-lanjar widths --delta-m 0.03 --bc no-slip # boundary-layer roots and widths
|
|
93
|
+
dewi-lanjar validate # convergence table
|
|
94
|
+
dewi-lanjar explain munk # derivation in the terminal
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Every run writes the animation, a figure, `summary.txt` with all diagnostics
|
|
98
|
+
(in sverdrups, kilometers and meters per second when dimensional scales are
|
|
99
|
+
given), `diagnostics.json`, `fields.npz` and the resolved `case.toml`.
|
|
100
|
+
|
|
101
|
+
### Python
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from dewi_lanjar import Grid, Params, WindForcing, solve_steady, exact_linear, diagnostics
|
|
105
|
+
|
|
106
|
+
grid = Grid(192, 192)
|
|
107
|
+
p = Params(delta_s=0.02, delta_m=0.03, bc="no-slip", bc_ns="free-slip")
|
|
108
|
+
wind = WindForcing("double-gyre")
|
|
109
|
+
|
|
110
|
+
sol = solve_steady(grid, p, wind)
|
|
111
|
+
exact = exact_linear(grid.X, grid.Y, p, wind)["psi"]
|
|
112
|
+
d = diagnostics.compute(sol.psi, sol.zeta, grid, p, wind)
|
|
113
|
+
print(diagnostics.describe(d))
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from dewi_lanjar import TransientSolver, RunConfig
|
|
118
|
+
|
|
119
|
+
p = Params(delta_s=0.02, delta_m=0.02, delta_i=0.035, bc="no-slip")
|
|
120
|
+
run = TransientSolver(Grid(192, 192), p, WindForcing()).run(
|
|
121
|
+
RunConfig(t_end=200, n_frames=100, n_floats=500))
|
|
122
|
+
run.series["KE"], run.final["transport_ratio"]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Cases
|
|
126
|
+
|
|
127
|
+
| case | kind | what it shows |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `stommel_1948` | steady | drag-closed gyre, dimensional values from a 5000 km basin |
|
|
130
|
+
| `munk_1950` | steady | viscous layer with its offshore counter-current |
|
|
131
|
+
| `f_plane_control` | steady | no beta, no intensification |
|
|
132
|
+
| `southern_hemisphere` | steady | anticlockwise gyre, still western-intensified |
|
|
133
|
+
| `asymmetric_double_gyre` | steady | unequal gyres, each with its own boundary current |
|
|
134
|
+
| `beta_emergence` | sweep | the boundary current forms as beta grows from zero |
|
|
135
|
+
| `munk_narrowing` | sweep | sharper current as viscosity falls |
|
|
136
|
+
| `stommel_munk_spinup` | transient | Rossby waves build the boundary current from rest |
|
|
137
|
+
| `pulsating_wind` | transient | periodic wind, basin Rossby waves, lagging transport |
|
|
138
|
+
| `inertial_gyre` | transient | overshoot and northwest recirculation |
|
|
139
|
+
| `inertial_runaway` | transient | free-slip walls: a basin-filling inertial mode |
|
|
140
|
+
| `double_gyre_eddies` | transient | separating jet that meanders and sheds eddies |
|
|
141
|
+
|
|
142
|
+
| | |
|
|
143
|
+
|---|---|
|
|
144
|
+
|  |  |
|
|
145
|
+
|  |  |
|
|
146
|
+
|
|
147
|
+
## Testing
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
pip install -e ".[test]"
|
|
151
|
+
pytest # operators, exact solution, solvers, diagnostics, CLI
|
|
152
|
+
pytest -m "not slow"
|
|
153
|
+
python tools/validation_report.py
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
What is tested, among others: Arakawa conservation and antisymmetry on random
|
|
157
|
+
grids (Hypothesis); exactness of the sine-transform Poisson solver; the exact
|
|
158
|
+
solution against 50-digit arithmetic and against its own ODE and wall
|
|
159
|
+
conditions for random parameters; second-order convergence of the steady
|
|
160
|
+
solver; convergence of the time integration to the steady solution; energy
|
|
161
|
+
conservation without forcing and friction; closure of the energy budget in
|
|
162
|
+
time; f-plane symmetry; hemispheric mirror symmetry; Munk widths against
|
|
163
|
+
theory; floats following streamlines; every built-in case and CLI command.
|
|
164
|
+
|
|
165
|
+
## Scope and limitations
|
|
166
|
+
|
|
167
|
+
- One homogeneous layer: no stratification, topography, realistic coastlines
|
|
168
|
+
or observed winds. The results are those of the idealized problem.
|
|
169
|
+
- Uniform grid; keep three or more points per boundary-layer decay scale and
|
|
170
|
+
watch the grid-scale fraction in eddying runs.
|
|
171
|
+
- The exact solution needs free-slip southern and northern walls when lateral
|
|
172
|
+
friction is present (set `bc_ns = "free-slip"`).
|
|
173
|
+
- The steady solver is linear; nonlinear states come from time integration.
|
|
174
|
+
|
|
175
|
+
## Documentation
|
|
176
|
+
|
|
177
|
+
Full documentation (installation, quick start, case gallery, CLI, case-file
|
|
178
|
+
reference, theory with every derivation, numerical methods, diagnostics,
|
|
179
|
+
validation, API) is built with Sphinx for Read the Docs from `docs/`:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
pip install -r docs/requirements.txt && sphinx-build -b html docs docs/_build/html
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Authors
|
|
186
|
+
|
|
187
|
+
Sandy H. S. Herho, Rima Rachmayani, Rusmawan Suwarman, Dasapta E. Irawan
|
|
188
|
+
|
|
189
|
+
## Citing
|
|
190
|
+
|
|
191
|
+
See [`CITATION.cff`](CITATION.cff).
|
|
192
|
+
|
|
193
|
+
## References
|
|
194
|
+
|
|
195
|
+
- Arakawa, A. (1966). Computational design for long-term numerical integration of the equations of fluid motion: Two-dimensional incompressible flow. Part I. *J. Comput. Phys.* 1(1), 119–143. https://doi.org/10.1016/0021-9991(66)90015-5
|
|
196
|
+
- Gottlieb, S., Shu, C.-W., Tadmor, E. (2001). Strong stability-preserving high-order time discretization methods. *SIAM Rev.* 43(1), 89–112. https://doi.org/10.1137/S003614450036757X
|
|
197
|
+
- Munk, W. H. (1950). On the wind-driven ocean circulation. *J. Meteorol.* 7(2), 80–93. https://doi.org/10.1175/1520-0469(1950)007<0080:OTWDOC>2.0.CO;2
|
|
198
|
+
- Pedlosky, J. (1996). *Ocean Circulation Theory*. Springer. https://doi.org/10.1007/978-3-662-03204-6
|
|
199
|
+
- Shu, C.-W., Osher, S. (1988). Efficient implementation of essentially non-oscillatory shock-capturing schemes. *J. Comput. Phys.* 77(2), 439–471. https://doi.org/10.1016/0021-9991(88)90177-5
|
|
200
|
+
- Stommel, H. (1948). The westward intensification of wind-driven ocean currents. *Trans. AGU* 29(2), 202–206. https://doi.org/10.1029/TR029i002p00202
|
|
201
|
+
- Sverdrup, H. U. (1947). Wind-driven currents in a baroclinic ocean. *PNAS* 33(11), 318–326. https://doi.org/10.1073/pnas.33.11.318
|
|
202
|
+
- Vallis, G. K. (2017). *Atmospheric and Oceanic Fluid Dynamics*, 2nd ed. Cambridge University Press. https://doi.org/10.1017/9781107588417
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT. Copyright © 2026 Applied Geology Research Group, Bandung Institute of Technology.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dewi-lanjar"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "Western boundary intensification of wind-driven ocean gyres: Stommel, Munk and inertial boundary currents with rigorous numerics, physical diagnostics and GIF animations"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Sandy H. S. Herho", email = "sandyherho@itb.ac.id"},
|
|
14
|
+
{name = "Rima Rachmayani"},
|
|
15
|
+
{name = "Rusmawan Suwarman"},
|
|
16
|
+
{name = "Dasapta E. Irawan"},
|
|
17
|
+
]
|
|
18
|
+
keywords = ["oceanography", "western boundary current", "Stommel", "Munk", "Sverdrup",
|
|
19
|
+
"barotropic vorticity equation", "geophysical fluid dynamics", "education"]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 3 - Alpha",
|
|
22
|
+
"Intended Audience :: Science/Research",
|
|
23
|
+
"Intended Audience :: Education",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
28
|
+
"Topic :: Scientific/Engineering :: Atmospheric Science",
|
|
29
|
+
]
|
|
30
|
+
dependencies = [
|
|
31
|
+
"numpy>=1.23",
|
|
32
|
+
"scipy>=1.9",
|
|
33
|
+
"matplotlib>=3.6",
|
|
34
|
+
"pillow>=9.1",
|
|
35
|
+
"tomli>=2.0; python_version < '3.11'",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
test = ["pytest>=7", "hypothesis>=6", "mpmath>=1.3"]
|
|
40
|
+
docs = ["sphinx>=7", "furo>=2023.9.10", "myst-parser>=2"]
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
dewi-lanjar = "dewi_lanjar.cli:main"
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Homepage = "https://github.com/sandyherho/dewi-lanjar"
|
|
47
|
+
Documentation = "https://dewi-lanjar.readthedocs.io"
|
|
48
|
+
Source = "https://github.com/sandyherho/dewi-lanjar"
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.packages.find]
|
|
51
|
+
where = ["src"]
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.package-data]
|
|
54
|
+
dewi_lanjar = ["cases/*.toml"]
|
|
55
|
+
|
|
56
|
+
[tool.pytest.ini_options]
|
|
57
|
+
testpaths = ["tests"]
|
|
58
|
+
addopts = "-q"
|
|
59
|
+
markers = ["slow: long-running tests (deselect with -m 'not slow')"]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""dewi-lanjar: western boundary intensification of wind-driven ocean gyres.
|
|
2
|
+
|
|
3
|
+
A library and command-line tool for the barotropic vorticity equation on a
|
|
4
|
+
beta plane in a closed basin, from the linear Stommel (1948) and Munk (1950)
|
|
5
|
+
problems to inertial, eddying double gyres. Every solution comes with
|
|
6
|
+
diagnostics (energy and vorticity budgets, boundary-layer widths compared
|
|
7
|
+
with theory, transport closure, energy-concentration entropies) and every
|
|
8
|
+
equation has a derivation in the documentation and in
|
|
9
|
+
``dewi-lanjar explain``.
|
|
10
|
+
|
|
11
|
+
Quick start::
|
|
12
|
+
|
|
13
|
+
from dewi_lanjar import Grid, Params, WindForcing, solve_steady, diagnostics
|
|
14
|
+
|
|
15
|
+
grid = Grid(128, 128)
|
|
16
|
+
p = Params(delta_s=0.04) # Stommel layer, 4 % of the basin width
|
|
17
|
+
sol = solve_steady(grid, p, WindForcing("single-gyre"))
|
|
18
|
+
d = diagnostics.compute(sol.psi, sol.zeta, grid, p, sol.forcing)
|
|
19
|
+
print(diagnostics.describe(d))
|
|
20
|
+
|
|
21
|
+
Named after Dewi Lanjar, queen of the spirits of the northern Java Sea in
|
|
22
|
+
Javanese tradition.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from . import analytic, diagnostics
|
|
26
|
+
from ._version import __version__
|
|
27
|
+
from .analytic import (
|
|
28
|
+
boundary_layer_profile,
|
|
29
|
+
exact_linear,
|
|
30
|
+
sverdrup_interior,
|
|
31
|
+
sverdrup_transport,
|
|
32
|
+
theory_widths,
|
|
33
|
+
)
|
|
34
|
+
from .cases import Case, builtin_names, case_from_dict, load_case, run_case
|
|
35
|
+
from .floats import Floats
|
|
36
|
+
from .forcing import PROFILES, WindForcing
|
|
37
|
+
from .grid import Grid
|
|
38
|
+
from .params import DimensionalScales, Params, beta_at_latitude, boundary_layer_roots
|
|
39
|
+
from .steady import SteadySolution, solve_steady
|
|
40
|
+
from .transient import RunConfig, TransientResult, TransientSolver
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"__version__",
|
|
44
|
+
"Grid", "Params", "DimensionalScales", "WindForcing", "PROFILES",
|
|
45
|
+
"beta_at_latitude", "boundary_layer_roots",
|
|
46
|
+
"solve_steady", "SteadySolution",
|
|
47
|
+
"TransientSolver", "RunConfig", "TransientResult", "Floats",
|
|
48
|
+
"exact_linear", "sverdrup_interior", "sverdrup_transport",
|
|
49
|
+
"boundary_layer_profile", "theory_widths",
|
|
50
|
+
"diagnostics", "analytic",
|
|
51
|
+
"Case", "load_case", "case_from_dict", "builtin_names", "run_case",
|
|
52
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|