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 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 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_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 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 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 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 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 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 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 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 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 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 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_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_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_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_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 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 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 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 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 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 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 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')
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., 25. * 86400., 86400., 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.e-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. hxmax_1 = .75 * lon.size - dX * t hymax_2 = 3. * lat.size / 7. + dY * t hxmax_2 = .75 * lon.size - dX * t P[:, :, t] = h0 * np.exp(-(x - hxmax_1)**2 / (sig * lon.size / 4.)**2 - (y - hymax_1)**2 / (sig * lat.size / 7.)**2) P[:, :, t] += h0 * np.exp(-(x - hxmax_2)**2 / (sig * lon.size / 4.)**2 - (y - hymax_2)**2 / (sig * lat.size / 7.)**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})