Skip to content

Commit b5daffb

Browse files
Merge pull request #1899 from OceanParcels/removing_Field_transpose
Removing transpose option in Field creation
2 parents 855a2e3 + fe06006 commit b5daffb

File tree

12 files changed

+136
-162
lines changed

12 files changed

+136
-162
lines changed

docs/examples/example_moving_eddies.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ def cosd(x):
6565
dy = (lat[1] - lat[0]) * 1852 * 60 if mesh == "spherical" else lat[1] - lat[0]
6666

6767
# Define arrays U (zonal), V (meridional), and P (sea surface height) on A-grid
68-
U = np.zeros((lon.size, lat.size, time.size), dtype=np.float32)
69-
V = np.zeros((lon.size, lat.size, time.size), dtype=np.float32)
70-
P = np.zeros((lon.size, lat.size, time.size), dtype=np.float32)
68+
U = np.zeros((time.size, lat.size, lon.size), dtype=np.float32)
69+
V = np.zeros((time.size, lat.size, lon.size), dtype=np.float32)
70+
P = np.zeros((time.size, lat.size, lon.size), dtype=np.float32)
7171

7272
# Some constants
7373
corio_0 = 1.0e-4 # Coriolis parameter
@@ -78,32 +78,32 @@ def cosd(x):
7878
dX = eddyspeed * 86400 / dx # Grid cell movement of eddy max each day
7979
dY = eddyspeed * 86400 / dy # Grid cell movement of eddy max each day
8080

81-
[x, y] = np.mgrid[: lon.size, : lat.size]
81+
[y, x] = np.mgrid[: lat.size, : lon.size]
8282
for t in range(time.size):
8383
hymax_1 = lat.size / 7.0
8484
hxmax_1 = 0.75 * lon.size - dX * t
8585
hymax_2 = 3.0 * lat.size / 7.0 + dY * t
8686
hxmax_2 = 0.75 * lon.size - dX * t
8787

88-
P[:, :, t] = h0 * np.exp(
88+
P[t, :, :] = h0 * np.exp(
8989
-((x - hxmax_1) ** 2) / (sig * lon.size / 4.0) ** 2
9090
- (y - hymax_1) ** 2 / (sig * lat.size / 7.0) ** 2
9191
)
92-
P[:, :, t] += h0 * np.exp(
92+
P[t, :, :] += h0 * np.exp(
9393
-((x - hxmax_2) ** 2) / (sig * lon.size / 4.0) ** 2
9494
- (y - hymax_2) ** 2 / (sig * lat.size / 7.0) ** 2
9595
)
9696

97-
V[:-1, :, t] = -np.diff(P[:, :, t], axis=0) / dx / corio_0 * g
98-
V[-1, :, t] = V[-2, :, t] # Fill in the last column
97+
V[t, :, :-1] = -np.diff(P[t, :, :], axis=1) / dx / corio_0 * g
98+
V[t, :, -1] = V[t, :, -2] # Fill in the last column
9999

100-
U[:, :-1, t] = np.diff(P[:, :, t], axis=1) / dy / corio_0 * g
101-
U[:, -1, t] = U[:, -2, t] # Fill in the last row
100+
U[t, :-1, :] = np.diff(P[t, :, :], axis=0) / dy / corio_0 * g
101+
U[t, -1, :] = U[t, -2, :] # Fill in the last row
102102

103103
data = {"U": U, "V": V, "P": P}
104104
dimensions = {"lon": lon, "lat": lat, "time": time}
105105

106-
fieldset = parcels.FieldSet.from_data(data, dimensions, transpose=True, mesh=mesh)
106+
fieldset = parcels.FieldSet.from_data(data, dimensions, mesh=mesh)
107107

108108
# setting some constants for AdvectionRK45 kernel
109109
fieldset.RK45_min_dt = 1e-3

parcels/field.py

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,7 @@ class Field:
105105
name : str
106106
Name of the field
107107
data : np.ndarray
108-
2D, 3D or 4D numpy array of field data.
109-
110-
1. If data shape is [xdim, ydim], [xdim, ydim, zdim], [xdim, ydim, tdim] or [xdim, ydim, zdim, tdim],
111-
whichever is relevant for the dataset, use the flag transpose=True
112-
2. If data shape is [ydim, xdim], [zdim, ydim, xdim], [tdim, ydim, xdim] or [tdim, zdim, ydim, xdim],
113-
use the flag transpose=False
114-
3. If data has any other shape, you first need to reorder it
108+
2D, 3D or 4D numpy array of field data with shape [ydim, xdim], [zdim, ydim, xdim], [tdim, ydim, xdim] or [tdim, zdim, ydim, xdim],
115109
lon : np.ndarray or list
116110
Longitude coordinates (numpy vector or array) of the field (only if grid is None)
117111
lat : np.ndarray or list
@@ -135,8 +129,6 @@ class Field:
135129
mesh and time_origin information. Can be constructed from any of the Grid objects
136130
fieldtype : str
137131
Type of Field to be used for UnitConverter (either 'U', 'V', 'Kh_zonal', 'Kh_meridional' or None)
138-
transpose : bool
139-
Transpose data to required (lon, lat) layout
140132
vmin : float
141133
Minimum allowed value on the field. Data below this value are set to zero
142134
vmax : float
@@ -174,7 +166,6 @@ def __init__(
174166
mesh: Mesh = "flat",
175167
timestamps=None,
176168
fieldtype=None,
177-
transpose: bool = False,
178169
vmin: float | None = None,
179170
vmax: float | None = None,
180171
time_origin: TimeConverter | None = None,
@@ -244,7 +235,7 @@ def __init__(
244235
self.vmax = vmax
245236

246237
if not self.grid.defer_load:
247-
self.data = self._reshape(self.data, transpose)
238+
self.data = self._reshape(self.data)
248239
self._loaded_time_indices = range(self.grid.tdim)
249240

250241
# Hack around the fact that NaN and ridiculously large values
@@ -597,7 +588,7 @@ def from_netcdf(
597588
errormessage = (
598589
f"Field {filebuffer.name} expecting a data shape of [tdim={grid.tdim}, zdim={grid.zdim}, "
599590
f"ydim={grid.ydim}, xdim={grid.xdim }] "
600-
f"but got shape {buffer_data.shape}. Flag transpose=True could help to reorder the data."
591+
f"but got shape {buffer_data.shape}."
601592
)
602593
assert buffer_data.shape[0] == grid.tdim, errormessage
603594
assert buffer_data.shape[2] == grid.ydim, errormessage
@@ -696,12 +687,10 @@ def from_xarray(
696687
**kwargs,
697688
)
698689

699-
def _reshape(self, data, transpose=False):
690+
def _reshape(self, data):
700691
# Ensure that field data is the right data type
701692
if not isinstance(data, (np.ndarray)):
702693
data = np.array(data)
703-
if transpose:
704-
data = np.transpose(data)
705694
if self.grid._lat_flipped:
706695
data = np.flip(data, axis=-2)
707696

@@ -718,10 +707,7 @@ def _reshape(self, data, transpose=False):
718707
if len(data.shape) == 4:
719708
data = data.reshape(sum(((data.shape[0],), data.shape[2:]), ()))
720709
if len(data.shape) == 4:
721-
errormessage = (
722-
f"Field {self.name} expecting a data shape of [tdim, zdim, ydim, xdim]. "
723-
"Flag transpose=True could help to reorder the data."
724-
)
710+
errormessage = f"Field {self.name} expecting a data shape of [tdim, zdim, ydim, xdim]. "
725711
assert data.shape[0] == self.grid.tdim, errormessage
726712
assert data.shape[2] == self.grid.ydim, errormessage
727713
assert data.shape[3] == self.grid.xdim, errormessage
@@ -734,10 +720,7 @@ def _reshape(self, data, transpose=False):
734720
self.grid.tdim,
735721
self.grid.ydim,
736722
self.grid.xdim,
737-
), (
738-
f"Field {self.name} expecting a data shape of [tdim, ydim, xdim]. "
739-
"Flag transpose=True could help to reorder the data."
740-
)
723+
), f"Field {self.name} expecting a data shape of [tdim, ydim, xdim]. "
741724

742725
return data
743726

parcels/fieldset.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ def from_data(
7373
cls,
7474
data,
7575
dimensions,
76-
transpose=False,
7776
mesh: Mesh = "spherical",
7877
allow_time_extrapolation: bool | None = None,
7978
**kwargs,
@@ -85,21 +84,14 @@ def from_data(
8584
data :
8685
Dictionary mapping field names to numpy arrays.
8786
Note that at least a 'U' and 'V' numpy array need to be given, and that
88-
the built-in Advection kernels assume that U and V are in m/s
89-
90-
1. If data shape is [xdim, ydim], [xdim, ydim, zdim], [xdim, ydim, tdim] or [xdim, ydim, zdim, tdim],
91-
whichever is relevant for the dataset, use the flag transpose=True
92-
2. If data shape is [ydim, xdim], [zdim, ydim, xdim], [tdim, ydim, xdim] or [tdim, zdim, ydim, xdim],
93-
use the flag transpose=False (default value)
94-
3. If data has any other shape, you first need to reorder it
87+
the built-in Advection kernels assume that U and V are in m/s.
88+
Data shape is either [ydim, xdim], [zdim, ydim, xdim], [tdim, ydim, xdim] or [tdim, zdim, ydim, xdim],
9589
dimensions : dict
9690
Dictionary mapping field dimensions (lon,
9791
lat, depth, time) to numpy arrays.
9892
Note that dimensions can also be a dictionary of dictionaries if
9993
dimension names are different for each variable
10094
(e.g. dimensions['U'], dimensions['V'], etc).
101-
transpose : bool
102-
Whether to transpose data on read-in (Default value = False)
10395
mesh : str
10496
String indicating the type of mesh coordinates and
10597
units used during velocity interpolation, see also `this tutorial <../examples/tutorial_unitconverters.ipynb>`__:
@@ -153,7 +145,6 @@ def from_data(
153145
name,
154146
datafld,
155147
grid=grid,
156-
transpose=transpose,
157148
allow_time_extrapolation=allow_time_extrapolation,
158149
**kwargs,
159150
)

tests/test_advection.py

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -61,22 +61,22 @@ def test_advection_zonal(lon, lat, depth):
6161
"""Particles at high latitude move geographically faster due to the pole correction in `GeographicPolar`."""
6262
npart = 10
6363
data2D = {
64-
"U": np.ones((lon.size, lat.size), dtype=np.float32),
65-
"V": np.zeros((lon.size, lat.size), dtype=np.float32),
64+
"U": np.ones((lat.size, lon.size), dtype=np.float32),
65+
"V": np.zeros((lat.size, lon.size), dtype=np.float32),
6666
}
6767
data3D = {
68-
"U": np.ones((lon.size, lat.size, depth.size), dtype=np.float32),
69-
"V": np.zeros((lon.size, lat.size, depth.size), dtype=np.float32),
68+
"U": np.ones((depth.size, lat.size, lon.size), dtype=np.float32),
69+
"V": np.zeros((depth.size, lat.size, lon.size), dtype=np.float32),
7070
}
7171
dimensions = {"lon": lon, "lat": lat}
72-
fieldset2D = FieldSet.from_data(data2D, dimensions, mesh="spherical", transpose=True)
72+
fieldset2D = FieldSet.from_data(data2D, dimensions, mesh="spherical")
7373

7474
pset2D = ParticleSet(fieldset2D, pclass=Particle, lon=np.zeros(npart) + 20.0, lat=np.linspace(0, 80, npart))
7575
pset2D.execute(AdvectionRK4, runtime=timedelta(hours=2), dt=timedelta(seconds=30))
7676
assert (np.diff(pset2D.lon) > 1.0e-4).all()
7777

7878
dimensions["depth"] = depth
79-
fieldset3D = FieldSet.from_data(data3D, dimensions, mesh="spherical", transpose=True)
79+
fieldset3D = FieldSet.from_data(data3D, dimensions, mesh="spherical")
8080
pset3D = ParticleSet(
8181
fieldset3D,
8282
pclass=Particle,
@@ -91,9 +91,9 @@ def test_advection_zonal(lon, lat, depth):
9191
def test_advection_meridional(lon, lat):
9292
"""Particles at high latitude move geographically faster due to the pole correction in `GeographicPolar`."""
9393
npart = 10
94-
data = {"U": np.zeros((lon.size, lat.size), dtype=np.float32), "V": np.ones((lon.size, lat.size), dtype=np.float32)}
94+
data = {"U": np.zeros((lat.size, lon.size), dtype=np.float32), "V": np.ones((lat.size, lon.size), dtype=np.float32)}
9595
dimensions = {"lon": lon, "lat": lat}
96-
fieldset = FieldSet.from_data(data, dimensions, mesh="spherical", transpose=True)
96+
fieldset = FieldSet.from_data(data, dimensions, mesh="spherical")
9797

9898
pset = ParticleSet(fieldset, pclass=Particle, lon=np.linspace(-60, 60, npart), lat=np.linspace(0, 30, npart))
9999
delta_lat = np.diff(pset.lat)
@@ -110,9 +110,9 @@ def test_advection_3D():
110110
"lat": np.linspace(0.0, 1e4, ydim, dtype=np.float32),
111111
"depth": np.linspace(0.0, 1.0, zdim, dtype=np.float32),
112112
}
113-
data = {"U": np.ones((xdim, ydim, zdim), dtype=np.float32), "V": np.zeros((xdim, ydim, zdim), dtype=np.float32)}
114-
data["U"][:, :, 0] = 0.0
115-
fieldset = FieldSet.from_data(data, dimensions, mesh="flat", transpose=True)
113+
data = {"U": np.ones((zdim, ydim, xdim), dtype=np.float32), "V": np.zeros((zdim, ydim, xdim), dtype=np.float32)}
114+
data["U"][0, :, :] = 0.0
115+
fieldset = FieldSet.from_data(data, dimensions, mesh="flat")
116116

117117
pset = ParticleSet(
118118
fieldset, pclass=Particle, lon=np.zeros(npart), lat=np.zeros(npart) + 1e2, depth=np.linspace(0, 1, npart)
@@ -171,11 +171,11 @@ def SubmergeParticle(particle, fieldset, time): # pragma: no cover
171171
def test_advection_RK45(lon, lat, rk45_tol):
172172
npart = 10
173173
data2D = {
174-
"U": np.ones((lon.size, lat.size), dtype=np.float32),
175-
"V": np.zeros((lon.size, lat.size), dtype=np.float32),
174+
"U": np.ones((lat.size, lon.size), dtype=np.float32),
175+
"V": np.zeros((lat.size, lon.size), dtype=np.float32),
176176
}
177177
dimensions = {"lon": lon, "lat": lat}
178-
fieldset = FieldSet.from_data(data2D, dimensions, mesh="spherical", transpose=True)
178+
fieldset = FieldSet.from_data(data2D, dimensions, mesh="spherical")
179179
fieldset.add_constant("RK45_tol", rk45_tol)
180180

181181
dt = timedelta(seconds=30).total_seconds()
@@ -266,8 +266,8 @@ def create_periodic_fieldset(xdim, ydim, uvel, vvel):
266266
"lat": np.linspace(0.0, 1.0, ydim + 1, dtype=np.float32)[1:],
267267
}
268268

269-
data = {"U": uvel * np.ones((xdim, ydim), dtype=np.float32), "V": vvel * np.ones((xdim, ydim), dtype=np.float32)}
270-
return FieldSet.from_data(data, dimensions, mesh="spherical", transpose=True)
269+
data = {"U": uvel * np.ones((ydim, xdim), dtype=np.float32), "V": vvel * np.ones((ydim, xdim), dtype=np.float32)}
270+
return FieldSet.from_data(data, dimensions, mesh="spherical")
271271

272272

273273
def periodicBC(particle, fieldset, time): # pragma: no cover
@@ -379,10 +379,10 @@ def create_fieldset_stationary(xdim=100, ydim=100, maxtime=timedelta(hours=6)):
379379
"time": time,
380380
}
381381
data = {
382-
"U": np.ones((xdim, ydim, 1), dtype=np.float32) * u_0 * np.cos(f * time),
383-
"V": np.ones((xdim, ydim, 1), dtype=np.float32) * -u_0 * np.sin(f * time),
382+
"U": np.transpose(np.ones((xdim, ydim, 1), dtype=np.float32) * u_0 * np.cos(f * time)),
383+
"V": np.transpose(np.ones((xdim, ydim, 1), dtype=np.float32) * -u_0 * np.sin(f * time)),
384384
}
385-
fieldset = FieldSet.from_data(data, dimensions, mesh="flat", transpose=True)
385+
fieldset = FieldSet.from_data(data, dimensions, mesh="flat")
386386
# setting some constants for AdvectionRK45 kernel
387387
fieldset.RK45_min_dt = 1e-3
388388
fieldset.RK45_max_dt = 1e2
@@ -441,13 +441,13 @@ def test_stationary_eddy_vertical():
441441
lon_data = np.linspace(0, 25000, xdim, dtype=np.float32)
442442
lat_data = np.linspace(0, 25000, ydim, dtype=np.float32)
443443
time_data = np.arange(0.0, 6 * 3600 + 1e-5, 60.0, dtype=np.float64)
444-
fld1 = np.ones((xdim, ydim, 1), dtype=np.float32) * u_0 * np.cos(f * time_data)
445-
fld2 = np.ones((xdim, ydim, 1), dtype=np.float32) * -u_0 * np.sin(f * time_data)
446-
fldzero = np.zeros((xdim, ydim, 1), dtype=np.float32) * time_data
444+
fld1 = np.transpose(np.ones((xdim, ydim, 1), dtype=np.float32) * u_0 * np.cos(f * time_data))
445+
fld2 = np.transpose(np.ones((xdim, ydim, 1), dtype=np.float32) * -u_0 * np.sin(f * time_data))
446+
fldzero = np.transpose(np.zeros((xdim, ydim, 1), dtype=np.float32) * time_data)
447447

448448
dimensions = {"lon": lon_data, "lat": lat_data, "time": time_data}
449449
data = {"U": fld1, "V": fldzero, "W": fld2}
450-
fieldset = FieldSet.from_data(data, dimensions, mesh="flat", transpose=True)
450+
fieldset = FieldSet.from_data(data, dimensions, mesh="flat")
451451

452452
pset = ParticleSet(fieldset, pclass=Particle, lon=lon, lat=lat, depth=depth)
453453
pset.execute(AdvectionRK4_3D, dt=dt, endtime=endtime)
@@ -459,7 +459,7 @@ def test_stationary_eddy_vertical():
459459
assert np.allclose(pset.depth, exp_depth, rtol=1e-5)
460460

461461
data = {"U": fldzero, "V": fld2, "W": fld1}
462-
fieldset = FieldSet.from_data(data, dimensions, mesh="flat", transpose=True)
462+
fieldset = FieldSet.from_data(data, dimensions, mesh="flat")
463463

464464
pset = ParticleSet(fieldset, pclass=Particle, lon=lon, lat=lat, depth=depth)
465465
pset.execute(AdvectionRK4_3D, dt=dt, endtime=endtime)
@@ -489,10 +489,10 @@ def create_fieldset_moving(xdim=100, ydim=100, maxtime=timedelta(hours=6)):
489489
"time": time,
490490
}
491491
data = {
492-
"U": np.ones((xdim, ydim, 1), dtype=np.float32) * u_g + (u_0 - u_g) * np.cos(f * time),
493-
"V": np.ones((xdim, ydim, 1), dtype=np.float32) * -(u_0 - u_g) * np.sin(f * time),
492+
"U": np.transpose(np.ones((xdim, ydim, 1), dtype=np.float32) * u_g + (u_0 - u_g) * np.cos(f * time)),
493+
"V": np.transpose(np.ones((xdim, ydim, 1), dtype=np.float32) * -(u_0 - u_g) * np.sin(f * time)),
494494
}
495-
return FieldSet.from_data(data, dimensions, mesh="flat", transpose=True)
495+
return FieldSet.from_data(data, dimensions, mesh="flat")
496496

497497

498498
@pytest.fixture
@@ -561,11 +561,15 @@ def create_fieldset_decaying(xdim=100, ydim=100, maxtime=timedelta(hours=6)):
561561
"time": time,
562562
}
563563
data = {
564-
"U": np.ones((xdim, ydim, 1), dtype=np.float32) * u_g * np.exp(-gamma_g * time)
565-
+ (u_0 - u_g) * np.exp(-gamma * time) * np.cos(f * time),
566-
"V": np.ones((xdim, ydim, 1), dtype=np.float32) * -(u_0 - u_g) * np.exp(-gamma * time) * np.sin(f * time),
564+
"U": np.transpose(
565+
np.ones((xdim, ydim, 1), dtype=np.float32) * u_g * np.exp(-gamma_g * time)
566+
+ (u_0 - u_g) * np.exp(-gamma * time) * np.cos(f * time)
567+
),
568+
"V": np.transpose(
569+
np.ones((xdim, ydim, 1), dtype=np.float32) * -(u_0 - u_g) * np.exp(-gamma * time) * np.sin(f * time)
570+
),
567571
}
568-
return FieldSet.from_data(data, dimensions, mesh="flat", transpose=True)
572+
return FieldSet.from_data(data, dimensions, mesh="flat")
569573

570574

571575
@pytest.fixture

tests/test_data/create_testfields.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ def generate_testfieldset(xdim, ydim, zdim, tdim):
2929
lat = np.linspace(0.0, 1.0, ydim, dtype=np.float32)
3030
depth = np.linspace(0.0, 0.5, zdim, dtype=np.float32)
3131
time = np.linspace(0.0, tdim, tdim, dtype=np.float64)
32-
U = np.ones((xdim, ydim, zdim, tdim), dtype=np.float32)
33-
V = np.zeros((xdim, ydim, zdim, tdim), dtype=np.float32)
34-
P = 2.0 * np.ones((xdim, ydim, zdim, tdim), dtype=np.float32)
32+
U = np.ones((tdim, zdim, ydim, xdim), dtype=np.float32)
33+
V = np.zeros((tdim, zdim, ydim, xdim), dtype=np.float32)
34+
P = 2.0 * np.ones((tdim, zdim, ydim, xdim), dtype=np.float32)
3535
data = {"U": U, "V": V, "P": P}
3636
dimensions = {"lon": lon, "lat": lat, "depth": depth, "time": time}
37-
fieldset = FieldSet.from_data(data, dimensions, mesh="flat", transpose=True)
37+
fieldset = FieldSet.from_data(data, dimensions, mesh="flat")
3838
fieldset.write("testfields")
3939

4040

@@ -66,7 +66,7 @@ def generate_perlin_testfield():
6666
if asizeof is not None:
6767
print(f"Perlin U-field requires {U.size * U.itemsize} bytes of memory.")
6868
print(f"Perlin V-field requires {V.size * V.itemsize} bytes of memory.")
69-
fieldset = FieldSet.from_data(data, dimensions, mesh="spherical", transpose=False)
69+
fieldset = FieldSet.from_data(data, dimensions, mesh="spherical")
7070
# fieldset.write("perlinfields") # can also be used, but then has a ghost depth dimension
7171
write_simple_2Dt(fieldset.U, os.path.join(os.path.dirname(__file__), "perlinfields"), varname="vozocrtx")
7272
write_simple_2Dt(fieldset.V, os.path.join(os.path.dirname(__file__), "perlinfields"), varname="vomecrty")

0 commit comments

Comments
 (0)