191
191
"__ge__",
192
192
)
193
193
194
- #_xxx = namedtuple(
195
- # "data_dimension", ["size", "axis", "keys", "coords", "coord_type", "scalar#"]
196
- #)
197
194
198
195
@dataclass()
199
- class _data_dimension:
200
- """TODO
196
+ class _Axis_characterisation:
197
+ """Characterise a domain axis.
198
+
199
+ Used by `_binary_operation` to help with ascertaining if there is
200
+ a common axis in two fields.
201
201
202
202
.. versionaddedd:: NEXTVERSION
203
203
204
204
"""
205
+
206
+ # The size of the axis, a positive integer.
205
207
size: int = -1
208
+ # The domain axis identifier. E.g. 'domainaxis0'
206
209
axis: str = ""
207
- keys: tuple = ()
210
+ # The coordinate constructs that characterize the axis
208
211
coords: tuple = ()
209
- coord_type: tuple = ()
212
+ # The identifiers of the coordinate
213
+ # constructs. E.g. ('dimensioncoordinate1',)
214
+ keys: tuple = ()
215
+ # Whether or not the axis is spanned by the field's data array
210
216
axis_in_data_axes: bool = True
211
-
212
-
213
- # _empty_set = set()
214
217
215
218
216
219
class Field(mixin.FieldDomain, mixin.PropertiesData, cfdm.Field):
@@ -1001,92 +1004,100 @@ def _binary_operation(self, other, method):
1001
1004
data_axes = f.get_data_axes()
1002
1005
for axis in f.domain_axes(todict=True):
1003
1006
identity = None
1004
- key = None
1005
- coord = None
1006
- coord_type = None
1007
1007
1008
- key, dim_coord = f.dimension_coordinate(
1009
- item=True, default=(None, None), filter_by_axis=(axis,)
1010
- )
1011
- if dim_coord is not None:
1012
- # This axis of the domain has a dimension
1013
- # coordinate
1014
- identity = dim_coord.identity(strict=True, default=None)
1015
- if identity is None:
1016
- # Dimension coordinate has no identity, but it
1017
- # may have a recognised axis.
1018
- for ctype in ("T", "X", "Y", "Z"):
1019
- if getattr(dim_coord, ctype, False):
1020
- identity = ctype
1021
- break
1022
-
1023
- if identity is None and relaxed_identities:
1024
- identity = dim_coord.identity(relaxed=True, default=None)
1025
-
1026
- keys = (key,)
1027
- coords = (dim_coord,)
1008
+ if self.is_discrete_axis(axis):
1009
+ # This is a discrete axis whose identity is
1010
+ # inferred from all of its auxiliary coordinates
1011
+ x = {}
1012
+ for key, aux_coord in f.auxiliary_coordinates(
1013
+ filter_by_axis=(axis,),
1014
+ axis_mode="and",
1015
+ todict=True,
1016
+ ).items():
1017
+ identity = aux_coord.identity(
1018
+ strict=True, default=None
1019
+ )
1020
+ if identity is None and relaxed_identities:
1021
+ identity = aux_coord.identity(
1022
+ relaxed=True, default=None
1023
+ )
1024
+ if identity is not None:
1025
+ x[identity] = key
1026
+
1027
+ if x:
1028
+ # E.g. {2:3, 4:6, 1:7} -> (1, 2, 4), (7, 3, 6)
1029
+ identity, keys = tuple(zip(*sorted(x.items())))
1030
+ coords = tuple(
1031
+ f.auxiliary_coordinate(key) for key in keys
1032
+ )
1033
+ else:
1034
+ identity = None
1035
+ keys = ()
1036
+ coords = ()
1028
1037
else:
1029
- discrete_axis = f.has_property('featureType') or f.domain_topologies(todict=True)
1030
- if discrete_axis:
1031
- x = {}
1032
- for key, aux_coord in f.auxiliary_coordinates(
1033
- filter_by_axis=(axis,),
1034
- axis_mode="exact", todict=True
1035
- ).items():
1036
- identity = aux_coord.identity( strict=True, default=None)
1037
- if identity is None and relaxed_identities:
1038
- identity = aux_coord.identity(
1039
- relaxed=True, default=None
1040
- )
1041
- if identity is not None :
1042
- x[identity] = key
1043
-
1044
- if x:
1045
- # E.g. {2:3, 4:6, 1:7} -> (1, 2, 4), (7, 3, 6)
1046
- identity, keys = tuple(zip(*sorted(x.items())))
1047
- coords = tuple(f.auxiliary_coordinate(key)
1048
- for key in keys)
1049
- else:
1050
- identity = None
1051
- keys = None
1052
- coords = None
1053
- else:
1038
+ key, dim_coord = f.dimension_coordinate(
1039
+ item=True, default=(None, None), filter_by_axis=(axis,)
1040
+ )
1041
+ if dim_coord is not None:
1042
+ # This non-discrete axis has a dimension
1043
+ # coordinate
1044
+ identity = dim_coord.identity(
1045
+ strict=True, default=None
1046
+ )
1047
+ if identity is None:
1048
+ # Dimension coordinate has no identity,
1049
+ # but it may have a recognised axis.
1050
+ for ctype in ("T", "X", "Y", "Z") :
1051
+ if getattr(dim_coord, ctype, False):
1052
+ identity = ctype
1053
+ break
1054
+
1055
+ if identity is None and relaxed_identities:
1056
+ identity = dim_coord.identity(
1057
+ relaxed=True, default=None
1058
+ )
1059
+
1060
+ keys = (key,)
1061
+ coords = (dim_coord,)
1062
+ else:
1054
1063
key, aux_coord = f.auxiliary_coordinate(
1055
1064
item=True,
1056
1065
default=(None, None),
1057
1066
filter_by_axis=(axis,),
1058
1067
axis_mode="exact",
1059
1068
)
1060
1069
if aux_coord is not None:
1061
- # This non-discrete axis of the domain
1062
- # does not have a dimension coordinate but
1063
- # it does have exactly one 1-d auxiliary
1064
- # coordinate, so that will do.
1070
+ # This non-discrete axis does not have a
1071
+ # dimension coordinate but it does have
1072
+ # exactly one 1-d auxiliary coordinate, so
1073
+ # that will do.
1065
1074
coords = (aux_coord,)
1066
- identity = aux_coord.identity(strict=True, default=None)
1075
+ identity = aux_coord.identity(
1076
+ strict=True, default=None
1077
+ )
1067
1078
if identity is None and relaxed_identities:
1068
1079
identity = aux_coord.identity(
1069
1080
relaxed=True, default=None
1070
1081
)
1071
-
1072
1082
1073
1083
if identity is None:
1074
1084
identity = i
1075
- else:
1076
- coord_type = coords[0].construct_type
1077
1085
1078
- out[identity] = _data_dimension (
1086
+ out[identity] = _Axis_characterisation (
1079
1087
size=f.domain_axis(axis).get_size(),
1080
1088
axis=axis,
1081
1089
keys=keys,
1082
1090
coords=coords,
1083
- coord_type=coord_type,
1084
1091
axis_in_data_axes=axis in data_axes,
1085
1092
)
1086
1093
1087
1094
for identity, y in tuple(out1.items()):
1088
1095
missing_axis_inserted = False
1089
- if not y.axis_in_data_axes and identity in out0 and isinstance(identity, str):
1096
+ if (
1097
+ not y.axis_in_data_axes
1098
+ and identity in out0
1099
+ and isinstance(identity, str)
1100
+ ):
1090
1101
a = out0[identity]
1091
1102
if a.size > 1:
1092
1103
# Put missing axis into data axes
@@ -1098,14 +1109,18 @@ def _binary_operation(self, other, method):
1098
1109
1099
1110
for identity, a in tuple(out0.items()):
1100
1111
missing_axis_inserted = False
1101
- if not a.axis_in_data_axes and identity in out1 and isinstance(identity, str):
1112
+ if (
1113
+ not a.axis_in_data_axes
1114
+ and identity in out1
1115
+ and isinstance(identity, str)
1116
+ ):
1102
1117
y = out1[identity]
1103
1118
if y.size > 1:
1104
1119
# Put missing axis into data axes
1105
1120
field0.insert_dimension(a.axis, position=0, inplace=True)
1106
1121
missing_axis_inserted = True
1107
1122
1108
- if not missing_axis_inserted and not a.axis_in_data_axes:
1123
+ if not missing_axis_inserted and not a.axis_in_data_axes:
1109
1124
del out0[identity]
1110
1125
1111
1126
squeeze1 = []
@@ -1116,15 +1131,14 @@ def _binary_operation(self, other, method):
1116
1131
axes_added_from_field1 = []
1117
1132
1118
1133
# Dictionary of size > 1 axes from field1 which will replace
1119
- # matching size 1 axes in field0. E.g. {'domainaxis1':
1120
- # data_dimension(
1121
- # size=8,
1122
- # axis='domainaxis1',
1123
- # key='dimensioncoordinate1',
1124
- # coord=<CF DimensionCoordinate: longitude(8) degrees_east>,
1125
- # coord_type='dimension_coordinate',
1126
- # scalar=False
1127
- # )
1134
+ # matching size 1 axes in field0.
1135
+ #
1136
+ # E.g. {'domainaxis1': _Axis_characterisation(
1137
+ # size=8,
1138
+ # axis='domainaxis1',
1139
+ # keys=('dimensioncoordinate1',),
1140
+ # coords=(CF DimensionCoordinate: longitude(8) degrees_east>,),
1141
+ # axis_in_data_axes=True)
1128
1142
# }
1129
1143
axes_to_replace_from_field1 = {}
1130
1144
@@ -1225,33 +1239,39 @@ def _binary_operation(self, other, method):
1225
1239
f"{a.size} {identity!r} axis"
1226
1240
)
1227
1241
1228
- for a_key, a_coord, y_key, y_coord in zip(a.keys, a.coords, y.keys, y.coords):
1242
+ for a_key, a_coord, y_key, y_coord in zip(
1243
+ a.keys, a.coords, y.keys, y.coords
1244
+ ):
1229
1245
# Ensure matching axis directions
1230
1246
if y_coord.direction() != a_coord.direction():
1231
1247
other.flip(y.axis, inplace=True)
1232
-
1248
+
1233
1249
# Check for matching coordinate values
1234
1250
if not y_coord._equivalent_data(a_coord):
1235
1251
raise ValueError(
1236
1252
f"Can't combine size {y.size} {identity!r} axes with "
1237
1253
f"non-matching coordinate values"
1238
1254
)
1239
-
1255
+
1240
1256
# Check coord refs
1241
- refs1 = field1.get_coordinate_reference(construct=y_key, key=True)
1242
- refs0 = field0.get_coordinate_reference(construct=a_key, key=True)
1243
-
1257
+ refs1 = field1.get_coordinate_reference(
1258
+ construct=y_key, key=True
1259
+ )
1260
+ refs0 = field0.get_coordinate_reference(
1261
+ construct=a_key, key=True
1262
+ )
1263
+
1244
1264
n_refs = len(refs1)
1245
1265
n0_refs = len(refs0)
1246
-
1266
+
1247
1267
if n_refs != n0_refs:
1248
1268
raise ValueError(
1249
1269
f"Can't combine {self.__class__.__name__!r} with "
1250
1270
f"{other.__class__.__name__!r} because the coordinate "
1251
1271
f"references have different lengths: {n_refs} and "
1252
1272
f"{n0_refs}."
1253
1273
)
1254
-
1274
+
1255
1275
n_equivalent_refs = 0
1256
1276
for ref1 in refs1:
1257
1277
for ref0 in refs0[:]:
@@ -1261,12 +1281,12 @@ def _binary_operation(self, other, method):
1261
1281
n_equivalent_refs += 1
1262
1282
refs0.remove(ref0)
1263
1283
break
1264
-
1284
+
1265
1285
if n_equivalent_refs != n_refs:
1266
1286
raise ValueError(
1267
1287
f"Can't combine {self.__class__.__name__!r} with "
1268
- f"{other.__class__.__name__!r} because the fields have "
1269
- "incompatible coordinate references."
1288
+ f"{other.__class__.__name__!r} because the fields "
1289
+ "have incompatible coordinate references."
1270
1290
)
1271
1291
1272
1292
# Change the domain axis sizes in field0 so that they match
@@ -1473,12 +1493,7 @@ def _conform_cell_methods(self):
1473
1493
axis_map = {}
1474
1494
1475
1495
for cm in self.cell_methods(todict=True).values():
1476
-
1477
- axes = cm.get_axis_identities(None)
1478
- if axes is None:
1479
- axes = cm.get_axes(None)
1480
-
1481
- for axis in axes:
1496
+ for axis in cm.get_axes(()):
1482
1497
if axis in axis_map:
1483
1498
continue
1484
1499
0 commit comments