Skip to content

cf.Field.collapse() fails when computing a volume mean where the volume cell measure is time-dependant #952

Description

@dlrhodson

Hi All,

If I try to compute a volume mean using cf.Field.collapse() where the cell measure is time dependant (sometimes true for ocean fields), cf.Field.collapse() throws an error.

mean=data.collapse('volume: mean',weights='volume')

Traceback (most recent call last):
  File "/gws/ssde/j25b/terrafirma/monitoring/references/cf_bug/./cf_collapse_test.py", line 7, in <module>
    mean=data.collapse('volume: mean',weights='volume')
  File "/home/users/dlrhodso/miniforge3/envs/cf20/lib/python3.14/site-packages/cf/decorators.py", line 71, in precede_with_kwarg_deprecation_check
    operation_method_result = operation_method(self, *args, **kwargs)
  File "/home/users/dlrhodso/miniforge3/envs/cf20/lib/python3.14/site-packages/cfdm/decorators.py", line 171, in verbose_override_wrapper
    return method_with_verbose_kwarg(*args, **kwargs)
  File "/home/users/dlrhodso/miniforge3/envs/cf20/lib/python3.14/site-packages/cf/field.py", line 7470, in collapse
    d_weights = f.weights(
        weights,
    ...<5 lines>...
        great_circle=great_circle,
    )
  File "/home/users/dlrhodso/miniforge3/envs/cf20/lib/python3.14/site-packages/cf/field.py", line 3488, in weights
    Weights.cell_measure(
    ~~~~~~~~~~~~~~~~~~~~^
        self,
        ^^^^^
    ...<4 lines>...
        auto=False,
        ^^^^^^^^^^^
    )
    ^
  File "/home/users/dlrhodso/miniforge3/envs/cf20/lib/python3.14/site-packages/cf/weights.py", line 1553, in cell_measure
    clm.squeeze(iaxes, inplace=True)
    ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/home/users/dlrhodso/miniforge3/envs/cf20/lib/python3.14/site-packages/cfdm/decorators.py", line 44, in inplace_wrapper
    processed_copy = operation_method(self, *args, **kwargs)
  File "/home/users/dlrhodso/miniforge3/envs/cf20/lib/python3.14/site-packages/cfdm/data/data.py", line 7178, in squeeze
    raise ValueError(
    ...<2 lines>...
    )
ValueError: Can't squeeze axis in position 1 from data with shape (1, 75, 332, 362): Axis size is greater than 1
cf.environment(paths=False)
Platform: Linux-5.14.0-611.54.1.el9_7.x86_64-x86_64-with-glibc2.34
Python: 3.14.4
packaging: 26.2
numpy: 2.4.3
cfdm.core: 1.13.1.0
udunits2 library: /home/users/dlrhodso/miniforge3/envs/cf20/lib/libudunits2.so.0
HDF5 library: 2.1.0
netcdf library: 4.10.0
netCDF4: 1.7.4
h5netcdf: 1.8.1
h5py: 3.16.0
pyfive: 1.1.2
zarr: 3.2.1
fsspec: 2026.4.0
scipy: 1.17.1
dask: 2026.3.0
distributed: 2026.3.0
cftime: 1.6.5
cfunits: 3.3.7
cfdm: 1.13.1.0
esmpy/ESMF: 8.9.1
psutil: 7.2.2
matplotlib: 3.10.9
activestorage: not available
cartopy: 0.25.0
cfplot: not available
cf: 3.20.0

This bug occurs due to a line in the Weights.cell_meaure() function. The line:
iaxes = [clm_axes0.index(axis) for axis in clm_axes]
is intended to select redundant axes to be squeezed, but instead selects the non-redundant axes. This part of the code is only ever reached if the cell_measure has at least one axis of size 1. This occurs if cell_measure is time dependant and we are trying to collapse a field with a single time point.
What this line should do is to select all the axes in clm_axes0 that are not in clm_axes. This line:
iaxes = [clm_axes0.index(axis) for axis in (set(clm_axes0)-set(clm_axes))]
does this. Replacing with this line fixes the bug.

Attached is a zip file with a demonstration of the bug.

cf_bug.zip

Activity

  1. beanscg commented on Jun 9, 2026

    @beanscg

    I checked current main, and the likely narrow spot is in the cell-measure axis reduction path:

    • cf/weights.py:1533-1537 builds clm_axes by keeping only cell-measure axes whose data shape is greater than 1.
    • When clm_axes != clm_axes0, cf/weights.py:1551-1553 computes iaxes from the retained axes and passes them to clm.squeeze(...).
    • For a time-dependent volume measure with a singleton time axis, that looks like it may try to squeeze the retained non-singleton axes rather than the singleton axis that was removed from clm_axes.
    • I did not see an open PR that appears to cover this issue.

    A focused regression could use a volume cell measure with shape like (1, z, y, x) and assert the singleton time axis is removed while the spatial axes remain in the weight key/data.

  2. davidhassell commented on Jun 12, 2026

    @davidhassell
    Collaborator

    Thanks for the report, @dlrhodson - I'm looking at your reproducer ...

  3. added
    bugSomething isn't working
    and removed on Jun 12, 2026
  4. added this to the NEXTVERSION milestone on Jun 12, 2026
  5. davidhassell commented on Jun 12, 2026

    @davidhassell
    Collaborator

    OK - it's bug, which I've fixed:

    In [2]: import cf
       ...: print(cf.__version__)
       ...: data=cf.read("test1.nc")[0]
       ...: print(data)
       ...: mean=data.collapse('volume: mean',weights='volume')
    3.20.0
    Field: sea_water_potential_temperature (ncvar%thetao)
    -----------------------------------------------------
    Data            : sea_water_potential_temperature(time(1), depth(5), Y(5), X(5)) degree_C
    Cell methods    : time(1): mean
    Dimension coords: time(1) = [1989-06-01 00:00:00] 360_day
                    : depth(5) = [0.5057600140571594, ..., 5.140361309051514] m
                    : Y(5) = [0, ..., 4]
                    : X(5) = [0, ..., 4]
    Auxiliary coords: latitude(Y(5), X(5)) = [[-84.21070861816406, ..., -83.79289245605469]] degrees_north
                    : longitude(Y(5), X(5)) = [[72.5, ..., 76.5]] degrees_east
    Cell measures   : measure:area(Y(5), X(5)) = [[16.0, ..., 16.0]] m2
                    : measure:volume(time(1), depth(5), Y(5), X(5)) = [[[[16.382505416870117, ..., 21.442960739135742]]]] m3
    
    In [3]: print(mean)
    Field: sea_water_potential_temperature (ncvar%thetao)
    -----------------------------------------------------
    Data            : sea_water_potential_temperature(time(1), depth(1), Y(1), X(1)) degree_C
    Cell methods    : time(1): mean depth(1): Y(1): X(1): mean
    Dimension coords: time(1) = [1989-06-01 00:00:00] 360_day
                    : depth(1) = [2.91261887550354] m
                    : Y(1) = [2.0]
                    : X(1) = [2.0]
    
    In [4]: data.weights('volume', methods=True)
    Out[4]: {(1, 2, 3): 'volume cell measure'}
    
    In [5]: data.weights('volume')
    Out[5]: <CF Field: long_name=weights(depth(5), Y(5), X(5)) 1>

    PR to follow, next release due in about 2 weeks (or you can pick it from main as soon as the PR is merged, etc)

    Or in verbose mode:

    In [5]: import cf
       ...: print(cf.__version__)
       ...: data=cf.read("test1.nc")[0]
       ...: mean=data.collapse('volume: mean',weights='volume', verbose=-1)
    3.20.0
    Can't create latitude and longitude coordinates: overwrite=False and latitude and/or longitude coordinates already exist: <CF AuxiliaryCoordinate: latitude(5, 5) degrees_north>, <CF AuxiliaryCoordinate: longitude(5, 5) degrees_east>
        all_methods, all_axes, all_within, all_over = ['mean'] [['domainaxis3', 'domainaxis2', 'domainaxis1']] [None] [None]
        axes                    = ['domainaxis3', 'domainaxis2', 'domainaxis1']
        method                  = mean
        collapse_axes_all_sizes = Constructs:
    {'domainaxis1': <CF DomainAxis: size(5)>,
     'domainaxis2': <CF DomainAxis: size(5)>,
     'domainaxis3': <CF DomainAxis: size(5)>}
        collapse_axes           = {'domainaxis1': <CF DomainAxis: size(5)>, 'domainaxis2': <CF DomainAxis: size(5)>, 'domainaxis3': <CF DomainAxis: size(5)>}
        collapse_axes_sizes     = [5, 5, 5]
        Input weights           = 'volume'
        Output weights          = {(1, 2, 3): <CF Data(5, 5, 5): [[[16.382505416870117, ..., 21.442960739135742]]] 1>}
      Before collapse of data:
        iaxes, d_kwargs = [1, 2, 3] {'weights': {(1, 2, 3): <CF Data(5, 5, 5): [[[16.382505416870117, ..., 21.442960739135742]]] 1>}}
        f.shape = (1, 5, 5, 5)
        f.dtype = float32
    
      After collapse of data:
        f.shape = (1, 1, 1, 1)
        f.dtype = float64
    collapse_axes = {'domainaxis1': <CF DomainAxis: size(5)>, 'domainaxis2': <CF DomainAxis: size(5)>, 'domainaxis3': <CF DomainAxis: size(5)>}
        Removing cell_measure
    Changing axis size to 1: domainaxis1
        Removing cell_measure
        Removing auxiliary_coordinate 'auxiliarycoordinate0'
        Removing auxiliary_coordinate 'auxiliarycoordinate1'
    Changing axis size to 1: domainaxis2
    Changing axis size to 1: domainaxis3
      Update cell methods:    Original cell methods = {'cellmethod0': <CF CellMethod: domainaxis0: mean>}    method        = 'mean'    within        = None    over          = None
        Modified cell methods = Constructs:
    {'cellmethod0': <CF CellMethod: domainaxis0: mean>,
     'cellmethod1': <CF CellMethod: domainaxis1: domainaxis2: domainaxis3: mean>}
    
    In [6]: print(mean)
    Field: sea_water_potential_temperature (ncvar%thetao)
    -----------------------------------------------------
    Data            : sea_water_potential_temperature(time(1), depth(1), Y(1), X(1)) degree_C
    Cell methods    : time(1): mean depth(1): Y(1): X(1): mean
    Dimension coords: time(1) = [1989-06-01 00:00:00] 360_day
                    : depth(1) = [2.91261887550354] m
                    : Y(1) = [2.0]
                    : X(1) = [2.0]
  6. davidhassell commented on Jun 12, 2026

    @davidhassell
    Collaborator

    @dlrhodson - is it OK if we add your test file to the cf-python test suite (and so will stored in this repo)?

  7. dlrhodson commented on Jun 17, 2026

    @dlrhodson
    Author

    Brilliant - thanks! Yes, please do add the test file to the test suite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions