Skip to content

Commit d77ad29

Browse files
committed
Update docstrings for clarity
1 parent 0804d1b commit d77ad29

File tree

2 files changed

+157
-41
lines changed

2 files changed

+157
-41
lines changed

cf_xarray/helpers.py

Lines changed: 21 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -218,18 +218,20 @@ def _bounds_helper(values, n_core_dims, nbounds, order):
218218

219219
def _get_ordered_vertices(bounds: np.ndarray) -> np.ndarray:
220220
"""
221-
Convert a bounds array of shape (..., N, 2) or (N, 2) into a 1D array of
222-
vertices.
221+
Convert a bounds array of shape (..., N, 2) or (N, 2) into a 1D array of vertices.
223222
224223
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.
224+
strictly monotonic and non-strictly monotonic bounds.
232225
226+
- For strictly monotonic bounds (all values increase or decrease when flattened),
227+
it concatenates the left endpoints and the last right endpoint.
228+
- For non-strictly monotonic bounds (bounds are consistently ascending or descending
229+
within intervals, but not strictly so), it:
230+
- Uses the minimum of each interval as the lower endpoint.
231+
- Uses the maximum of the last interval as the final vertex.
232+
- Sorts the vertices in ascending or descending order to match the direction of the bounds.
233+
234+
Features:
233235
- Handles both ascending and descending bounds.
234236
- Does not require bounds to be strictly monotonic.
235237
- Preserves repeated coordinates if present.
@@ -238,13 +240,12 @@ def _get_ordered_vertices(bounds: np.ndarray) -> np.ndarray:
238240
Parameters
239241
----------
240242
bounds : np.ndarray
241-
An array containing bounds information, typically with shape (N, 2)
242-
or (..., N, 2).
243+
Array of bounds, typically with shape (N, 2) or (..., N, 2).
243244
244245
Returns
245246
-------
246247
np.ndarray
247-
An array of vertices with shape (..., N+1) or (N+1,).
248+
Array of vertices with shape (..., N+1) or (N+1,).
248249
"""
249250
if _is_bounds_strictly_monotonic(bounds):
250251
# Example: [[51.0, 50.5], [50.5, 50.0]]
@@ -279,11 +280,8 @@ def _get_ordered_vertices(bounds: np.ndarray) -> np.ndarray:
279280

280281
def _is_bounds_strictly_monotonic(arr: np.ndarray) -> bool:
281282
"""
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.
283+
Check if the array is strictly monotonic (all values ascend or descend
284+
across all intervals if flattened).
287285
288286
Parameters
289287
----------
@@ -293,39 +291,21 @@ def _is_bounds_strictly_monotonic(arr: np.ndarray) -> bool:
293291
Returns
294292
-------
295293
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-
... [
303-
... [76.25, 73.75],
304-
... [73.75, 71.25],
305-
... [71.25, 68.75],
306-
... [68.75, 66.25],
307-
... [66.25, 63.75],
308-
... ],
309-
... dtype=np.float32,
310-
... )
311-
>>> _is_bounds_strictly_monotonic(bounds)
312-
True
294+
True if the flattened array is strictly increasing or decreasing,
295+
False otherwise.
313296
"""
314297
# NOTE: Python 3.10 uses numpy 1.26.4. If the input is a datetime64 array,
315298
# numpy 1.26.4 may raise: numpy.core._exceptions._UFuncInputCastingError:
316299
# Cannot cast ufunc 'greater' input 0 from dtype('<m8[ns]') to dtype('<m8')
317300
# with casting rule 'same_kind' To avoid this, always cast to float64 before
318301
# np.diff.
319-
arr_numeric = arr.astype("float64")
320-
diffs = np.diff(arr_numeric, axis=-2)
321-
strictly_increasing = np.all(diffs > 0, axis=-2)
322-
strictly_decreasing = np.all(diffs < 0, axis=-2)
323-
324-
return np.all(strictly_increasing | strictly_decreasing)
302+
arr_numeric = arr.astype("float64").flatten()
303+
diffs = np.diff(arr_numeric)
304+
return np.all(diffs > 0) or np.all(diffs < 0)
325305

326306

327307
def is_bounds_ascending(bounds: np.ndarray) -> bool:
328-
"""Check if bounds are in ascending order.
308+
"""Check if bounds are in ascending order (between intervals).
329309
330310
Parameters
331311
----------

qa/mvce.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import xarray as xr
2+
import cf_xarray as cfxr
3+
4+
5+
def print_section(title):
6+
print(f"\n{title}")
7+
print("=" * len(title))
8+
9+
10+
def print_bounds_and_vertices(bounds, vertices):
11+
print("Bounds:")
12+
print(bounds)
13+
print("Vertices:")
14+
print(vertices)
15+
print("-" * 40)
16+
17+
18+
# 0a. Strictly monotonic bounds (increasing)
19+
print_section("0a. Strictly monotonic bounds (increasing)")
20+
bounds = xr.DataArray(
21+
[[10.0, 10.5], [10.5, 11.0], [11.0, 11.5], [11.5, 12.0], [12.0, 12.5]],
22+
dims=("lat", "bounds"),
23+
)
24+
vertices = cfxr.bounds_to_vertices(bounds, "bounds")
25+
print_bounds_and_vertices(bounds, vertices)
26+
27+
# 0b. Strictly monotonic bounds (decreasing)
28+
print_section("0b. Strictly monotonic bounds (decreasing)")
29+
bounds = xr.DataArray(
30+
[[12.5, 12.0], [12.0, 11.5], [11.5, 11.0], [11.0, 10.5], [10.5, 10.0]],
31+
dims=("lat", "bounds"),
32+
)
33+
vertices = cfxr.bounds_to_vertices(bounds, "bounds")
34+
print_bounds_and_vertices(bounds, vertices)
35+
36+
# 1. Descending coordinates
37+
print_section("1. Descending coordinates")
38+
bounds = xr.DataArray(
39+
[[50.5, 50.0], [51.0, 50.5], [51.5, 51.0], [52.0, 51.5], [52.5, 52.0]],
40+
dims=("lat", "bounds"),
41+
)
42+
vertices = cfxr.bounds_to_vertices(bounds, "bounds")
43+
print_bounds_and_vertices(bounds, vertices)
44+
45+
# 2. Descending coordinates with duplicated values
46+
print_section("2. Descending coordinates with duplicated values")
47+
bounds = xr.DataArray(
48+
[[50.5, 50.0], [51.0, 50.5], [51.0, 50.5], [52.0, 51.5], [52.5, 52.0]],
49+
dims=("lat", "bounds"),
50+
)
51+
vertices = cfxr.bounds_to_vertices(bounds, "bounds")
52+
print_bounds_and_vertices(bounds, vertices)
53+
54+
# 3. Ascending coordinates
55+
print_section("3. Ascending coordinates")
56+
bounds = xr.DataArray(
57+
[[50.0, 50.5], [50.5, 51.0], [51.0, 51.5], [51.5, 52.0], [52.0, 52.5]],
58+
dims=("lat", "bounds"),
59+
)
60+
vertices = cfxr.bounds_to_vertices(bounds, "bounds")
61+
print_bounds_and_vertices(bounds, vertices)
62+
63+
# 4. Ascending coordinates with duplicated values
64+
print_section("4. Ascending coordinates with duplicated values")
65+
bounds = xr.DataArray(
66+
[[50.0, 50.5], [50.5, 51.0], [50.5, 51.0], [51.0, 51.5], [51.5, 52.0]],
67+
dims=("lat", "bounds"),
68+
)
69+
vertices = cfxr.bounds_to_vertices(bounds, "bounds")
70+
print_bounds_and_vertices(bounds, vertices)
71+
72+
# 5. 3D array (extra non-core dim)
73+
print_section("5. 3D array (extra non-core dim)")
74+
bounds_3d = xr.DataArray(
75+
[
76+
[
77+
[50.0, 50.5],
78+
[50.5, 51.0],
79+
[51.0, 51.5],
80+
[51.5, 52.0],
81+
[52.0, 52.5],
82+
],
83+
[
84+
[60.0, 60.5],
85+
[60.5, 61.0],
86+
[61.0, 61.5],
87+
[61.5, 62.0],
88+
[62.0, 62.5],
89+
],
90+
],
91+
dims=("extra", "lat", "bounds"),
92+
)
93+
vertices_3d = cfxr.bounds_to_vertices(bounds_3d, "bounds", core_dims=["lat"])
94+
print_bounds_and_vertices(bounds_3d, vertices_3d)
95+
96+
# 6. 4D array (time, extra, lat, bounds)
97+
print_section("6. 4D array (time, extra, lat, bounds)")
98+
bounds_4d = xr.DataArray(
99+
[
100+
[
101+
[
102+
[50.0, 50.5],
103+
[50.5, 51.0],
104+
[51.0, 51.5],
105+
[51.5, 52.0],
106+
[52.0, 52.5],
107+
],
108+
[
109+
[60.0, 60.5],
110+
[60.5, 61.0],
111+
[61.0, 61.5],
112+
[61.5, 62.0],
113+
[62.0, 62.5],
114+
],
115+
],
116+
[
117+
[
118+
[70.0, 70.5],
119+
[70.5, 71.0],
120+
[71.0, 71.5],
121+
[71.5, 72.0],
122+
[72.0, 72.5],
123+
],
124+
[
125+
[80.0, 80.5],
126+
[80.5, 81.0],
127+
[81.0, 81.5],
128+
[81.5, 82.0],
129+
[82.0, 82.5],
130+
],
131+
],
132+
],
133+
dims=("time", "extra", "lat", "bounds"),
134+
)
135+
vertices_4d = cfxr.bounds_to_vertices(bounds_4d, "bounds", core_dims=["lat"])
136+
print_bounds_and_vertices(bounds_4d, vertices_4d)

0 commit comments

Comments
 (0)