From 2586c99c85f83dd018ee12653293a1fe0d3acc07 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Wed, 30 Oct 2024 19:11:19 +0000 Subject: [PATCH 01/14] dev --- cf/mixin/fielddomain.py | 113 +++++++++++++++++++++++++++------------- cf/test/test_Field.py | 4 +- 2 files changed, 80 insertions(+), 37 deletions(-) diff --git a/cf/mixin/fielddomain.py b/cf/mixin/fielddomain.py index 1e9e1f67e1..867c39ec81 100644 --- a/cf/mixin/fielddomain.py +++ b/cf/mixin/fielddomain.py @@ -18,7 +18,7 @@ bounds_combination_mode, normalize_slice, ) -from ..query import Query +from ..query import Query, wi from ..units import Units logger = logging.getLogger(__name__) @@ -447,43 +447,84 @@ def _indices(self, config, data_axes, ancillary_mask, kwargs): anchor0 = value.value[1] anchor1 = value.value[0] - a = self.anchor(axis, anchor0, dry_run=True)["roll"] - b = self.flip(axis).anchor(axis, anchor1, dry_run=True)[ - "roll" - ] - - size = item.size - if abs(anchor1 - anchor0) >= item.period(): - if value.operator == "wo": - set_start_stop = 0 - else: - set_start_stop = -a - - start = set_start_stop - stop = set_start_stop - elif a + b == size: - b = self.anchor(axis, anchor1, dry_run=True)["roll"] - if (b == a and value.operator == "wo") or not ( - b == a or value.operator == "wo" - ): - set_start_stop = -a - else: - set_start_stop = 0 - - start = set_start_stop - stop = set_start_stop +# if value.operator == "wi": + if item.increasing: + a= value.value[0] + else: + a = value.value[1] + + offset = self.anchor(axis, a, dry_run=True)["roll"] + nperiod = self.anchor(axis, a, dry_run=True)["nperiod"] + item = item + nperiod + item = item.roll(0, offset) + n = np.roll(np.arange(item.size), offset, 0) + print(n, (item == value).array, item.array, value) + + if value.operator == "wi": + n = n[item == value] + if not n.size: + raise ValueError( + f"No indices found from: {identity}={value!r}" + ) + + start = n[0] + stop = n[-1] + 1 else: - if value.operator == "wo": - start = b - size - stop = -a + size + n = n[item == wi(*value.value)] + if n.size == item.size: + raise ValueError( + f"No indices found from: {identity}={value!r}" + ) + + start = n[-1] + 1 + stop = start - n.size + print(n) + print ('start, stop=',start, stop) + + if False: #else: + a = self.anchor(axis, anchor0, dry_run=True)["roll"] + b = self.flip(axis).anchor(axis, anchor1, dry_run=True)[ + "roll" + ] + print('a, b=', a, b) + size = item.size + if abs(anchor1 - anchor0) >= item.period(): + if value.operator == "wo": + start = 0 + stop = 0 + else: + start = -a + stop = -a + size + elif a + b == size: + b = self.anchor(axis, anchor1, dry_run=True)["roll"] + if value.operator == "wi": + start = -a + stop = -a + size + elif (b == a and value.operator == "wo") or not ( + b == a or value.operator == "wo" + ): + start = -a + stop = -a + else: + start = 0 + stop = 0 + + # start = set_start_stop + # stop = set_start_stop else: - start = -a - stop = b - size - - if start == stop == 0: - raise ValueError( - f"No indices found from: {identity}={value!r}" - ) + if value.operator == "wo": + print('HERE0.9') + start = b - size + stop = -a + size + else: + print('HERE1') + start = -a + stop = b - size + + #if start == stop == 0: + # raise ValueError( + # f"No indices found from: {identity}={value!r}" + # ) index = slice(start, stop, 1) diff --git a/cf/test/test_Field.py b/cf/test/test_Field.py index 04517ed4f2..39b4f2a986 100644 --- a/cf/test/test_Field.py +++ b/cf/test/test_Field.py @@ -1163,7 +1163,7 @@ def test_Field_insert_dimension(self): with self.assertRaises(ValueError): f.insert_dimension(1, "qwerty") - def test_Field_indices(self): + def test_Field__aaa__indices(self): f = cf.read(self.filename)[0] array = np.ma.array(f.array, copy=False) @@ -1214,6 +1214,8 @@ def test_Field_indices(self): (x == [-80, -40, 0, 40, 80, 120, 160, 200, 240.0]).all() ) + print(f) + print(f.dimension_coordinate('X').array, f.cyclic()) with self.assertRaises(ValueError): # No X coordinate values lie inside the range [90, 100] f.indices(grid_longitude=cf.wi(90, 100)) From 6329c614b5897023e94f1bb453c94aac76dc72da Mon Sep 17 00:00:00 2001 From: David Hassell Date: Wed, 30 Oct 2024 19:26:33 +0000 Subject: [PATCH 02/14] dev --- cf/mixin/fielddomain.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cf/mixin/fielddomain.py b/cf/mixin/fielddomain.py index 867c39ec81..25a47e6421 100644 --- a/cf/mixin/fielddomain.py +++ b/cf/mixin/fielddomain.py @@ -475,9 +475,15 @@ def _indices(self, config, data_axes, ancillary_mask, kwargs): raise ValueError( f"No indices found from: {identity}={value!r}" ) - - start = n[-1] + 1 - stop = start - n.size + + if n.size: + start = n[-1] + 1 + stop = start - n.size + else: + start = item.size -offset + stop = start + + print(n) print ('start, stop=',start, stop) From e0c2cfff15caaa69c573f0a1e41088225037c317 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Thu, 31 Oct 2024 00:53:10 +0000 Subject: [PATCH 03/14] dev --- cf/dimensioncoordinate.py | 51 +++++++++++- cf/mixin/fielddomain.py | 163 ++++++++++++++------------------------ cf/test/test_Field.py | 30 ++++++- 3 files changed, 137 insertions(+), 107 deletions(-) diff --git a/cf/dimensioncoordinate.py b/cf/dimensioncoordinate.py index 347a0d7dd5..f40453bfad 100644 --- a/cf/dimensioncoordinate.py +++ b/cf/dimensioncoordinate.py @@ -8,7 +8,7 @@ _inplace_enabled, _inplace_enabled_define_and_cleanup, ) -from .functions import _DEPRECATION_ERROR_ATTRIBUTE, _DEPRECATION_ERROR_KWARGS +from .functions import _DEPRECATION_ERROR_ATTRIBUTE, _DEPRECATION_ERROR_KWARGS, bounds_combination_mode from .timeduration import TimeDuration from .units import Units @@ -246,6 +246,55 @@ def increasing(self): """ return self.direction() + @_inplace_enabled(default=False) + def anchor(self, value, parameters=None, inplace=False): + """TODO""" + + d = _inplace_enabled_define_and_cleanup(self) + + period = d.period() + if period is None: + raise ValueError(f"Cyclic {d!r} has no period") + + axis_size = d.size + if axis_size <= 1: + # Don't need to roll a size one axis + if parameters is not None: + parameters.update({"shift": 0, "nperiod": 0}) + + return d + + c = d.get_data(_fill_value=False) + if not inplace: + c.persist(inplace=True) + + if d.increasing: + # Adjust value so it's in the range [c[0], c[0]+period) + n = ((c[0] - value) / period).ceil() + value1 = value + n * period + shift = axis_size - np.argmax((c - value1 >= 0).array) + d.roll(0, shift, inplace=True) + n = ((value - d.data[0]) / period).ceil().persist() + else: + # Adjust value so it's in the range (c[0]-period, c[0]] + n = ((c[0] - value) / period).floor() + value1 = value + n * period + shift = axis_size - np.argmax((value1 - c >= 0).array) + d.roll(0, shift, inplace=True) + n = ((value - d.data[0]) / period).floor().persist() + + if n: + nperiod = n * period + with bounds_combination_mode("OR"): + d += nperiod + else: + nperiod = 0 + + if parameters is not None: + parameters.update( {"shift": shift, "nperiod": nperiod}) + + return d + def direction(self): """Return True if the dimension coordinate values are increasing, otherwise return False. diff --git a/cf/mixin/fielddomain.py b/cf/mixin/fielddomain.py index 25a47e6421..dcc8525374 100644 --- a/cf/mixin/fielddomain.py +++ b/cf/mixin/fielddomain.py @@ -440,26 +440,16 @@ def _indices(self, config, data_axes, ancillary_mask, kwargs): if debug: logger.debug(" 1-d CASE 2:") # pragma: no cover + size = item.size if item.increasing: - anchor0 = value.value[0] - anchor1 = value.value[1] + anchor = value.value[0] else: - anchor0 = value.value[1] - anchor1 = value.value[0] + anchor = value.value[1] -# if value.operator == "wi": - if item.increasing: - a= value.value[0] - else: - a = value.value[1] - - offset = self.anchor(axis, a, dry_run=True)["roll"] - nperiod = self.anchor(axis, a, dry_run=True)["nperiod"] - item = item + nperiod - item = item.roll(0, offset) - n = np.roll(np.arange(item.size), offset, 0) - print(n, (item == value).array, item.array, value) - + item = item.persist() + parameters = {} + item = item.anchor(anchor, parameters=parameters) + n = np.roll(np.arange(size), parameters['shift'], 0) if value.operator == "wi": n = n[item == value] if not n.size: @@ -470,8 +460,9 @@ def _indices(self, config, data_axes, ancillary_mask, kwargs): start = n[0] stop = n[-1] + 1 else: + # "wo" operator n = n[item == wi(*value.value)] - if n.size == item.size: + if n.size == size: raise ValueError( f"No indices found from: {identity}={value!r}" ) @@ -480,58 +471,9 @@ def _indices(self, config, data_axes, ancillary_mask, kwargs): start = n[-1] + 1 stop = start - n.size else: - start = item.size -offset + start = size - parameters['shift'] stop = start - - print(n) - print ('start, stop=',start, stop) - - if False: #else: - a = self.anchor(axis, anchor0, dry_run=True)["roll"] - b = self.flip(axis).anchor(axis, anchor1, dry_run=True)[ - "roll" - ] - print('a, b=', a, b) - size = item.size - if abs(anchor1 - anchor0) >= item.period(): - if value.operator == "wo": - start = 0 - stop = 0 - else: - start = -a - stop = -a + size - elif a + b == size: - b = self.anchor(axis, anchor1, dry_run=True)["roll"] - if value.operator == "wi": - start = -a - stop = -a + size - elif (b == a and value.operator == "wo") or not ( - b == a or value.operator == "wo" - ): - start = -a - stop = -a - else: - start = 0 - stop = 0 - - # start = set_start_stop - # stop = set_start_stop - else: - if value.operator == "wo": - print('HERE0.9') - start = b - size - stop = -a + size - else: - print('HERE1') - start = -a - stop = b - size - - #if start == stop == 0: - # raise ValueError( - # f"No indices found from: {identity}={value!r}" - # ) - index = slice(start, stop, 1) if full: @@ -1348,9 +1290,9 @@ def anchor( f"{f.constructs.domain_axis_identity(da_key)!r} axis" ) - period = dim.period() - if period is None: - raise ValueError(f"Cyclic {dim.identity()!r} axis has no period") +# period = dim.period() +# if period is None: + # raise ValueError(f"Cyclic {dim.identity()!r} axis has no period") value = f._Data.asdata(value) if not value.Units: @@ -1369,45 +1311,62 @@ def anchor( return f - c = dim.get_data(_fill_value=False) - - if dim.increasing: - # Adjust value so it's in the range [c[0], c[0]+period) - n = ((c[0] - value) / period).ceil() - value1 = value + n * period + parameters = {} + dim = dim.anchor(value, parameters=parameters) - shift = axis_size - np.argmax((c - value1 >= 0).array) - if not dry_run: - f.roll(da_key, shift, inplace=True) - - # Re-get dim - dim = f.dimension_coordinate(filter_by_axis=(da_key,)) - # TODO CHECK n for dry run or not - n = ((value - dim.data[0]) / period).ceil() - else: - # Adjust value so it's in the range (c[0]-period, c[0]] - n = ((c[0] - value) / period).floor() - value1 = value + n * period - - shift = axis_size - np.argmax((value1 - c >= 0).array) - - if not dry_run: - f.roll(da_key, shift, inplace=True) - - # Re-get dim - dim = f.dimension_coordinate(filter_by_axis=(da_key,)) - # TODO CHECK n for dry run or not - n = ((value - dim.data[0]) / period).floor() + f.roll(da_key, parameters['shift'], inplace=True) if dry_run: - return {"axis": da_key, "roll": shift, "nperiod": n * period} + out = {"axis": da_key} + out.update(parameters) + return out - if n: + if parameters['nperiod']: + dim = f.dimension_coordinate(filter_by_axis=(da_key,)) with bounds_combination_mode("OR"): - dim += n * period + dim += parameters['nperiod'] return f +# c = dim.get_data(_fill_value=False) +# +# if dim.increasing: +# # Adjust value so it's in the range [c[0], c[0]+period) +# n = ((c[0] - value) / period).ceil() +# value1 = value + n * period +# +# shift = axis_size - np.argmax((c - value1 >= 0).array) +# if not dry_run: +# f.roll(da_key, shift, inplace=True) +# +# # Re-get dim +# dim = f.dimension_coordinate(filter_by_axis=(da_key,)) +# # TODO CHECK n for dry run or not +# n = ((value - dim.data[0]) / period).ceil() +# else: +# # Adjust value so it's in the range (c[0]-period, c[0]] +# n = ((c[0] - value) / period).floor() +# value1 = value + n * period +# +# shift = axis_size - np.argmax((value1 - c >= 0).array) +# +# if not dry_run: +# f.roll(da_key, shift, inplace=True) +# +# # Re-get dim +# dim = f.dimension_coordinate(filter_by_axis=(da_key,)) +# # TODO CHECK n for dry run or not +# n = ((value - dim.data[0]) / period).floor() +# +# if dry_run: +# return {"axis": da_key, "roll": shift, "nperiod": n * period} +# +# if n: +# with bounds_combination_mode("OR"): +# dim += n * period +# +# return f + @_manage_log_level_via_verbosity def autocyclic(self, key=None, coord=None, verbose=None, config={}): """Set dimensions to be cyclic. diff --git a/cf/test/test_Field.py b/cf/test/test_Field.py index 39b4f2a986..b0717f8c80 100644 --- a/cf/test/test_Field.py +++ b/cf/test/test_Field.py @@ -1163,7 +1163,7 @@ def test_Field_insert_dimension(self): with self.assertRaises(ValueError): f.insert_dimension(1, "qwerty") - def test_Field__aaa__indices(self): + def test_Field_indices(self): f = cf.read(self.filename)[0] array = np.ma.array(f.array, copy=False) @@ -1214,8 +1214,30 @@ def test_Field__aaa__indices(self): (x == [-80, -40, 0, 40, 80, 120, 160, 200, 240.0]).all() ) - print(f) - print(f.dimension_coordinate('X').array, f.cyclic()) + indices = f.indices(grid_longitude=cf.wi(-90, 270)) + g = f[indices] + self.assertEqual(g.shape, (1, 10, 9)) + x = g.dimension_coordinate("X").array + self.assertTrue( + (x == [-80, -40, 0, 40, 80, 120, 160, 200, 240.0]).all() + ) + + indices = f.indices(grid_longitude=cf.wi(-180, 180)) + g = f[indices] + self.assertEqual(g.shape, (1, 10, 9)) + x = g.dimension_coordinate("X").array + self.assertTrue( + (x == [-160, -120, -80, -40, 0, 40, 80, 120, 160]).all() + ) + + indices = f.indices(grid_longitude=cf.wi(-170, 170)) + g = f[indices] + self.assertEqual(g.shape, (1, 10, 9)) + x = g.dimension_coordinate("X").array + self.assertTrue( + (x == [-160, -120, -80, -40, 0, 40, 80, 120, 160]).all() + ) + with self.assertRaises(ValueError): # No X coordinate values lie inside the range [90, 100] f.indices(grid_longitude=cf.wi(90, 100)) @@ -1276,7 +1298,7 @@ def test_Field__aaa__indices(self): # wi (decreasing) f.flip("X", inplace=True) - + indices = f.indices(grid_longitude=cf.wi(50, 130)) self.assertTrue(indices[0], "mask") g = f[indices] From 48098f20d875a01eb6be66ad7259ddedd16118d2 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Thu, 31 Oct 2024 01:02:56 +0000 Subject: [PATCH 04/14] dev --- cf/dimensioncoordinate.py | 79 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/cf/dimensioncoordinate.py b/cf/dimensioncoordinate.py index f40453bfad..b9f50f308f 100644 --- a/cf/dimensioncoordinate.py +++ b/cf/dimensioncoordinate.py @@ -248,7 +248,84 @@ def increasing(self): @_inplace_enabled(default=False) def anchor(self, value, parameters=None, inplace=False): - """TODO""" + """Anchor the coordinate values. + + + + axis to the *value*.Roll cyclic coordinates so that a value lies in the first cell (REALLY?) + coordinate cell. + + A unique axis is selected with the *axes* and *kwargs* + parameters. + + .. versionadded:: NEXTVERSION + +TODO provide a bounds treament: bounds=False|True + + .. seealso:: `period`, `roll` + + :Parameters: + + axis: + The cyclic axis to be anchored. + + domain axis selection TODO. + + value: + + Anchor the coordinate values for the selected cyclic + axis to the *value*. May be any numeric scalar object + that can be converted to a `Data` object (which + includes `numpy` and `Data` objects). If *value* has + units then they must be compatible with those of the + coordinates, otherwise it is assumed to have the same + units as the coordinates. The coordinate values are + transformed so that *value* is "equal to or just + before" the new first coordinate value. More + specifically: + + * Increasing coordinates with positive period, P, + are transformed so that *value* lies in the + half-open range (L-P, F], where F and L are the + transformed first and last coordinate values, + respectively. + + .. + + * Decreasing coordinates with positive period, P, + are transformed so that *value* lies in the + half-open range (L+P, F], where F and L are the + transformed first and last coordinate values, + respectively. + + *Parameter example:* + If the original coordinates are ``0, 5, ..., 355`` + (evenly spaced) and the period is ``360`` then + ``value=0`` implies transformed coordinates of ``0, + 5, ..., 355``; ``value=-12`` implies transformed + coordinates of ``-10, -5, ..., 345``; ``value=380`` + implies transformed coordinates of ``380, 385, ..., + 715``. + + *Parameter example:* + If the original coordinates are ``355, 350, ..., 0`` + (evenly spaced) and the period is ``360`` then + ``value=355`` implies transformed coordinates of + ``355, 350, ..., 0``; ``value=0`` implies + transformed coordinates of ``0, -5, ..., -355``; + ``value=392`` implies transformed coordinates of + ``390, 385, ..., 30``. + + parameters: `dict`, optional + TODO Return a dictionary of parameters which describe the + anchoring process. The construct is not changed, even + if *inplace* is True. + + {{inplace: `bool`, optional}} + + :Returns: + + """ d = _inplace_enabled_define_and_cleanup(self) From bfa25b88e3700c2f29a7a55380a2b80516fa054a Mon Sep 17 00:00:00 2001 From: David Hassell Date: Thu, 31 Oct 2024 09:29:06 +0000 Subject: [PATCH 05/14] dev --- cf/cfimplementation.py | 1 - cf/data/data.py | 1 - cf/dimensioncoordinate.py | 110 ++++++++++++++++++++++---------------- cf/mixin/fielddomain.py | 92 ++++++------------------------- cf/regrid/regrid.py | 1 - cf/test/test_Field.py | 2 +- 6 files changed, 82 insertions(+), 125 deletions(-) diff --git a/cf/cfimplementation.py b/cf/cfimplementation.py index db8601627c..118e73ce7a 100644 --- a/cf/cfimplementation.py +++ b/cf/cfimplementation.py @@ -26,7 +26,6 @@ TiePointIndex, ) from .data import Data - from .data.array import ( BoundsFromNodesArray, CellConnectivityArray, diff --git a/cf/data/data.py b/cf/data/data.py index 61abf86bc7..d965a09473 100644 --- a/cf/data/data.py +++ b/cf/data/data.py @@ -43,7 +43,6 @@ from ..units import Units from .collapse import Collapse from .creation import generate_axis_identifiers, to_dask - from .dask_utils import ( _da_ma_allclose, cf_asanyarray, diff --git a/cf/dimensioncoordinate.py b/cf/dimensioncoordinate.py index b9f50f308f..bd6289a82b 100644 --- a/cf/dimensioncoordinate.py +++ b/cf/dimensioncoordinate.py @@ -8,7 +8,11 @@ _inplace_enabled, _inplace_enabled_define_and_cleanup, ) -from .functions import _DEPRECATION_ERROR_ATTRIBUTE, _DEPRECATION_ERROR_KWARGS, bounds_combination_mode +from .functions import ( + _DEPRECATION_ERROR_ATTRIBUTE, + _DEPRECATION_ERROR_KWARGS, + bounds_combination_mode, +) from .timeduration import TimeDuration from .units import Units @@ -247,57 +251,58 @@ def increasing(self): return self.direction() @_inplace_enabled(default=False) - def anchor(self, value, parameters=None, inplace=False): + def anchor(self, value, cell=False, parameters=None, inplace=False): """Anchor the coordinate values. + By default, the coordinate values are transformed so that the + first coordinate is the closet to *value* from above (below) + for increasing (decreasing) coordinates. - - axis to the *value*.Roll cyclic coordinates so that a value lies in the first cell (REALLY?) - coordinate cell. - - A unique axis is selected with the *axes* and *kwargs* - parameters. - + If the *cell* parameter is True, then the coordinate values + are transformed so that the first cell either contains + *value*; or is the closet to cell to *value* from above + (below) for increasing (decreasing) coordinates. + .. versionadded:: NEXTVERSION -TODO provide a bounds treament: bounds=False|True - .. seealso:: `period`, `roll` :Parameters: - + axis: The cyclic axis to be anchored. - + domain axis selection TODO. - - value: - + + value: scalar array_like + Anchor the coordinate values for the selected cyclic axis to the *value*. May be any numeric scalar object that can be converted to a `Data` object (which includes `numpy` and `Data` objects). If *value* has units then they must be compatible with those of the coordinates, otherwise it is assumed to have the same - units as the coordinates. The coordinate values are - transformed so that *value* is "equal to or just - before" the new first coordinate value. More - specifically: - + units as the coordinates. + + The coordinate values are transformed so the first + corodinate is the closet to *value* from above (for + increasing coordinates), or the closet to *value* from + above (for idereasing coordinates) + * Increasing coordinates with positive period, P, are transformed so that *value* lies in the half-open range (L-P, F], where F and L are the transformed first and last coordinate values, respectively. - + .. - + * Decreasing coordinates with positive period, P, are transformed so that *value* lies in the half-open range (L+P, F], where F and L are the transformed first and last coordinate values, respectively. - + *Parameter example:* If the original coordinates are ``0, 5, ..., 355`` (evenly spaced) and the period is ``360`` then @@ -306,7 +311,7 @@ def anchor(self, value, parameters=None, inplace=False): coordinates of ``-10, -5, ..., 345``; ``value=380`` implies transformed coordinates of ``380, 385, ..., 715``. - + *Parameter example:* If the original coordinates are ``355, 350, ..., 0`` (evenly spaced) and the period is ``360`` then @@ -315,51 +320,66 @@ def anchor(self, value, parameters=None, inplace=False): transformed coordinates of ``0, -5, ..., -355``; ``value=392`` implies transformed coordinates of ``390, 385, ..., 30``. + + cell: `bool`, optional + TODO parameters: `dict`, optional TODO Return a dictionary of parameters which describe the anchoring process. The construct is not changed, even if *inplace* is True. - + {{inplace: `bool`, optional}} - + :Returns: """ - d = _inplace_enabled_define_and_cleanup(self) period = d.period() if period is None: raise ValueError(f"Cyclic {d!r} has no period") - axis_size = d.size - if axis_size <= 1: - # Don't need to roll a size one axis - if parameters is not None: - parameters.update({"shift": 0, "nperiod": 0}) - - return d - - c = d.get_data(_fill_value=False) - if not inplace: - c.persist(inplace=True) + value = d._Data.asdata(value) + if not value.Units: + value = value.override_units(d.Units) + elif not value.Units.equivalent(d.Units): + raise ValueError( + f"Anchor value has incompatible units: {value.Units!r}" + ) - if d.increasing: + if cell: + c = d.upper_bounds.persist() + else: + d.persist(inplace=True) + c = d.get_data(_fill_value=False) + + if d.increasing: # Adjust value so it's in the range [c[0], c[0]+period) n = ((c[0] - value) / period).ceil() value1 = value + n * period - shift = axis_size - np.argmax((c - value1 >= 0).array) + shift = c.size - np.argmax((c - value1 >= 0).array) d.roll(0, shift, inplace=True) - n = ((value - d.data[0]) / period).ceil().persist() + if cell: + d0 = d[0].upper_bounds + else: + d0 = d.get_data(_fill_value=False)[0] + + n = ((value - d0) / period).ceil() else: # Adjust value so it's in the range (c[0]-period, c[0]] n = ((c[0] - value) / period).floor() value1 = value + n * period - shift = axis_size - np.argmax((value1 - c >= 0).array) + shift = c.size - np.argmax((value1 - c >= 0).array) d.roll(0, shift, inplace=True) - n = ((value - d.data[0]) / period).floor().persist() + if cell: + d0 = d[0].upper_bounds + else: + d0 = d.get_data(_fill_value=False)[0] + + n = ((value - d0) / period).floor() + n.persist(inplace=True) if n: nperiod = n * period with bounds_combination_mode("OR"): @@ -368,7 +388,7 @@ def anchor(self, value, parameters=None, inplace=False): nperiod = 0 if parameters is not None: - parameters.update( {"shift": shift, "nperiod": nperiod}) + parameters.update({"shift": shift, "nperiod": nperiod}) return d diff --git a/cf/mixin/fielddomain.py b/cf/mixin/fielddomain.py index dcc8525374..d9a9d8c9b4 100644 --- a/cf/mixin/fielddomain.py +++ b/cf/mixin/fielddomain.py @@ -440,7 +440,7 @@ def _indices(self, config, data_axes, ancillary_mask, kwargs): if debug: logger.debug(" 1-d CASE 2:") # pragma: no cover - size = item.size + size = item.size if item.increasing: anchor = value.value[0] else: @@ -449,14 +449,14 @@ def _indices(self, config, data_axes, ancillary_mask, kwargs): item = item.persist() parameters = {} item = item.anchor(anchor, parameters=parameters) - n = np.roll(np.arange(size), parameters['shift'], 0) + n = np.roll(np.arange(size), parameters["shift"], 0) if value.operator == "wi": n = n[item == value] if not n.size: raise ValueError( f"No indices found from: {identity}={value!r}" ) - + start = n[0] stop = n[-1] + 1 else: @@ -471,7 +471,7 @@ def _indices(self, config, data_axes, ancillary_mask, kwargs): start = n[-1] + 1 stop = start - n.size else: - start = size - parameters['shift'] + start = size - parameters["shift"] stop = start index = slice(start, stop, 1) @@ -1276,97 +1276,37 @@ def anchor( self, "anchor", kwargs ) # pragma: no cover - da_key, axis = self.domain_axis(axis, item=True) + axis = self.domain_axis(axis, key=True) if dry_run: f = self else: f = _inplace_enabled_define_and_cleanup(self) - dim = f.dimension_coordinate(filter_by_axis=(da_key,), default=None) + dim = f.dimension_coordinate(filter_by_axis=(axis,), default=None) if dim is None: raise ValueError( "Can't shift non-cyclic " - f"{f.constructs.domain_axis_identity(da_key)!r} axis" - ) - -# period = dim.period() -# if period is None: - # raise ValueError(f"Cyclic {dim.identity()!r} axis has no period") - - value = f._Data.asdata(value) - if not value.Units: - value = value.override_units(dim.Units) - elif not value.Units.equivalent(dim.Units): - raise ValueError( - f"Anchor value has incompatible units: {value.Units!r}" + f"{f.constructs.domain_axis_identity(axis)!r} axis" ) - axis_size = axis.get_size() - - if axis_size <= 1: - # Don't need to roll a size one axis - if dry_run: - return {"axis": da_key, "roll": 0, "nperiod": 0} - - return f - - parameters = {} + parameters = {"axis": axis} dim = dim.anchor(value, parameters=parameters) - f.roll(da_key, parameters['shift'], inplace=True) - if dry_run: - out = {"axis": da_key} - out.update(parameters) - return out + return parameters - if parameters['nperiod']: - dim = f.dimension_coordinate(filter_by_axis=(da_key,)) + f.roll(axis, parameters["shift"], inplace=True) + + if parameters["nperiod"]: + # Get the rolled dimension coordinate and adjust the + # values by the non-zero integer multiple of 'period' + dim = f.dimension_coordinate(filter_by_axis=(axis,)) with bounds_combination_mode("OR"): - dim += parameters['nperiod'] + dim += parameters["nperiod"] return f -# c = dim.get_data(_fill_value=False) -# -# if dim.increasing: -# # Adjust value so it's in the range [c[0], c[0]+period) -# n = ((c[0] - value) / period).ceil() -# value1 = value + n * period -# -# shift = axis_size - np.argmax((c - value1 >= 0).array) -# if not dry_run: -# f.roll(da_key, shift, inplace=True) -# -# # Re-get dim -# dim = f.dimension_coordinate(filter_by_axis=(da_key,)) -# # TODO CHECK n for dry run or not -# n = ((value - dim.data[0]) / period).ceil() -# else: -# # Adjust value so it's in the range (c[0]-period, c[0]] -# n = ((c[0] - value) / period).floor() -# value1 = value + n * period -# -# shift = axis_size - np.argmax((value1 - c >= 0).array) -# -# if not dry_run: -# f.roll(da_key, shift, inplace=True) -# -# # Re-get dim -# dim = f.dimension_coordinate(filter_by_axis=(da_key,)) -# # TODO CHECK n for dry run or not -# n = ((value - dim.data[0]) / period).floor() -# -# if dry_run: -# return {"axis": da_key, "roll": shift, "nperiod": n * period} -# -# if n: -# with bounds_combination_mode("OR"): -# dim += n * period -# -# return f - @_manage_log_level_via_verbosity def autocyclic(self, key=None, coord=None, verbose=None, config={}): """Set dimensions to be cyclic. diff --git a/cf/regrid/regrid.py b/cf/regrid/regrid.py index a06d1cc960..eb7ee6656a 100644 --- a/cf/regrid/regrid.py +++ b/cf/regrid/regrid.py @@ -2465,7 +2465,6 @@ def create_esmpy_weights( from netCDF4 import Dataset from .. import __version__ - from ..data.array.locks import netcdf_lock if ( diff --git a/cf/test/test_Field.py b/cf/test/test_Field.py index b0717f8c80..6c16c7019e 100644 --- a/cf/test/test_Field.py +++ b/cf/test/test_Field.py @@ -1298,7 +1298,7 @@ def test_Field_indices(self): # wi (decreasing) f.flip("X", inplace=True) - + indices = f.indices(grid_longitude=cf.wi(50, 130)) self.assertTrue(indices[0], "mask") g = f[indices] From 736d03c6d4ab552817b9556b5473cc574ef300b7 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 4 Nov 2024 11:00:39 +0000 Subject: [PATCH 06/14] dev --- cf/dimensioncoordinate.py | 44 ++++++++++++++++++++------------------- cf/test/test_Field.py | 11 ++++++++-- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/cf/dimensioncoordinate.py b/cf/dimensioncoordinate.py index bd6289a82b..9ba296c936 100644 --- a/cf/dimensioncoordinate.py +++ b/cf/dimensioncoordinate.py @@ -262,20 +262,14 @@ def anchor(self, value, cell=False, parameters=None, inplace=False): are transformed so that the first cell either contains *value*; or is the closet to cell to *value* from above (below) for increasing (decreasing) coordinates. - + .. versionadded:: NEXTVERSION .. seealso:: `period`, `roll` :Parameters: - - axis: - The cyclic axis to be anchored. - - domain axis selection TODO. - + value: scalar array_like - Anchor the coordinate values for the selected cyclic axis to the *value*. May be any numeric scalar object that can be converted to a `Data` object (which @@ -283,26 +277,26 @@ def anchor(self, value, cell=False, parameters=None, inplace=False): units then they must be compatible with those of the coordinates, otherwise it is assumed to have the same units as the coordinates. - + The coordinate values are transformed so the first corodinate is the closet to *value* from above (for increasing coordinates), or the closet to *value* from above (for idereasing coordinates) - + * Increasing coordinates with positive period, P, are transformed so that *value* lies in the half-open range (L-P, F], where F and L are the transformed first and last coordinate values, respectively. - + .. - + * Decreasing coordinates with positive period, P, are transformed so that *value* lies in the half-open range (L+P, F], where F and L are the transformed first and last coordinate values, respectively. - + *Parameter example:* If the original coordinates are ``0, 5, ..., 355`` (evenly spaced) and the period is ``360`` then @@ -311,7 +305,7 @@ def anchor(self, value, cell=False, parameters=None, inplace=False): coordinates of ``-10, -5, ..., 345``; ``value=380`` implies transformed coordinates of ``380, 385, ..., 715``. - + *Parameter example:* If the original coordinates are ``355, 350, ..., 0`` (evenly spaced) and the period is ``360`` then @@ -320,17 +314,25 @@ def anchor(self, value, cell=False, parameters=None, inplace=False): transformed coordinates of ``0, -5, ..., -355``; ``value=392`` implies transformed coordinates of ``390, 385, ..., 30``. - + cell: `bool`, optional - TODO + If True, then the coordinate values are transformed so + that the first cell either contains *value*, or is the + closet to cell to *value* from above (below) for + increasing (decreasing) coordinates. + + If False (the default) then the coordinate values are + transformed so that the first coordinate is the closet + to *value* from above (below) for increasing + (decreasing) coordinates. parameters: `dict`, optional TODO Return a dictionary of parameters which describe the anchoring process. The construct is not changed, even if *inplace* is True. - + {{inplace: `bool`, optional}} - + :Returns: """ @@ -353,8 +355,8 @@ def anchor(self, value, cell=False, parameters=None, inplace=False): else: d.persist(inplace=True) c = d.get_data(_fill_value=False) - - if d.increasing: + + if d.increasing: # Adjust value so it's in the range [c[0], c[0]+period) n = ((c[0] - value) / period).ceil() value1 = value + n * period @@ -364,7 +366,7 @@ def anchor(self, value, cell=False, parameters=None, inplace=False): d0 = d[0].upper_bounds else: d0 = d.get_data(_fill_value=False)[0] - + n = ((value - d0) / period).ceil() else: # Adjust value so it's in the range (c[0]-period, c[0]] diff --git a/cf/test/test_Field.py b/cf/test/test_Field.py index 6c16c7019e..430a22656a 100644 --- a/cf/test/test_Field.py +++ b/cf/test/test_Field.py @@ -1396,6 +1396,15 @@ def test_Field_indices(self): x = g.dimension_coordinate("X").array self.assertTrue((x == [80, 120, 160, 200, 240, 280, 320]).all()) + indices = f.indices(grid_longitude=cf.wi(-45, 45)) + g = f[indices] + self.assertEqual(g.shape, (1, 10, 9)) + x = g.dimension_coordinate("X").array + print(x) + self.assertTrue( + (x == [-160, -120, -80, -40, 0, 40, 80, 120, 160]).all() + ) + # 2-d lon = f.construct("longitude").array lon = np.transpose(lon) @@ -1490,7 +1499,6 @@ def test_Field_indices(self): shape = (1, 1, 1) self.assertEqual(g.shape, shape) - # REVIEW: getitem: `test_Field_indices`: make sure works when 'g.array' is not masked self.assertEqual(np.ma.compressed(g.array), 29) if mode != "full": self.assertEqual(g.construct("longitude").array, 83) @@ -1509,7 +1517,6 @@ def test_Field_indices(self): shape = (1, 2, 2) self.assertEqual(g.shape, shape) - # REVIEW: getitem: `test_Field_indices`: make sure works when 'g.array' is not masked self.assertTrue((np.ma.compressed(g.array) == [4, 29]).all()) # Add 2-d auxiliary coordinates with bounds, so we can From 3e3e5bdd3866e23994e2bbf67100aa676aed6c34 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 4 Nov 2024 12:02:00 +0000 Subject: [PATCH 07/14] dev --- Changelog.rst | 3 ++ cf/test/test_DimensionCoordinate.py | 64 +++++++++++++++++++++++++++++ cf/test/test_Field.py | 32 ++++++++++++--- 3 files changed, 93 insertions(+), 6 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index d1ed7a8d2f..af6059b0c6 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -46,6 +46,9 @@ version NEXTVERSION * Fix bug where `cf.normalize_slice` doesn't correctly handle certain cyclic slices (https://github.com/NCAS-CMS/cf-python/issues/774) +* Fix bug where `cf.Field.subspace` doesn't also correctly + handle some global cyclic subspaces + (https://github.com/NCAS-CMS/cf-python/issues/828) ---- diff --git a/cf/test/test_DimensionCoordinate.py b/cf/test/test_DimensionCoordinate.py index 9244a202b1..b39181b245 100644 --- a/cf/test/test_DimensionCoordinate.py +++ b/cf/test/test_DimensionCoordinate.py @@ -737,6 +737,70 @@ def test_DimensionCoordinate_cell_characteristics(self): with self.assertRaises(ValueError): d.get_cell_characteristics() + def test_DimensionCoordinate_anchor(self): + """Test the DimensionCoordinate.anchor""" + f = cf.example_field(0) + d = f.dimension_coordinate("X") + + self.assertEqual(d.period(), 360) + + e = d.anchor(-1) + self.assertIsInstance(e, cf.DimensionCoordinate) + self.assertTrue(e.equals(d)) + + # Increasing + e = d.anchor(15) + self.assertTrue(e[0].equals(d[0])) + self.assertEqual(e[0].array, d[0].array) + + e = d.anchor(30) + self.assertEqual(e[0].array, d[1].array) + + e = d.anchor(361) + self.assertEqual(e[0].array, d[0].array + 360) + + e = d.anchor(721) + self.assertEqual(e[0].array, d[0].array + 720) + + e = d.anchor(15, cell=True) + self.assertEqual(e[0].array, d[0].array) + + e = d.anchor(30, cell=True) + self.assertEqual(e[0].array, d[0].array) + + e = d.anchor(361, cell=True) + self.assertEqual(e[0].array, d[0].array + 360) + + e = d.anchor(721, cell=True) + self.assertEqual(e[0].array, d[0].array + 720) + + # Decreasing + d = d[::-1] + + e = d.anchor(721) + self.assertEqual(e[0].array, d[0].array + 360) + + e = d.anchor(361) + self.assertEqual(e[0].array, d[0].array) + + e = d.anchor(30) + self.assertEqual(e[0].array, d[-1].array) + + e = d.anchor(15) + self.assertEqual(e[0].array, d[0].array - 360) + + e = d.anchor(721, cell=True) + self.assertEqual(e[0].array, d[0].array + 360) + + e = d.anchor(361, cell=True) + self.assertEqual(e[0].array, d[0].array) + + e = d.anchor(30, cell=True) + self.assertEqual(e[0].array, d[0].array - 360) + + e = d.anchor(15, cell=True) + self.assertEqual(e[0].array, d[0].array - 360) + if __name__ == "__main__": print("Run date:", datetime.datetime.now()) diff --git a/cf/test/test_Field.py b/cf/test/test_Field.py index 430a22656a..8084323518 100644 --- a/cf/test/test_Field.py +++ b/cf/test/test_Field.py @@ -1396,14 +1396,34 @@ def test_Field_indices(self): x = g.dimension_coordinate("X").array self.assertTrue((x == [80, 120, 160, 200, 240, 280, 320]).all()) - indices = f.indices(grid_longitude=cf.wi(-45, 45)) + indices = f.indices(grid_longitude=cf.wo(-45, 45)) g = f[indices] - self.assertEqual(g.shape, (1, 10, 9)) + self.assertEqual(g.shape, (1, 10, 6)) x = g.dimension_coordinate("X").array - print(x) - self.assertTrue( - (x == [-160, -120, -80, -40, 0, 40, 80, 120, 160]).all() - ) + self.assertTrue((x == [80, 120, 160, 200, 240, 280]).all()) + + indices = f.indices(grid_longitude=cf.wo(35, 85)) + g = f[indices] + x = g.dimension_coordinate("X").array + self.assertEqual(g.shape, (1, 10, 7)) + x = g.dimension_coordinate("X").array + self.assertTrue((x == [-240, -200, -160, -120, -80, -40, 0]).all()) + + indices = f.indices(grid_longitude=cf.wo(35, 85)) + g = f[indices] + x = g.dimension_coordinate("X").array + self.assertEqual(g.shape, (1, 10, 7)) + x = g.dimension_coordinate("X").array + self.assertTrue((x == [-240, -200, -160, -120, -80, -40, 0]).all()) + + with self.assertRaises(ValueError): + f.indices(grid_longitude=cf.wo(0, 360)) + + with self.assertRaises(ValueError): + f.indices(grid_longitude=cf.wo(-90, 270)) + + with self.assertRaises(ValueError): + f.indices(grid_longitude=cf.wo(-180, 180)) # 2-d lon = f.construct("longitude").array From 922af47485a4ac7823e72ab0421417c7fa76b84c Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 11 Nov 2024 21:35:49 +0000 Subject: [PATCH 08/14] Typo Co-authored-by: Sadie L. Bartholomew --- cf/dimensioncoordinate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cf/dimensioncoordinate.py b/cf/dimensioncoordinate.py index 9ba296c936..bc5977ec6b 100644 --- a/cf/dimensioncoordinate.py +++ b/cf/dimensioncoordinate.py @@ -255,7 +255,7 @@ def anchor(self, value, cell=False, parameters=None, inplace=False): """Anchor the coordinate values. By default, the coordinate values are transformed so that the - first coordinate is the closet to *value* from above (below) + first coordinate is the closest to *value* from above (below) for increasing (decreasing) coordinates. If the *cell* parameter is True, then the coordinate values From f7e9a4c2792afb2225e164fe2dcf92c8cd534cd6 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 11 Nov 2024 21:37:30 +0000 Subject: [PATCH 09/14] Redundant code Co-authored-by: Sadie L. Bartholomew --- cf/test/test_Field.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cf/test/test_Field.py b/cf/test/test_Field.py index 8084323518..0d2e9d114a 100644 --- a/cf/test/test_Field.py +++ b/cf/test/test_Field.py @@ -1406,14 +1406,12 @@ def test_Field_indices(self): g = f[indices] x = g.dimension_coordinate("X").array self.assertEqual(g.shape, (1, 10, 7)) - x = g.dimension_coordinate("X").array self.assertTrue((x == [-240, -200, -160, -120, -80, -40, 0]).all()) indices = f.indices(grid_longitude=cf.wo(35, 85)) g = f[indices] x = g.dimension_coordinate("X").array self.assertEqual(g.shape, (1, 10, 7)) - x = g.dimension_coordinate("X").array self.assertTrue((x == [-240, -200, -160, -120, -80, -40, 0]).all()) with self.assertRaises(ValueError): From a52076b34923b69bdad2b1a3887b240c7186c647 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 11 Nov 2024 21:38:07 +0000 Subject: [PATCH 10/14] Typos Co-authored-by: Sadie L. Bartholomew --- cf/dimensioncoordinate.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cf/dimensioncoordinate.py b/cf/dimensioncoordinate.py index bc5977ec6b..253808d45a 100644 --- a/cf/dimensioncoordinate.py +++ b/cf/dimensioncoordinate.py @@ -260,7 +260,7 @@ def anchor(self, value, cell=False, parameters=None, inplace=False): If the *cell* parameter is True, then the coordinate values are transformed so that the first cell either contains - *value*; or is the closet to cell to *value* from above + *value*; or is the closest to cell to *value* from above (below) for increasing (decreasing) coordinates. .. versionadded:: NEXTVERSION @@ -279,9 +279,9 @@ def anchor(self, value, cell=False, parameters=None, inplace=False): units as the coordinates. The coordinate values are transformed so the first - corodinate is the closet to *value* from above (for - increasing coordinates), or the closet to *value* from - above (for idereasing coordinates) + corodinate is the closest to *value* from above (for + increasing coordinates), or the closest to *value* from + above (for decreasing coordinates) * Increasing coordinates with positive period, P, are transformed so that *value* lies in the @@ -318,11 +318,11 @@ def anchor(self, value, cell=False, parameters=None, inplace=False): cell: `bool`, optional If True, then the coordinate values are transformed so that the first cell either contains *value*, or is the - closet to cell to *value* from above (below) for + closest to cell to *value* from above (below) for increasing (decreasing) coordinates. If False (the default) then the coordinate values are - transformed so that the first coordinate is the closet + transformed so that the first coordinate is the closest to *value* from above (below) for increasing (decreasing) coordinates. From eeeb66ff93cd3fe32c13718531efdca15e312e60 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 11 Nov 2024 21:38:30 +0000 Subject: [PATCH 11/14] Improve changelog description Co-authored-by: Sadie L. Bartholomew --- Changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog.rst b/Changelog.rst index 04608aac45..99d4c88c4b 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -34,7 +34,7 @@ version NEXTVERSION handle certain cyclic slices (https://github.com/NCAS-CMS/cf-python/issues/774) * Fix bug where `cf.Field.subspace` doesn't always correctly handle - global cyclic subspaces + global or near-global cyclic subspaces (https://github.com/NCAS-CMS/cf-python/issues/828) * New dependency: ``h5netcdf>=1.3.0`` * New dependency: ``h5py>=3.10.0`` From 018040821c3e2b126195b3da8bef9c8ae1c13bab Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 11 Nov 2024 21:40:50 +0000 Subject: [PATCH 12/14] Fix coordinate values in examples Co-authored-by: Sadie L. Bartholomew --- cf/dimensioncoordinate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cf/dimensioncoordinate.py b/cf/dimensioncoordinate.py index 253808d45a..599d4be297 100644 --- a/cf/dimensioncoordinate.py +++ b/cf/dimensioncoordinate.py @@ -304,7 +304,7 @@ def anchor(self, value, cell=False, parameters=None, inplace=False): 5, ..., 355``; ``value=-12`` implies transformed coordinates of ``-10, -5, ..., 345``; ``value=380`` implies transformed coordinates of ``380, 385, ..., - 715``. + 735``. *Parameter example:* If the original coordinates are ``355, 350, ..., 0`` @@ -313,7 +313,7 @@ def anchor(self, value, cell=False, parameters=None, inplace=False): ``355, 350, ..., 0``; ``value=0`` implies transformed coordinates of ``0, -5, ..., -355``; ``value=392`` implies transformed coordinates of - ``390, 385, ..., 30``. + ``390, 385, ..., 35``. cell: `bool`, optional If True, then the coordinate values are transformed so From 5e5727210d551ca14abfcd6b00fce3d70c68a53b Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 11 Nov 2024 21:46:37 +0000 Subject: [PATCH 13/14] docstring --- cf/dimensioncoordinate.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cf/dimensioncoordinate.py b/cf/dimensioncoordinate.py index 599d4be297..3124035e66 100644 --- a/cf/dimensioncoordinate.py +++ b/cf/dimensioncoordinate.py @@ -327,14 +327,18 @@ def anchor(self, value, cell=False, parameters=None, inplace=False): (decreasing) coordinates. parameters: `dict`, optional - TODO Return a dictionary of parameters which describe the - anchoring process. The construct is not changed, even - if *inplace* is True. + If a `dict` is provided then it will be updated + in-place with parameters which describe thethe + anchoring process. {{inplace: `bool`, optional}} :Returns: + `{{class}}` or `None` + The anchored dimension coordinates, or `None` if the + operation was in-place. + """ d = _inplace_enabled_define_and_cleanup(self) From 228abd412f036e94453dbd736a2a34f5f3f0eb42 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 11 Nov 2024 22:32:57 +0000 Subject: [PATCH 14/14] cf.indices wo fix --- cf/mixin/fielddomain.py | 4 +++- cf/test/test_Field.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/cf/mixin/fielddomain.py b/cf/mixin/fielddomain.py index d9a9d8c9b4..833f86ad3a 100644 --- a/cf/mixin/fielddomain.py +++ b/cf/mixin/fielddomain.py @@ -472,7 +472,9 @@ def _indices(self, config, data_axes, ancillary_mask, kwargs): stop = start - n.size else: start = size - parameters["shift"] - stop = start + stop = start + size + if stop > size: + stop -= size index = slice(start, stop, 1) diff --git a/cf/test/test_Field.py b/cf/test/test_Field.py index 0d2e9d114a..b1df9ef1f1 100644 --- a/cf/test/test_Field.py +++ b/cf/test/test_Field.py @@ -1361,6 +1361,34 @@ def test_Field_indices(self): x = g.dimension_coordinate("X").array self.assertTrue((x == [-200, -160, -120, -80, -40, 0, 40]).all()) + indices = f.indices(grid_longitude=cf.wo(1, 5)) + g = f[indices] + self.assertEqual(g.shape, (1, 10, 9)) + x = g.dimension_coordinate("X").array + self.assertTrue( + (x == [-320, -280, -240, -200, -160, -120, -80, -40, 0]).all() + ) + + indices = f.indices(grid_longitude=cf.wo(41, 45)) + g = f[indices] + self.assertEqual(g.shape, (1, 10, 9)) + x = g.dimension_coordinate("X").array + self.assertTrue( + (x == [-280, -240, -200, -160, -120, -80, -40, 0, 40]).all() + ) + + indices = f.indices(grid_longitude=cf.wo(-5, -1)) + g = f[indices] + self.assertEqual(g.shape, (1, 10, 9)) + x = g.dimension_coordinate("X").array + self.assertTrue((x == [0, 40, 80, 120, 160, 200, 240, 280, 320]).all()) + + indices = f.indices(grid_longitude=cf.wo(-45, -41)) + g = f[indices] + self.assertEqual(g.shape, (1, 10, 9)) + x = g.dimension_coordinate("X").array + self.assertTrue((x == [-40, 0, 40, 80, 120, 160, 200, 240, 280]).all()) + with self.assertRaises(ValueError): # No X coordinate values lie outside the range [-90, 370] f.indices(grid_longitude=cf.wo(-90, 370))