Repository: ioos/compliance-checker
Pull request #966 (2022-10-19 20:39:08)

updated check_cell_measures
This is in reference to [issue #953](https://github.com/ioos/compliance-checker/issues/953)

Updated the function "check_cell_measures" to include:
- [ ] A measure variable must have units that are consistent with the measure type, i.e., square meters for area measures and cubic meters for volume measures.


Patch:
diff --git a/compliance_checker/cf/appendix_d.py b/compliance_checker/cf/appendix_d.py
index c11c5d9..5e80ca5 100644
--- a/compliance_checker/cf/appendix_d.py
+++ b/compliance_checker/cf/appendix_d.py
@@ -73,7 +73,7 @@ dimless_vertical_coordinates_1_6 = {  # only for CF-1.6
         ocean_computed_standard_names,
     ),
     "ocean_sigma_z_coordinate": (
-        {"sigma", "eta", "depth", "depth_c", "zlev"},
+        {"sigma", "eta", "depth", "depth_c", "nsigma", "zlev"},
         ocean_computed_standard_names,
     ),
     "ocean_double_sigma_coordinate": (
diff --git a/compliance_checker/cf/cf_1_6.py b/compliance_checker/cf/cf_1_6.py
index 39f4c5b..41f3292 100644
--- a/compliance_checker/cf/cf_1_6.py
+++ b/compliance_checker/cf/cf_1_6.py
@@ -94,13 +94,6 @@ class CF1_6Check(CFNCCheck):
                         k, v.datatype
                     )
                 )
-            # TODO: DRY, don't repeat code path
-            if v.dtype is not str and v.dtype.kind == "S" and v.ndim not in (1, 2):
-                fails.append(
-                    "The fixed-length string variable {} must be one or "
-                    "two-dimensional, current length is {}".format(v.name, v.ndim)
-                )
-
         return Result(
             BaseCheck.HIGH,
             (total - len(fails), total),
@@ -411,73 +404,6 @@ class CF1_6Check(CFNCCheck):
                 )
         return valid_dimension_order.to_result()
 
-    def check_fill_value_equal_missing_value(self, ds):
-        """
-        If both missing_value and _FillValue be used, they should have the same value. 
-        This according to CF §2.5.1 Recommendations:
-        
-        :param netCDF4.Dataset ds: An open netCDF dataset
-        :rtype: list
-        :return: List of Results
-        """
-        fails = []
-        total = 0
-
-        for name, variable in ds.variables.items():
-            # If the variable have a defined _FillValue a defined missing_value check it.
-            
-            if hasattr(variable, "_FillValue") and hasattr(variable, "missing_value"):
-                total = total + 1
-                if variable._FillValue != variable.missing_value:
-                    fails.append(
-                        "For the variable {} the missing_value must be equal to the _FillValue".format(
-                        variable.name
-                        )
-                        )             
-                    
-        return Result(
-            BaseCheck.MEDIUM, 
-            (len(fails), total),
-            self.section_titles["2.5"], 
-            msgs=fails,
-        )
-    
-    def check_valid_range_or_valid_min_max_present(self,ds):
-        '''
-        The valid_range attribute must not be present if the valid_min
-        and/or valid_max attributes are present. This according to 2.5.1 Requirements.
-
-        :param netCDF4.Dataset ds: An open netCDF dataset
-        :rtype: list
-        :return: List of Results
-        '''
-        fails = []
-        total = 0
-
-        for name, variable in ds.variables.items():
-            
-            if (hasattr(variable, "valid_max") 
-                and (hasattr(variable, "valid_min") 
-                or hasattr(variable, "valid_range")
-                )
-                ):
-
-                total = total + 1
-            
-                fails.append(
-                    "For the variable {} the valid_range attribute must not be present "
-                    "if the valid_min and/or valid_max attributes are present".format(
-                    variable.name
-                    )
-                    )               
-                    
-        return Result(
-            BaseCheck.MEDIUM, 
-            (len(fails), total),
-            self.section_titles["2.5"], 
-            msgs=fails,
-        )
-    
     def check_fill_value_outside_valid_range(self, ds):
         """
         Checks each variable's _FillValue to ensure that it's in valid_range or
@@ -2735,6 +2661,22 @@ class CF1_6Check(CFNCCheck):
                                 cell_meas_var_name
                             )
                         )
+                    else:
+                        # IMPLEMENTATION CONFORMANCE REQUIRED 2/2
+                        # verify this combination {area: 'm2', volume: 'm3'}
+                        dic_expected = {"area": "m2", "volume": "m3"}
+                        dic_to_be_verified = {
+                            (var.cell_measures).split(":")[0]: cell_meas_var.units
+                        }
+                        print(dic_to_be_verified, "\n")
+                        if not set(dic_to_be_verified).issubset(dic_expected):
+                            valid = False
+                            reasoning.append(
+                                "Cell measure variable {} must have "
+                                "units that are consistent with the measure type."
+                                "i.e. {}.".format(cell_meas_var_name, dic_expected)
+                            )
+
                     if not set(cell_meas_var.dimensions).issubset(var.dimensions):
                         valid = False
                         reasoning.append(


Test patch:
diff --git a/compliance_checker/tests/test_cf.py b/compliance_checker/tests/test_cf.py
index 1aade9d..e3e3ac6 100644
--- a/compliance_checker/tests/test_cf.py
+++ b/compliance_checker/tests/test_cf.py
@@ -150,35 +150,6 @@ class TestCF1_6(BaseTestCase):
         dataset = self.load_dataset(STATIC_FILES["bad_data_type"])
         result = self.cf.check_data_types(dataset)
 
-        dataset = MockTimeSeries()
-        # test dimensionality of string
-        dataset.createDimension("num_strings", 3)
-        dataset.createDimension("max_string_length", 80)
-        dataset.createDimension("extra", 9)
-
-        # TEST CONFORMANCE 2.2 1/2
-        # create strings with no dimensions -- fail
-        dataset.createVariable("zero_dimensional", "S1", ())
-        # create strings with one dimension -- valid
-        dataset.createVariable("one_dimensional", "S1", ("max_string_length"))
-        # create strings with two dimensions - valid
-        # NB: string length is supposed to be last, but this can't be inferred
-        #     easily with
-        dataset.createVariable(
-            "two_dimensional", "S1", ("num_strings", "max_string_length")
-        )
-        # create strings with three dimensions - valid
-        dataset.createVariable(
-            "three_dimensional", "S1", ("num_strings", "max_string_length", "extra")
-        )
-
-        result = self.cf.check_data_types(dataset)
-        expected_msgs = [
-            f"The fixed-length string variable {dim_name}_dimensional must "
-            f"be one or two-dimensional, current length is {dim_len}"
-            for dim_name, dim_len in (("zero", 0), ("three", 3))
-        ]
-        assert result.msgs == expected_msgs
         # TODO
         # the acdd_reformat_rebase branch has a new .nc file
         # which constructs the temp variable with an int64 dtype --
@@ -382,77 +353,7 @@ class TestCF1_6(BaseTestCase):
         result = self.cf.check_dimension_order(dataset)
         self.assertEqual((3, 3), result.value)
         self.assertEqual([], result.msgs)
-        
-    def test_check_fill_value_equal_missing_value(self):
-        """
-        According to CF §2.5.1 Recommendations: If both missing_value and _FillValue be used, 
-        they should have the same value.  
-        """
-        # TEST CONFORMANCE 2.5.1 RECOMMENDED
-        dataset = MockTimeSeries()
-        # Case of _FillValue and missing_value are not equal
-        dataset.createVariable("a", "d", ("time",), fill_value=9999.9)
-        dataset.variables["a"][0] = 1
-        dataset.variables["a"][1] = 2   
-        dataset.variables["a"].setncattr("missing_value", [9939.9])
 
-        # Case of _FillValue and missing_value are equal
-        dataset.createVariable("b", "d", ("time",), fill_value=9999.9)
-        dataset.variables["b"][0] = 1
-        dataset.variables["b"][1] = 2   
-        dataset.variables["b"].setncattr("missing_value", [9999.9])
-
-
-        result = self.cf.check_fill_value_equal_missing_value(dataset)
-        
-        # check if the test fails when when variable "a" is checked.
-        expected_msgs = [
-            f"For the variable {v_name} the missing_value must be equal to the _FillValue"
-            for v_name in ("a")]
- 
-        assert result.msgs == expected_msgs 
-
-    def test_check_valid_range_or_valid_min_max_present(self):
-        """
-        2.5.1 Missing data, valid and actual range of data
-        Requirements:
-        The valid_range attribute must not be present if the 
-        valid_min and/or valid_max attributes are present.
-        """
-        # TEST CONFORMANCE 2.5.1 REQUIRED
-        dataset = MockTimeSeries()
-        # Case of valid_min, valid_max, and valid_range are present
-        dataset.createVariable("a", "d", ("time",), fill_value=9999.9)
-        dataset.variables["a"][0] = 1
-        dataset.variables["a"][1] = 2   
-        dataset.variables["a"].setncattr("valid_min", [-10])
-        dataset.variables["a"].setncattr("valid_max", [10])
-        dataset.variables["a"].setncattr("valid_range", [-10, 10])
-
-        # Case of valid_min and valid_max are present and valid_range is absent
-        dataset.createVariable("b", "d", ("time",), fill_value=9999.9)
-        dataset.variables["b"][0] = 1
-        dataset.variables["b"][1] = 2   
-        dataset.variables["a"].setncattr("valid_min", [-10])
-        dataset.variables["a"].setncattr("valid_max", [10])
-
-        # Case of valid_min and valid_max are absent and valid_range is present
-        dataset.createVariable("c", "d", ("time",), fill_value=9999.9)
-        dataset.variables["c"][0] = 1
-        dataset.variables["c"][1] = 2   
-        dataset.variables["c"].setncattr("valid_range", [-10, 10]) 
-
-        result = self.cf.check_valid_range_or_valid_min_max_present(dataset)
-        
-        # check if the test fails when when variable "a" is checked.
-        expected_msgs = [
-            f"For the variable {v_name} the valid_range attribute must not be present "
-            f"if the valid_min and/or valid_max attributes are present"
-            for v_name in ("a")]
- 
-        assert result.msgs == expected_msgs
-        assert result.value[0] == result.value[1]
-    
     def test_check_fill_value_outside_valid_range(self):
         """
         2.5.1 The _FillValue should be outside the range specified by valid_range (if used) for a variable.
@@ -1159,7 +1060,7 @@ class TestCF1_6(BaseTestCase):
         self.assertTrue(
             no_missing_terms(
                 "ocean_sigma_z_coordinate",
-                {"sigma", "eta", "depth", "depth_c", "zlev"},
+                {"sigma", "eta", "depth", "depth_c", "nsigma", "zlev"},
                 dimless_vertical_coordinates_1_6,
             )
         )