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