|
1 | | -from parcels import FieldSet, ParticleSet, ScipyParticle, JITParticle |
| 1 | +from parcels import FieldSet, Field, ParticleSet, ScipyParticle, JITParticle, BrownianMotion2D |
2 | 2 | import numpy as np |
3 | 3 | from datetime import timedelta as delta |
4 | | -import math |
5 | 4 | from parcels import random |
6 | 5 | import pytest |
7 | 6 |
|
8 | 7 | ptype = {'scipy': ScipyParticle, 'jit': JITParticle} |
9 | 8 |
|
10 | 9 |
|
11 | | -def two_dim_brownian_flat(particle, fieldset, time, dt): |
12 | | - # Kernel for simple Brownian particle diffusion in zonal and meridional direction. |
13 | | - |
14 | | - particle.lat += random.normalvariate(0, 1)*math.sqrt(2*dt*fieldset.Kh_meridional) |
15 | | - particle.lon += random.normalvariate(0, 1)*math.sqrt(2*dt*fieldset.Kh_zonal) |
16 | | - |
17 | | - |
18 | | -def brownian_fieldset(xdim=200, ydim=200): # Define a flat fieldset of zeros, for simplicity. |
19 | | - dimensions = {'lon': np.linspace(0, 600000, xdim, dtype=np.float32), |
20 | | - 'lat': np.linspace(0, 600000, ydim, dtype=np.float32)} |
| 10 | +def zeros_fieldset(xdim=2, ydim=2): |
| 11 | + """Generates a zero velocity field""" |
| 12 | + lon = np.linspace(-20, 20, xdim, dtype=np.float32) |
| 13 | + lat = np.linspace(-20, 20, ydim, dtype=np.float32) |
21 | 14 |
|
| 15 | + dimensions = {'lon': lon, 'lat': lat} |
22 | 16 | data = {'U': np.zeros((xdim, ydim), dtype=np.float32), |
23 | 17 | 'V': np.zeros((xdim, ydim), dtype=np.float32)} |
24 | | - |
25 | | - return FieldSet.from_data(data, dimensions, mesh='flat') |
| 18 | + return FieldSet.from_data(data, dimensions, mesh='spherical') |
26 | 19 |
|
27 | 20 |
|
28 | 21 | @pytest.mark.parametrize('mode', ['scipy', 'jit']) |
29 | 22 | def test_brownian_example(mode, npart=3000): |
30 | | - fieldset = brownian_fieldset() |
| 23 | + fieldset = zeros_fieldset() |
31 | 24 |
|
32 | 25 | # Set diffusion constants. |
33 | | - fieldset.Kh_meridional = 100. |
34 | | - fieldset.Kh_zonal = 100. |
| 26 | + kh_zonal = 100 |
| 27 | + kh_meridional = 100 |
| 28 | + |
| 29 | + # Create field of Kh_zonal and Kh_meridional, using same grid as U |
| 30 | + grid = fieldset.U.grid |
| 31 | + fieldset.add_field(Field('Kh_zonal', kh_zonal*np.ones((2, 2)), grid=grid)) |
| 32 | + fieldset.add_field(Field('Kh_meridional', kh_meridional*np.ones((2, 2)), grid=grid)) |
35 | 33 |
|
36 | 34 | # Set random seed |
37 | 35 | random.seed(123456) |
38 | 36 |
|
39 | | - ptcls_start = 300000. # Start all particles at same location in middle of grid. |
40 | | - pset = ParticleSet.from_line(fieldset=fieldset, size=npart, pclass=ptype[mode], |
41 | | - start=(ptcls_start, ptcls_start), |
42 | | - finish=(ptcls_start, ptcls_start)) |
43 | | - |
44 | | - endtime = delta(days=1) |
45 | | - dt = delta(hours=1) |
46 | | - interval = delta(hours=1) |
| 37 | + runtime = delta(days=1) |
47 | 38 |
|
48 | | - k_brownian = pset.Kernel(two_dim_brownian_flat) |
| 39 | + random.seed(1234) |
| 40 | + pset = ParticleSet(fieldset=fieldset, pclass=ptype[mode], |
| 41 | + lon=np.zeros(npart), lat=np.zeros(npart)) |
| 42 | + pset.execute(pset.Kernel(BrownianMotion2D), |
| 43 | + runtime=runtime, dt=delta(hours=1)) |
49 | 44 |
|
50 | | - pset.execute(k_brownian, endtime=endtime, dt=dt, interval=interval, |
51 | | - output_file=pset.ParticleFile(name="BrownianParticle"), |
52 | | - show_movie=False) |
| 45 | + expected_std_x = np.sqrt(2*kh_zonal*runtime.total_seconds()) |
| 46 | + expected_std_y = np.sqrt(2*kh_meridional*runtime.total_seconds()) |
53 | 47 |
|
54 | | - lats = np.array([particle.lat for particle in pset.particles]) |
55 | | - lons = np.array([particle.lon for particle in pset.particles]) |
56 | | - expected_std_lat = np.sqrt(2*fieldset.Kh_meridional*endtime.total_seconds()) |
57 | | - expected_std_lon = np.sqrt(2*fieldset.Kh_zonal*endtime.total_seconds()) |
| 48 | + conversion = (1852 * 60) # to convert from degrees to m |
| 49 | + ys = np.array([p.lat for p in pset]) * conversion |
| 50 | + xs = np.array([p.lon for p in pset]) * conversion # since near equator, we do not need to care about curvature effect |
58 | 51 |
|
59 | | - assert np.allclose(np.std(lats), expected_std_lat, rtol=.1) |
60 | | - assert np.allclose(np.std(lons), expected_std_lon, rtol=.1) |
61 | | - assert np.allclose(np.mean(lons), ptcls_start, rtol=.1) |
62 | | - assert np.allclose(np.mean(lats), ptcls_start, rtol=.1) |
| 52 | + tol = 200 # 200m tolerance |
| 53 | + assert np.allclose(np.std(xs), expected_std_x, atol=tol) |
| 54 | + assert np.allclose(np.std(ys), expected_std_y, atol=tol) |
| 55 | + assert np.allclose(np.mean(xs), 0, atol=tol) |
| 56 | + assert np.allclose(np.mean(ys), 0, atol=tol) |
63 | 57 |
|
64 | 58 |
|
65 | 59 | if __name__ == "__main__": |
|
0 commit comments