Skip to content

Commit e1af68d

Browse files
authored
Merge pull request #765 from davidhassell/aggregation-valid-range
Fix `cf.aggregate` for actual_range and np.array properties
2 parents 1314aef + 73b8647 commit e1af68d

File tree

4 files changed

+125
-8
lines changed

4 files changed

+125
-8
lines changed

Changelog.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ version 3.16.2
3232
* Fix bug in `cf.aggregate` that sometimes put a null transpose
3333
operation into the Dask graph when one was not needed
3434
(https://github.com/NCAS-CMS/cf-python/issues/754)
35+
* Fix bug in `cf.aggregate` that caused a failure when property values
36+
were `numpy` arrays with two or more elements
37+
(https://github.com/NCAS-CMS/cf-python/issues/764)
38+
* Fix bug in `cf.aggregate` that didn't correctly handle the
39+
"actual_range" CF attribute
40+
(https://github.com/NCAS-CMS/cf-python/issues/764)
3541
* Fix bug whereby `Field.cyclic` is not updated after a
3642
`Field.del_construct` operation
3743
(https://github.com/NCAS-CMS/cf-python/issues/758)

cf/aggregate.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4852,20 +4852,36 @@ def _aggregate_2_fields(
48524852
value0 = parent0.get_property(prop, None)
48534853
value1 = parent1.get_property(prop, None)
48544854

4855+
if prop in ("_FillValue", "missing_value"):
4856+
continue
4857+
48554858
if prop in ("valid_min", "valid_max", "valid_range"):
48564859
if not m0.respect_valid:
48574860
parent0.del_property(prop, None)
48584861

48594862
continue
48604863

4861-
if prop in ("_FillValue", "missing_value"):
4864+
if prop == "actual_range":
4865+
try:
4866+
# Try to extend the actual range to encompass both
4867+
# value0 and value1
4868+
actual_range = (
4869+
min(value0[0], value1[0]),
4870+
max(value0[1], value1[1]),
4871+
)
4872+
except (TypeError, IndexError, KeyError):
4873+
# value0 and/or value1 is not set, or is
4874+
# non-CF-compliant.
4875+
parent0.del_property(prop, None)
4876+
else:
4877+
parent0.set_property(prop, actual_range)
4878+
48624879
continue
48634880

48644881
# Still here?
4865-
if isinstance(value0, str) or isinstance(value1, str):
4866-
if value0 == value1:
4867-
continue
4868-
elif parent0._equals(value0, value1):
4882+
if parent0._equals(value0, value1):
4883+
# Both values are equal, so no need to update the
4884+
# property.
48694885
continue
48704886

48714887
if concatenate:
@@ -4876,9 +4892,30 @@ def _aggregate_2_fields(
48764892
)
48774893
else:
48784894
parent0.set_property(prop, f" :AGGREGATED: {value1}")
4879-
else:
4880-
if value0 is not None:
4881-
parent0.del_property(prop)
4895+
elif value0 is not None:
4896+
parent0.del_property(prop)
4897+
4898+
# Check that actual_range is within the bounds of valid_range, and
4899+
# delete it if it isn't.
4900+
actual_range = parent0.get_property("actual_range", None)
4901+
if actual_range is not None:
4902+
valid_range = parent0.get_property("valid_range", None)
4903+
if valid_range is not None:
4904+
try:
4905+
if (
4906+
actual_range[0] < valid_range[0]
4907+
or actual_range[1] > valid_range[1]
4908+
):
4909+
actual_range = parent0.del_property("actual_range", None)
4910+
if actual_range is not None and is_log_level_info(logger):
4911+
logger.info(
4912+
"Deleted 'actual_range' attribute due to being "
4913+
"outside of 'valid_range' attribute limits."
4914+
)
4915+
4916+
except (TypeError, IndexError):
4917+
# valid_range is non-CF-compliant
4918+
pass
48824919

48834920
# Make a note that the parent construct in this _Meta object has
48844921
# already been aggregated

cf/test/test_aggregate.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import unittest
55
import warnings
66

7+
import numpy as np
8+
79
faulthandler.enable() # to debug seg faults and timeouts
810

911
import cf
@@ -664,6 +666,77 @@ def test_aggregate_trajectory(self):
664666
)
665667
)
666668

669+
def test_aggregate_actual_range(self):
670+
"""Test aggregation of actual_range"""
671+
f = cf.example_field(0)
672+
f.set_property("actual_range", (5, 10))
673+
f.set_property("valid_range", (0, 15))
674+
f0 = f[:, :2]
675+
f1 = f[:, 2:4]
676+
f2 = f[:, 4:]
677+
678+
g = cf.aggregate([f0, f1, f2])
679+
self.assertEqual(len(g), 1)
680+
self.assertEqual(g[0].get_property("actual_range"), (5, 10))
681+
682+
f1.set_property("actual_range", [2, 13])
683+
g = cf.aggregate([f0, f1, f2])
684+
self.assertEqual(len(g), 1)
685+
self.assertEqual(g[0].get_property("actual_range"), (2, 13))
686+
687+
f1.set_property("actual_range", [-2, 17])
688+
g = cf.aggregate([f0, f1, f2])
689+
self.assertEqual(len(g), 1)
690+
self.assertEqual(g[0].get_property("actual_range"), (-2, 17))
691+
692+
g = cf.aggregate([f0, f1, f2], respect_valid=True)
693+
self.assertEqual(len(g), 1)
694+
self.assertEqual(g[0].get_property("valid_range"), (0, 15))
695+
self.assertFalse(g[0].has_property("actual_range"))
696+
697+
f1.set_property("actual_range", [0, 15])
698+
g = cf.aggregate([f0, f1, f2], respect_valid=True)
699+
self.assertEqual(len(g), 1)
700+
self.assertEqual(g[0].get_property("valid_range"), (0, 15))
701+
self.assertEqual(g[0].get_property("actual_range"), (0, 15))
702+
703+
def test_aggregate_numpy_array_property(self):
704+
"""Test aggregation of numpy array-valued properties"""
705+
a = np.array([5, 10])
706+
f = cf.example_field(0)
707+
f.set_property("array", a)
708+
f0 = f[:, :2]
709+
f1 = f[:, 2:4]
710+
f2 = f[:, 4:]
711+
712+
g = cf.aggregate([f0, f1, f2])
713+
self.assertEqual(len(g), 1)
714+
self.assertTrue((g[0].get_property("array") == a).all())
715+
716+
f1.set_property("array", np.array([-5, 20]))
717+
g = cf.aggregate([f0, f1, f2])
718+
self.assertEqual(len(g), 1)
719+
self.assertEqual(
720+
g[0].get_property("array"),
721+
"[ 5 10] :AGGREGATED: [-5 20] :AGGREGATED: [ 5 10]",
722+
)
723+
724+
f2.set_property("array", np.array([-5, 20]))
725+
g = cf.aggregate([f0, f1, f2])
726+
self.assertEqual(len(g), 1)
727+
self.assertEqual(
728+
g[0].get_property("array"),
729+
"[ 5 10] :AGGREGATED: [-5 20] :AGGREGATED: [-5 20]",
730+
)
731+
732+
f1.set_property("array", np.array([5, 10]))
733+
g = cf.aggregate([f0, f1, f2])
734+
self.assertEqual(len(g), 1)
735+
self.assertEqual(
736+
g[0].get_property("array"),
737+
"[ 5 10] :AGGREGATED: [-5 20]",
738+
)
739+
667740

668741
if __name__ == "__main__":
669742
print("Run date:", datetime.datetime.now())

docs/source/contributing.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ ideas, code, and documentation to the cf library:
121121
* Jonathan Gregory
122122
* Klaus Zimmermann
123123
* Kristian Sebastián
124+
* Mark Rhodes-Smith
124125
* Michael Decker
125126
* Sadie Bartholomew
126127
* Thibault Hallouin

0 commit comments

Comments
 (0)