Skip to content

Commit 392ad50

Browse files
authored
Merge pull request #724 from davidhassell/dsg-aggregate
Allow DSG tractories with identical `trajectory_id` values to be aggregated
2 parents e5c13f3 + ccc7422 commit 392ad50

File tree

3 files changed

+111
-4
lines changed

3 files changed

+111
-4
lines changed

Changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ version 3.17.0
33

44
**2024-??-??**
55

6+
* Allow DSG tractories with identical `trajectory_id` values to be
7+
aggregated (https://github.com/NCAS-CMS/cf-python/issues/723)
68
* New methods: `cf.Field.pad_missing` and `cf.Data.pad_missing`
79
(https://github.com/NCAS-CMS/cf-python/issues/717)
810
* Fix occasional bug when calculating UGRID cell areas when

cf/aggregate.py

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"add_offset",
5555
"calendar",
5656
"cell_methods",
57+
"featureType",
5758
"_FillValue",
5859
"flag_masks",
5960
"flag_meanings",
@@ -210,6 +211,7 @@ class _Meta:
210211
(
211212
"Type",
212213
"Identity",
214+
"featureType",
213215
"Units",
214216
"Cell_methods",
215217
"Data",
@@ -389,6 +391,10 @@ def __init__(
389391
if field_identity:
390392
self.identity = f.get_property(field_identity, None)
391393

394+
# Set the DSG featureType
395+
featureType = f.get_property("featureType", None)
396+
self.featureType = featureType
397+
392398
construct_axes = f.constructs.data_axes()
393399

394400
# ------------------------------------------------------------
@@ -502,6 +508,7 @@ def __init__(
502508
"identity": dim_identity,
503509
"key": dim_coord_key,
504510
"units": units,
511+
"cf_role": None,
505512
"hasdata": dim_coord.has_data(),
506513
"hasbounds": hasbounds,
507514
"coordrefs": self.find_coordrefs(axis),
@@ -539,11 +546,18 @@ def __init__(
539546
aux_coord, aux_identity, relaxed_units=relaxed_units
540547
)
541548

549+
# Set the cf_role for DSGs
550+
if not featureType:
551+
cf_role = None
552+
else:
553+
cf_role = aux_coord.get_property("cf_role", None)
554+
542555
info_aux.append(
543556
{
544557
"identity": aux_identity,
545558
"key": key,
546559
"units": units,
560+
"cf_role": cf_role,
547561
"hasdata": aux_coord.has_data(),
548562
"hasbounds": aux_coord.has_bounds(),
549563
"coordrefs": self.find_coordrefs(key),
@@ -586,12 +600,14 @@ def __init__(
586600

587601
return
588602

603+
identity = f"ncvar%{identity}"
589604
size = domain_axis.get_size()
590605

591606
axis_identities = {
592607
"ids": "identity",
593608
"keys": "key",
594609
"units": "units",
610+
"cf_role": "cf_role",
595611
"hasdata": "hasdata",
596612
"hasbounds": "hasbounds",
597613
"coordrefs": "coordrefs",
@@ -1773,6 +1789,9 @@ def structural_signature(self):
17731789
Cell_methods = self.cell_methods
17741790
Data = self.has_field_data
17751791

1792+
# DSG FeatureType
1793+
featureType = self.featureType
1794+
17761795
# Properties
17771796
Properties = self.properties
17781797

@@ -1812,6 +1831,7 @@ def structural_signature(self):
18121831
]
18131832
),
18141833
),
1834+
("cf_role", axis[identity]["cf_role"]),
18151835
("hasdata", axis[identity]["hasdata"]),
18161836
("hasbounds", axis[identity]["hasbounds"]),
18171837
("coordrefs", axis[identity]["coordrefs"]),
@@ -1918,6 +1938,7 @@ def structural_signature(self):
19181938
self.signature = self._structural_signature(
19191939
Type=Type,
19201940
Identity=Identity,
1941+
featureType=featureType,
19211942
Units=Units,
19221943
Cell_methods=Cell_methods,
19231944
Data=Data,
@@ -4147,6 +4168,15 @@ def _hash_values(m):
41474168

41484169
hash0 = hash1
41494170

4171+
# If 'count' is 0 then all of the 1-d coordinates have the
4172+
# same values across fields. However, for a DSG featureType
4173+
# axis we can still aggregate it, because it's OK to aggregate
4174+
# featureTypes with the timeseries_id, profile_id, or
4175+
# trajectory_id.
4176+
if not count and dsg_feature_type_axis(m0, axis):
4177+
a_identity = axis
4178+
count = 1
4179+
41504180
if count == 1:
41514181
# --------------------------------------------------------
41524182
# Exactly one axis has different 1-d coordinate values
@@ -4248,10 +4278,22 @@ def _hash_values(m):
42484278
# aggregate anything in this entire group.
42494279
# --------------------------------------------------------
42504280
if info:
4251-
meta[
4252-
0
4253-
].message = (
4254-
"Some fields have identical sets of 1-d coordinates."
4281+
coord_ids = []
4282+
for k, v in m0.axis.items():
4283+
coord_ids.extend([repr(i) for i in v["ids"]])
4284+
4285+
if len(coord_ids) > 1:
4286+
coord_ids = (
4287+
f"{', '.join(coord_ids[:-1])} and {coord_ids[-1]}"
4288+
)
4289+
elif coord_ids:
4290+
coord_ids = coord_ids[0]
4291+
else:
4292+
coord_ids = ""
4293+
4294+
meta[0].message = (
4295+
f"Some fields have identical sets of 1-d {coord_ids} "
4296+
"coordinates."
42554297
)
42564298

42574299
return ()
@@ -4839,6 +4881,19 @@ def _aggregate_2_fields(
48394881

48404882

48414883
def f_identity(meta):
4884+
"""Return the field identity for logging strings.
4885+
4886+
:Parameters:
4887+
4888+
meta: `_Meta`
4889+
The `_Meta` instance containing the field.
4890+
4891+
:Returns:
4892+
4893+
`str`
4894+
The identity.
4895+
4896+
"""
48424897
identity = meta.identity
48434898
f_identity = meta.field.identity()
48444899
if f_identity == identity:
@@ -4847,3 +4902,38 @@ def f_identity(meta):
48474902
identity = f"{meta.identity!r} ({f_identity})"
48484903

48494904
return identity
4905+
4906+
4907+
def dsg_feature_type_axis(meta, axis):
4908+
"""True if the given axis is a DSG featureType axis.
4909+
4910+
A DSG featureType axis has no dimension coordinates and at least
4911+
one 1-d auxiliary coordinate with a ``cf-role`` property.
4912+
4913+
:Parameters:
4914+
4915+
meta: `_Meta`
4916+
The `_Meta` instance
4917+
4918+
axis: `str`
4919+
One of the axes in ``meta.axis_ids``.
4920+
4921+
:Returns:
4922+
4923+
`bool`
4924+
`True` if the given axis is a DSG featureType axis.
4925+
4926+
"""
4927+
if not meta.featureType:
4928+
# The field/domain is not a DSG
4929+
return False
4930+
4931+
coords = meta.axis[axis]
4932+
if coords["dim_coord_index"] is not None:
4933+
# The axis has dimension coordinates
4934+
return False
4935+
4936+
# Return True if one of the 1-d auxiliary coordinates has a
4937+
# cf_role property
4938+
cf_role = coords["cf_role"]
4939+
return cf_role.count(None) != len(cf_role)

cf/test/test_aggregate.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,21 @@ def test_aggregate_ugrid(self):
649649
d += 0.1
650650
self.assertEqual(len(cf.aggregate([f, g])), 2)
651651

652+
def test_aggregate_trajectory(self):
653+
"""Test DSG trajectory aggregation"""
654+
# Test that aggregation occurs when the tractory_id axes have
655+
# identical 1-d auxiliary coordinates
656+
f = cf.example_field(11)
657+
g = cf.aggregate([f, f], relaxed_identities=True)
658+
self.assertEqual(len(g), 1)
659+
660+
g = g[0]
661+
self.assertTrue(
662+
g.subspace(**{"cf_role=trajectory_id": [0]}).equals(
663+
g.subspace(**{"cf_role=trajectory_id": [1]})
664+
)
665+
)
666+
652667

653668
if __name__ == "__main__":
654669
print("Run date:", datetime.datetime.now())

0 commit comments

Comments
 (0)