1+ import math
2+
13import numpy as np
24import xarray as xr
35
@@ -18,3 +20,267 @@ def simple_UV_dataset(dims=(360, 2, 30, 4), maxdepth=1, mesh_type="spherical"):
1820 "lon" : (["XG" ], np .linspace (- max_lon , max_lon , dims [3 ]), {"axis" : "X" , "c_grid_axis_shift" : - 0.5 }),
1921 },
2022 )
23+
24+
25+ def radial_rotation_dataset (xdim = 200 , ydim = 200 ): # Define 2D flat, square fieldset for testing purposes.
26+ lon = np .linspace (0 , 60 , xdim , dtype = np .float32 )
27+ lat = np .linspace (0 , 60 , ydim , dtype = np .float32 )
28+
29+ x0 = 30.0 # Define the origin to be the centre of the Field.
30+ y0 = 30.0
31+
32+ U = np .zeros ((2 , 1 , ydim , xdim ), dtype = np .float32 )
33+ V = np .zeros ((2 , 1 , ydim , xdim ), dtype = np .float32 )
34+
35+ omega = 2 * np .pi / 86400.0 # Define the rotational period as 1 day.
36+
37+ for i in range (lon .size ):
38+ for j in range (lat .size ):
39+ r = np .sqrt ((lon [i ] - x0 ) ** 2 + (lat [j ] - y0 ) ** 2 )
40+ assert r >= 0.0
41+ assert r <= np .sqrt (x0 ** 2 + y0 ** 2 )
42+
43+ theta = np .arctan2 ((lat [j ] - y0 ), (lon [i ] - x0 ))
44+ assert abs (theta ) <= np .pi
45+
46+ U [:, :, j , i ] = r * np .sin (theta ) * omega
47+ V [:, :, j , i ] = - r * np .cos (theta ) * omega
48+
49+ return xr .Dataset (
50+ {"U" : (["time" , "depth" , "YG" , "XG" ], U ), "V" : (["time" , "depth" , "YG" , "XG" ], V )},
51+ coords = {
52+ "time" : (["time" ], [np .timedelta64 (0 , "s" ), np .timedelta64 (10 , "D" )], {"axis" : "T" }),
53+ "depth" : (["depth" ], np .array ([0.0 ]), {"axis" : "Z" }),
54+ "YC" : (["YC" ], np .arange (ydim ) + 0.5 , {"axis" : "Y" }),
55+ "YG" : (["YG" ], np .arange (ydim ), {"axis" : "Y" , "c_grid_axis_shift" : - 0.5 }),
56+ "XC" : (["XC" ], np .arange (xdim ) + 0.5 , {"axis" : "X" }),
57+ "XG" : (["XG" ], np .arange (xdim ), {"axis" : "X" , "c_grid_axis_shift" : - 0.5 }),
58+ "lat" : (["YG" ], lat , {"axis" : "Y" , "c_grid_axis_shift" : 0.5 }),
59+ "lon" : (["XG" ], lon , {"axis" : "X" , "c_grid_axis_shift" : - 0.5 }),
60+ },
61+ )
62+
63+
64+ def moving_eddy_dataset (xdim = 2 , ydim = 2 ): # TODO check if this also works with xdim=1, ydim=1
65+ """Create a dataset with an eddy moving in time. Note that there is no spatial variation in the flow."""
66+ f , u_0 , u_g = 1.0e-4 , 0.3 , 0.04 # Some constants
67+
68+ lon = np .linspace (0 , 25000 , xdim , dtype = np .float32 )
69+ lat = np .linspace (0 , 25000 , ydim , dtype = np .float32 )
70+
71+ time = np .arange (np .timedelta64 (0 , "s" ), np .timedelta64 (7 , "h" ), np .timedelta64 (1 , "m" ))
72+
73+ U = np .zeros ((len (time ), 1 , ydim , xdim ), dtype = np .float32 )
74+ V = np .zeros ((len (time ), 1 , ydim , xdim ), dtype = np .float32 )
75+
76+ for t in range (len (time )):
77+ U [t , :, :, :] = u_g + (u_0 - u_g ) * np .cos (f * (time [t ] / np .timedelta64 (1 , "s" )))
78+ V [t , :, :, :] = - (u_0 - u_g ) * np .sin (f * (time [t ] / np .timedelta64 (1 , "s" )))
79+
80+ return xr .Dataset (
81+ {"U" : (["time" , "depth" , "YG" , "XG" ], U ), "V" : (["time" , "depth" , "YG" , "XG" ], V )},
82+ coords = {
83+ "time" : (["time" ], time , {"axis" : "T" }),
84+ "depth" : (["depth" ], np .array ([0.0 ]), {"axis" : "Z" }),
85+ "YC" : (["YC" ], np .arange (ydim ) + 0.5 , {"axis" : "Y" }),
86+ "YG" : (["YG" ], np .arange (ydim ), {"axis" : "Y" , "c_grid_axis_shift" : - 0.5 }),
87+ "XC" : (["XC" ], np .arange (xdim ) + 0.5 , {"axis" : "X" }),
88+ "XG" : (["XG" ], np .arange (xdim ), {"axis" : "X" , "c_grid_axis_shift" : - 0.5 }),
89+ "lat" : (["YG" ], lat , {"axis" : "Y" , "c_grid_axis_shift" : 0.5 }),
90+ "lon" : (["XG" ], lon , {"axis" : "X" , "c_grid_axis_shift" : - 0.5 }),
91+ },
92+ attrs = {
93+ "u_0" : u_0 ,
94+ "u_g" : u_g ,
95+ "f" : f ,
96+ },
97+ )
98+
99+
100+ def decaying_moving_eddy_dataset (xdim = 2 , ydim = 2 ):
101+ """Simulate an ocean that accelerates subject to Coriolis force
102+ and dissipative effects, upon which a geostrophic current is
103+ superimposed.
104+
105+ The original test description can be found in: N. Fabbroni, 2009,
106+ Numerical Simulation of Passive tracers dispersion in the sea,
107+ Ph.D. dissertation, University of Bologna
108+ http://amsdottorato.unibo.it/1733/1/Fabbroni_Nicoletta_Tesi.pdf
109+ """
110+ u_g = 0.04 # Geostrophic current
111+ u_0 = 0.3 # Initial speed in x dirrection. v_0 = 0
112+ gamma = 1.0 / (2.89 * 86400 ) # Dissipitave effects due to viscousity.
113+ gamma_g = 1.0 / (28.9 * 86400 )
114+ f = 1.0e-4 # Coriolis parameter.
115+
116+ time = np .arange (np .timedelta64 (0 , "s" ), np .timedelta64 (1 , "D" ) + np .timedelta64 (1 , "h" ), np .timedelta64 (2 , "m" ))
117+ lon = np .linspace (0 , 20000 , xdim , dtype = np .float32 )
118+ lat = np .linspace (5000 , 12000 , ydim , dtype = np .float32 )
119+
120+ U = np .zeros ((time .size , 1 , lat .size , lon .size ), dtype = np .float32 )
121+ V = np .zeros ((time .size , 1 , lat .size , lon .size ), dtype = np .float32 )
122+
123+ for t in range (time .size ):
124+ t_float = time [t ] / np .timedelta64 (1 , "s" )
125+ U [t , :, :, :] = u_g * np .exp (- gamma_g * t_float ) + (u_0 - u_g ) * np .exp (- gamma * t_float ) * np .cos (f * t_float )
126+ V [t , :, :, :] = - (u_0 - u_g ) * np .exp (- gamma * t_float ) * np .sin (f * t_float )
127+
128+ return xr .Dataset (
129+ {"U" : (["time" , "depth" , "YG" , "XG" ], U ), "V" : (["time" , "depth" , "YG" , "XG" ], V )},
130+ coords = {
131+ "time" : (["time" ], time , {"axis" : "T" }),
132+ "depth" : (["depth" ], np .array ([0.0 ]), {"axis" : "Z" }),
133+ "YC" : (["YC" ], np .arange (ydim ) + 0.5 , {"axis" : "Y" }),
134+ "YG" : (["YG" ], np .arange (ydim ), {"axis" : "Y" , "c_grid_axis_shift" : - 0.5 }),
135+ "XC" : (["XC" ], np .arange (xdim ) + 0.5 , {"axis" : "X" }),
136+ "XG" : (["XG" ], np .arange (xdim ), {"axis" : "X" , "c_grid_axis_shift" : - 0.5 }),
137+ "lat" : (["YG" ], lat , {"axis" : "Y" , "c_grid_axis_shift" : 0.5 }),
138+ "lon" : (["XG" ], lon , {"axis" : "X" , "c_grid_axis_shift" : - 0.5 }),
139+ },
140+ attrs = {
141+ "u_0" : u_0 ,
142+ "u_g" : u_g ,
143+ "f" : f ,
144+ "gamma" : gamma ,
145+ "gamma_g" : gamma_g ,
146+ },
147+ )
148+
149+
150+ def peninsula_dataset (xdim = 100 , ydim = 50 , mesh = "flat" , grid_type = "A" ):
151+ """Construct a fieldset encapsulating the flow field around an idealised peninsula.
152+
153+ Parameters
154+ ----------
155+ xdim :
156+ Horizontal dimension of the generated fieldset
157+ ydim :
158+ Vertical dimension of the generated fieldset
159+ mesh : str
160+ String indicating the type of mesh coordinates and
161+ units used during velocity interpolation:
162+
163+ 1. spherical: Lat and lon in degree, with a
164+ correction for zonal velocity U near the poles.
165+ 2. flat (default): No conversion, lat/lon are assumed to be in m.
166+ grid_type :
167+ Option whether grid is either Arakawa A (default) or C
168+
169+ The original test description can be found in Fig. 2.2.3 in:
170+ North, E. W., Gallego, A., Petitgas, P. (Eds). 2009. Manual of
171+ recommended practices for modelling physical - biological
172+ interactions during fish early life.
173+ ICES Cooperative Research Report No. 295. 111 pp.
174+ http://archimer.ifremer.fr/doc/00157/26792/24888.pdf
175+ """
176+ domainsizeX , domainsizeY = (1.0e5 , 5.0e4 )
177+ La = np .linspace (1e3 , domainsizeX , xdim , dtype = np .float32 )
178+ Wa = np .linspace (1e3 , domainsizeY , ydim , dtype = np .float32 )
179+
180+ u0 = 1
181+ x0 = domainsizeX / 2
182+ R = 0.32 * domainsizeX / 2
183+
184+ # Create the fields
185+ P = np .zeros ((ydim , xdim ), dtype = np .float32 )
186+ U = np .zeros_like (P )
187+ V = np .zeros_like (P )
188+ x , y = np .meshgrid (La , Wa , sparse = True , indexing = "xy" )
189+ P [:, :] = u0 * R ** 2 * y / ((x - x0 ) ** 2 + y ** 2 ) - u0 * y
190+
191+ # Set land points to zero
192+ landpoints = P >= 0.0
193+ P [landpoints ] = 0.0
194+
195+ if grid_type == "A" :
196+ U [:, :] = u0 - u0 * R ** 2 * ((x - x0 ) ** 2 - y ** 2 ) / (((x - x0 ) ** 2 + y ** 2 ) ** 2 )
197+ V [:, :] = - 2 * u0 * R ** 2 * ((x - x0 ) * y ) / (((x - x0 ) ** 2 + y ** 2 ) ** 2 )
198+ U [landpoints ] = 0.0
199+ V [landpoints ] = 0.0
200+ Udims = ["YC" , "XG" ]
201+ Vdims = ["YG" , "XC" ]
202+ elif grid_type == "C" :
203+ U = np .zeros (P .shape )
204+ V = np .zeros (P .shape )
205+ V [:, 1 :] = (P [:, 1 :] - P [:, :- 1 ]) / (La [1 ] - La [0 ])
206+ U [1 :, :] = - (P [1 :, :] - P [:- 1 , :]) / (Wa [1 ] - Wa [0 ])
207+ Udims = ["YG" , "XG" ]
208+ Vdims = ["YG" , "XG" ]
209+ else :
210+ raise RuntimeError (f"Grid_type { grid_type } is not a valid option" )
211+
212+ # Convert from m to lat/lon for spherical meshes
213+ lon = La / 1852.0 / 60.0 if mesh == "spherical" else La
214+ lat = Wa / 1852.0 / 60.0 if mesh == "spherical" else Wa
215+
216+ return xr .Dataset (
217+ {
218+ "U" : (Udims , U ),
219+ "V" : (Vdims , V ),
220+ "P" : (["YG" , "XG" ], P ),
221+ },
222+ coords = {
223+ "YC" : (["YC" ], np .arange (ydim ) + 0.5 , {"axis" : "Y" }),
224+ "YG" : (["YG" ], np .arange (ydim ), {"axis" : "Y" , "c_grid_axis_shift" : - 0.5 }),
225+ "XC" : (["XC" ], np .arange (xdim ) + 0.5 , {"axis" : "X" }),
226+ "XG" : (["XG" ], np .arange (xdim ), {"axis" : "X" , "c_grid_axis_shift" : - 0.5 }),
227+ "lat" : (["YG" ], lat , {"axis" : "Y" , "c_grid_axis_shift" : 0.5 }),
228+ "lon" : (["XG" ], lon , {"axis" : "X" , "c_grid_axis_shift" : - 0.5 }),
229+ },
230+ )
231+
232+
233+ def stommel_gyre_dataset (xdim = 200 , ydim = 200 , grid_type = "A" ):
234+ """Simulate a periodic current along a western boundary, with significantly
235+ larger velocities along the western edge than the rest of the region
236+
237+ The original test description can be found in: N. Fabbroni, 2009,
238+ Numerical Simulation of Passive tracers dispersion in the sea,
239+ Ph.D. dissertation, University of Bologna
240+ http://amsdottorato.unibo.it/1733/1/Fabbroni_Nicoletta_Tesi.pdf
241+ """
242+ a = b = 10000 * 1e3
243+ scalefac = 0.05 # to scale for physically meaningful velocities
244+ dx , dy = a / xdim , b / ydim
245+
246+ # Coordinates of the test fieldset (on A-grid in deg)
247+ lon = np .linspace (0 , a , xdim , dtype = np .float32 )
248+ lat = np .linspace (0 , b , ydim , dtype = np .float32 )
249+
250+ # Define arrays U (zonal), V (meridional) and P (sea surface height)
251+ U = np .zeros ((lat .size , lon .size ), dtype = np .float32 )
252+ V = np .zeros ((lat .size , lon .size ), dtype = np .float32 )
253+ P = np .zeros ((lat .size , lon .size ), dtype = np .float32 )
254+
255+ beta = 2e-11
256+ r = 1 / (11.6 * 86400 )
257+ es = r / (beta * a )
258+
259+ for j in range (lat .size ):
260+ for i in range (lon .size ):
261+ xi = lon [i ] / a
262+ yi = lat [j ] / b
263+ P [j , i ] = (1 - math .exp (- xi / es ) - xi ) * math .pi * np .sin (math .pi * yi ) * scalefac
264+ if grid_type == "A" :
265+ U [j , i ] = - (1 - math .exp (- xi / es ) - xi ) * math .pi ** 2 * np .cos (math .pi * yi ) * scalefac
266+ V [j , i ] = (math .exp (- xi / es ) / es - 1 ) * math .pi * np .sin (math .pi * yi ) * scalefac
267+ if grid_type == "C" :
268+ V [:, 1 :] = (P [:, 1 :] - P [:, 0 :- 1 ]) / dx * a
269+ U [1 :, :] = - (P [1 :, :] - P [0 :- 1 , :]) / dy * b
270+ Udims = ["YC" , "XG" ]
271+ Vdims = ["YG" , "XC" ]
272+ else :
273+ Udims = ["YG" , "XG" ]
274+ Vdims = ["YG" , "XG" ]
275+
276+ return xr .Dataset (
277+ {"U" : (Udims , U ), "V" : (Vdims , V ), "P" : (["YG" , "XG" ], P )},
278+ coords = {
279+ "YC" : (["YC" ], np .arange (ydim ) + 0.5 , {"axis" : "Y" }),
280+ "YG" : (["YG" ], np .arange (ydim ), {"axis" : "Y" , "c_grid_axis_shift" : - 0.5 }),
281+ "XC" : (["XC" ], np .arange (xdim ) + 0.5 , {"axis" : "X" }),
282+ "XG" : (["XG" ], np .arange (xdim ), {"axis" : "X" , "c_grid_axis_shift" : - 0.5 }),
283+ "lat" : (["YG" ], lat , {"axis" : "Y" , "c_grid_axis_shift" : 0.5 }),
284+ "lon" : (["XG" ], lon , {"axis" : "X" , "c_grid_axis_shift" : - 0.5 }),
285+ },
286+ )
0 commit comments