diff --git a/Changelog.rst b/Changelog.rst index 9b1ce2931c..71cd3c52ae 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -9,6 +9,9 @@ Version NEXTVERSION * Allow multiple conditions for the same axis in `cf.Field.subspace` and `cf.Field.indices` (https://github.com/NCAS-CMS/cf-python/issues/881) +* Fix bug in `cf.Field.collapse` that causes a ``ValueError`` to be raised + for missing external cell measures data + (https://github.com/NCAS-CMS/cf-python/issues/885) * New dependency: ``distributed>=2025.5.1`` ---- diff --git a/cf/field.py b/cf/field.py index 994eb66fa6..937b4c383a 100644 --- a/cf/field.py +++ b/cf/field.py @@ -3708,7 +3708,12 @@ def weights( weights_axes.discard(xaxis) weights_axes.discard(yaxis) if not Weights.cell_measure( - self, "area", comp, weights_axes, methods=methods + self, + "area", + comp, + weights_axes, + methods=methods, + auto=True, ): Weights.area_XY( self, @@ -5472,52 +5477,60 @@ def collapse( weights: optional Specify the weights for the collapse axes. The weights - are, in general, those that would be returned by this - call of the field construct's `weights` method: - ``f.weights(weights, axes=axes, measure=measure, - scale=scale, radius=radius, great_circle=great_circle, - components=True)``. See the *axes*, *measure*, - *scale*, *radius* and *great_circle* parameters and - `cf.Field.weights` for details, and note that the - value of *scale* may be modified depending on the - value of *measure*. + are created internally as the output of this call of + the field construct's `weights` method: + ``f.weights(weights, components=True, axes=axes, + measure=measure, scale=scale, radius=radius, + great_circle=great_circle)``. - .. note:: By default *weights* is `None`, resulting in - **unweighted calculations**. + See the `cf.Field.weights` and the *axes*, *measure*, + *scale*, *radius*, and *great_circle* parameters for + details; and note that the value of *scale* may be + modified depending on the value of *measure*. + + .. warning:: By default *weights* is `None`, resulting + in **unweighted calculations**. + + .. note:: Setting *weights* to `True` is generally a + good way to ensure that all collapses are + appropriately weighted according to the + field construct's metadata. In this case, if + it is not possible to create weights for any + axis then an exception will be raised. .. note:: Unless the *method* is ``'integral'``, the units of the weights are not combined with the field's units in the collapsed field. - If the alternative form of providing the collapse method - and axes combined as a CF cell methods-like string via the - *method* parameter has been used, then the *axes* - parameter is ignored and the axes are derived from the - *method* parameter. For example, if *method* is ``'T: - area: minimum'`` then this defines axes of ``['T', + .. note:: A pre-calculated weights array or dictionary + may also be provided as the *weights* + parameter. See `cf.Field.weights` for + details + + If the collapse method and axes have been provided as + a CF cell methods-like string via the *method* + parameter, then the *axes* parameter is ignored and + the axes for weights instead inferred from that + string. For instance, if *method* is ``'T: area: + minimum'`` then this defines axes of ``['T', 'area']``. If *method* specifies multiple collapses, - e.g. ``'T: minimum area: mean'`` then this implies axes of - ``'T'`` for the first collapse, and axes of ``'area'`` for - the second collapse. - - .. note:: Setting *weights* to `True` is generally a good - way to ensure that all collapses are - appropriately weighted according to the field - construct's metadata. In this case, if it is not - possible to create weights for any axis then an - exception will be raised. - - However, care needs to be taken if *weights* is - `True` when cell volume weights are desired. The - volume weights will be taken from a "volume" - cell measure construct if one exists, otherwise - the cell volumes will be calculated as being - proportional to the sizes of one-dimensional - vertical coordinate cells. In the latter case - **if the vertical dimension coordinates do not - define the actual height or depth thickness of - every cell in the domain then the weights will - be incorrect**. + e.g. ``'T: minimum area: mean'`` then this implies + axes of ``'T'`` for the first collapse, and axes of + ``'area'`` for the second collapse. + + .. warning:: Care needs to be taken if *weights* is + set to `True` when cell volume weights + are desired. The volume weights will be + taken from a "volume" cell measure + construct if one exists, otherwise the + cell volumes will be calculated as being + proportional to the sizes of + one-dimensional vertical coordinate + cells. In the latter case **if the + vertical dimension coordinates do not + define the actual height or depth + thickness of every cell in the domain + then the weights will be incorrect**. *Parameter example:* To specify weights based on the field construct's diff --git a/cf/weights.py b/cf/weights.py index 24a63aa8f7..c0d50f3fbc 100644 --- a/cf/weights.py +++ b/cf/weights.py @@ -1508,7 +1508,7 @@ def cell_measure( return False raise ValueError( - f"Can't find weights: No {measure!r} cell measure" + f"Can't find weights for {f!r}: No {measure!r} cell measure" ) elif len_m > 1: @@ -1516,7 +1516,8 @@ def cell_measure( return False raise ValueError( - f"Can't find weights: Multiple {measure!r} cell measures" + f"Can't find weights for {f!r}: Multiple {measure!r} cell " + "measures" ) key, clm = m.popitem() @@ -1526,9 +1527,11 @@ def cell_measure( return False raise ValueError( - f"Can't find weights: Cell measure {m!r} has no data, " - "possibly because it is external. " - "Consider setting cell_measures=False" + f"Can't find weights for {f!r}: Cell measure {clm!r} has no " + "data, possibly because it is in a missing external file. " + "Consider setting cell_measures=False or, if applicable, " + "re-reading the dataset whilst providing the external " + "file (see cf.read for details)." ) clm_axes0 = f.get_data_axes(key) @@ -1543,7 +1546,8 @@ def cell_measure( return False raise ValueError( - "Multiple weights specifications for " + f"Can't find weights for {f!r}: Multiple weights " + "specifications for " f"{f.constructs.domain_axis_identity(axis)!r} axis" )