Skip to content

Commit 05e655b

Browse files
committed
aggregation actual_range
1 parent 0e45291 commit 05e655b

File tree

2 files changed

+114
-8
lines changed

2 files changed

+114
-8
lines changed

cf/aggregate.py

Lines changed: 39 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):
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,24 @@ 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+
parent0.del_property("actual_range", None)
4910+
except (TypeError, IndexError):
4911+
# valid_range is non-CF-compliant
4912+
pass
48824913

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

cf/test/test_aggregate.py

Lines changed: 75 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,79 @@ def test_aggregate_trajectory(self):
664666
)
665667
)
666668

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

668743
if __name__ == "__main__":
669744
print("Run date:", datetime.datetime.now())

0 commit comments

Comments
 (0)