diff --git a/compliance_checker/cf/cf_1_6.py b/compliance_checker/cf/cf_1_6.py index 00e44ad9..afbe6bbf 100644 --- a/compliance_checker/cf/cf_1_6.py +++ b/compliance_checker/cf/cf_1_6.py @@ -1078,14 +1078,21 @@ def check_flags(self, ds): ret_val.append(valid_masks) if flag_values is not None and flag_masks is not None: - allv = list( - map(lambda a, b: a & b == a, list(zip(flag_values, flag_masks))) - ) + vals_arr = np.array(flag_values, ndmin=1) + masks_arr = np.array(flag_masks, ndmin=1) + # IMPLEMENTATION CONFORMANCE 3.5 RECOMMENDED 1/1 + # If shapes aren't equal, we can't do proper elementwise + # comparison + if vals_arr.size != masks_arr.size: + allv = False + else: + allv = np.all(vals_arr & masks_arr == vals_arr) - allvr = Result(BaseCheck.MEDIUM, all(allv), self.section_titles["3.5"]) + allvr = Result(BaseCheck.MEDIUM, allv, + self.section_titles["3.5"]) if not allvr.value: allvr.msgs = [ - "flag masks and flag values for '{}' combined don't equal flag value".format( + "flag masks and flag values for '{}' combined don't equal flag values".format( name ) ] @@ -1121,9 +1128,9 @@ def _check_flag_values(self, ds, name): # the flag values must be independent, no repeating values - flag_set = set(flag_values) + flag_set = np.unique(flag_values) valid_values.assert_true( - len(flag_set) == np.array(flag_values).size, + flag_set.size == np.array(flag_values).size, "{}'s flag_values must be independent and can not be repeated".format(name), ) @@ -1167,7 +1174,7 @@ def _check_flag_masks(self, ds, name): valid_masks.assert_true( variable.dtype.type == flag_masks.dtype.type, - "flag_masks ({}) mustbe the same data type as {} ({})" + "flag_masks ({}) must be the same data type as {} ({})" "".format(flag_masks.dtype.type, name, variable.dtype.type), ) @@ -1177,6 +1184,11 @@ def _check_flag_masks(self, ds, name): or np.issubdtype(variable.dtype, "b") ) + valid_masks.assert_true(0 not in np.array(flag_masks), + f"flag_masks for variable {variable.name} must " + "not contain zero as an element") + + valid_masks.assert_true( type_ok, "{}'s data type must be capable of bit-field expression".format(name), diff --git a/compliance_checker/tests/test_cf.py b/compliance_checker/tests/test_cf.py index 51b7dc34..10a3a107 100644 --- a/compliance_checker/tests/test_cf.py +++ b/compliance_checker/tests/test_cf.py @@ -483,7 +483,8 @@ def test_check_standard_name(self): temperature.standard_name = "sea_water_temperature" temperature.ancillary_variables = "temperature_flag" - temperature_flag = dataset.createVariable("temperature_flag", "i2", ("time",)) + temperature_flag = dataset.createVariable("temperature_flag", "i2", + ("time",)) # bad modifier temperature_flag.standard_name = "sea_water_temperature status flag" _, _, messages = get_results(self.cf.check_standard_name(dataset)) @@ -677,6 +678,10 @@ def test_check_flags(self): # only 4 variables in this dataset do not have perfect scores imperfect = [r.value for r in results if r.value[0] < r.value[1]] assert len(imperfect) == 4 + dataset.variables["conductivity_qc"] = MockVariable(dataset.variables["conductivity_qc"]) + # Test with single element. Will fail, but should not throw exception. + dataset.variables["conductivity_qc"].flag_values = np.array([1], dtype=np.int8) + results = self.cf.check_flags(dataset) def test_check_flag_masks(self): dataset = self.load_dataset(STATIC_FILES["ghrsst"]) @@ -690,9 +695,24 @@ def test_check_flag_masks(self): flags_var = dataset.createVariable("flags", "f8", ("time",)) flags_var.standard_name = "quality_flag" flags_var.flag_meanings = "LAND" + flags_var.flag_values = [1] + # test single element flags_var.flag_masks = np.array([1], dtype="i2") results = self.cf.check_flags(dataset) assert scored > 0 and scored == out_of + # TEST CONFORMANCE 3.5 REQUIRED 7/8 + flags_var.flag_masks = np.array([0, 1], dtype="i2") + results = self.cf.check_flags(dataset) + score, out_of, messages = get_results(results) + assert ("flag_masks for variable flags must not contain zero as an " + "element" in messages) + # IMPLEMENTATION 3.5 REQUIRED 1/1 + flags_var.flag_masks = np.array([1], dtype="i2") + flags_var.flag_values = np.array([2], dtype="i2") + results = self.cf.check_flags(dataset) + score, out_of, messages = get_results(results) + assert ("flag masks and flag values for 'flags' combined don't equal " + "flag values" in messages) def test_check_bad_units(self): """Load a dataset with units that are expected to fail (bad_units.nc).