Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Changelog.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
Version NEXTVERSION
--------------

**2026-10-??**

* Fix problems creating CFA variables with `cf.write` from
aggregation-promoted field ancillaries
(https://github.com/NCAS-CMS/cf-python/issues/971)

----

Version 3.21.0
--------------

Expand Down
48 changes: 14 additions & 34 deletions cf/aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3845,41 +3845,13 @@ def _create_hash_and_first_values(

# ------------------------------------------------------------
# Field ancillaries
#
# No need to calculate actual hash values for field
# ancillaries (which can be very slow), because they always
# get concatenated regardless of their values.
# ------------------------------------------------------------
if donotchecknonaggregatingaxes:
for anc in m.field_anc.values():
anc["hash_value"] = (None,)
else:
for anc in m.field_anc.values():
key = anc["key"]
canonical_units = anc["units"]

field_anc = constructs[key]

c_axes = anc["axes"]
canonical_axes = anc["canonical_axes"]
if c_axes != canonical_axes:
# Transpose the field ancillary so that it has the
# canonical axis order
iaxes = [c_axes.index(axis) for axis in canonical_axes]
field_anc = field_anc.transpose(iaxes)

sort_indices, needs_sorting = _sort_indices(m, canonical_axes)

# Get the hash of the data array
h = _get_hfl(
field_anc,
canonical_units,
sort_indices,
needs_sorting,
False,
False,
hfl_cache,
rtol,
atol,
)

anc["hash_value"] = (h,)
for anc in m.field_anc.values():
anc["hash_value"] = (None,)

# ------------------------------------------------------------
# Domain ancillaries
Expand Down Expand Up @@ -5048,6 +5020,14 @@ def _fix_promoted_field_ancillaries(output_meta, axes_aggregated):
fa.squeeze(squeeze, inplace=True)
fa_axes = [a for i, a in enumerate(fa_axes) if i not in squeeze]

# Note that this field ancillary can be written as a
# CF-netCDF aggregation variable. (The previous indexing
# and squeezing will likely have set the aggregation write
# status to False, but we know that promoted field
# ancillaries, which only contain a single broadcast
# value, are safe in this regard.)
fa.data._nc_set_aggregation_write_status(True)

# Record the field ancillary as being able to be written
# as a CF-netCDF aggregation 'value' variable
fa.data._nc_set_aggregation_fragment_type("value")
Expand Down
7 changes: 5 additions & 2 deletions cf/test/test_CFA.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def test_CFA_constructs(self):
f = cf.read(tmpfile1, cfa_write="all")[0]

# No constructs
cf.write(f, tmpfile2, cfa={"constructs": []})
cf.write(f, tmpfile2, cfa=[])
nc = netCDF4.Dataset(tmpfile2, "r")
for var in nc.variables.values():
attrs = var.ncattrs()
Expand Down Expand Up @@ -311,6 +311,9 @@ def test_CFA_cfa(self):
cf.write(f, tmpfile2, cfa="field")
g = cf.read(tmpfile2, cfa_write="field")[0]

cf.write(f, tmpfile2, cfa=["field"])
g = cf.read(tmpfile2, cfa_write="field")[0]

# Default of cfa="auto" - check that aggregation variable
# gets written
cf.write(g, cfa_file)
Expand Down Expand Up @@ -345,7 +348,7 @@ def test_CFA_cfa(self):
nc.close()

# Check bad values of cfa
for cfa in (False, True, (), []):
for cfa in (False, True):
with self.assertRaises(ValueError):
cf.write(g, cfa_file, cfa=cfa)

Expand Down
11 changes: 11 additions & 0 deletions cf/test/test_aggregate.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import datetime
import faulthandler
import os
import tempfile
import unittest
import warnings

Expand All @@ -10,6 +11,14 @@

import cf

n_tmpfiles = 1
tmpfiles = [
tempfile.mkstemp("_test_aggregate.nc", dir=os.getcwd())[1]
for i in range(n_tmpfiles)
]
[tmpfile] = tmpfiles


# To facilitate the testing of logging outputs (see test_aggregate_verbosity)
log_name = __name__
logger = cf.logging.getLogger(log_name)
Expand Down Expand Up @@ -345,6 +354,8 @@ def test_aggregate_promote_field_ancillaries(self):
self.assertTrue((anc[:2] == "bar_a").all())
self.assertTrue((anc[2:] == "bar_b").all())

cf.write(c, tmpfile, cfa="field_ancillary")

def test_aggregate_cells(self):
"""Test the 'cells' keyword of cf.aggregate"""
f = cf.example_field(0)
Expand Down
Loading