@@ -426,6 +426,7 @@ def __init__(
426
426
# Promote selected properties to field ancillaries that span
427
427
# the same domain axes as the field
428
428
# ------------------------------------------------------------
429
+ self .promoted_field_ancillaries = []
429
430
if field_ancillaries :
430
431
f = self .promote_to_field_ancillary (field_ancillaries )
431
432
@@ -2088,13 +2089,6 @@ def promote_to_field_ancillary(self, properties):
2088
2089
ancillary construct that spans the entire domain, with the
2089
2090
constant value of the property.
2090
2091
2091
- The `Data` of any new field ancillary construct is marked
2092
- as a CFA term, meaning that it will only be written to disk if
2093
- the parent field construct is written as a CFA aggregation
2094
- variable, and in that case the field ancillary is written as a
2095
- non-standard CFA aggregation instruction variable, rather than
2096
- a CF-netCDF ancillary variable.
2097
-
2098
2092
If a domain construct is being aggregated then it is always
2099
2093
returned unchanged.
2100
2094
@@ -2125,7 +2119,6 @@ def promote_to_field_ancillary(self, properties):
2125
2119
data = Data (
2126
2120
FullArray (value , shape = f .shape , dtype = np .array (value ).dtype )
2127
2121
)
2128
- data ._cfa_set_term (True )
2129
2122
2130
2123
field_anc = FieldAncillary (
2131
2124
data = data , properties = {"long_name" : prop }, copy = False
@@ -2137,9 +2130,15 @@ def promote_to_field_ancillary(self, properties):
2137
2130
f = f .copy ()
2138
2131
copy = False
2139
2132
2140
- f .set_construct (field_anc , axes = f .get_data_axes (), copy = False )
2133
+ key = f .set_construct (
2134
+ field_anc , axes = f .get_data_axes (), copy = False
2135
+ )
2141
2136
f .del_property (prop )
2142
2137
2138
+ # Record that this field ancillary is derived from a
2139
+ # promotion
2140
+ self .promoted_field_ancillaries .append (key )
2141
+
2143
2142
self .field = f
2144
2143
return f
2145
2144
@@ -2434,9 +2433,9 @@ def aggregate(
2434
2433
Create new field ancillary constructs for each input field
2435
2434
which has one or more of the given properties. For each
2436
2435
input field, each property is converted to a field
2437
- ancillary construct that spans the entire domain, with the
2438
- constant value of the property, and the property itself is
2439
- deleted.
2436
+ ancillary construct that spans the aggregation axes with
2437
+ the constant value of the property, and the property
2438
+ itself is deleted.
2440
2439
2441
2440
.. versionadded:: 3.15.0
2442
2441
@@ -3039,6 +3038,9 @@ def aggregate(
3039
3038
3040
3039
unaggregatable = False
3041
3040
3041
+ # Record the names of the axes that are actually aggregated
3042
+ axes_aggregated = []
3043
+
3042
3044
for axis in aggregating_axes :
3043
3045
number_of_fields = len (meta )
3044
3046
if number_of_fields == 1 :
@@ -3251,6 +3253,7 @@ def aggregate(
3251
3253
# the aggregated fields as a single list ready for
3252
3254
# aggregation along the next axis.
3253
3255
# --------------------------------------------------------
3256
+ axes_aggregated .append (axis )
3254
3257
meta = [m for gm in grouped_meta for m in gm ]
3255
3258
3256
3259
# Add fields to the output list
@@ -3267,6 +3270,10 @@ def aggregate(
3267
3270
if cells :
3268
3271
_set_cell_conditions (output_meta )
3269
3272
3273
+ # Remove non-aggregated axes from promoted field ancillaries
3274
+ if field_ancillaries :
3275
+ _fix_promoted_field_ancillaries (output_meta , axes_aggregated )
3276
+
3270
3277
output_constructs = [m .field for m in output_meta ]
3271
3278
3272
3279
aggregate .status = status
@@ -4724,6 +4731,14 @@ def _aggregate_2_fields(
4724
4731
hash_value1 = anc1 ["hash_value" ]
4725
4732
anc0 ["hash_value" ] = hash_value0 + hash_value1
4726
4733
4734
+ # The result of aggregating a promoted amd non-promoted
4735
+ # field ancillary is a non-promoted field ancillary
4736
+ if (
4737
+ key0 in m0 .promoted_field_ancillaries
4738
+ and key1 not in m1 .promoted_field_ancillaries
4739
+ ):
4740
+ m0 .promoted_field_ancillaries .remove (key0 )
4741
+
4727
4742
# Domain ancillaries
4728
4743
for identity in m0 .domain_anc :
4729
4744
anc0 = m0 .domain_anc [identity ]
@@ -4745,9 +4760,9 @@ def _aggregate_2_fields(
4745
4760
anc0 ["hash_value" ] = hash_value0 + hash_value1
4746
4761
4747
4762
# ----------------------------------------------------------------
4748
- # For each matching pair of coordinates, cell measures, field and
4749
- # domain ancillaries which span the aggregating axis, insert the
4750
- # one from parent1 into the one from parent0
4763
+ # For each matching pair of coordinates, cell measures, and field
4764
+ # and domain ancillaries which span the aggregating axis, insert
4765
+ # the one from parent1 into the one from parent0
4751
4766
# ----------------------------------------------------------------
4752
4767
for key0 , key1 , construct0 , construct1 in spanning_variables :
4753
4768
construct_axes0 = parent0 .get_data_axes (key0 )
@@ -4909,7 +4924,7 @@ def _aggregate_2_fields(
4909
4924
actual_range = parent0 .del_property ("actual_range" , None )
4910
4925
if actual_range is not None and is_log_level_info (logger ):
4911
4926
logger .info (
4912
- "Deleted 'actual_range' attribute due to being "
4927
+ "Deleted 'actual_range' attribute due to it being "
4913
4928
"outside of 'valid_range' attribute limits."
4914
4929
)
4915
4930
@@ -4919,7 +4934,6 @@ def _aggregate_2_fields(
4919
4934
4920
4935
# Make a note that the parent construct in this _Meta object has
4921
4936
# already been aggregated
4922
-
4923
4937
m0 .aggregated_field = True
4924
4938
4925
4939
# ----------------------------------------------------------------
@@ -4986,3 +5000,53 @@ def dsg_feature_type_axis(meta, axis):
4986
5000
# cf_role property
4987
5001
cf_role = coords ["cf_role" ]
4988
5002
return cf_role .count (None ) != len (cf_role )
5003
+
5004
+
5005
+ def _fix_promoted_field_ancillaries (output_meta , axes_aggregated ):
5006
+ """Remove non-aggregated axes from promoted field ancillaries.
5007
+
5008
+ .. versionadded:: NEXTVERSION
5009
+
5010
+ :Parameters:
5011
+
5012
+ output_meta: `list`
5013
+ The list of `_Meta` objects. If any include promoted field
5014
+ ancillaries then these will be updated in-place.
5015
+
5016
+ :Returns:
5017
+
5018
+ `None`
5019
+
5020
+ """
5021
+ for m in output_meta :
5022
+ for value in m .field_anc .values ():
5023
+ index = []
5024
+ squeeze = []
5025
+
5026
+ key = value ["key" ]
5027
+ if key not in m .promoted_field_ancillaries :
5028
+ continue
5029
+
5030
+ # Remove the non-aggregated axes from the promoted field
5031
+ # ancillary
5032
+ for i , axis in enumerate (value ["axes" ]):
5033
+ if axis in axes_aggregated :
5034
+ index .append (slice (None ))
5035
+ else :
5036
+ index .append (0 )
5037
+ squeeze .append (i )
5038
+
5039
+ if not squeeze :
5040
+ continue
5041
+
5042
+ fa_axes = m .field .get_data_axes (key )
5043
+ fa = m .field .del_construct (key )
5044
+ fa = fa [tuple (index )]
5045
+ fa .squeeze (squeeze , inplace = True )
5046
+ fa_axes = [a for i , a in enumerate (fa_axes ) if i not in squeeze ]
5047
+
5048
+ # Record the field ancillary as being able to be written
5049
+ # as a CF-netCDF aggregation 'value' variable
5050
+ fa .data ._nc_set_aggregation_fragment_type ("value" )
5051
+
5052
+ m .field .set_construct (fa , axes = fa_axes , copy = False )
0 commit comments