Skip to content

Commit e3bc06c

Browse files
authored
Merge pull request #842 from davidhassell/cfa-in-cfdm
Import CFA from cfdm
2 parents fbabb83 + 55be346 commit e3bc06c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+1444
-10341
lines changed

Changelog.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
version 3.17.0
22
--------------
33

4-
**2025-02-??**
4+
**2025-??-??**
55

6+
* Replace dataset aggregation functionality (CFA) with that imported
7+
from `cfdm` (https://github.com/NCAS-CMS/cf-python/issues/841)
68
* New keyword parameter to `cf.Field.compute_vertical_coordinates`:
79
``key`` (https://github.com/NCAS-CMS/cf-python/issues/802)
10+
* Changed dependency: ``1.12.0.0<=cfdm<1.12.1.0``
11+
* Changed dependency: ``h5py>=3.12.0``
12+
13+
----
814

915

1016
version 3.16.3

cf/__init__.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@
105105
raise ImportError(_error0 + str(error1))
106106

107107
__cf_version__ = cfdm.core.__cf_version__
108-
__cfa_version__ = "0.6.2"
109108

110109
from packaging.version import Version
111110
import importlib.util
@@ -207,13 +206,10 @@
207206
)
208207

209208
# Check the version of cfdm
210-
_minimum_vn = "1.11.2.0"
211-
_maximum_vn = "1.11.3.0"
212-
if (
213-
not Version(_minimum_vn)
214-
<= Version(cfdm.__version__)
215-
< Version(_maximum_vn)
216-
):
209+
_minimum_vn = "1.12.0.0"
210+
_maximum_vn = "1.12.1.0"
211+
_cfdm_version = Version(cfdm.__version__)
212+
if not Version(_minimum_vn) <= _cfdm_version < Version(_maximum_vn):
217213
raise RuntimeError(
218214
f"Bad cfdm version: cf requires {_minimum_vn}<=cfdm<{_maximum_vn}. "
219215
f"Got {cfdm.__version__} at {cfdm.__file__}"
@@ -291,10 +287,9 @@
291287
from .field import Field
292288
from .data import Data
293289
from .data.array import (
290+
AggregatedArray,
294291
BoundsFromNodesArray,
295292
CellConnectivityArray,
296-
CFAH5netcdfArray,
297-
CFANetCDF4Array,
298293
FullArray,
299294
GatheredArray,
300295
H5netcdfArray,
@@ -308,12 +303,6 @@
308303
UMArray,
309304
)
310305

311-
from .data.fragment import (
312-
FullFragmentArray,
313-
NetCDFFragmentArray,
314-
UMFragmentArray,
315-
)
316-
317306
from .aggregate import aggregate, climatology_cells
318307
from .query import (
319308
Query,

cf/aggregate.py

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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)

cf/cfimplementation.py

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,9 @@
2727
)
2828
from .data import Data
2929
from .data.array import (
30+
AggregatedArray,
3031
BoundsFromNodesArray,
3132
CellConnectivityArray,
32-
CFAH5netcdfArray,
33-
CFANetCDF4Array,
3433
GatheredArray,
3534
H5netcdfArray,
3635
NetCDF4Array,
@@ -114,49 +113,14 @@ def set_construct(self, parent, construct, axes=None, copy=True, **kwargs):
114113
parent, construct, axes=axes, copy=copy, **kwargs
115114
)
116115

117-
def initialise_CFANetCDF4Array(self, **kwargs):
118-
"""Return a `CFANetCDF4Array` instance.
119-
120-
:Parameters:
121-
122-
kwargs: optional
123-
Initialisation parameters to pass to the new instance.
124-
125-
:Returns:
126-
127-
`CFANetCDF4Array`
128-
129-
"""
130-
cls = self.get_class("CFANetCDF4Array")
131-
return cls(**kwargs)
132-
133-
def initialise_CFAH5netcdfArray(self, **kwargs):
134-
"""Return a `CFAH5netcdfArray` instance.
135-
136-
.. versionadded:: 1.11.2.0
137-
138-
:Parameters:
139-
140-
kwargs: optional
141-
Initialisation parameters to pass to the new instance.
142-
143-
:Returns:
144-
145-
`CFAH5netcdfArray`
146-
147-
"""
148-
cls = self.get_class("CFAH5netcdfArray")
149-
return cls(**kwargs)
150-
151116

152117
_implementation = CFImplementation(
153118
cf_version=CF(),
119+
AggregatedArray=AggregatedArray,
154120
AuxiliaryCoordinate=AuxiliaryCoordinate,
155121
CellConnectivity=CellConnectivity,
156122
CellMeasure=CellMeasure,
157123
CellMethod=CellMethod,
158-
CFAH5netcdfArray=CFAH5netcdfArray,
159-
CFANetCDF4Array=CFANetCDF4Array,
160124
CoordinateReference=CoordinateReference,
161125
DimensionCoordinate=DimensionCoordinate,
162126
Domain=Domain,
@@ -214,8 +178,6 @@ def implementation():
214178
'CellConnectivityArray': cf.data.array.cellconnectivityarray.CellConnectivityArray,
215179
'CellMeasure': cf.cellmeasure.CellMeasure,
216180
'CellMethod': cf.cellmethod.CellMethod,
217-
'CFAH5netcdfArray': cf.data.array.cfah5netcdfarray.CFAH5netcdfArray,
218-
'CFANetCDF4Array': cf.data.array.cfanetcdf4array.CFANetCDF4Array,
219181
'CoordinateReference': cf.coordinatereference.CoordinateReference,
220182
'DimensionCoordinate': cf.dimensioncoordinate.DimensionCoordinate,
221183
'Domain': cf.domain.Domain,

cf/data/array/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1+
from .aggregatedarray import AggregatedArray
12
from .boundsfromnodesarray import BoundsFromNodesArray
23
from .cellconnectivityarray import CellConnectivityArray
3-
from .cfah5netcdfarray import CFAH5netcdfArray
4-
from .cfanetcdf4array import CFANetCDF4Array
54
from .fullarray import FullArray
65
from .gatheredarray import GatheredArray
76
from .h5netcdfarray import H5netcdfArray

cf/data/array/abstract/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
from .array import Array
2-
from .filearray import FileArray

cf/data/array/abstract/array.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import cfdm
22

33
from ....mixin_container import Container
4-
from ..mixin import ArrayMixin
54

65

7-
class Array(ArrayMixin, Container, cfdm.Array):
6+
class Array(Container, cfdm.Array):
87
"""Abstract base class for a container of an underlying array.
98
109
The form of the array is defined by the initialisation parameters

0 commit comments

Comments
 (0)