Skip to content

Commit 8b1b830

Browse files
Fixing CGrid_Velocity interpolation for multiple particles
Using vectorized version of dot-product calculation as suggested by @michaeldenes in #2152 (comment)
1 parent 1ecab22 commit 8b1b830

File tree

2 files changed

+22
-15
lines changed

2 files changed

+22
-15
lines changed

parcels/application_kernels/interpolation.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,16 +149,24 @@ def CGrid_Velocity(
149149
py = np.array([grid.lat[yi, xi], grid.lat[yi, xi + 1], grid.lat[yi + 1, xi + 1], grid.lat[yi + 1, xi]])
150150

151151
if vectorfield._mesh_type == "spherical":
152-
px[0] = px[0] + 360 if px[0] < x - 225 else px[0]
153-
px[0] = px[0] - 360 if px[0] > x + 225 else px[0]
152+
px[0] = np.where(px[0] < x - 225, px[0] + 360, px[0])
153+
px[0] = np.where(px[0] > x + 225, px[0] - 360, px[0])
154154
px[1:] = np.where(px[1:] - px[0] > 180, px[1:] - 360, px[1:])
155155
px[1:] = np.where(-px[1:] + px[0] > 180, px[1:] + 360, px[1:])
156156
xx = (1 - xsi) * (1 - eta) * px[0] + xsi * (1 - eta) * px[1] + xsi * eta * px[2] + (1 - xsi) * eta * px[3]
157157
np.testing.assert_allclose(xx, x, atol=1e-4)
158-
c1 = i_u._geodetic_distance(py[0], py[1], px[0], px[1], vectorfield._mesh_type, np.dot(i_u.phi2D_lin(0.0, xsi), py))
159-
c2 = i_u._geodetic_distance(py[1], py[2], px[1], px[2], vectorfield._mesh_type, np.dot(i_u.phi2D_lin(eta, 1.0), py))
160-
c3 = i_u._geodetic_distance(py[2], py[3], px[2], px[3], vectorfield._mesh_type, np.dot(i_u.phi2D_lin(1.0, xsi), py))
161-
c4 = i_u._geodetic_distance(py[3], py[0], px[3], px[0], vectorfield._mesh_type, np.dot(i_u.phi2D_lin(eta, 0.0), py))
158+
c1 = i_u._geodetic_distance(
159+
py[0], py[1], px[0], px[1], vectorfield._mesh_type, np.einsum("ij,ji->i", i_u.phi2D_lin(0.0, xsi), py)
160+
)
161+
c2 = i_u._geodetic_distance(
162+
py[1], py[2], px[1], px[2], vectorfield._mesh_type, np.einsum("ij,ji->i", i_u.phi2D_lin(eta, 1.0), py)
163+
)
164+
c3 = i_u._geodetic_distance(
165+
py[2], py[3], px[2], px[3], vectorfield._mesh_type, np.einsum("ij,ji->i", i_u.phi2D_lin(1.0, xsi), py)
166+
)
167+
c4 = i_u._geodetic_distance(
168+
py[3], py[0], px[3], px[0], vectorfield._mesh_type, np.einsum("ij,ji->i", i_u.phi2D_lin(eta, 0.0), py)
169+
)
162170

163171
lenT = 2 if np.any(tau > 0) else 1
164172
lenZ = 2 if np.any(zeta > 0) else 1

parcels/tools/interpolation_utils.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import math
21
from collections.abc import Callable
32
from typing import Literal
43

@@ -179,7 +178,7 @@ def _geodetic_distance(lat1: float, lat2: float, lon1: float, lon2: float, mesh:
179178
if mesh == "spherical":
180179
rad = np.pi / 180.0
181180
deg2m = 1852 * 60.0
182-
return np.sqrt(((lon2 - lon1) * deg2m * math.cos(rad * lat)) ** 2 + ((lat2 - lat1) * deg2m) ** 2)
181+
return np.sqrt(((lon2 - lon1) * deg2m * np.cos(rad * lat)) ** 2 + ((lat2 - lat1) * deg2m) ** 2)
183182
else:
184183
return np.sqrt((lon2 - lon1) ** 2 + (lat2 - lat1) ** 2)
185184

@@ -188,10 +187,10 @@ def _compute_jacobian_determinant(py: np.ndarray, px: np.ndarray, eta: float, xs
188187
dphidxsi = np.column_stack([eta - 1, 1 - eta, eta, -eta])
189188
dphideta = np.column_stack([xsi - 1, -xsi, xsi, 1 - xsi])
190189

191-
dxdxsi = np.dot(dphidxsi, px)
192-
dxdeta = np.dot(dphideta, px)
193-
dydxsi = np.dot(dphidxsi, py)
194-
dydeta = np.dot(dphideta, py)
195-
jac = dxdxsi * dydeta - dxdeta * dydxsi
196-
# TODO check how to properly vectorize this function (ok to return diagonal of the Jacobian?)
197-
return jac.diagonal()
190+
dxdxsi_diag = np.einsum("ij,ji->i", dphidxsi, px)
191+
dxdeta_diag = np.einsum("ij,ji->i", dphideta, px)
192+
dydxsi_diag = np.einsum("ij,ji->i", dphidxsi, py)
193+
dydeta_diag = np.einsum("ij,ji->i", dphideta, py)
194+
195+
jac_diag = dxdxsi_diag * dydeta_diag - dxdeta_diag * dydxsi_diag
196+
return jac_diag

0 commit comments

Comments
 (0)