@@ -426,6 +426,7 @@ def __init__(
426426 # Promote selected properties to field ancillaries that span
427427 # the same domain axes as the field
428428 # ------------------------------------------------------------
429+ self .promoted_field_ancillaries = []
429430 if field_ancillaries :
430431 f = self .promote_to_field_ancillary (field_ancillaries )
431432
@@ -2088,13 +2089,6 @@ def promote_to_field_ancillary(self, properties):
20882089 ancillary construct that spans the entire domain, with the
20892090 constant value of the property.
20902091
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-
20982092 If a domain construct is being aggregated then it is always
20992093 returned unchanged.
21002094
@@ -2125,7 +2119,6 @@ def promote_to_field_ancillary(self, properties):
21252119 data = Data (
21262120 FullArray (value , shape = f .shape , dtype = np .array (value ).dtype )
21272121 )
2128- data ._cfa_set_term (True )
21292122
21302123 field_anc = FieldAncillary (
21312124 data = data , properties = {"long_name" : prop }, copy = False
@@ -2137,9 +2130,15 @@ def promote_to_field_ancillary(self, properties):
21372130 f = f .copy ()
21382131 copy = False
21392132
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+ )
21412136 f .del_property (prop )
21422137
2138+ # Record that this field ancillary is derived from a
2139+ # promotion
2140+ self .promoted_field_ancillaries .append (key )
2141+
21432142 self .field = f
21442143 return f
21452144
@@ -2434,9 +2433,9 @@ def aggregate(
24342433 Create new field ancillary constructs for each input field
24352434 which has one or more of the given properties. For each
24362435 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.
24402439
24412440 .. versionadded:: 3.15.0
24422441
@@ -3039,6 +3038,9 @@ def aggregate(
30393038
30403039 unaggregatable = False
30413040
3041+ # Record the names of the axes that are actually aggregated
3042+ axes_aggregated = []
3043+
30423044 for axis in aggregating_axes :
30433045 number_of_fields = len (meta )
30443046 if number_of_fields == 1 :
@@ -3251,6 +3253,7 @@ def aggregate(
32513253 # the aggregated fields as a single list ready for
32523254 # aggregation along the next axis.
32533255 # --------------------------------------------------------
3256+ axes_aggregated .append (axis )
32543257 meta = [m for gm in grouped_meta for m in gm ]
32553258
32563259 # Add fields to the output list
@@ -3267,6 +3270,10 @@ def aggregate(
32673270 if cells :
32683271 _set_cell_conditions (output_meta )
32693272
3273+ # Remove non-aggregated axes from promoted field ancillaries
3274+ if field_ancillaries :
3275+ _fix_promoted_field_ancillaries (output_meta , axes_aggregated )
3276+
32703277 output_constructs = [m .field for m in output_meta ]
32713278
32723279 aggregate .status = status
@@ -4724,6 +4731,14 @@ def _aggregate_2_fields(
47244731 hash_value1 = anc1 ["hash_value" ]
47254732 anc0 ["hash_value" ] = hash_value0 + hash_value1
47264733
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+
47274742 # Domain ancillaries
47284743 for identity in m0 .domain_anc :
47294744 anc0 = m0 .domain_anc [identity ]
@@ -4745,9 +4760,9 @@ def _aggregate_2_fields(
47454760 anc0 ["hash_value" ] = hash_value0 + hash_value1
47464761
47474762 # ----------------------------------------------------------------
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
47514766 # ----------------------------------------------------------------
47524767 for key0 , key1 , construct0 , construct1 in spanning_variables :
47534768 construct_axes0 = parent0 .get_data_axes (key0 )
@@ -4909,7 +4924,7 @@ def _aggregate_2_fields(
49094924 actual_range = parent0 .del_property ("actual_range" , None )
49104925 if actual_range is not None and is_log_level_info (logger ):
49114926 logger .info (
4912- "Deleted 'actual_range' attribute due to being "
4927+ "Deleted 'actual_range' attribute due to it being "
49134928 "outside of 'valid_range' attribute limits."
49144929 )
49154930
@@ -4919,7 +4934,6 @@ def _aggregate_2_fields(
49194934
49204935 # Make a note that the parent construct in this _Meta object has
49214936 # already been aggregated
4922-
49234937 m0 .aggregated_field = True
49244938
49254939 # ----------------------------------------------------------------
@@ -4986,3 +5000,53 @@ def dsg_feature_type_axis(meta, axis):
49865000 # cf_role property
49875001 cf_role = coords ["cf_role" ]
49885002 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