def test_moving_eddies_file_subsettime( indstime, gridfile='examples/MovingEddies_data/moving_eddies'): gridfull = Grid.from_nemo(gridfile, extra_vars={'P': 'P'}) gridsub = Grid.from_nemo(gridfile, extra_vars={'P': 'P'}, indices={'time': indstime}) assert np.allclose(gridsub.P.time, gridfull.P.time[indstime]) assert np.allclose(gridsub.P.data, gridfull.P.data[indstime, :, :])
def test_grid_from_nemo(xdim, ydim, tmpdir, filename='test_nemo'): """ Simple test for grid initialisation from NEMO file format. """ filepath = tmpdir.join(filename) u, v, lon, lat, depth, time = generate_grid(xdim, ydim) grid_out = Grid.from_data(u, lon, lat, v, lon, lat, depth, time) grid_out.write(filepath) grid = Grid.from_nemo(filepath) u_t = np.transpose(u).reshape((lat.size, lon.size)) v_t = np.transpose(v).reshape((lat.size, lon.size)) assert len(grid.U.data.shape) == 3 # Will be 4 once we use depth assert len(grid.V.data.shape) == 3 assert np.allclose(grid.U.data[0, :], u_t, rtol=1e-12) assert np.allclose(grid.V.data[0, :], v_t, rtol=1e-12)
def test_nearest_neighbour_interpolation(mode, k_sample_p, npart=81): dims = (2, 2) lon = np.linspace(0., 1., dims[0], dtype=np.float32) lat = np.linspace(0., 1., dims[1], dtype=np.float32) U = np.zeros(dims, dtype=np.float32) V = np.zeros(dims, dtype=np.float32) P = np.zeros(dims, dtype=np.float32) P[0, 0] = 1. grid = Grid.from_data(U, lon, lat, V, lon, lat, mesh='flat', field_data={'P': np.asarray(P, dtype=np.float32)}) grid.P.interp_method = 'nearest' xv, yv = np.meshgrid(np.linspace(0.1, 0.9, np.sqrt(npart)), np.linspace(0.1, 0.9, np.sqrt(npart))) pset = ParticleSet(grid, pclass=pclass(mode), lon=xv.flatten(), lat=yv.flatten()) pset.execute(k_sample_p, endtime=1, dt=1) assert np.allclose(np.array( [p.p for p in pset if p.lon < 0.5 and p.lat < 0.5]), 1.0, rtol=1e-5) assert np.allclose(np.array( [p.p for p in pset if p.lon > 0.5 or p.lat > 0.5]), 0.0, rtol=1e-5)
def radial_rotation_grid( xdim=200, ydim=200): # Define 2D flat, square grid for testing purposes. lon = np.linspace(0, 60, xdim, dtype=np.float32) lat = np.linspace(0, 60, ydim, dtype=np.float32) x0 = 30. # Define the origin to be the centre of the grid. y0 = 30. U = np.zeros((xdim, ydim), dtype=np.float32) V = np.zeros((xdim, ydim), dtype=np.float32) T = delta(days=1) omega = 2 * np.pi / T.total_seconds( ) # Define the rotational period as 1 day. for i in range(lon.size): for j in range(lat.size): r = np.sqrt((lon[i] - x0)**2 + (lat[j] - y0)**2) # Define radial displacement. assert (r >= 0.) assert (r <= np.sqrt(x0**2 + y0**2)) theta = math.atan2((lat[j] - y0), (lon[i] - x0)) # Define the polar angle. assert (abs(theta) <= np.pi) U[i, j] = r * math.sin(theta) * omega V[i, j] = -r * math.cos(theta) * omega return Grid.from_data(U, lon, lat, V, lon, lat, mesh='flat')
def radial_rotation_grid(xdim=200, ydim=200): # Define 2D flat, square grid for testing purposes. lon = np.linspace(0, 60, xdim, dtype=np.float32) lat = np.linspace(0, 60, ydim, dtype=np.float32) x0 = 30. # Define the origin to be the centre of the grid. y0 = 30. U = np.zeros((xdim, ydim), dtype=np.float32) V = np.zeros((xdim, ydim), dtype=np.float32) T = delta(days=1) omega = 2*np.pi/T.total_seconds() # Define the rotational period as 1 day. for i in range(lon.size): for j in range(lat.size): r = np.sqrt((lon[i]-x0)**2 + (lat[j]-y0)**2) # Define radial displacement. assert(r >= 0.) assert(r <= np.sqrt(x0**2 + y0**2)) theta = math.atan2((lat[j]-y0), (lon[i]-x0)) # Define the polar angle. assert(abs(theta) <= np.pi) U[i, j] = r * math.sin(theta) * omega V[i, j] = -r * math.cos(theta) * omega return Grid.from_data(U, lon, lat, V, lon, lat, mesh='flat')
def test_random_field(mode, k_sample_p, xdim=20, ydim=20, npart=100): """Sampling test that test for overshoots by sampling a field of random numbers between 0 and 1. """ np.random.seed(123456) lon = np.linspace(0., 1., xdim, dtype=np.float32) lat = np.linspace(0., 1., ydim, dtype=np.float32) U = np.zeros((xdim, ydim), dtype=np.float32) V = np.zeros((xdim, ydim), dtype=np.float32) P = np.random.uniform(0, 1., size=(xdim, ydim)) S = np.ones((xdim, ydim), dtype=np.float32) grid = Grid.from_data(U, lon, lat, V, lon, lat, mesh='flat', field_data={ 'P': np.asarray(P, dtype=np.float32), 'start': S }) pset = ParticleSet.from_field(grid, size=npart, pclass=pclass(mode), start_field=grid.start) pset.execute(k_sample_p, endtime=1., dt=1.0) sampled = np.array([p.p for p in pset]) assert ((sampled >= 0.).all())
def set_ofam_grid(): filenames = {'U': "examples/OFAM_example_data/OFAM_simple_U.nc", 'V': "examples/OFAM_example_data/OFAM_simple_V.nc"} variables = {'U': 'u', 'V': 'v'} dimensions = {'lat': 'yu_ocean', 'lon': 'xu_ocean', 'depth': 'st_ocean', 'time': 'Time'} return Grid.from_netcdf(filenames, variables, dimensions)
def test_delay_start_example(mode, npart=10, show_movie=False): """Example script that shows how to 'delay' the start of particle advection. This is useful for example when particles need to be started at different times In this example, we use pset.add statements to add one particle every hour in the peninsula grid. Note that the title in the movie may not show correct time""" grid = Grid.from_nemo('examples/Peninsula_data/peninsula', extra_vars={'P': 'P'}) # Initialise particles as in the Peninsula example x = 3. * (1. / 1.852 / 60) # 3 km offset from boundary y = (grid.U.lat[0] + x, grid.U.lat[-1] - x) # latitude range, including offsets lat = np.linspace(y[0], y[1], npart, dtype=np.float32) pset = grid.ParticleSet(0, lon=[], lat=[], pclass=ptype[mode]) delaytime = delta(hours=1) # delay time between particle releases for t in range(npart): pset.add(ptype[mode](lon=x, lat=lat[t], grid=grid)) pset.execute(AdvectionRK4, runtime=delaytime, dt=delta(minutes=5), interval=delta(hours=1), show_movie=show_movie) # Note that time on the movie is not parsed correctly pset.execute(AdvectionRK4, runtime=delta(hours=24)-npart*delaytime, dt=delta(minutes=5), interval=delta(hours=1), show_movie=show_movie) londist = np.array([(p.lon - x) for p in pset]) assert(londist > 0.1).all()
def stommel_grid(xdim=200, ydim=200): """Simulate a periodic current along a western boundary, with significantly larger velocities along the western edge than the rest of the region The original test description can be found in: N. Fabbroni, 2009, Numerical Simulation of Passive tracers dispersion in the sea, Ph.D. dissertation, University of Bologna http://amsdottorato.unibo.it/1733/1/Fabbroni_Nicoletta_Tesi.pdf """ # Set NEMO grid variables depth = np.zeros(1, dtype=np.float32) time = np.linspace(0., 100000. * 86400., 2, dtype=np.float64) # Some constants A = 100 eps = 0.05 a = 10000 b = 10000 # Coordinates of the test grid (on A-grid in deg) lon = np.linspace(0, a, xdim, dtype=np.float32) lat = np.linspace(0, b, ydim, dtype=np.float32) # Define arrays U (zonal), V (meridional), W (vertical) and P (sea # surface height) all on A-grid U = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) V = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) P = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) [x, y] = np.mgrid[:lon.size, :lat.size] l1 = (-1 + math.sqrt(1 + 4 * math.pi**2 * eps**2)) / (2 * eps) l2 = (-1 - math.sqrt(1 + 4 * math.pi**2 * eps**2)) / (2 * eps) c1 = (1 - math.exp(l2)) / (math.exp(l2) - math.exp(l1)) c2 = -(1 + c1) for t in range(time.size): for i in range(lon.size): for j in range(lat.size): xi = lon[i] / a yi = lat[j] / b P[i, j, t] = A * (c1 * math.exp(l1 * xi) + c2 * math.exp(l2 * xi) + 1) * math.sin(math.pi * yi) for i in range(lon.size - 2): for j in range(lat.size): V[i + 1, j, t] = (P[i + 2, j, t] - P[i, j, t]) / (2 * a / xdim) for i in range(lon.size): for j in range(lat.size - 2): U[i, j + 1, t] = -(P[i, j + 2, t] - P[i, j, t]) / (2 * b / ydim) return Grid.from_data(U, lon, lat, V, lon, lat, depth, time, field_data={'P': P}, mesh='flat')
def test_meridionalflow_sperical(mode, xdim=100, ydim=200): """ Create uniform NORTHWARD flow on sperical earth and advect particles As flow is so simple, it can be directly compared to analytical solution """ maxvel = 1. lon = np.linspace(-180, 180, xdim, dtype=np.float32) lat = np.linspace(-90, 90, ydim, dtype=np.float32) U = np.zeros([xdim, ydim]) V = maxvel * np.ones([xdim, ydim]) grid = Grid.from_data(np.array(U, dtype=np.float32), lon, lat, np.array(V, dtype=np.float32), lon, lat) lonstart = [0, 45] latstart = [0, 45] endtime = delta(hours=24) pset = grid.ParticleSet(2, pclass=pclass(mode), lon=lonstart, lat=latstart) pset.execute(pset.Kernel(AdvectionRK4), endtime=endtime, dt=delta(hours=1)) assert(pset[0].lat - (latstart[0] + endtime.total_seconds() * maxvel / 1852 / 60) < 1e-4) assert(pset[0].lon - lonstart[0] < 1e-4) assert(pset[1].lat - (latstart[1] + endtime.total_seconds() * maxvel / 1852 / 60) < 1e-4) assert(pset[1].lon - lonstart[1] < 1e-4)
def test_zonalflow_sperical(mode, k_sample_p, xdim=100, ydim=200): """ Create uniform EASTWARD flow on sperical earth and advect particles As flow is so simple, it can be directly compared to analytical solution Note that in this case the cosine conversion is needed """ maxvel = 1. p_fld = 10 lon = np.linspace(-180, 180, xdim, dtype=np.float32) lat = np.linspace(-90, 90, ydim, dtype=np.float32) V = np.zeros([xdim, ydim]) U = maxvel * np.ones([xdim, ydim]) P = p_fld * np.ones([xdim, ydim]) grid = Grid.from_data(np.array(U, dtype=np.float32), lon, lat, np.array(V, dtype=np.float32), lon, lat, field_data={'P': np.array(P, dtype=np.float32)}) lonstart = [0, 45] latstart = [0, 45] endtime = delta(hours=24) pset = grid.ParticleSet(2, pclass=pclass(mode), lon=lonstart, lat=latstart) pset.execute(pset.Kernel(AdvectionRK4) + k_sample_p, endtime=endtime, dt=delta(hours=1)) assert(pset[0].lat - latstart[0] < 1e-4) assert(pset[0].lon - (lonstart[0] + endtime.total_seconds() * maxvel / 1852 / 60 / cos(latstart[0] * pi / 180)) < 1e-4) assert(abs(pset[0].p - p_fld) < 1e-4) assert(pset[1].lat - latstart[1] < 1e-4) assert(pset[1].lon - (lonstart[1] + endtime.total_seconds() * maxvel / 1852 / 60 / cos(latstart[1] * pi / 180)) < 1e-4) assert(abs(pset[1].p - p_fld) < 1e-4)
def set_globcurrent_grid(): filenames = {'U': "examples/GlobCurrent_example_data/20*-GLOBCURRENT-L4-CUReul_hs-ALT_SUM-v02.0-fv01.0.nc", 'V': "examples/GlobCurrent_example_data/20*-GLOBCURRENT-L4-CUReul_hs-ALT_SUM-v02.0-fv01.0.nc"} variables = {'U': 'eastward_eulerian_current_velocity', 'V': 'northward_eulerian_current_velocity'} dimensions = {'lat': 'lat', 'lon': 'lon', 'time': 'time'} return Grid.from_netcdf(filenames, variables, dimensions)
def moving_eddies_grid(xdim=200, ydim=350): """Generate a grid encapsulating the flow field consisting of two moving eddies, one moving westward and the other moving northwestward. Note that this is not a proper geophysical flow. Rather, a Gaussian eddy is moved artificially with uniform velocities. Velocities are calculated from geostrophy. """ # Set NEMO grid variables depth = np.zeros(1, dtype=np.float32) time = np.arange(0.0, 25.0 * 86400.0, 86400.0, dtype=np.float64) # Coordinates of the test grid (on A-grid in deg) lon = np.linspace(0, 4, xdim, dtype=np.float32) lat = np.linspace(45, 52, ydim, dtype=np.float32) # Grid spacing in m def cosd(x): return math.cos(math.radians(float(x))) dx = (lon[1] - lon[0]) * 1852 * 60 * cosd(lat.mean()) dy = (lat[1] - lat[0]) * 1852 * 60 # Define arrays U (zonal), V (meridional), W (vertical) and P (sea # surface height) all on A-grid U = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) V = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) P = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) # Some constants corio_0 = 1.0e-4 # Coriolis parameter h0 = 1 # Max eddy height sig = 0.5 # Eddy e-folding decay scale (in degrees) g = 10 # Gravitational constant eddyspeed = 0.1 # Translational speed in m/s dX = eddyspeed * 86400 / dx # Grid cell movement of eddy max each day dY = eddyspeed * 86400 / dy # Grid cell movement of eddy max each day [x, y] = np.mgrid[: lon.size, : lat.size] for t in range(time.size): hymax_1 = lat.size / 7.0 hxmax_1 = 0.75 * lon.size - dX * t hymax_2 = 3.0 * lat.size / 7.0 + dY * t hxmax_2 = 0.75 * lon.size - dX * t P[:, :, t] = h0 * np.exp( -(x - hxmax_1) ** 2 / (sig * lon.size / 4.0) ** 2 - (y - hymax_1) ** 2 / (sig * lat.size / 7.0) ** 2 ) P[:, :, t] += h0 * np.exp( -(x - hxmax_2) ** 2 / (sig * lon.size / 4.0) ** 2 - (y - hymax_2) ** 2 / (sig * lat.size / 7.0) ** 2 ) V[:-1, :, t] = -np.diff(P[:, :, t], axis=0) / dx / corio_0 * g V[-1, :, t] = V[-2, :, t] # Fill in the last column U[:, :-1, t] = np.diff(P[:, :, t], axis=1) / dy / corio_0 * g U[:, -1, t] = U[:, -2, t] # Fill in the last row return Grid.from_data(U, lon, lat, V, lon, lat, depth, time, field_data={"P": P})
def test_add_field(xdim, ydim, tmpdir, filename="test_add"): filepath = tmpdir.join(filename) u, v, lon, lat, depth, time = generate_grid(xdim, ydim) grid = Grid.from_data(u, lon, lat, v, lon, lat, depth, time) field = Field("newfld", grid.U.data, grid.U.lon, grid.U.lat) grid.add_field(field) assert grid.newfld.data.shape == grid.U.data.shape grid.write(filepath)
def test_add_field(xdim, ydim, tmpdir, filename='test_add'): filepath = tmpdir.join(filename) u, v, lon, lat, depth, time = generate_grid(xdim, ydim) grid = Grid.from_data(u, lon, lat, v, lon, lat, depth, time) field = Field('newfld', grid.U.data, grid.U.lon, grid.U.lat) grid.add_field(field) assert grid.newfld.data.shape == grid.U.data.shape grid.write(filepath)
def grid(xdim=100, ydim=100): U = np.zeros((xdim, ydim), dtype=np.float32) V = np.zeros((xdim, ydim), dtype=np.float32) lon = np.linspace(0, 1, xdim, dtype=np.float32) lat = np.linspace(0, 1, ydim, dtype=np.float32) depth = np.zeros(1, dtype=np.float32) time = np.zeros(1, dtype=np.float64) return Grid.from_data(U, lon, lat, V, lon, lat, depth, time)
def grid(xdim=20, ydim=20): """ Standard unit mesh grid """ lon = np.linspace(0., 1., xdim, dtype=np.float32) lat = np.linspace(0., 1., ydim, dtype=np.float32) U, V = np.meshgrid(lat, lon) return Grid.from_data(np.array(U, dtype=np.float32), lon, lat, np.array(V, dtype=np.float32), lon, lat, mesh='flat')
def brownian_grid(xdim=200, ydim=200): # Define a flat grid of zeros, for simplicity. lon = np.linspace(0, 600000, xdim, dtype=np.float32) lat = np.linspace(0, 600000, ydim, dtype=np.float32) U = np.zeros((lon.size, lat.size), dtype=np.float32) V = np.zeros((lon.size, lat.size), dtype=np.float32) return Grid.from_data(U, lon, lat, V, lon, lat, mesh='flat')
def periodicgrid(xdim, ydim, uvel, vvel): lon = np.linspace( 0., 1., xdim + 1, dtype=np.float32)[1:] # don't include both 0 and 1, for periodic b.c. lat = np.linspace(0., 1., ydim + 1, dtype=np.float32)[1:] U = uvel * np.ones((xdim, ydim), dtype=np.float32) V = vvel * np.ones((xdim, ydim), dtype=np.float32) return Grid.from_data(U, lon, lat, V, lon, lat, mesh='spherical')
def peninsula_grid(xdim, ydim): """Construct a grid encapsulating the flow field around an idealised peninsula. :param xdim: Horizontal dimension of the generated grid :param xdim: Vertical dimension of the generated grid The original test description can be found in Fig. 2.2.3 in: North, E. W., Gallego, A., Petitgas, P. (Eds). 2009. Manual of recommended practices for modelling physical - biological interactions during fish early life. ICES Cooperative Research Report No. 295. 111 pp. http://archimer.ifremer.fr/doc/00157/26792/24888.pdf Note that the problem is defined on an A-grid while NEMO normally returns C-grids. However, to avoid accuracy problems with interpolation from A-grid to C-grid, we return NetCDF files that are on an A-grid. """ # Set NEMO grid variables depth = np.zeros(1, dtype=np.float32) time = np.zeros(1, dtype=np.float64) # Generate the original test setup on A-grid in km dx = 100. / xdim / 2. dy = 50. / ydim / 2. La = np.linspace(dx, 100.-dx, xdim, dtype=np.float32) Wa = np.linspace(dy, 50.-dy, ydim, dtype=np.float32) # Define arrays U (zonal), V (meridional), W (vertical) and P (sea # surface height) all on A-grid U = np.zeros((xdim, ydim), dtype=np.float32) V = np.zeros((xdim, ydim), dtype=np.float32) W = np.zeros((xdim, ydim), dtype=np.float32) P = np.zeros((xdim, ydim), dtype=np.float32) u0 = 1 x0 = 50. R = 0.32 * 50. # Create the fields x, y = np.meshgrid(La, Wa, sparse=True, indexing='ij') P = u0*R**2*y/((x-x0)**2+y**2)-u0*y U = u0-u0*R**2*((x-x0)**2-y**2)/(((x-x0)**2+y**2)**2) V = -2*u0*R**2*((x-x0)*y)/(((x-x0)**2+y**2)**2) # Set land points to NaN I = P >= 0. U[I] = np.nan V[I] = np.nan W[I] = np.nan # Convert from km to lat/lon lon = La / 1.852 / 60. lat = Wa / 1.852 / 60. return Grid.from_data(U, lon, lat, V, lon, lat, depth, time, field_data={'P': P})
def test_peninsula_file(gridfile, mode): """Open grid files and execute""" grid = Grid.from_nemo(gridfile, extra_vars={'P': 'P'}) pset = pensinsula_example(grid, 100, mode=mode, degree=1) # Test advection accuracy by comparing streamline values err_adv = np.array([abs(p.p_start - p.p) for p in pset]) assert(err_adv <= 1.e-3).all() # Test grid sampling accuracy by comparing kernel against grid sampling err_smpl = np.array([abs(p.p - pset.grid.P[0., p.lon, p.lat]) for p in pset]) assert(err_smpl <= 1.e-3).all()
def test_grid_from_data(xdim, ydim): """ Simple test for grid initialisation from data. """ u, v, lon, lat, depth, time = generate_grid(xdim, ydim) grid = Grid.from_data(u, lon, lat, v, lon, lat, depth, time) u_t = np.transpose(u).reshape((lat.size, lon.size)) v_t = np.transpose(v).reshape((lat.size, lon.size)) assert len(grid.U.data.shape) == 3 # Will be 4 once we use depth assert len(grid.V.data.shape) == 3 assert np.allclose(grid.U.data[0, :], u_t, rtol=1e-12) assert np.allclose(grid.V.data[0, :], v_t, rtol=1e-12)
def grid(xdim=200, ydim=100): """ Standard grid spanning the earth's coordinates with U and V equivalent to longitude and latitude in deg. """ lon = np.linspace(-180, 180, xdim, dtype=np.float32) lat = np.linspace(-90, 90, ydim, dtype=np.float32) U, V = np.meshgrid(lat, lon) return Grid.from_data(np.array(U, dtype=np.float32), lon, lat, np.array(V, dtype=np.float32), lon, lat, mesh='flat')
def test_grid_from_file_subsets(indslon, indslat, tmpdir, filename='test_subsets'): """ Test for subsetting grid from file using indices dict. """ u, v, lon, lat, depth, time = generate_grid(100, 100) filepath = tmpdir.join(filename) gridfull = Grid.from_data(u, lon, lat, v, lon, lat, depth, time) gridfull.write(filepath) indices = {'lon': indslon, 'lat': indslat} gridsub = Grid.from_nemo(filepath, indices=indices) assert np.allclose(gridsub.U.lon, gridfull.U.lon[indices['lon']]) assert np.allclose(gridsub.U.lat, gridfull.U.lat[indices['lat']]) assert np.allclose(gridsub.V.lon, gridfull.V.lon[indices['lon']]) assert np.allclose(gridsub.V.lat, gridfull.V.lat[indices['lat']]) ixgrid = np.ix_([0], indices['lat'], indices['lon']) assert np.allclose(gridsub.U.data, gridfull.U.data[ixgrid]) assert np.allclose(gridsub.V.data, gridfull.V.data[ixgrid])
def grid_geometric(xdim=200, ydim=100): """ Standard earth grid with U and V equivalent to lon/lat in m. """ lon = np.linspace(-180, 180, xdim, dtype=np.float32) lat = np.linspace(-90, 90, ydim, dtype=np.float32) U, V = np.meshgrid(lat, lon) U *= 1000. * 1.852 * 60. V *= 1000. * 1.852 * 60. grid = Grid.from_data(np.array(U, dtype=np.float32), lon, lat, np.array(V, dtype=np.float32), lon, lat) grid.U.units = Geographic() grid.V.units = Geographic() return grid
def grid_stationary(xdim=100, ydim=100, maxtime=delta(hours=6)): """Generate a grid encapsulating the flow field of a stationary eddy. Reference: N. Fabbroni, 2009, "Numerical simulations of passive tracers dispersion in the sea" """ lon = np.linspace(0, 25000, xdim, dtype=np.float32) lat = np.linspace(0, 25000, ydim, dtype=np.float32) time = np.arange(0., maxtime.total_seconds(), 60., dtype=np.float64) U = np.ones((xdim, ydim, 1), dtype=np.float32) * u_0 * np.cos(f * time) V = np.ones((xdim, ydim, 1), dtype=np.float32) * -u_0 * np.sin(f * time) return Grid.from_data(U, lon, lat, V, lon, lat, time=time, mesh='flat')
def test_delay_start_example(mode, npart=10, show_movie=False): """Example script that shows how to 'delay' the start of particle advection. This is useful for example when particles need to be started at different times In this example, we use pset.add statements to add one particle every hour in the peninsula grid. Note that the title in the movie may not show correct time""" grid = Grid.from_nemo('examples/Peninsula_data/peninsula', extra_vars={'P': 'P'}, allow_time_extrapolation=True) # Initialise particles as in the Peninsula example x = 3. * (1. / 1.852 / 60) # 3 km offset from boundary y = (grid.U.lat[0] + x, grid.U.lat[-1] - x ) # latitude range, including offsets lat = np.linspace(y[0], y[1], npart, dtype=np.float32) pset = ParticleSet(grid, lon=[], lat=[], pclass=ptype[mode]) delaytime = delta(hours=1) # delay time between particle releases # Since we are going to add particles during runtime, we need "indexed" NetCDF file output_file = pset.ParticleFile(name="DelayParticle", type="indexed") for t in range(npart): pset.add(ptype[mode](lon=x, lat=lat[t], grid=grid)) pset.execute(AdvectionRK4, runtime=delaytime, dt=delta(minutes=5), interval=delta(hours=1), show_movie=show_movie, starttime=delaytime * t, output_file=output_file) # Note that time on the movie is not parsed correctly pset.execute(AdvectionRK4, runtime=delta(hours=24) - npart * delaytime, starttime=delaytime * npart, dt=delta(minutes=5), interval=delta(hours=1), show_movie=show_movie, output_file=output_file) londist = np.array([(p.lon - x) for p in pset]) assert (londist > 0.1).all() # Test whether time was written away correctly in file pfile = Dataset("DelayParticle.nc", 'r') id = pfile.variables['trajectory'][:] time = pfile.variables['time'][id == id[0]] assert all(time[1:] - time[0:-1] == time[1] - time[0]) pfile.close()
def test_ofam_grid(filepath): filenames = {'U': path.join(filepath, "OFAM_simple_U.nc"), 'V': path.join(filepath, "OFAM_simple_V.nc")} variables = {'U': 'u', 'V': 'v'} dimensions = {'lat': 'yu_ocean', 'lon': 'xu_ocean', 'depth': 'st_ocean', 'time': 'Time'} grid = Grid.from_netcdf(filenames, variables, dimensions) assert(grid.U.lon.size == 2001) assert(grid.U.lat.size == 601) assert(grid.U.data.shape == (4, 601, 2001)) assert(grid.V.lon.size == 2001) assert(grid.V.lat.size == 601) assert(grid.V.data.shape == (4, 601, 2001))
def set_globcurrent_grid(): filenames = { 'U': "examples/GlobCurrent_example_data/20*-GLOBCURRENT-L4-CUReul_hs-ALT_SUM-v02.0-fv01.0.nc", 'V': "examples/GlobCurrent_example_data/20*-GLOBCURRENT-L4-CUReul_hs-ALT_SUM-v02.0-fv01.0.nc" } variables = { 'U': 'eastward_eulerian_current_velocity', 'V': 'northward_eulerian_current_velocity' } dimensions = {'lat': 'lat', 'lon': 'lon', 'time': 'time'} return Grid.from_netcdf(filenames, variables, dimensions)
def set_ofam_grid(): filenames = { 'U': "examples/OFAM_example_data/OFAM_simple_U.nc", 'V': "examples/OFAM_example_data/OFAM_simple_V.nc" } variables = {'U': 'u', 'V': 'v'} dimensions = { 'lat': 'yu_ocean', 'lon': 'xu_ocean', 'depth': 'st_ocean', 'time': 'Time' } return Grid.from_netcdf(filenames, variables, dimensions)
def test_advection_zonal(lon, lat, mode, npart=10): """ Particles at high latitude move geographically faster due to the pole correction in `GeographicPolar`. """ U = np.ones((lon.size, lat.size), dtype=np.float32) V = np.zeros((lon.size, lat.size), dtype=np.float32) grid = Grid.from_data(U, lon, lat, V, lon, lat, mesh='spherical') pset = grid.ParticleSet(npart, pclass=ptype[mode], lon=np.zeros(npart, dtype=np.float32) + 20., lat=np.linspace(0, 80, npart, dtype=np.float32)) pset.execute(AdvectionRK4, endtime=delta(hours=2), dt=delta(seconds=30)) assert (np.diff(np.array([p.lon for p in pset])) > 1.e-4).all()
def test_globcurrent_grid(): filenames = {'U': "examples/GlobCurrent_example_data/20*-GLOBCURRENT-L4-CUReul_hs-ALT_SUM-v02.0-fv01.0.nc", 'V': "examples/GlobCurrent_example_data/20*-GLOBCURRENT-L4-CUReul_hs-ALT_SUM-v02.0-fv01.0.nc"} variables = {'U': 'eastward_eulerian_current_velocity', 'V': 'northward_eulerian_current_velocity'} dimensions = {'lat': 'lat', 'lon': 'lon', 'time': 'time'} grid = Grid.from_netcdf(filenames, variables, dimensions) assert(grid.U.lon.size == 81) assert(grid.U.lat.size == 41) assert(grid.U.data.shape == (365, 41, 81)) assert(grid.V.lon.size == 81) assert(grid.V.lat.size == 41) assert(grid.V.data.shape == (365, 41, 81))
def stommel_grid(xdim=200, ydim=200): """Simulate a periodic current along a western boundary, with significantly larger velocities along the western edge than the rest of the region The original test description can be found in: N. Fabbroni, 2009, Numerical Simulation of Passive tracers dispersion in the sea, Ph.D. dissertation, University of Bologna http://amsdottorato.unibo.it/1733/1/Fabbroni_Nicoletta_Tesi.pdf """ # Set NEMO grid variables depth = np.zeros(1, dtype=np.float32) time = np.linspace(0., 100000. * 86400., 2, dtype=np.float64) # Some constants A = 100 eps = 0.05 a = 10000 b = 10000 # Coordinates of the test grid (on A-grid in deg) lon = np.linspace(0, a, xdim, dtype=np.float32) lat = np.linspace(0, b, ydim, dtype=np.float32) # Define arrays U (zonal), V (meridional), W (vertical) and P (sea # surface height) all on A-grid U = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) V = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) P = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) [x, y] = np.mgrid[:lon.size, :lat.size] l1 = (-1 + math.sqrt(1 + 4 * math.pi**2 * eps**2)) / (2 * eps) l2 = (-1 - math.sqrt(1 + 4 * math.pi**2 * eps**2)) / (2 * eps) c1 = (1 - math.exp(l2)) / (math.exp(l2) - math.exp(l1)) c2 = -(1 + c1) for t in range(time.size): for i in range(lon.size): for j in range(lat.size): xi = lon[i] / a yi = lat[j] / b P[i, j, t] = A * (c1*math.exp(l1*xi) + c2*math.exp(l2*xi) + 1) * math.sin(math.pi * yi) for i in range(lon.size-2): for j in range(lat.size): V[i+1, j, t] = (P[i+2, j, t] - P[i, j, t]) / (2 * a / xdim) for i in range(lon.size): for j in range(lat.size-2): U[i, j+1, t] = -(P[i, j+2, t] - P[i, j, t]) / (2 * b / ydim) return Grid.from_data(U, lon, lat, V, lon, lat, depth, time, field_data={'P': P}, mesh='flat')
def grid_geometric_polar(xdim=200, ydim=100): """ Standard earth grid with U and V equivalent to lon/lat in m and the inversion of the pole correction applied to U. """ lon = np.linspace(-180, 180, xdim, dtype=np.float32) lat = np.linspace(-90, 90, ydim, dtype=np.float32) U, V = np.meshgrid(lat, lon) # Apply inverse of pole correction to U for i, y in enumerate(lat): U[:, i] *= cos(y * pi / 180) U *= 1000. * 1.852 * 60. V *= 1000. * 1.852 * 60. grid = Grid.from_data(np.array(U, dtype=np.float32), lon, lat, np.array(V, dtype=np.float32), lon, lat, mesh='spherical') return grid
def test_grid_constant(mode): u, v, lon, lat, depth, time = generate_grid(100, 100) grid = Grid.from_data(u, lon, lat, v, lon, lat, depth, time) westval = -0.2 eastval = 0.3 grid.add_constant('movewest', westval) grid.add_constant('moveeast', eastval) assert grid.movewest == westval pset = ParticleSet.from_line(grid, size=1, pclass=ptype[mode], start=(0.5, 0.5), finish=(0.5, 0.5)) pset.execute(pset.Kernel(addConst), dt=1, runtime=1) assert abs(pset[0].lon - (0.5 + westval + eastval)) < 1e-4
def test_zonalflow_sperical(mode, xdim=100, ydim=200): """ Create uniform EASTWARD flow on sperical earth and advect particles As flow is so simple, it can be directly compared to analytical solution Note that in this case the cosine conversion is needed """ ParticleClass = JITParticle if mode == 'jit' else Particle class MyParticle(ParticleClass): user_vars = {'p': np.float32} def __init__(self, *args, **kwargs): super(MyParticle, self).__init__(*args, **kwargs) self.p = 1. def __repr__(self): return "P(%.4f, %.4f)[p=%.5f]" % (self.lon, self.lat, self.p) maxvel = 1. p_fld = 10 lon = np.linspace(-180, 180, xdim, dtype=np.float32) lat = np.linspace(-90, 90, ydim, dtype=np.float32) V = np.zeros([xdim, ydim]) U = maxvel * np.ones([xdim, ydim]) P = p_fld * np.ones([xdim, ydim]) grid = Grid.from_data(np.array(U, dtype=np.float32), lon, lat, np.array(V, dtype=np.float32), lon, lat, field_data={'P': np.array(P, dtype=np.float32)}) lonstart = [0, 45] latstart = [0, 45] endtime = delta(hours=24) pset = grid.ParticleSet(2, pclass=MyParticle, lon=lonstart, lat=latstart) pset.execute(pset.Kernel(AdvectionRK4) + pset.Kernel(UpdateP), endtime=endtime, dt=delta(hours=1)) assert(pset[0].lat - latstart[0] < 1e-4) assert(pset[0].lon - (lonstart[0] + endtime.total_seconds() * maxvel / 1852 / 60 / cos(latstart[0] * pi / 180)) < 1e-4) assert(abs(pset[0].p - p_fld) < 1e-4) assert(pset[1].lat - latstart[1] < 1e-4) assert(pset[1].lon - (lonstart[1] + endtime.total_seconds() * maxvel / 1852 / 60 / cos(latstart[1] * pi / 180)) < 1e-4) assert(abs(pset[1].p - p_fld) < 1e-4)
def test_sampling_out_of_bounds_time(mode, allow_time_extrapolation, k_sample_p, xdim=10, ydim=10, tdim=10): lon = np.linspace(0., 1., xdim, dtype=np.float32) lat = np.linspace(0., 1., ydim, dtype=np.float32) time = np.linspace(0., 1., tdim, dtype=np.float64) U = np.zeros((xdim, ydim, tdim), dtype=np.float32) V = np.zeros((xdim, ydim, tdim), dtype=np.float32) P = np.ones((xdim, ydim, 1), dtype=np.float32) * time grid = Grid.from_data(U, lon, lat, V, lon, lat, time=time, mesh='flat', field_data={'P': np.asarray(P, dtype=np.float32)}, allow_time_extrapolation=allow_time_extrapolation) pset = ParticleSet.from_line(grid, size=1, pclass=pclass(mode), start=(0.5, 0.5), finish=(0.5, 0.5)) if allow_time_extrapolation: pset.execute(k_sample_p, starttime=-1.0, endtime=-0.9, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 0.0, rtol=1e-5) else: with pytest.raises(RuntimeError): pset.execute(k_sample_p, starttime=-1.0, endtime=-0.9, dt=0.1) pset.execute(k_sample_p, starttime=0.0, endtime=0.1, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 0.0, rtol=1e-5) pset.execute(k_sample_p, starttime=0.5, endtime=0.6, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 0.5, rtol=1e-5) pset.execute(k_sample_p, starttime=1.0, endtime=1.1, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 1.0, rtol=1e-5) if allow_time_extrapolation: pset.execute(k_sample_p, starttime=2.0, endtime=2.1, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 1.0, rtol=1e-5) else: with pytest.raises(RuntimeError): pset.execute(k_sample_p, starttime=2.0, endtime=2.1, dt=0.1)
def test_random_field(mode, k_sample_p, xdim=20, ydim=20, npart=100): """Sampling test that test for overshoots by sampling a field of random numbers between 0 and 1. """ np.random.seed(123456) lon = np.linspace(0., 1., xdim, dtype=np.float32) lat = np.linspace(0., 1., ydim, dtype=np.float32) U = np.zeros((xdim, ydim), dtype=np.float32) V = np.zeros((xdim, ydim), dtype=np.float32) P = np.random.uniform(0, 1., size=(xdim, ydim)) S = np.ones((xdim, ydim), dtype=np.float32) grid = Grid.from_data(U, lon, lat, V, lon, lat, mesh='flat', field_data={'P': np.asarray(P, dtype=np.float32), 'start': S}) pset = grid.ParticleSet(size=npart, pclass=pclass(mode), start_field=grid.start) pset.execute(k_sample_p, endtime=1., dt=1.0) sampled = np.array([p.p for p in pset]) assert((sampled >= 0.).all())
def stommel_grid(xdim=200, ydim=200): """Simulate a periodic current along a western boundary, with significantly larger velocities along the western edge than the rest of the region The original test description can be found in: N. Fabbroni, 2009, Numerical Simulation of Passive tracers dispersion in the sea, Ph.D. dissertation, University of Bologna http://amsdottorato.unibo.it/1733/1/Fabbroni_Nicoletta_Tesi.pdf """ # Set NEMO grid variables depth = np.zeros(1, dtype=np.float32) time = np.linspace(0., 100000. * 86400., 2, dtype=np.float64) # Coordinates of the test grid (on A-grid in deg) lon = np.linspace(0, 60, xdim, dtype=np.float32) lat = np.linspace(0, 60, ydim, dtype=np.float32) # Define arrays U (zonal), V (meridional), W (vertical) and P (sea # surface height) all on A-grid U = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) V = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) # Some constants day = 11.6 r = 1 / (day * 86400) beta = 2e-11 a = 2000000 e_s = r / (beta * a) [x, y] = np.mgrid[:lon.size, :lat.size] for t in range(time.size): for i in range(lon.size): for j in range(lat.size): U[i, j, t] = -(1 - math.exp(-lon[i] * math.pi / 180 / e_s) - lon[i] * math.pi / 180) * math.pi ** 2 *\ math.cos(math.pi ** 2 * lat[j] / 180) V[i, j, t] = (math.exp(-lon[i] * math.pi / 180 / e_s) / e_s - 1) * math.pi * math.sin(math.pi**2 * lat[j] / 180) return Grid.from_data(U, lon, lat, V, lon, lat, depth, time)
def test_sampling_out_of_bounds_time(mode, k_sample_p, xdim=10, ydim=10, tdim=10): lon = np.linspace(0., 1., xdim, dtype=np.float32) lat = np.linspace(0., 1., ydim, dtype=np.float32) time = np.linspace(0., 1., tdim, dtype=np.float64) U = np.zeros((xdim, ydim, tdim), dtype=np.float32) V = np.zeros((xdim, ydim, tdim), dtype=np.float32) P = np.ones((xdim, ydim, 1), dtype=np.float32) * time grid = Grid.from_data(U, lon, lat, V, lon, lat, time=time, mesh='flat', field_data={'P': P}) pset = grid.ParticleSet(size=1, pclass=pclass(mode), start=(0.5, 0.5), finish=(0.5, 0.5)) pset.execute(k_sample_p, starttime=-1.0, endtime=-0.9, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 0.0, rtol=1e-5) pset.execute(k_sample_p, starttime=0.0, endtime=0.1, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 0.0, rtol=1e-5) pset.execute(k_sample_p, starttime=0.5, endtime=0.6, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 0.5, rtol=1e-5) pset.execute(k_sample_p, starttime=1.0, endtime=1.1, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 1.0, rtol=1e-5) pset.execute(k_sample_p, starttime=2.0, endtime=2.1, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 1.0, rtol=1e-5)
def test_sampling_out_of_bounds_time(mode, k_sample_p, xdim=10, ydim=10, tdim=10): lon = np.linspace(0., 1., xdim, dtype=np.float32) lat = np.linspace(0., 1., ydim, dtype=np.float32) time = np.linspace(0., 1., tdim, dtype=np.float64) U = np.zeros((xdim, ydim, tdim), dtype=np.float32) V = np.zeros((xdim, ydim, tdim), dtype=np.float32) P = np.ones((xdim, ydim, 1), dtype=np.float32) * time grid = Grid.from_data(U, lon, lat, V, lon, lat, time=time, mesh='flat', field_data={'P': np.asarray(P, dtype=np.float32)}) pset = grid.ParticleSet(size=1, pclass=pclass(mode), start=(0.5, 0.5), finish=(0.5, 0.5)) pset.execute(k_sample_p, starttime=-1.0, endtime=-0.9, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 0.0, rtol=1e-5) pset.execute(k_sample_p, starttime=0.0, endtime=0.1, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 0.0, rtol=1e-5) pset.execute(k_sample_p, starttime=0.5, endtime=0.6, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 0.5, rtol=1e-5) pset.execute(k_sample_p, starttime=1.0, endtime=1.1, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 1.0, rtol=1e-5) pset.execute(k_sample_p, starttime=2.0, endtime=2.1, dt=0.1) assert np.allclose(np.array([p.p for p in pset]), 1.0, rtol=1e-5)
def grid_decaying(xdim=100, ydim=100, maxtime=delta(hours=6)): """Generate a grid encapsulating the flow field of a decaying eddy. Reference: N. Fabbroni, 2009, "Numerical simulations of passive tracers dispersion in the sea" """ lon = np.linspace(0, 25000, xdim, dtype=np.float32) lat = np.linspace(0, 25000, ydim, dtype=np.float32) time = np.arange(0., maxtime.total_seconds(), 60., dtype=np.float64) U = np.ones((xdim, ydim, 1), dtype=np.float32) * u_g *\ np.exp(-gamma_g * time) + (u_0 - u_g) * np.exp(-gamma * time) * np.cos(f * time) V = np.ones((xdim, ydim, 1), dtype=np.float32) * -(u_0 - u_g) *\ np.exp(-gamma * time) * np.sin(f * time) return Grid.from_data(np.asarray(U, np.float32), lon, lat, np.asarray(V, np.float32), lon, lat, time=time, mesh='flat')
def stommel_grid(xdim=200, ydim=200): """Simulate a periodic current along a western boundary, with significantly larger velocities along the western edge than the rest of the region The original test description can be found in: N. Fabbroni, 2009, Numerical Simulation of Passive tracers dispersion in the sea, Ph.D. dissertation, University of Bologna http://amsdottorato.unibo.it/1733/1/Fabbroni_Nicoletta_Tesi.pdf """ # Set NEMO grid variables depth = np.zeros(1, dtype=np.float32) time = np.linspace(0., 100000. * 86400., 2, dtype=np.float64) # Coordinates of the test grid (on A-grid in deg) lon = np.linspace(0, 60, xdim, dtype=np.float32) lat = np.linspace(0, 60, ydim, dtype=np.float32) # Define arrays U (zonal), V (meridional), W (vertical) and P (sea # surface height) all on A-grid U = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) V = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) # Some constants day = 11.6 r = 1 / (day * 86400) beta = 2e-11 a = 2000000 e_s = r / (beta * a) [x, y] = np.mgrid[:lon.size, :lat.size] for t in range(time.size): for i in range(lon.size): for j in range(lat.size): U[i, j, t] = -(1 - math.exp(-lon[i] * math.pi / 180 / e_s) - lon[i] * math.pi / 180) * math.pi ** 2 *\ math.cos(math.pi ** 2 * lat[j] / 180) V[i, j, t] = (math.exp(-lon[i] * math.pi / 180 / e_s) / e_s - 1) * math.pi * math.sin(math.pi ** 2 * lat[j] / 180) return Grid.from_data(U, lon, lat, V, lon, lat, depth, time)
def decaying_moving_eddy_grid(xdim=2, ydim=2): # Define 2D flat, square grid for testing purposes. """Simulate an ocean that accelerates subject to Coriolis force and dissipative effects, upon which a geostrophic current is superimposed. The original test description can be found in: N. Fabbroni, 2009, Numerical Simulation of Passive tracers dispersion in the sea, Ph.D. dissertation, University of Bologna http://amsdottorato.unibo.it/1733/1/Fabbroni_Nicoletta_Tesi.pdf """ depth = np.zeros(1, dtype=np.float32) time = np.arange(0., 2. * 86400., 60.*5., dtype=np.float64) lon = np.linspace(0, 20000, xdim, dtype=np.float32) lat = np.linspace(5000, 12000, ydim, dtype=np.float32) U = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) V = np.zeros((lon.size, lat.size, time.size), dtype=np.float32) for t in range(time.size): U[:, :, t] = u_g*np.exp(-gamma_g*time[t]) + (u_0-u_g)*np.exp(-gamma*time[t])*np.cos(f*time[t]) V[:, :, t] = -(u_0-u_g)*np.exp(-gamma*time[t])*np.sin(f*time[t]) return Grid.from_data(U, lon, lat, V, lon, lat, depth, time, mesh='flat')
def test_random_field(mode, xdim=20, ydim=20, npart=100): """Sampling test that test for overshoots by sampling a field of random numbers between 0 and 1. """ np.random.seed(123456) lon = np.linspace(0., 1., xdim, dtype=np.float32) lat = np.linspace(0., 1., ydim, dtype=np.float32) U = np.zeros((xdim, ydim), dtype=np.float32) V = np.zeros((xdim, ydim), dtype=np.float32) K = np.random.uniform(0, 1., size=(xdim, ydim)) S = np.ones((xdim, ydim), dtype=np.float32) grid = Grid.from_data(U, lon, lat, V, lon, lat, mesh='flat', field_data={'K': K, 'start': S}) class SampleParticle(ptype[mode]): user_vars = {'k': np.float32} pset = grid.ParticleSet(size=npart, pclass=SampleParticle, start_field=grid.start) def SampleK(particle, grid, time, dt): particle.k = grid.K[time, particle.lon, particle.lat] pset.execute(SampleK, endtime=1., dt=1.0) sampled = np.array([p.k for p in pset]) assert((sampled >= 0.).all())
def test_moving_eddies_file(gridfile, mode): grid = Grid.from_nemo(gridfile, extra_vars={'P': 'P'}) pset = moving_eddies_example(grid, 2, mode=mode) assert(pset[0].lon < 0.5 and 46.0 < pset[0].lat < 46.35) assert(pset[1].lon < 0.5 and 49.4 < pset[1].lat < 49.8)
def grid(xdim=100, ydim=100): U = np.zeros((xdim, ydim), dtype=np.float32) V = np.zeros((xdim, ydim), dtype=np.float32) lon = np.linspace(0, 1, xdim, dtype=np.float32) lat = np.linspace(0, 1, ydim, dtype=np.float32) return Grid.from_data(U, lon, lat, V, lon, lat, mesh='flat')