@@ -263,25 +263,15 @@ def ellipse_params_via_border_pca_from(border_grid, xp=np, eps=1e-12):
263263 return origin , a , b , phi
264264
265265
266- def relocated_grid_via_ellipse_border_from (grid , origin , a , b , phi , xp = np , border_frac = 1e-2 ):
266+ def relocated_grid_via_ellipse_border_from (
267+ grid , origin , a , b , phi , xp = np , eps = 1e-12 , border_frac = 1e-3
268+ ):
267269 """
268- Rotated ellipse centered at origin with semi-axes a (major, x'), b (minor, y'),
269- rotated by phi radians (counterclockwise) .
270+ Move points outside the ellipse to a contour slightly *outside* the border
271+ to avoid geometric degeneracy in Voronoi/Delaunay .
270272
271- Parameters
272- ----------
273- grid : (N,2)
274- Coordinates in (y, x) order.
275- origin : (2,)
276- Ellipse center (y0, x0).
277- a, b : float
278- Semi-major and semi-minor axes.
279- phi : float
280- Rotation angle in radians.
281- xp : module
282- numpy-like module (np, jnp, cupy, etc.).
283- eps : float
284- Numerical safety epsilon.
273+ border_frac: fractional expansion of ellipse radius
274+ (e.g. 1e-3 = +0.1% outside).
285275 """
286276
287277 dy = grid [:, 0 ] - origin [0 ]
@@ -290,26 +280,27 @@ def relocated_grid_via_ellipse_border_from(grid, origin, a, b, phi, xp=np, borde
290280 c = xp .cos (phi )
291281 s = xp .sin (phi )
292282
283+ # Rotate into ellipse-aligned frame
293284 xprime = c * dx + s * dy
294285 yprime = - s * dx + c * dy
295286
287+ # Normalized ellipse radius
296288 q = (xprime / a ) ** 2 + (yprime / b ) ** 2
297289
298290 outside = q > 1.0
299291
300- # Target radius in normalized coords (slightly inside 1.0)
301- # Using squared target so it matches q's definition.
302- shrink = xp .asarray (1.0 - border_frac , dtype = q .dtype )
303- q_target = shrink * shrink # (1 - border_frac)^2
292+ # Target radius slightly OUTSIDE ellipse
293+ expand = xp .asarray (1.0 + border_frac , dtype = q .dtype )
294+ q_target = expand * expand # (1 + border_frac)^2
304295
305- # Project outside points to q = q_target
306- # scale^2 * q = q_target -> scale = sqrt(q_target / q)
307- safe_q = xp .maximum (q , xp .asarray (1.0 , dtype = q .dtype )) # outside => q>=1; inside => 1
296+ # Project only outside points
297+ safe_q = xp .maximum (q , xp .asarray (1.0 , dtype = q .dtype ))
308298 scale = xp .sqrt (q_target / safe_q )
309299
310300 xprime2 = xprime * scale
311301 yprime2 = yprime * scale
312302
303+ # Rotate back
313304 dx2 = c * xprime2 - s * yprime2
314305 dy2 = s * xprime2 + c * yprime2
315306
@@ -318,6 +309,7 @@ def relocated_grid_via_ellipse_border_from(grid, origin, a, b, phi, xp=np, borde
318309 return xp .where (outside [:, None ], moved , grid )
319310
320311
312+
321313class BorderRelocator :
322314 def __init__ (
323315 self ,
0 commit comments