Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/oceanography/ocean_currents.tex
51 views
unlisted
1
\documentclass[a4paper, 11pt]{article}
2
\usepackage[utf8]{inputenc}
3
\usepackage[T1]{fontenc}
4
\usepackage{amsmath, amssymb}
5
\usepackage{graphicx}
6
\usepackage{booktabs}
7
\usepackage{siunitx}
8
\usepackage[makestderr]{pythontex}
9
10
\title{Ocean Currents: Geostrophic Flow and Wind-Driven Circulation}
11
\author{Physical Oceanography Templates}
12
\date{\today}
13
14
\begin{document}
15
\maketitle
16
17
\section{Introduction}
18
Ocean currents are driven by wind stress, density differences, and Earth's rotation. This template explores geostrophic balance, Ekman spiral dynamics, Sverdrup transport, and western boundary current intensification.
19
20
\section{Mathematical Framework}
21
22
\subsection{Geostrophic Balance}
23
Large-scale ocean flow balances Coriolis force and pressure gradient:
24
\begin{equation}
25
f \mathbf{k} \times \mathbf{u}_g = -\frac{1}{\rho_0} \nabla p
26
\end{equation}
27
where $f = 2\Omega \sin\phi$ is the Coriolis parameter.
28
29
Component form:
30
\begin{align}
31
u_g &= -\frac{1}{\rho_0 f} \frac{\partial p}{\partial y} \\
32
v_g &= \frac{1}{\rho_0 f} \frac{\partial p}{\partial x}
33
\end{align}
34
35
\subsection{Ekman Transport}
36
Wind stress drives Ekman transport perpendicular to wind direction:
37
\begin{equation}
38
\mathbf{M}_E = \frac{\boldsymbol{\tau} \times \mathbf{k}}{\rho_0 f}
39
\end{equation}
40
41
The Ekman spiral describes velocity decay with depth:
42
\begin{align}
43
u(z) &= V_0 e^{z/D_E} \cos\left(\frac{\pi}{4} + \frac{z}{D_E}\right) \\
44
v(z) &= V_0 e^{z/D_E} \sin\left(\frac{\pi}{4} + \frac{z}{D_E}\right)
45
\end{align}
46
where $D_E = \sqrt{2A_v/f}$ is the Ekman depth.
47
48
\subsection{Sverdrup Balance}
49
Wind stress curl drives meridional transport:
50
\begin{equation}
51
\beta V = \frac{1}{\rho_0} \nabla \times \boldsymbol{\tau}
52
\end{equation}
53
where $\beta = \partial f/\partial y$ is the planetary vorticity gradient.
54
55
\subsection{Western Boundary Current}
56
Munk's model includes lateral friction:
57
\begin{equation}
58
\beta v = A_H \nabla^4 \psi + \frac{1}{\rho_0 H} \left(\frac{\partial \tau_y}{\partial x} - \frac{\partial \tau_x}{\partial y}\right)
59
\end{equation}
60
61
\section{Environment Setup}
62
63
\begin{pycode}
64
import numpy as np
65
import matplotlib.pyplot as plt
66
from scipy.integrate import odeint
67
68
plt.rc('text', usetex=True)
69
plt.rc('font', family='serif')
70
np.random.seed(42)
71
72
def save_plot(filename, caption=""):
73
plt.savefig(filename, bbox_inches='tight', dpi=150)
74
print(r'\begin{figure}[htbp]')
75
print(r'\centering')
76
print(r'\includegraphics[width=0.9\textwidth]{' + filename + '}')
77
if caption:
78
print(r'\caption{' + caption + '}')
79
print(r'\end{figure}')
80
plt.close()
81
82
# Physical constants
83
Omega = 7.292e-5 # Earth's rotation rate (rad/s)
84
rho_0 = 1025 # Reference density (kg/m^3)
85
g = 9.81 # Gravity (m/s^2)
86
\end{pycode}
87
88
\section{Geostrophic Flow Computation}
89
90
\begin{pycode}
91
# Create pressure field for geostrophic calculation
92
nx, ny = 50, 50
93
x = np.linspace(0, 1000e3, nx) # 1000 km domain
94
y = np.linspace(0, 1000e3, ny)
95
X, Y = np.meshgrid(x, y)
96
97
# Sea surface height anomaly (mesoscale eddy)
98
eta = 0.3 * np.exp(-((X - 500e3)**2 + (Y - 500e3)**2) / (200e3)**2)
99
100
# Coriolis parameter (mid-latitude)
101
lat = 30 # degrees
102
f = 2 * Omega * np.sin(np.radians(lat))
103
beta = 2 * Omega * np.cos(np.radians(lat)) / 6.371e6
104
105
# Geostrophic velocities from SSH gradient
106
dx = x[1] - x[0]
107
dy = y[1] - y[0]
108
109
deta_dx = np.gradient(eta, dx, axis=1)
110
deta_dy = np.gradient(eta, dy, axis=0)
111
112
u_g = -g / f * deta_dy
113
v_g = g / f * deta_dx
114
115
speed = np.sqrt(u_g**2 + v_g**2)
116
117
# Visualization
118
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
119
120
# Plot 1: Sea surface height
121
im1 = axes[0, 0].contourf(X/1e3, Y/1e3, eta*100, levels=20, cmap='RdBu_r')
122
axes[0, 0].set_xlabel('x (km)')
123
axes[0, 0].set_ylabel('y (km)')
124
axes[0, 0].set_title('Sea Surface Height Anomaly (cm)')
125
plt.colorbar(im1, ax=axes[0, 0])
126
127
# Plot 2: Geostrophic velocity vectors
128
skip = 3
129
axes[0, 1].quiver(X[::skip, ::skip]/1e3, Y[::skip, ::skip]/1e3,
130
u_g[::skip, ::skip], v_g[::skip, ::skip],
131
speed[::skip, ::skip], cmap='viridis', alpha=0.8)
132
axes[0, 1].set_xlabel('x (km)')
133
axes[0, 1].set_ylabel('y (km)')
134
axes[0, 1].set_title('Geostrophic Velocity Field')
135
136
# Plot 3: Speed magnitude
137
im3 = axes[1, 0].contourf(X/1e3, Y/1e3, speed*100, levels=20, cmap='plasma')
138
axes[1, 0].contour(X/1e3, Y/1e3, eta*100, levels=10, colors='white', linewidths=0.5)
139
axes[1, 0].set_xlabel('x (km)')
140
axes[1, 0].set_ylabel('y (km)')
141
axes[1, 0].set_title('Current Speed (cm/s)')
142
plt.colorbar(im3, ax=axes[1, 0])
143
144
# Plot 4: Cross-section of velocity
145
mid_idx = ny // 2
146
axes[1, 1].plot(x/1e3, u_g[mid_idx, :]*100, 'b-', label='u (E-W)', linewidth=2)
147
axes[1, 1].plot(x/1e3, v_g[mid_idx, :]*100, 'r-', label='v (N-S)', linewidth=2)
148
axes[1, 1].axhline(y=0, color='k', linestyle='--', linewidth=0.5)
149
axes[1, 1].set_xlabel('x (km)')
150
axes[1, 1].set_ylabel('Velocity (cm/s)')
151
axes[1, 1].set_title(f'Cross-section at y = {y[mid_idx]/1e3:.0f} km')
152
axes[1, 1].legend()
153
axes[1, 1].grid(True, alpha=0.3)
154
155
plt.tight_layout()
156
save_plot('geostrophic_flow.pdf', 'Geostrophic flow around a mesoscale eddy')
157
158
max_speed = np.max(speed) * 100
159
\end{pycode}
160
161
\section{Ekman Spiral}
162
163
\begin{pycode}
164
# Ekman layer parameters
165
A_v = 0.01 # Vertical eddy viscosity (m^2/s)
166
D_E = np.sqrt(2 * A_v / f) # Ekman depth
167
168
# Wind stress
169
tau_x = 0.1 # N/m^2 (eastward wind)
170
tau_y = 0.0
171
172
# Surface velocity
173
V_0 = tau_x / (rho_0 * np.sqrt(A_v * f / 2))
174
175
# Depth array
176
z = np.linspace(0, -5 * D_E, 100)
177
178
# Ekman spiral velocities
179
u_ekman = V_0 * np.exp(z / D_E) * np.cos(np.pi/4 + z/D_E)
180
v_ekman = V_0 * np.exp(z / D_E) * np.sin(np.pi/4 + z/D_E)
181
182
# Ekman transport
183
M_E_x = -tau_y / (rho_0 * f)
184
M_E_y = tau_x / (rho_0 * f)
185
186
# Visualization
187
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
188
189
# Plot 1: Ekman spiral hodograph
190
axes[0, 0].plot(u_ekman*100, v_ekman*100, 'b-', linewidth=2)
191
axes[0, 0].scatter([u_ekman[0]*100], [v_ekman[0]*100], c='red', s=100, zorder=5, label='Surface')
192
axes[0, 0].scatter([0], [0], c='black', s=50, marker='x')
193
axes[0, 0].arrow(0, 0, 5, 0, head_width=0.3, head_length=0.2, fc='green', ec='green', label='Wind')
194
axes[0, 0].set_xlabel('u (cm/s)')
195
axes[0, 0].set_ylabel('v (cm/s)')
196
axes[0, 0].set_title('Ekman Spiral Hodograph')
197
axes[0, 0].axis('equal')
198
axes[0, 0].grid(True, alpha=0.3)
199
axes[0, 0].legend()
200
201
# Plot 2: Velocity profiles with depth
202
axes[0, 1].plot(u_ekman*100, z, 'b-', label='u', linewidth=2)
203
axes[0, 1].plot(v_ekman*100, z, 'r-', label='v', linewidth=2)
204
axes[0, 1].axvline(x=0, color='k', linestyle='--', linewidth=0.5)
205
axes[0, 1].axhline(y=-D_E, color='gray', linestyle=':', label=f'$D_E$ = {D_E:.1f} m')
206
axes[0, 1].set_xlabel('Velocity (cm/s)')
207
axes[0, 1].set_ylabel('Depth (m)')
208
axes[0, 1].set_title('Ekman Velocity Profiles')
209
axes[0, 1].legend()
210
axes[0, 1].grid(True, alpha=0.3)
211
212
# Plot 3: 3D Ekman spiral
213
from mpl_toolkits.mplot3d import Axes3D
214
ax3d = fig.add_subplot(2, 2, 3, projection='3d')
215
ax3d.plot(u_ekman*100, v_ekman*100, z, 'b-', linewidth=2)
216
ax3d.scatter([u_ekman[0]*100], [v_ekman[0]*100], [z[0]], c='red', s=100)
217
ax3d.set_xlabel('u (cm/s)')
218
ax3d.set_ylabel('v (cm/s)')
219
ax3d.set_zlabel('Depth (m)')
220
ax3d.set_title('3D Ekman Spiral')
221
222
# Plot 4: Cumulative transport
223
cumulative_u = np.cumsum(u_ekman) * np.abs(z[1] - z[0])
224
cumulative_v = np.cumsum(v_ekman) * np.abs(z[1] - z[0])
225
axes[1, 1].plot(cumulative_u, z, 'b-', label='$M_x$', linewidth=2)
226
axes[1, 1].plot(cumulative_v, z, 'r-', label='$M_y$', linewidth=2)
227
axes[1, 1].axvline(x=M_E_y, color='r', linestyle='--', alpha=0.5, label=f'$M_E$ = {M_E_y:.2f}')
228
axes[1, 1].set_xlabel('Cumulative Transport (m$^2$/s)')
229
axes[1, 1].set_ylabel('Depth (m)')
230
axes[1, 1].set_title('Ekman Transport Integration')
231
axes[1, 1].legend()
232
axes[1, 1].grid(True, alpha=0.3)
233
234
plt.tight_layout()
235
save_plot('ekman_spiral.pdf', 'Ekman spiral and transport in the surface boundary layer')
236
\end{pycode}
237
238
\section{Sverdrup Transport}
239
240
\begin{pycode}
241
# Basin setup for Sverdrup calculation
242
Lx = 6000e3 # Basin width (m)
243
Ly = 3000e3 # Basin length (m)
244
nx, ny = 100, 50
245
246
x_s = np.linspace(0, Lx, nx)
247
y_s = np.linspace(0, Ly, ny)
248
X_s, Y_s = np.meshgrid(x_s, y_s)
249
250
# Latitude range (20-50 degrees N)
251
lat_s = 20 + 30 * Y_s / Ly
252
f_s = 2 * Omega * np.sin(np.radians(lat_s))
253
beta_s = 2 * Omega * np.cos(np.radians(lat_s)) / 6.371e6
254
255
# Wind stress (idealized westerlies/trades)
256
tau_x_s = -0.1 * np.cos(2 * np.pi * Y_s / Ly) # Sinusoidal wind pattern
257
tau_y_s = np.zeros_like(X_s)
258
259
# Wind stress curl
260
dtau_x_dy = np.gradient(tau_x_s, y_s[1] - y_s[0], axis=0)
261
curl_tau = -dtau_x_dy # For tau_y = 0
262
263
# Sverdrup transport
264
V_sv = curl_tau / (rho_0 * beta_s)
265
266
# Integrate from eastern boundary to get stream function
267
psi = np.zeros_like(V_sv)
268
for i in range(nx-2, -1, -1):
269
psi[:, i] = psi[:, i+1] - V_sv[:, i] * (x_s[1] - x_s[0])
270
271
# Calculate interior velocities
272
u_sv = -np.gradient(psi, y_s[1] - y_s[0], axis=0)
273
v_sv = np.gradient(psi, x_s[1] - x_s[0], axis=1)
274
275
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
276
277
# Plot 1: Wind stress pattern
278
im1 = axes[0, 0].contourf(X_s/1e3, Y_s/1e3, tau_x_s, levels=20, cmap='RdBu_r')
279
axes[0, 0].set_xlabel('x (km)')
280
axes[0, 0].set_ylabel('y (km)')
281
axes[0, 0].set_title('Zonal Wind Stress (N/m$^2$)')
282
plt.colorbar(im1, ax=axes[0, 0])
283
284
# Plot 2: Wind stress curl
285
im2 = axes[0, 1].contourf(X_s/1e3, Y_s/1e3, curl_tau*1e7, levels=20, cmap='RdBu_r')
286
axes[0, 1].set_xlabel('x (km)')
287
axes[0, 1].set_ylabel('y (km)')
288
axes[0, 1].set_title('Wind Stress Curl ($\\times 10^{-7}$ N/m$^3$)')
289
plt.colorbar(im2, ax=axes[0, 1])
290
291
# Plot 3: Sverdrup stream function
292
levels = np.linspace(psi.min(), psi.max(), 20)
293
im3 = axes[1, 0].contourf(X_s/1e3, Y_s/1e3, psi/1e6, levels=20, cmap='coolwarm')
294
cs = axes[1, 0].contour(X_s/1e3, Y_s/1e3, psi/1e6, levels=15, colors='black', linewidths=0.5)
295
axes[1, 0].set_xlabel('x (km)')
296
axes[1, 0].set_ylabel('y (km)')
297
axes[1, 0].set_title('Stream Function (Sv)')
298
plt.colorbar(im3, ax=axes[1, 0])
299
300
# Plot 4: Meridional transport
301
axes[1, 1].plot(y_s/1e3, V_sv[:, nx//2]/1e6, 'b-', linewidth=2)
302
axes[1, 1].axhline(y=0, color='k', linestyle='--', linewidth=0.5)
303
axes[1, 1].set_xlabel('y (km)')
304
axes[1, 1].set_ylabel('V (Sv/m)')
305
axes[1, 1].set_title('Sverdrup Meridional Transport')
306
axes[1, 1].grid(True, alpha=0.3)
307
308
plt.tight_layout()
309
save_plot('sverdrup_transport.pdf', 'Sverdrup wind-driven circulation')
310
311
max_transport = np.max(np.abs(psi)) / 1e6
312
\end{pycode}
313
314
\section{Western Boundary Current}
315
316
\begin{pycode}
317
# Munk layer solution for western boundary current
318
# Simplified 1D model
319
320
# Parameters
321
A_H = 1e4 # Horizontal eddy viscosity (m^2/s)
322
H = 1000 # Layer depth (m)
323
L_basin = 5000e3 # Basin width (m)
324
325
# Munk layer width
326
delta_M = (A_H / beta)**0.333
327
328
# Interior Sverdrup transport (constant for simplicity)
329
psi_I = 30e6 # 30 Sv
330
331
# Distance from western boundary
332
x_wb = np.linspace(0, 500e3, 500)
333
334
# Munk solution (normalized)
335
xi = x_wb / delta_M
336
psi_wb = psi_I * (1 - np.exp(-xi/2) * (np.cos(np.sqrt(3)/2 * xi) +
337
1/np.sqrt(3) * np.sin(np.sqrt(3)/2 * xi)))
338
339
# Velocity in western boundary layer
340
v_wb = np.gradient(psi_wb, x_wb[1] - x_wb[0]) / H
341
342
# Also compute Stommel solution (bottom friction)
343
r = 1e-7 # Bottom friction coefficient
344
delta_S = r / beta
345
psi_stommel = psi_I * (1 - np.exp(-x_wb / delta_S))
346
v_stommel = np.gradient(psi_stommel, x_wb[1] - x_wb[0]) / H
347
348
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
349
350
# Plot 1: Stream function comparison
351
axes[0, 0].plot(x_wb/1e3, psi_wb/1e6, 'b-', linewidth=2, label='Munk')
352
axes[0, 0].plot(x_wb/1e3, psi_stommel/1e6, 'r--', linewidth=2, label='Stommel')
353
axes[0, 0].axhline(y=psi_I/1e6, color='gray', linestyle=':', label='Interior')
354
axes[0, 0].axvline(x=delta_M/1e3, color='b', linestyle=':', alpha=0.5)
355
axes[0, 0].axvline(x=delta_S/1e3, color='r', linestyle=':', alpha=0.5)
356
axes[0, 0].set_xlabel('Distance from western boundary (km)')
357
axes[0, 0].set_ylabel('$\\psi$ (Sv)')
358
axes[0, 0].set_title('Western Boundary Current Structure')
359
axes[0, 0].legend()
360
axes[0, 0].grid(True, alpha=0.3)
361
362
# Plot 2: Velocity profiles
363
axes[0, 1].plot(x_wb/1e3, v_wb*100, 'b-', linewidth=2, label='Munk')
364
axes[0, 1].plot(x_wb/1e3, v_stommel*100, 'r--', linewidth=2, label='Stommel')
365
axes[0, 1].set_xlabel('Distance from western boundary (km)')
366
axes[0, 1].set_ylabel('v (cm/s)')
367
axes[0, 1].set_title('Northward Velocity')
368
axes[0, 1].legend()
369
axes[0, 1].grid(True, alpha=0.3)
370
axes[0, 1].set_xlim(0, 200)
371
372
# Plot 3: 2D gyre circulation (simplified)
373
nx_gyre, ny_gyre = 100, 50
374
x_gyre = np.linspace(0, L_basin, nx_gyre)
375
y_gyre = np.linspace(0, Ly, ny_gyre)
376
X_gyre, Y_gyre = np.meshgrid(x_gyre, y_gyre)
377
378
# Simple subtropical gyre
379
psi_gyre = psi_I * np.sin(np.pi * Y_gyre / Ly)
380
# Western intensification
381
xi_gyre = X_gyre / delta_M
382
wb_factor = 1 - np.exp(-xi_gyre/2) * (np.cos(np.sqrt(3)/2 * xi_gyre) +
383
1/np.sqrt(3) * np.sin(np.sqrt(3)/2 * xi_gyre))
384
wb_factor = np.clip(wb_factor, 0, 1)
385
psi_gyre = psi_gyre * wb_factor
386
387
im3 = axes[1, 0].contourf(X_gyre/1e3, Y_gyre/1e3, psi_gyre/1e6, levels=20, cmap='coolwarm')
388
cs = axes[1, 0].contour(X_gyre/1e3, Y_gyre/1e3, psi_gyre/1e6, levels=10, colors='black', linewidths=0.5)
389
axes[1, 0].set_xlabel('x (km)')
390
axes[1, 0].set_ylabel('y (km)')
391
axes[1, 0].set_title('Subtropical Gyre with Western Intensification')
392
plt.colorbar(im3, ax=axes[1, 0], label='$\\psi$ (Sv)')
393
394
# Plot 4: Cross-basin transport profile
395
mid_y = ny_gyre // 2
396
axes[1, 1].plot(x_gyre/1e3, psi_gyre[mid_y, :]/1e6, 'b-', linewidth=2)
397
axes[1, 1].axvline(x=delta_M/1e3, color='r', linestyle='--',
398
label=f'$\\delta_M$ = {delta_M/1e3:.0f} km')
399
axes[1, 1].set_xlabel('x (km)')
400
axes[1, 1].set_ylabel('$\\psi$ (Sv)')
401
axes[1, 1].set_title('Cross-basin Transport Profile')
402
axes[1, 1].legend()
403
axes[1, 1].grid(True, alpha=0.3)
404
405
plt.tight_layout()
406
save_plot('western_boundary.pdf', 'Western boundary current dynamics')
407
408
max_wbc_vel = np.max(v_wb) * 100
409
\end{pycode}
410
411
\section{Results Summary}
412
413
\subsection{Geostrophic Flow Statistics}
414
\begin{pycode}
415
print(r'\begin{table}[htbp]')
416
print(r'\centering')
417
print(r'\caption{Geostrophic flow around mesoscale eddy}')
418
print(r'\begin{tabular}{lr}')
419
print(r'\toprule')
420
print(r'Parameter & Value \\')
421
print(r'\midrule')
422
print(f"Maximum SSH anomaly & {np.max(eta)*100:.1f} cm \\\\")
423
print(f"Maximum current speed & {max_speed:.1f} cm/s \\\\")
424
print(f"Coriolis parameter & {f:.2e} s$^{{-1}}$ \\\\")
425
print(f"Eddy radius & 200 km \\\\")
426
print(r'\bottomrule')
427
print(r'\end{tabular}')
428
print(r'\end{table}')
429
\end{pycode}
430
431
\subsection{Ekman Layer Parameters}
432
\begin{pycode}
433
print(r'\begin{table}[htbp]')
434
print(r'\centering')
435
print(r'\caption{Ekman spiral characteristics}')
436
print(r'\begin{tabular}{lr}')
437
print(r'\toprule')
438
print(r'Parameter & Value \\')
439
print(r'\midrule')
440
print(f"Ekman depth & {D_E:.1f} m \\\\")
441
print(f"Surface velocity & {V_0*100:.1f} cm/s \\\\")
442
print(f"Wind stress & {tau_x:.2f} N/m$^2$ \\\\")
443
print(f"Ekman transport & {M_E_y:.2f} m$^2$/s \\\\")
444
print(f"Surface deflection & 45$^\\circ$ \\\\")
445
print(r'\bottomrule')
446
print(r'\end{tabular}')
447
print(r'\end{table}')
448
\end{pycode}
449
450
\subsection{Gyre Circulation}
451
\begin{pycode}
452
print(r'\begin{table}[htbp]')
453
print(r'\centering')
454
print(r'\caption{Wind-driven gyre parameters}')
455
print(r'\begin{tabular}{lr}')
456
print(r'\toprule')
457
print(r'Parameter & Value \\')
458
print(r'\midrule')
459
print(f"Maximum transport & {max_transport:.1f} Sv \\\\")
460
print(f"Munk layer width & {delta_M/1e3:.0f} km \\\\")
461
print(f"Stommel layer width & {delta_S/1e3:.0f} km \\\\")
462
print(f"Max WBC velocity & {max_wbc_vel:.1f} cm/s \\\\")
463
print(f"Basin width & {L_basin/1e3:.0f} km \\\\")
464
print(r'\bottomrule')
465
print(r'\end{tabular}')
466
print(r'\end{table}')
467
\end{pycode}
468
469
\subsection{Physical Summary}
470
\begin{itemize}
471
\item Ekman depth: \py{f"{D_E:.1f}"} m
472
\item Munk boundary layer: \py{f"{delta_M/1e3:.0f}"} km
473
\item Maximum gyre transport: \py{f"{max_transport:.1f}"} Sv
474
\item WBC velocity: \py{f"{max_wbc_vel:.1f}"} cm/s
475
\end{itemize}
476
477
\section{Conclusion}
478
This template demonstrates the fundamental dynamics of ocean circulation. Geostrophic balance governs large-scale flow, with velocities determined by pressure gradients and Earth's rotation. The Ekman spiral shows how wind stress drives surface currents deflected from the wind direction. Sverdrup theory relates interior transport to wind stress curl, while western boundary current intensification results from the need to close the gyre circulation and conserve vorticity.
479
480
\end{document}
481
482