1
1
import logging
2
- from collections import namedtuple
2
+ from dataclasses import dataclass
3
3
from functools import reduce
4
4
from operator import mul as operator_mul
5
5
from os import sep
190
190
"__ge__",
191
191
)
192
192
193
- _xxx = namedtuple(
194
- "data_dimension", ["size", "axis", "key", "coord", "coord_type", "scalar"]
195
- )
196
193
197
- # _empty_set = set()
194
+ @dataclass()
195
+ class _Axis_characterisation:
196
+ """Characterise a domain axis.
197
+
198
+ Used by `_binary_operation` to help with ascertaining if there is
199
+ a common axis in two fields.
200
+
201
+ .. versionaddedd:: NEXTVERSION
202
+
203
+ """
204
+
205
+ # The size of the axis, an integer.
206
+ size: int = -1
207
+ # The domain axis identifier. E.g. 'domainaxis0'
208
+ axis: str = ""
209
+ # The coordinate constructs that characterize the axis
210
+ coords: tuple = ()
211
+ # The identifiers of the coordinate
212
+ # constructs. E.g. ('dimensioncoordinate1',)
213
+ keys: tuple = ()
214
+ # Whether or not the axis is spanned by the field's data array
215
+ axis_in_data_axes: bool = True
198
216
199
217
200
218
class Field(mixin.FieldDomain, mixin.PropertiesData, cfdm.Field):
@@ -985,80 +1003,127 @@ def _binary_operation(self, other, method):
985
1003
data_axes = f.get_data_axes()
986
1004
for axis in f.domain_axes(todict=True):
987
1005
identity = None
988
- key = None
989
- coord = None
990
- coord_type = None
991
1006
992
- key, coord = f.dimension_coordinate(
993
- item=True, default=(None, None), filter_by_axis=(axis,)
994
- )
995
- if coord is not None:
996
- # This axis of the domain has a dimension
997
- # coordinate
998
- identity = coord.identity(strict=True, default=None)
999
- if identity is None:
1000
- # Dimension coordinate has no identity, but it
1001
- # may have a recognised axis.
1002
- for ctype in ("T", "X", "Y", "Z"):
1003
- if getattr(coord, ctype, False):
1004
- identity = ctype
1005
- break
1006
-
1007
- if identity is None and relaxed_identities:
1008
- identity = coord.identity(relaxed=True, default=None)
1009
- else:
1010
- key, coord = f.auxiliary_coordinate(
1011
- item=True,
1012
- default=(None, None),
1007
+ if self.is_discrete_axis(axis):
1008
+ # This is a discrete axis whose identity is
1009
+ # inferred from all of its auxiliary coordinates
1010
+ x = {}
1011
+ for key, aux_coord in f.auxiliary_coordinates(
1013
1012
filter_by_axis=(axis,),
1014
- axis_mode="exact",
1013
+ axis_mode="and",
1014
+ todict=True,
1015
+ ).items():
1016
+ identity = aux_coord.identity(
1017
+ strict=True, default=None
1018
+ )
1019
+ if identity is None and relaxed_identities:
1020
+ identity = aux_coord.identity(
1021
+ relaxed=True, default=None
1022
+ )
1023
+ if identity is not None:
1024
+ x[identity] = key
1025
+
1026
+ if x:
1027
+ # Get the sorted identities (sorted so that
1028
+ # they're comparable between fields) and their
1029
+ # corresponding keys.
1030
+ #
1031
+ # E.g. {2:3, 4:6, 1:7} -> (1, 2, 4), (7, 3, 6)
1032
+ identity, keys = tuple(zip(*sorted(x.items())))
1033
+ coords = tuple(
1034
+ f.auxiliary_coordinate(key) for key in keys
1035
+ )
1036
+ else:
1037
+ identity = None
1038
+ keys = ()
1039
+ coords = ()
1040
+ else:
1041
+ key, dim_coord = f.dimension_coordinate(
1042
+ item=True, default=(None, None), filter_by_axis=(axis,)
1015
1043
)
1016
- if coord is not None:
1017
- # This axis of the domain does not have a
1018
- # dimension coordinate but it does have
1019
- # exactly one 1-d auxiliary coordinate, so
1020
- # that will do.
1021
- identity = coord.identity(strict=True, default=None)
1044
+ if dim_coord is not None:
1045
+ # This non-discrete axis has a dimension
1046
+ # coordinate
1047
+ identity = dim_coord.identity(
1048
+ strict=True, default=None
1049
+ )
1050
+ if identity is None:
1051
+ # Dimension coordinate has no identity,
1052
+ # but it may have a recognised axis.
1053
+ for ctype in ("T", "X", "Y", "Z"):
1054
+ if getattr(dim_coord, ctype, False):
1055
+ identity = ctype
1056
+ break
1022
1057
1023
1058
if identity is None and relaxed_identities:
1024
- identity = coord .identity(
1059
+ identity = dim_coord .identity(
1025
1060
relaxed=True, default=None
1026
1061
)
1027
1062
1063
+ keys = (key,)
1064
+ coords = (dim_coord,)
1065
+ else:
1066
+ key, aux_coord = f.auxiliary_coordinate(
1067
+ item=True,
1068
+ default=(None, None),
1069
+ filter_by_axis=(axis,),
1070
+ axis_mode="exact",
1071
+ )
1072
+ if aux_coord is not None:
1073
+ # This non-discrete axis does not have a
1074
+ # dimension coordinate but it does have
1075
+ # exactly one 1-d auxiliary coordinate, so
1076
+ # that will do.
1077
+ coords = (aux_coord,)
1078
+ identity = aux_coord.identity(
1079
+ strict=True, default=None
1080
+ )
1081
+ if identity is None and relaxed_identities:
1082
+ identity = aux_coord.identity(
1083
+ relaxed=True, default=None
1084
+ )
1085
+
1028
1086
if identity is None:
1029
1087
identity = i
1030
- else:
1031
- coord_type = coord.construct_type
1032
1088
1033
- out[identity] = _xxx (
1089
+ out[identity] = _Axis_characterisation (
1034
1090
size=f.domain_axis(axis).get_size(),
1035
1091
axis=axis,
1036
- key=key,
1037
- coord=coord,
1038
- coord_type=coord_type,
1039
- scalar=(axis not in data_axes),
1092
+ keys=keys,
1093
+ coords=coords,
1094
+ axis_in_data_axes=axis in data_axes,
1040
1095
)
1041
1096
1042
1097
for identity, y in tuple(out1.items()):
1043
- asdas = True
1044
- if y.scalar and identity in out0 and isinstance(identity, str):
1098
+ missing_axis_inserted = False
1099
+ if (
1100
+ not y.axis_in_data_axes
1101
+ and identity in out0
1102
+ and isinstance(identity, str)
1103
+ ):
1045
1104
a = out0[identity]
1046
1105
if a.size > 1:
1106
+ # Put missing axis into data axes
1047
1107
field1.insert_dimension(y.axis, position=0, inplace=True)
1048
- asdas = False
1108
+ missing_axis_inserted = True
1049
1109
1050
- if y.scalar and asdas :
1110
+ if not missing_axis_inserted and not y.axis_in_data_axes :
1051
1111
del out1[identity]
1052
1112
1053
1113
for identity, a in tuple(out0.items()):
1054
- asdas = True
1055
- if a.scalar and identity in out1 and isinstance(identity, str):
1114
+ missing_axis_inserted = False
1115
+ if (
1116
+ not a.axis_in_data_axes
1117
+ and identity in out1
1118
+ and isinstance(identity, str)
1119
+ ):
1056
1120
y = out1[identity]
1057
1121
if y.size > 1:
1122
+ # Put missing axis into data axes
1058
1123
field0.insert_dimension(a.axis, position=0, inplace=True)
1059
- asdas = False
1124
+ missing_axis_inserted = True
1060
1125
1061
- if a.scalar and asdas :
1126
+ if not missing_axis_inserted and not a.axis_in_data_axes :
1062
1127
del out0[identity]
1063
1128
1064
1129
squeeze1 = []
@@ -1069,15 +1134,14 @@ def _binary_operation(self, other, method):
1069
1134
axes_added_from_field1 = []
1070
1135
1071
1136
# Dictionary of size > 1 axes from field1 which will replace
1072
- # matching size 1 axes in field0. E.g. {'domainaxis1':
1073
- # data_dimension(
1074
- # size=8,
1075
- # axis='domainaxis1',
1076
- # key='dimensioncoordinate1',
1077
- # coord=<CF DimensionCoordinate: longitude(8) degrees_east>,
1078
- # coord_type='dimension_coordinate',
1079
- # scalar=False
1080
- # )
1137
+ # matching size 1 axes in field0.
1138
+ #
1139
+ # E.g. {'domainaxis1': _Axis_characterisation(
1140
+ # size=8,
1141
+ # axis='domainaxis1',
1142
+ # keys=('dimensioncoordinate1',),
1143
+ # coords=(CF DimensionCoordinate: longitude(8) degrees_east>,),
1144
+ # axis_in_data_axes=True)
1081
1145
# }
1082
1146
axes_to_replace_from_field1 = {}
1083
1147
@@ -1178,48 +1242,55 @@ def _binary_operation(self, other, method):
1178
1242
f"{a.size} {identity!r} axis"
1179
1243
)
1180
1244
1181
- # Ensure matching axis directions
1182
- if y.coord.direction() != a.coord.direction():
1183
- other.flip(y.axis, inplace=True)
1245
+ for a_key, a_coord, y_key, y_coord in zip(
1246
+ a.keys, a.coords, y.keys, y.coords
1247
+ ):
1248
+ # Ensure matching axis directions
1249
+ if y_coord.direction() != a_coord.direction():
1250
+ other.flip(y.axis, inplace=True)
1184
1251
1185
- # Check for matching coordinate values
1186
- if not y.coord. _equivalent_data(a.coord ):
1187
- raise ValueError(
1188
- f"Can't combine size {y.size} {identity!r} axes with "
1189
- f"non-matching coordinate values"
1190
- )
1252
+ # Check for matching coordinate values
1253
+ if not y_coord. _equivalent_data(a_coord ):
1254
+ raise ValueError(
1255
+ f"Can't combine size {y.size} {identity!r} axes with "
1256
+ f"non-matching coordinate values"
1257
+ )
1191
1258
1192
- # Check coord refs
1193
- refs1 = field1.get_coordinate_reference(construct=y.key, key=True)
1194
- refs0 = field0.get_coordinate_reference(construct=a.key, key=True)
1259
+ # Check coord refs
1260
+ refs1 = field1.get_coordinate_reference(
1261
+ construct=y_key, key=True
1262
+ )
1263
+ refs0 = field0.get_coordinate_reference(
1264
+ construct=a_key, key=True
1265
+ )
1195
1266
1196
- n_refs = len(refs1)
1197
- n0_refs = len(refs0)
1267
+ n_refs = len(refs1)
1268
+ n0_refs = len(refs0)
1198
1269
1199
- if n_refs != n0_refs:
1200
- raise ValueError(
1201
- f"Can't combine {self.__class__.__name__!r} with "
1202
- f"{other.__class__.__name__!r} because the coordinate "
1203
- f"references have different lengths: {n_refs} and "
1204
- f"{n0_refs}."
1205
- )
1270
+ if n_refs != n0_refs:
1271
+ raise ValueError(
1272
+ f"Can't combine {self.__class__.__name__!r} with "
1273
+ f"{other.__class__.__name__!r} because the coordinate "
1274
+ f"references have different lengths: {n_refs} and "
1275
+ f"{n0_refs}."
1276
+ )
1206
1277
1207
- n_equivalent_refs = 0
1208
- for ref1 in refs1:
1209
- for ref0 in refs0[:]:
1210
- if field1._equivalent_coordinate_references(
1211
- field0, key0=ref1, key1=ref0, axis_map=axis_map
1212
- ):
1213
- n_equivalent_refs += 1
1214
- refs0.remove(ref0)
1215
- break
1278
+ n_equivalent_refs = 0
1279
+ for ref1 in refs1:
1280
+ for ref0 in refs0[:]:
1281
+ if field1._equivalent_coordinate_references(
1282
+ field0, key0=ref1, key1=ref0, axis_map=axis_map
1283
+ ):
1284
+ n_equivalent_refs += 1
1285
+ refs0.remove(ref0)
1286
+ break
1216
1287
1217
- if n_equivalent_refs != n_refs:
1218
- raise ValueError(
1219
- f"Can't combine {self.__class__.__name__!r} with "
1220
- f"{other.__class__.__name__!r} because the fields have "
1221
- " incompatible coordinate references."
1222
- )
1288
+ if n_equivalent_refs != n_refs:
1289
+ raise ValueError(
1290
+ f"Can't combine {self.__class__.__name__!r} with "
1291
+ f"{other.__class__.__name__!r} because the fields "
1292
+ "have incompatible coordinate references."
1293
+ )
1223
1294
1224
1295
# Change the domain axis sizes in field0 so that they match
1225
1296
# the broadcasted result data
0 commit comments