Skip to content

Commit c7d2dce

Browse files
authored
Merge pull request #55 from OceanParcels/idealised_example
Creating an idealised example
2 parents a692124 + 9e4b578 commit c7d2dce

File tree

3 files changed

+1103
-1
lines changed

3 files changed

+1103
-1
lines changed

docs/examples.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Examples
22
========
33

4-
``plasticparcels`` has a few example notebooks, see below.
4+
``plasticparcels`` has a few example notebooks, see below. Most of these examples require hydrodynamic, physical, and biogeochemical model data, however, the idealised flow example requires no additional data.
55

66

77

@@ -14,4 +14,5 @@ Examples
1414
examples/example_Croatian_fisheries.ipynb
1515
examples/example_add_your_own_kernel.ipynb
1616
examples/example_initialisation_maps.ipynb
17+
examples/example_idealised_flow.ipynb
1718

docs/examples/example_idealised_flow.ipynb

Lines changed: 843 additions & 0 deletions
Large diffs are not rendered by default.

docs/examples/idealised_flow.py

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import parcels
2+
import plasticparcels as pp
3+
4+
from datetime import timedelta, datetime
5+
import numpy as np
6+
import xarray as xr
7+
8+
def create_fieldset(indices=None):
9+
""" Create a fieldset with a Bickley Jet, temperature/salinity, biogeochemistry, wind and Stokes drift fields.
10+
To be used with the parcels.FieldSet.from_modulefile() method."""
11+
# List of times the analytic fieldset is evaluated on
12+
times = np.arange(0, 5.1, 0.1) * 86400
13+
14+
# Create the fieldset object
15+
fieldset = bickleyjet_fieldset_3d(times=times)
16+
fieldset = add_uniform_temp_salt_field(fieldset, times)
17+
fieldset = add_biogeochemistry_field(fieldset, times)
18+
fieldset = add_wind_field(fieldset, times)
19+
fieldset = add_stokes_field(fieldset, times)
20+
21+
return fieldset
22+
23+
def bickleyjet_fieldset_3d(times, xdim=51, ydim=51, zdim=11):
24+
""" A Bickley Jet Field based on Hadjighasem et al 2017, 10.1063/1.4982720,
25+
with a vertical dimension introduced via a linear decay of the 2D field in depth."""
26+
27+
U0 = 0.06266
28+
L = 1770.0
29+
r0 = 6371.0
30+
k1 = 2 * 1 / r0
31+
k2 = 2 * 2 / r0
32+
k3 = 2 * 3 / r0
33+
eps1 = 0.075
34+
eps2 = 0.4
35+
eps3 = 0.3
36+
c3 = 0.461 * U0
37+
c2 = 0.205 * U0
38+
c1 = c3 + ((np.sqrt(5) - 1) / 2.0) * (k2 / k1) * (c2 - c3)
39+
40+
a, b = np.pi * r0, 7000.0 # domain size
41+
lon = np.linspace(0, a, xdim, dtype=np.float32)
42+
lat = np.linspace(-b / 2, b / 2, ydim, dtype=np.float32)
43+
depth = np.linspace(0, 1000, zdim, dtype=np.float32)
44+
dx, dy = lon[2] - lon[1], lat[2] - lat[1]
45+
46+
U = np.zeros((times.size, depth.size, lat.size, lon.size), dtype=np.float32)
47+
V = np.zeros((times.size, depth.size, lat.size, lon.size), dtype=np.float32)
48+
W = np.zeros((times.size, depth.size, lat.size, lon.size), dtype=np.float32)
49+
50+
for i in range(lon.size):
51+
for j in range(lat.size):
52+
x1 = lon[i] - dx / 2
53+
x2 = lat[j] - dy / 2
54+
for t in range(len(times)):
55+
time = times[t]
56+
57+
f1 = eps1 * np.exp(-1j * k1 * c1 * time)
58+
f2 = eps2 * np.exp(-1j * k2 * c2 * time)
59+
f3 = eps3 * np.exp(-1j * k3 * c3 * time)
60+
F1 = f1 * np.exp(1j * k1 * x1)
61+
F2 = f2 * np.exp(1j * k2 * x1)
62+
F3 = f3 * np.exp(1j * k3 * x1)
63+
G = np.real(np.sum([F1, F2, F3]))
64+
G_x = np.real(np.sum([1j * k1 * F1, 1j * k2 * F2, 1j * k3 * F3]))
65+
U[t, 0, j, i] = (
66+
U0 / (np.cosh(x2 / L) ** 2)
67+
+ 2 * U0 * np.sinh(x2 / L) / (np.cosh(x2 / L) ** 3) * G
68+
)
69+
V[t, 0, j, i] = U0 * L * (1.0 / np.cosh(x2 / L)) ** 2 * G_x
70+
71+
# Add a linear decay in depth
72+
for k in range(1, depth.size):
73+
U[:, k, :, :] = U[:, 0, :, :] * ( (depth.size - k) / depth.size)
74+
V[:, k, :, :] = V[:, 0, :, :] * ( (depth.size - k) / depth.size)
75+
76+
# Construct the fieldset
77+
data = {"U": U, "V": V, "W": W}
78+
dimensions = {"lon": lon, "lat": lat, "depth": depth, "time": times}
79+
allow_time_extrapolation = True if len(times) == 1 else False
80+
fieldset = parcels.FieldSet.from_data(
81+
data, dimensions, mesh="flat", allow_time_extrapolation=allow_time_extrapolation
82+
)
83+
84+
fieldset.U.interp_method = "cgrid_velocity"
85+
fieldset.V.interp_method = "cgrid_velocity"
86+
fieldset.W.interp_method = "cgrid_velocity"
87+
88+
# Add a flat bathymetry field
89+
bathymetry = np.zeros((lat.size, lon.size), dtype=np.float32)
90+
bathymetry[:, :] = depth[-1]
91+
data = {"bathymetry": bathymetry}
92+
dimensions = {"lon": lon, "lat": lat}
93+
94+
fieldsetBathymetry = parcels.FieldSet.from_data(
95+
data, dimensions, mesh="flat"
96+
)
97+
98+
fieldset.add_field(fieldsetBathymetry.bathymetry)
99+
100+
# Add in an unbeaching field
101+
unbeach_U = np.zeros((lat.size, lon.size), dtype=np.float32)
102+
unbeach_V = np.zeros((lat.size, lon.size), dtype=np.float32)
103+
104+
unbeach_V[0,:] = 1.0 # Meridional unbeaching at the top and bottom of the domain
105+
unbeach_V[-1,:] = -1.0
106+
107+
data = {"unbeach_U": unbeach_U, "unbeach_V": unbeach_V}
108+
dimensions = {"lon": lon, "lat": lat}
109+
110+
fieldsetunbeaching = parcels.FieldSet.from_data(
111+
data, dimensions, mesh="flat"
112+
)
113+
114+
fieldset.add_field(fieldsetunbeaching.unbeach_U)
115+
fieldset.add_field(fieldsetunbeaching.unbeach_V)
116+
117+
118+
return fieldset
119+
120+
def add_uniform_temp_salt_field(fieldset, times):
121+
""" Add a uniform temperature and salinity field to the fieldset.
122+
The temperature/salinity field is time-invariant and has a linear decay with depth.
123+
Of course, this makes no sense in reality, but it is a simple example."""
124+
lon = fieldset.U.grid.lon
125+
lat = fieldset.U.grid.lat
126+
depth = fieldset.U.grid.depth
127+
128+
T = np.zeros((times.size, depth.size, lat.size, lon.size), dtype=np.float32)
129+
S = np.zeros((times.size, depth.size, lat.size, lon.size), dtype=np.float32)
130+
131+
T[:, 0, :, :] = 25.0
132+
S[:, 0, :, :] = 35.0
133+
134+
for d in range(1, depth.size):
135+
T[:, d, :, :] = T[:, 0, :, :]
136+
S[:, d, :, :] = S[:, 0, :, :]
137+
138+
data = {"conservative_temperature": T, "absolute_salinity": S}
139+
140+
dimensions = {"lon": lon, "lat": lat, "depth": depth, "time": times}
141+
allow_time_extrapolation = True if len(times) == 1 else False
142+
fieldsetTS = parcels.FieldSet.from_data(
143+
data, dimensions, mesh="flat", allow_time_extrapolation=allow_time_extrapolation
144+
)
145+
146+
#fieldsetTS.add_periodic_halo(zonal=True)
147+
fieldset.add_field(fieldsetTS.conservative_temperature)
148+
fieldset.add_field(fieldsetTS.absolute_salinity)
149+
150+
return fieldset
151+
152+
def add_biogeochemistry_field(fieldset, times):
153+
lon = fieldset.U.grid.lon # Is this right?
154+
lat = fieldset.U.grid.lat
155+
depth = fieldset.U.grid.depth
156+
157+
pp_phyto = np.zeros((times.size, depth.size, lat.size, lon.size), dtype=np.float32)
158+
bio_nanophy = np.zeros((times.size, depth.size, lat.size, lon.size), dtype=np.float32)
159+
bio_diatom = np.zeros((times.size, depth.size, lat.size, lon.size), dtype=np.float32)
160+
161+
162+
_, yy = np.meshgrid(fieldset.U.grid.lon, fieldset.U.grid.lat)
163+
r0 = 6371.0
164+
_, dy = np.pi * r0, 7000.0 # domain size
165+
f_pp_phyto = np.abs(np.cos(16. * yy / dy))
166+
f_bio_nanophy = np.abs(np.cos(8. * yy / dy + np.pi / 24.)) # shifted
167+
f_bio_diatom = np.abs(np.cos(4. * yy / dy - np.pi / 24.)) # shifted
168+
169+
pp_phyto[:, 0, :, :] = 50. * f_pp_phyto # TODO: Get the units right!
170+
bio_nanophy[:, 0, :, :] = 5. * f_bio_nanophy
171+
bio_diatom[:, 0, :, :] = 10. * f_bio_diatom
172+
173+
for d in range(1, depth.size):
174+
pp_phyto[:, d, :, :] = pp_phyto[:, 0, :, :] * ( (depth.size - 0.5 * d) / depth.size)
175+
bio_nanophy[:, d, :, :] = bio_nanophy[:, 0, :, :] * ( (depth.size - 0.5 * d) / depth.size)
176+
bio_diatom[:, d, :, :] = bio_diatom[:, 0, :, :] * ( (depth.size - 0.5 * d) / depth.size)
177+
178+
data = {"pp_phyto": pp_phyto, "bio_nanophy": bio_nanophy, "bio_diatom": bio_diatom}
179+
180+
dimensions = {"lon": lon, "lat": lat, "depth": depth, "time": times}
181+
allow_time_extrapolation = True if len(times) == 1 else False
182+
fieldsetbgc = parcels.FieldSet.from_data(
183+
data, dimensions, mesh="flat", allow_time_extrapolation=allow_time_extrapolation
184+
)
185+
186+
fieldset.add_field(fieldsetbgc.pp_phyto)
187+
fieldset.add_field(fieldsetbgc.bio_nanophy)
188+
fieldset.add_field(fieldsetbgc.bio_diatom)
189+
190+
return fieldset
191+
192+
193+
def add_wind_field(fieldset, times):
194+
""" Horizontal wind field with a sinusoidal variation in time."""
195+
lon = fieldset.U.grid.lon
196+
lat = fieldset.U.grid.lat
197+
198+
wind_U = np.zeros((times.size, lat.size, lon.size), dtype=np.float32)
199+
wind_V = np.zeros((times.size, lat.size, lon.size), dtype=np.float32)
200+
201+
_, yy = np.meshgrid(fieldset.U.grid.lon, fieldset.U.grid.lat)
202+
r0 = 6371.0
203+
_, dy = np.pi * r0, 7000.0 # domain size
204+
f_wind = np.cos(yy/dy)
205+
206+
wind_U[0, :, :] = 10. * f_wind
207+
208+
# Vary the wind speed with time
209+
for time in range(1, times.size):
210+
wind_U[time, :, :] = wind_U[0, :, :] * np.sin(2. * np.pi * time / times.size)
211+
212+
data = {"Wind_U": wind_U, "Wind_V": wind_V}
213+
214+
dimensions = {"lon": lon, "lat": lat, "time": times}
215+
allow_time_extrapolation = True if len(times) == 1 else False
216+
fieldsetwind = parcels.FieldSet.from_data(
217+
data, dimensions, mesh="flat", allow_time_extrapolation=allow_time_extrapolation
218+
)
219+
220+
fieldset.add_field(fieldsetwind.Wind_U)
221+
fieldset.add_field(fieldsetwind.Wind_V)
222+
223+
return fieldset
224+
225+
def add_stokes_field(fieldset, times):
226+
""" Horizontal wave field that varies in time."""
227+
lon = fieldset.U.grid.lon
228+
lat = fieldset.U.grid.lat
229+
230+
stokes_U = np.zeros((times.size, lat.size, lon.size), dtype=np.float32)
231+
stokes_V = np.zeros((times.size, lat.size, lon.size), dtype=np.float32)
232+
wave_Tp = np.zeros((times.size, lat.size, lon.size), dtype=np.float32)
233+
234+
xx, yy = np.meshgrid(fieldset.U.grid.lon, fieldset.U.grid.lat)
235+
r0 = 6371.0
236+
dx, dy = np.pi * r0, 7000.0 # domain size
237+
238+
stokes_U[0, :, :] = 5. * np.sin(xx * np.pi / (dx) )
239+
wave_Tp[0, :, :] = 10. * np.cos(yy/dy)
240+
241+
# Vary in time
242+
for time in range(1, times.size):
243+
stokes_U[time, :, :] = stokes_U[0, :, :] * np.sin(2. * np.pi * time / times.size)
244+
wave_Tp[time, :, :] = wave_Tp[0, :, :]
245+
246+
data = {"Stokes_U": stokes_U, "Stokes_V": stokes_V, 'wave_Tp': wave_Tp}
247+
248+
dimensions = {"lon": lon, "lat": lat, "time": times}
249+
allow_time_extrapolation = True if len(times) == 1 else False
250+
fieldsetStokes = parcels.FieldSet.from_data(
251+
data, dimensions, mesh="flat", allow_time_extrapolation=allow_time_extrapolation
252+
)
253+
254+
fieldset.add_field(fieldsetStokes.Stokes_U)
255+
fieldset.add_field(fieldsetStokes.Stokes_V)
256+
fieldset.add_field(fieldsetStokes.wave_Tp)
257+
258+
return fieldset

0 commit comments

Comments
 (0)