Repository: NCAS-CMS/cf-python

Issue:
Bounds of computed vertical coordinates retain incorrect properties
When computing non-parametric vertical coordinates with `cf.Field.compute_vertical_coordinates`, the computed bounds can inherit properties from the parametric coordinates, which is incorrect. Here's an example of incorrect retained bounds that shows the bounds having an inconsistent standard name: 

```python
    Auxiliary coordinate: altitude
        long_name = 'Computed from parametric
                     atmosphere_hybrid_height_coordinate vertical coordinates'
        standard_name = 'altitude'
        units = 'm'
        Data(50, 201, 200) = [[[20.0, ..., 18220.9921875]]] m
        Bounds:formula = 'z = a + b*orog'
        Bounds:standard_name = 'atmosphere_hybrid_height_coordinate'
        Bounds:units = 'm'
        Bounds:Data(50, 201, 200, 2) = [[[[0.0, ..., 18681.5124544]]]] m
```

The solution is simply to clear these bounds properties.

```python
>>> import cf
>>> cf.environment(paths=False)
Platform: Linux-5.15.0-67-generic-x86_64-with-glibc2.35
HDF5 library: 1.12.1
netcdf library: 4.8.1
udunits2 library: /home/david/miniconda3/lib/libudunits2.so.0
ESMF: 8.2.0
Python: 3.10.9
dask: 2023.3.0
netCDF4: 1.6.0
psutil: 5.9.4
packaging: 23.0
numpy: 1.22.3
scipy: 1.8.1
matplotlib: 3.4.3
cftime: 1.6.2
cfunits: 3.3.5
cfplot: 3.1.31
cfdm: 1.10.0.3
cf: 3.14.1
```


Discussion:

Patch:
diff --git a/Changelog.rst b/Changelog.rst
index 90f0c77f9..e761a3c0a 100644
--- a/Changelog.rst
+++ b/Changelog.rst
@@ -5,6 +5,9 @@ version 3.14.2
 
 * Fixed bug in `cf.Field.read` when reading UM/PP data that are
   runlength encoded (https://github.com/NCAS-CMS/cf-python/issues/621)
+* Fixed bug in `cf.Field.compute_vertical_coordinates` that retained
+  incorrect bounds properties
+  (https://github.com/NCAS-CMS/cf-python/issues/626)
 * Removed benign UserWarning from `cf.Field.percentile`
   (https://github.com/NCAS-CMS/cf-python/issues/619)
 
diff --git a/cf/field.py b/cf/field.py
index 4846c1495..640606cf1 100644
--- a/cf/field.py
+++ b/cf/field.py
@@ -10245,6 +10245,9 @@ class Field(mixin.FieldDomain, mixin.PropertiesData, cfdm.Field):
             if computed_standard_name:
                 c.standard_name = computed_standard_name
 
+            if c.has_bounds():
+                c.bounds.clear_properties()
+
             logger.detail(
                 "Non-parametric coordinates:\n"
                 f"{c.dump(display=False, _level=1)}"


Test patch:
diff --git a/cf/test/test_formula_terms.py b/cf/test/test_formula_terms.py
index 7483c41e2..17f0d04e6 100644
--- a/cf/test/test_formula_terms.py
+++ b/cf/test/test_formula_terms.py
@@ -746,18 +746,27 @@ class FormulaTermsTest(unittest.TestCase):
         f = cf.example_field(1)
         self.assertIsNone(f.auxiliary_coordinate("altitude", default=None))
 
-        g = f.compute_vertical_coordinates(verbose=None)
-
-        altitude = g.auxiliary_coordinate("altitude")
         orog = f.domain_ancillary("surface_altitude")
         a = f.domain_ancillary("ncvar%a")
         b = f.domain_ancillary("ncvar%b")
 
+        # Set bounds properties on 'a' to check that they do not
+        # appear on the bounds of the computed vertical coordinates
+        a.bounds.set_property("foo", "bar")
+
+        g = f.compute_vertical_coordinates(verbose=None)
+        altitude = g.auxiliary_coordinate("altitude")
+
         self.assertTrue(altitude)
         self.assertTrue(altitude.has_bounds())
         self.assertEqual(altitude.shape, (1,) + orog.shape)
         self.assertEqual(altitude.bounds.shape, altitude.shape + (2,))
 
+        # Check that bounds properties were cleared
+        properties = altitude.bounds.properties()
+        properties.pop("units", None)
+        self.assertFalse(properties)
+
         # Check array values
         orog = orog.data.insert_dimension(-1)
         x = a.data + b.data * orog