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.
Files changed (44) hide show
  1. dewi_lanjar-0.0.1/LICENSE +21 -0
  2. dewi_lanjar-0.0.1/PKG-INFO +243 -0
  3. dewi_lanjar-0.0.1/README.md +206 -0
  4. dewi_lanjar-0.0.1/pyproject.toml +59 -0
  5. dewi_lanjar-0.0.1/setup.cfg +4 -0
  6. dewi_lanjar-0.0.1/src/dewi_lanjar/__init__.py +52 -0
  7. dewi_lanjar-0.0.1/src/dewi_lanjar/__main__.py +3 -0
  8. dewi_lanjar-0.0.1/src/dewi_lanjar/_version.py +1 -0
  9. dewi_lanjar-0.0.1/src/dewi_lanjar/analytic.py +285 -0
  10. dewi_lanjar-0.0.1/src/dewi_lanjar/cases/asymmetric_double_gyre.toml +31 -0
  11. dewi_lanjar-0.0.1/src/dewi_lanjar/cases/beta_emergence.toml +30 -0
  12. dewi_lanjar-0.0.1/src/dewi_lanjar/cases/double_gyre_eddies.toml +39 -0
  13. dewi_lanjar-0.0.1/src/dewi_lanjar/cases/f_plane_control.toml +29 -0
  14. dewi_lanjar-0.0.1/src/dewi_lanjar/cases/inertial_gyre.toml +34 -0
  15. dewi_lanjar-0.0.1/src/dewi_lanjar/cases/inertial_runaway.toml +35 -0
  16. dewi_lanjar-0.0.1/src/dewi_lanjar/cases/munk_1950.toml +42 -0
  17. dewi_lanjar-0.0.1/src/dewi_lanjar/cases/munk_narrowing.toml +32 -0
  18. dewi_lanjar-0.0.1/src/dewi_lanjar/cases/pulsating_wind.toml +36 -0
  19. dewi_lanjar-0.0.1/src/dewi_lanjar/cases/southern_hemisphere.toml +32 -0
  20. dewi_lanjar-0.0.1/src/dewi_lanjar/cases/stommel_1948.toml +39 -0
  21. dewi_lanjar-0.0.1/src/dewi_lanjar/cases/stommel_munk_spinup.toml +33 -0
  22. dewi_lanjar-0.0.1/src/dewi_lanjar/cases.py +422 -0
  23. dewi_lanjar-0.0.1/src/dewi_lanjar/cli.py +321 -0
  24. dewi_lanjar-0.0.1/src/dewi_lanjar/diagnostics.py +327 -0
  25. dewi_lanjar-0.0.1/src/dewi_lanjar/explain.py +345 -0
  26. dewi_lanjar-0.0.1/src/dewi_lanjar/floats.py +95 -0
  27. dewi_lanjar-0.0.1/src/dewi_lanjar/forcing.py +153 -0
  28. dewi_lanjar-0.0.1/src/dewi_lanjar/grid.py +85 -0
  29. dewi_lanjar-0.0.1/src/dewi_lanjar/operators.py +137 -0
  30. dewi_lanjar-0.0.1/src/dewi_lanjar/params.py +320 -0
  31. dewi_lanjar-0.0.1/src/dewi_lanjar/plotting.py +518 -0
  32. dewi_lanjar-0.0.1/src/dewi_lanjar/steady.py +100 -0
  33. dewi_lanjar-0.0.1/src/dewi_lanjar/transient.py +300 -0
  34. dewi_lanjar-0.0.1/src/dewi_lanjar.egg-info/PKG-INFO +243 -0
  35. dewi_lanjar-0.0.1/src/dewi_lanjar.egg-info/SOURCES.txt +42 -0
  36. dewi_lanjar-0.0.1/src/dewi_lanjar.egg-info/dependency_links.txt +1 -0
  37. dewi_lanjar-0.0.1/src/dewi_lanjar.egg-info/entry_points.txt +2 -0
  38. dewi_lanjar-0.0.1/src/dewi_lanjar.egg-info/requires.txt +17 -0
  39. dewi_lanjar-0.0.1/src/dewi_lanjar.egg-info/top_level.txt +1 -0
  40. dewi_lanjar-0.0.1/tests/test_analytic.py +134 -0
  41. dewi_lanjar-0.0.1/tests/test_cli_cases.py +85 -0
  42. dewi_lanjar-0.0.1/tests/test_operators.py +109 -0
  43. dewi_lanjar-0.0.1/tests/test_params_diagnostics.py +109 -0
  44. 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
+ [![Version](https://img.shields.io/badge/version-0.0.1-1f6f8b)](https://github.com/sandyherho/dewi-lanjar)
43
+ [![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-3776AB?logo=python&logoColor=white)](https://www.python.org/)
44
+ [![License: MIT](https://img.shields.io/badge/license-MIT-yellow)](LICENSE)
45
+ [![Docs](https://readthedocs.org/projects/dewi-lanjar/badge/?version=latest)](https://dewi-lanjar.readthedocs.io)
46
+ [![pytest](https://img.shields.io/badge/tested%20with-pytest-0A9EDC?logo=pytest&logoColor=white)](https://pytest.org)
47
+ [![Hypothesis](https://img.shields.io/badge/property%20tests-Hypothesis-6f42c1)](https://hypothesis.readthedocs.io)
48
+ [![Sphinx](https://img.shields.io/badge/docs-Sphinx%20%2B%20Furo-0a507a?logo=sphinx&logoColor=white)](https://www.sphinx-doc.org)
49
+ [![No GUI](https://img.shields.io/badge/interface-library%20%2B%20CLI-2a9d6f)](#use)
50
+
51
+ ![Double gyre: a separating jet that sheds eddies](docs/img/double_gyre_eddies.gif)
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
+ | ![stommel](docs/img/stommel_1948.gif) | ![spinup](docs/img/stommel_munk_spinup.gif) |
182
+ | ![inertial](docs/img/inertial_gyre.gif) | ![beta](docs/img/beta_emergence.gif) |
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
+ [![Version](https://img.shields.io/badge/version-0.0.1-1f6f8b)](https://github.com/sandyherho/dewi-lanjar)
6
+ [![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-3776AB?logo=python&logoColor=white)](https://www.python.org/)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-yellow)](LICENSE)
8
+ [![Docs](https://readthedocs.org/projects/dewi-lanjar/badge/?version=latest)](https://dewi-lanjar.readthedocs.io)
9
+ [![pytest](https://img.shields.io/badge/tested%20with-pytest-0A9EDC?logo=pytest&logoColor=white)](https://pytest.org)
10
+ [![Hypothesis](https://img.shields.io/badge/property%20tests-Hypothesis-6f42c1)](https://hypothesis.readthedocs.io)
11
+ [![Sphinx](https://img.shields.io/badge/docs-Sphinx%20%2B%20Furo-0a507a?logo=sphinx&logoColor=white)](https://www.sphinx-doc.org)
12
+ [![No GUI](https://img.shields.io/badge/interface-library%20%2B%20CLI-2a9d6f)](#use)
13
+
14
+ ![Double gyre: a separating jet that sheds eddies](docs/img/double_gyre_eddies.gif)
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
+ | ![stommel](docs/img/stommel_1948.gif) | ![spinup](docs/img/stommel_munk_spinup.gif) |
145
+ | ![inertial](docs/img/inertial_gyre.gif) | ![beta](docs/img/beta_emergence.gif) |
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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1 @@
1
+ __version__ = "0.0.1"