Skip to content

Commit 9269711

Browse files
Restrict barycentric coordinate calculation to triangle faces
In developing the tutorial, the generalized waschpress point implementation was found to erroneously detect inside/outside . I've rolled back to a more simple barycentric coordinate calculation for 3-sided faces and put in a restriction that ux.grid objects have at most 3 nodes per face. I'll open an issue for implementing a more robust n-sided waschpress point implementation
1 parent c788cf6 commit 9269711

File tree

2 files changed

+26
-27
lines changed

2 files changed

+26
-27
lines changed

src/parcels/_core/index_search.py

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,9 @@ def uxgrid_point_in_cell(grid, y: np.ndarray, x: np.ndarray, yi: np.ndarray, xi:
209209
x : np.ndarray
210210
Array of longitudes of the points to check.
211211
yi : np.ndarray
212-
Array of face indices corresponding to the points.
213-
xi : np.ndarray
214212
Not used, but included for compatibility with other search functions.
213+
xi : np.ndarray
214+
Array of face indices corresponding to the points.
215215
216216
Returns
217217
-------
@@ -227,7 +227,7 @@ def uxgrid_point_in_cell(grid, y: np.ndarray, x: np.ndarray, yi: np.ndarray, xi:
227227
points = np.column_stack((x_cart.flatten(), y_cart.flatten(), z_cart.flatten()))
228228

229229
# Get the vertex indices for each face
230-
nids = grid.uxgrid.face_node_connectivity[yi].values
230+
nids = grid.uxgrid.face_node_connectivity[xi].values
231231
face_vertices = np.stack(
232232
(
233233
grid.uxgrid.node_x[nids.ravel()].values.reshape(nids.shape),
@@ -237,7 +237,7 @@ def uxgrid_point_in_cell(grid, y: np.ndarray, x: np.ndarray, yi: np.ndarray, xi:
237237
axis=-1,
238238
)
239239
else:
240-
nids = grid.uxgrid.face_node_connectivity[yi].values
240+
nids = grid.uxgrid.face_node_connectivity[xi].values
241241
face_vertices = np.stack(
242242
(
243243
grid.uxgrid.node_lon[nids.ravel()].values.reshape(nids.shape),
@@ -249,11 +249,11 @@ def uxgrid_point_in_cell(grid, y: np.ndarray, x: np.ndarray, yi: np.ndarray, xi:
249249

250250
M = len(points)
251251

252-
is_in_cell = np.zeros(M, dtype=np.int32)
253-
254252
coords = _barycentric_coordinates(face_vertices, points)
255-
is_in_cell = np.where(np.all((coords >= -1e-6) & (coords <= 1 + 1e-6), axis=1), 1, 0)
256253

254+
is_in_cell = np.zeros(M, dtype=np.int32)
255+
is_in_cell = np.where(np.all((coords >= -1e-6), axis=1), 1, 0)
256+
is_in_cell &= np.isclose(np.sum(coords, axis=1), 1.0, rtol=1e-3, atol=1e-6)
257257
return is_in_cell, coords
258258

259259

@@ -278,8 +278,10 @@ def _triangle_area(A, B, C):
278278
def _barycentric_coordinates(nodes, points, min_area=1e-8):
279279
"""
280280
Compute the barycentric coordinates of a point P inside a convex polygon using area-based weights.
281-
So that this method generalizes to n-sided polygons, we use the Waschpress points as the generalized
282-
barycentric coordinates, which is only valid for convex polygons.
281+
This method currently only works for triangular cells (K=3)
282+
283+
TODO: So that this method generalizes to n-sided polygons, we can use the Waschpress points as the generalized
284+
barycentric coordinates, which is valid for convex polygons.
283285
284286
Parameters
285287
----------
@@ -300,29 +302,24 @@ def _barycentric_coordinates(nodes, points, min_area=1e-8):
300302
"""
301303
M, K = nodes.shape[:2]
302304

303-
# roll(-1) to get vi+1, roll(+1) to get vi-1
304-
vi = nodes # (M,K,2)
305-
vi1 = np.roll(nodes, shift=-1, axis=1) # (M,K,2)
306-
vim1 = np.roll(nodes, shift=+1, axis=1) # (M,K,2)
305+
vi = np.squeeze(nodes[:, 0, :]) # (M,K,2)
306+
vi1 = np.squeeze(nodes[:, 1, :]) # (M,K,2)
307+
vim1 = np.squeeze(nodes[:, 2, :]) # (M,K,2)
307308

308309
# a0 = area(v_{i-1}, v_i, v_{i+1})
309310
a0 = _triangle_area(vim1, vi, vi1) # (M,K)
310311

311312
# a1 = area(P, v_{i-1}, v_i); a2 = area(P, v_i, v_{i+1})
312-
P = points[:, None, :] # (M,1,2) -> (M,K,2)
313-
a1 = _triangle_area(P, vim1, vi)
314-
a2 = _triangle_area(P, vi, vi1)
315-
316-
# clamp tiny denominators for stability
317-
a1c = np.maximum(a1, min_area)
318-
a2c = np.maximum(a2, min_area)
319-
320-
wi = a0 / (a1c * a2c) # (M,K)
321-
322-
sum_wi = wi.sum(axis=1, keepdims=True) # (M,1)
323-
# Avoid 0/0: if sum_wi==0 (degenerate), keep zeros
324-
with np.errstate(invalid="ignore", divide="ignore"):
325-
bcoords = wi / sum_wi
313+
P = points
314+
a1 = _triangle_area(P, vi1, vim1)
315+
a2 = _triangle_area(P, vim1, vi)
316+
a3 = _triangle_area(P, vi, vi1)
317+
318+
# wi = a0 / (a1c * a2c) # (M,K)
319+
bcoords = np.zeros((M, K))
320+
bcoords[:, 0] = a1 / a0
321+
bcoords[:, 1] = a2 / a0
322+
bcoords[:, 2] = a3 / a0
326323

327324
return bcoords
328325

src/parcels/_core/uxgrid.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ def __init__(self, grid: ux.grid.Grid, z: ux.UxDataArray, mesh) -> UxGrid:
3333
mesh : str
3434
The type of mesh used for the grid. Either "flat" or "spherical".
3535
"""
36+
if grid.n_max_face_nodes > 3:
37+
raise ValueError("Provided ux.grid.Grid must contain only triangular cells (n_max_face_nodes=3)")
3638
self.uxgrid = grid
3739
if not isinstance(z, ux.UxDataArray):
3840
raise TypeError("z must be an instance of ux.UxDataArray")

0 commit comments

Comments
 (0)