@@ -216,49 +216,126 @@ def _bounds_helper(values, n_core_dims, nbounds, order):
216
216
return vertex_vals
217
217
218
218
219
- def _get_ordered_vertices (bounds : xr .DataArray ) -> np .ndarray :
220
- """Extracts a sorted 1D array of unique vertex values from a bounds DataArray.
221
-
222
- This function takes a DataArray (or array-like) containing bounds information,
223
- typically as pairs of values along the last dimension. It flattens the
224
- bounds into pairs, extracts all unique vertex values, and returns them in
225
- sorted order. The sorting order (ascending or descending) is determined by
226
- inspecting the direction of the first non-equal bounds pair.
219
+ def _get_ordered_vertices (bounds : np .ndarray ) -> np .ndarray :
220
+ """
221
+ Convert a bounds array of shape (..., N, 2) or (N, 2) into a 1D array of
222
+ vertices.
223
+
224
+ This function reconstructs the vertices from a bounds array, handling both
225
+ strictly monotonic and non-strictly monotonic bounds. For strictly monotonic
226
+ bounds, it concatenates the left endpoints and the last right endpoint. For
227
+ non-strictly monotonic bounds (i.e., bounds that are consistently ascending or
228
+ descending within their intervals, but not strictly so), it uses the minimum
229
+ of each interval as the lower endpoint and the maximum of the last interval as
230
+ the final vertex, then sorts the vertices in ascending or descending order to
231
+ match the direction of the bounds.
232
+
233
+ - Handles both ascending and descending bounds.
234
+ - Does not require bounds to be strictly monotonic.
235
+ - Preserves repeated coordinates if present.
236
+ - Output shape is (..., N+1) or (N+1,).
227
237
228
238
Parameters
229
239
----------
230
- bounds : xr.DataArray
231
- A DataArray containing bounds information, typically with shape (..., 2).
240
+ bounds : np.ndarray
241
+ An array containing bounds information, typically with shape (N, 2)
242
+ or (..., N, 2).
232
243
233
244
Returns
234
245
-------
235
246
np.ndarray
236
- A 1D NumPy array of sorted unique vertex values extracted from the
237
- bounds.
247
+ An array of vertices with shape (..., N+1) or (N+1,).
238
248
"""
239
- # Convert to array if needed
240
- arr = bounds .values if isinstance (bounds , xr .DataArray ) else bounds
241
- arr = np .asarray (arr )
242
-
243
- # Flatten to (N, 2) pairs and get all unique values.
244
- pairs = arr .reshape (- 1 , 2 )
245
- vertices = np .unique (pairs )
246
-
247
- # Determine order: find the first pair with different values
248
- ascending = True
249
- for left , right in pairs :
250
- if left != right :
251
- ascending = right > left
252
- break
253
-
254
- # Sort vertices in ascending or descending order as needed.
255
- vertices = np .sort (vertices )
256
- if not ascending :
257
- vertices = vertices [::- 1 ]
249
+ if _is_bounds_strictly_monotonic (bounds ):
250
+ # Example: [[51.0, 50.5], [50.5, 50.0]]
251
+ # Example Result: [51.0, 50.5, 50.0]
252
+ vertices = np .concatenate ((bounds [..., :, 0 ], bounds [..., - 1 :, 1 ]), axis = - 1 )
253
+ else :
254
+ # Example with bounds (descending) [[50.5, 50.0], [51.0, 50.5]]
255
+ # Get the lower endpoints of each bounds interval
256
+ # Example Result: [50, 50.5]
257
+ lower_endpoints = np .minimum (bounds [..., :, 0 ], bounds [..., :, 1 ])
258
+
259
+ # Get the upper endpoint of the last interval.
260
+ # Example Result: 51.0
261
+ last_upper_endpoint = np .maximum (bounds [..., - 1 , 0 ], bounds [..., - 1 , 1 ])
262
+
263
+ # Concatenate lower endpoints and the last upper endpoint.
264
+ # Example Result: [50.0, 50.5, 51.0]
265
+ vertices = np .concatenate (
266
+ [lower_endpoints , np .expand_dims (last_upper_endpoint , axis = - 1 )], axis = - 1
267
+ )
268
+
269
+ # Sort vertices based on the direction of the bounds
270
+ # Example Result: [51.0, 50.5, 50.0]
271
+ ascending = is_bounds_ascending (bounds )
272
+ if ascending :
273
+ vertices = np .sort (vertices , axis = - 1 )
274
+ else :
275
+ vertices = np .sort (vertices , axis = - 1 )[..., ::- 1 ]
258
276
259
277
return vertices
260
278
261
279
280
+ def _is_bounds_strictly_monotonic (arr : np .ndarray ) -> bool :
281
+ """
282
+ Check if the second-to-last axis of a numpy array is strictly monotonic.
283
+
284
+ This function checks if the second-to-last axis of the input array is
285
+ strictly monotonic (either strictly increasing or strictly decreasing)
286
+ for arrays of shape (..., N, 2), preserving leading dimensions.
287
+
288
+ Parameters
289
+ ----------
290
+ arr : np.ndarray
291
+ Numpy array to check, typically with shape (..., N, 2).
292
+
293
+ Returns
294
+ -------
295
+ bool
296
+ True if the array is strictly monotonic along the second-to-last axis
297
+ for all leading dimensions, False otherwise.
298
+
299
+ Examples
300
+ --------
301
+ >>> bounds = np.array([
302
+ ... [76.25, 73.75],
303
+ ... [73.75, 71.25],
304
+ ... [71.25, 68.75],
305
+ ... [68.75, 66.25],
306
+ ... [66.25, 63.75]
307
+ ... ], dtype=np.float32)
308
+ >>> _is_bounds_strictly_monotonic(bounds)
309
+ True
310
+ """
311
+ diffs = np .diff (arr , axis = - 2 )
312
+ strictly_increasing = np .all (diffs > 0 , axis = - 2 )
313
+ strictly_decreasing = np .all (diffs < 0 , axis = - 2 )
314
+
315
+ return np .all (strictly_increasing | strictly_decreasing )
316
+
317
+
318
+ def is_bounds_ascending (bounds : np .ndarray ) -> bool :
319
+ """Check if bounds are in ascending order.
320
+
321
+ Parameters
322
+ ----------
323
+ bounds : np.ndarray
324
+ An array containing bounds information, typically with shape (N, 2)
325
+ or (..., N, 2).
326
+
327
+ Returns
328
+ -------
329
+ bool
330
+ True if bounds are in ascending order, False if they are in descending
331
+ order.
332
+ """
333
+ lower = bounds [..., :, 0 ]
334
+ upper = bounds [..., :, 1 ]
335
+
336
+ return np .all (lower < upper )
337
+
338
+
262
339
def vertices_to_bounds (
263
340
vertices : DataArray , out_dims : Sequence [str ] = ("bounds" , "x" , "y" )
264
341
) -> DataArray :
0 commit comments