From 0f6ac715db81fa75f3bda44cfca1ae8457848566 Mon Sep 17 00:00:00 2001 From: Charles Kropiewnicki Date: Fri, 3 Apr 2026 11:21:54 -0400 Subject: [PATCH 1/2] added np.squeeze for all non-scalar fields at the top of the BaseMetric class, fixes an error where some fields have a lingering size one dimension causes a dimension mismatch with the reference data --- ndsl/testing/comparison.py | 72 ++++++++++++++------------------------ 1 file changed, 27 insertions(+), 45 deletions(-) diff --git a/ndsl/testing/comparison.py b/ndsl/testing/comparison.py index b5c1ac0d..0cf98d85 100644 --- a/ndsl/testing/comparison.py +++ b/ndsl/testing/comparison.py @@ -27,8 +27,8 @@ def __init__( reference_values: np.ndarray, computed_values: np.ndarray, ): - self.references = np.atleast_1d(reference_values) - self.computed = np.atleast_1d(computed_values) + self.references = np.squeeze(np.atleast_1d(reference_values)) + self.computed = np.squeeze(np.atleast_1d(computed_values)) self.check = False @abstractmethod @@ -80,15 +80,11 @@ def _compute_errors( # Avoid division by 0. If reference is 0, we expect the computed value to be 0 too. # (abs(computed - reference) / 1.0) is a good value for the error in this case. denom[self.references == 0] = 1.0 - self._calculated_metric = np.asarray( - np.abs((self.computed - self.references) / denom) - ) + self._calculated_metric = np.asarray(np.abs((self.computed - self.references) / denom)) elif self.references.dtype in (np.bool_, bool): self._calculated_metric = np.logical_xor(self.computed, self.references) else: - raise TypeError( - f"Received data with unexpected dtype `{self.references.dtype}`." - ) + raise TypeError(f"Received data with unexpected dtype `{self.references.dtype}`.") success = np.logical_or( np.logical_and(np.isnan(self.computed), np.isnan(self.references)), self._calculated_metric < self.eps, @@ -263,9 +259,7 @@ def __init__( self.number_changing_values = (input_values != reference_values).sum() # column information is only relevant if data is three-dimensional if len(input_values.shape) == 3: - self.changing_column_map = (input_values != reference_values).any( - axis=2 - ) + self.changing_column_map = (input_values != reference_values).any(axis=2) else: self.changing_column_map = None else: @@ -276,23 +270,15 @@ def _compute_all_metrics( self, ) -> npt.NDArray[np.bool_]: if self.references.dtype in (np.float64, np.int64, np.float32, np.int32): - max_values = np.maximum( - np.absolute(self.computed), np.absolute(self.references) - ) + max_values = np.maximum(np.absolute(self.computed), np.absolute(self.references)) # Absolute distance self.absolute_distance = np.absolute(self.computed - self.references) - self.absolute_distance_metric = ( - self.absolute_distance < self.absolute_eps.value - ) + self.absolute_distance_metric = self.absolute_distance < self.absolute_eps.value # Relative distance (in pct) self.relative_distance = np.divide(self.absolute_distance, max_values) - self.relative_distance_metric = ( - self.absolute_distance < self.relative_fraction.value * max_values - ) + self.relative_distance_metric = self.absolute_distance < self.relative_fraction.value * max_values # ULP distance - self.ulp_distance = np.divide( - self.absolute_distance, np.spacing(max_values) - ) + self.ulp_distance = np.divide(self.absolute_distance, np.spacing(max_values)) self.ulp_distance_metric = self.ulp_distance <= self.ulp_threshold.value # Combine all distances into success or failure @@ -301,12 +287,8 @@ def _compute_all_metrics( # - absolute distance pass OR # - relative distance pass OR # - ulp distance pass - naninf_success = np.logical_and( - np.isnan(self.computed), np.isnan(self.references) - ) - metric_success = np.logical_or( - self.relative_distance_metric, self.absolute_distance_metric - ) + naninf_success = np.logical_and(np.isnan(self.computed), np.isnan(self.references)) + metric_success = np.logical_or(self.relative_distance_metric, self.absolute_distance_metric) metric_success = np.logical_or(metric_success, self.ulp_distance_metric) success = np.logical_or(naninf_success, metric_success) return success @@ -314,9 +296,7 @@ def _compute_all_metrics( success = np.logical_xor(self.computed, self.references) return success else: - raise TypeError( - f"received data with unexpected dtype {self.references.dtype}" - ) + raise TypeError(f"received data with unexpected dtype {self.references.dtype}") def _has_override(self) -> bool: return ( @@ -326,9 +306,13 @@ def _has_override(self) -> bool: ) def one_line_report(self) -> str: - metric_thresholds = f"{'🔶 ' if not self.absolute_eps.is_default else ''}Absolute E(<{self.absolute_eps.value:.2e}) " + metric_thresholds = ( + f"{'🔶 ' if not self.absolute_eps.is_default else ''}Absolute E(<{self.absolute_eps.value:.2e}) " + ) metric_thresholds += f"{'🔶 ' if not self.relative_fraction.is_default else ''}Relative E(<{self.relative_fraction.value * 100:.2e}%) " - metric_thresholds += f"{'🔶 ' if not self.ulp_threshold.is_default else ''}ULP E(<{self.ulp_threshold.value})" + metric_thresholds += ( + f"{'🔶 ' if not self.ulp_threshold.is_default else ''}ULP E(<{self.ulp_threshold.value})" + ) if self.check and self._has_override(): return f"🔶 No numerical differences with threshold override - metric: {metric_thresholds}" elif self.check: @@ -336,7 +320,9 @@ def one_line_report(self) -> str: else: failed_indices = len(np.logical_not(self.success).nonzero()[0]) all_indices = len(self.references.flatten()) - return f"❌ Numerical failures: {failed_indices}/{all_indices} failed - metric: {metric_thresholds}" + return ( + f"❌ Numerical failures: {failed_indices}/{all_indices} failed - metric: {metric_thresholds}" + ) def report(self, file_path: str | None = None) -> list[str]: report = [] @@ -346,9 +332,7 @@ def report(self, file_path: str | None = None) -> list[str]: bad_indices_count = len(failed_indices[0]) if self.changing_column_map is not None: if self.success.ndim == 3: - bad_column_count = ( - np.logical_not(self.success).any(axis=2) & self.changing_column_map - ).sum() + bad_column_count = (np.logical_not(self.success).any(axis=2) & self.changing_column_map).sum() total_column_count = self.changing_column_map.sum() bad_column_pct = round(bad_column_count / total_column_count * 100, 2) else: @@ -373,7 +357,9 @@ def report(self, file_path: str | None = None) -> list[str]: ) report_local_failures = f"Failures: (changing columns, chainging points, all points) | {bad_column_count}/{total_column_count} - {bad_column_pct}%, {bad_indices_count}/{self.number_changing_values} - {failures_of_changing_gridpoint_pct}%, {bad_indices_count}/{full_count} - {failures_of_all_grid_points_pct}%\n" else: - report_local_failures = f"all grid points: {bad_indices_count}/{full_count} - {failures_of_all_grid_points_pct}%\n" + report_local_failures = ( + f"all grid points: {bad_indices_count}/{full_count} - {failures_of_all_grid_points_pct}%\n" + ) report = [ f"{report_local_failures}" f"Index Computed Reference " @@ -391,17 +377,13 @@ def report(self, file_path: str | None = None) -> list[str]: elif self.sort_report == "index": indices_flatten = list(range(self.ulp_distance.size - 1, -1, -1)) else: - RuntimeError( - f"[Translate test] Unknown {self.sort_report} report sorting option." - ) + RuntimeError(f"[Translate test] Unknown {self.sort_report} report sorting option.") for iFlat in indices_flatten[::-1]: fi = np.unravel_index(iFlat, shape=self.ulp_distance.shape) if np.isnan(self.computed[fi]) and np.isnan(self.references[fi]): continue ulp_dist = ( - self.ulp_distance[fi] - if np.isnan(self.ulp_distance[fi]) - else int(self.ulp_distance[fi]) + self.ulp_distance[fi] if np.isnan(self.ulp_distance[fi]) else int(self.ulp_distance[fi]) ) index_as_string = "(" for i in fi: From 2a2c736edaa22e986d63e5edcdd50dc6e29c37bf Mon Sep 17 00:00:00 2001 From: Charles Kropiewnicki Date: Fri, 3 Apr 2026 11:24:20 -0400 Subject: [PATCH 2/2] linting --- ndsl/testing/comparison.py | 68 ++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/ndsl/testing/comparison.py b/ndsl/testing/comparison.py index 0cf98d85..b04f09c0 100644 --- a/ndsl/testing/comparison.py +++ b/ndsl/testing/comparison.py @@ -80,11 +80,15 @@ def _compute_errors( # Avoid division by 0. If reference is 0, we expect the computed value to be 0 too. # (abs(computed - reference) / 1.0) is a good value for the error in this case. denom[self.references == 0] = 1.0 - self._calculated_metric = np.asarray(np.abs((self.computed - self.references) / denom)) + self._calculated_metric = np.asarray( + np.abs((self.computed - self.references) / denom) + ) elif self.references.dtype in (np.bool_, bool): self._calculated_metric = np.logical_xor(self.computed, self.references) else: - raise TypeError(f"Received data with unexpected dtype `{self.references.dtype}`.") + raise TypeError( + f"Received data with unexpected dtype `{self.references.dtype}`." + ) success = np.logical_or( np.logical_and(np.isnan(self.computed), np.isnan(self.references)), self._calculated_metric < self.eps, @@ -259,7 +263,9 @@ def __init__( self.number_changing_values = (input_values != reference_values).sum() # column information is only relevant if data is three-dimensional if len(input_values.shape) == 3: - self.changing_column_map = (input_values != reference_values).any(axis=2) + self.changing_column_map = (input_values != reference_values).any( + axis=2 + ) else: self.changing_column_map = None else: @@ -270,15 +276,23 @@ def _compute_all_metrics( self, ) -> npt.NDArray[np.bool_]: if self.references.dtype in (np.float64, np.int64, np.float32, np.int32): - max_values = np.maximum(np.absolute(self.computed), np.absolute(self.references)) + max_values = np.maximum( + np.absolute(self.computed), np.absolute(self.references) + ) # Absolute distance self.absolute_distance = np.absolute(self.computed - self.references) - self.absolute_distance_metric = self.absolute_distance < self.absolute_eps.value + self.absolute_distance_metric = ( + self.absolute_distance < self.absolute_eps.value + ) # Relative distance (in pct) self.relative_distance = np.divide(self.absolute_distance, max_values) - self.relative_distance_metric = self.absolute_distance < self.relative_fraction.value * max_values + self.relative_distance_metric = ( + self.absolute_distance < self.relative_fraction.value * max_values + ) # ULP distance - self.ulp_distance = np.divide(self.absolute_distance, np.spacing(max_values)) + self.ulp_distance = np.divide( + self.absolute_distance, np.spacing(max_values) + ) self.ulp_distance_metric = self.ulp_distance <= self.ulp_threshold.value # Combine all distances into success or failure @@ -287,8 +301,12 @@ def _compute_all_metrics( # - absolute distance pass OR # - relative distance pass OR # - ulp distance pass - naninf_success = np.logical_and(np.isnan(self.computed), np.isnan(self.references)) - metric_success = np.logical_or(self.relative_distance_metric, self.absolute_distance_metric) + naninf_success = np.logical_and( + np.isnan(self.computed), np.isnan(self.references) + ) + metric_success = np.logical_or( + self.relative_distance_metric, self.absolute_distance_metric + ) metric_success = np.logical_or(metric_success, self.ulp_distance_metric) success = np.logical_or(naninf_success, metric_success) return success @@ -296,7 +314,9 @@ def _compute_all_metrics( success = np.logical_xor(self.computed, self.references) return success else: - raise TypeError(f"received data with unexpected dtype {self.references.dtype}") + raise TypeError( + f"received data with unexpected dtype {self.references.dtype}" + ) def _has_override(self) -> bool: return ( @@ -306,13 +326,9 @@ def _has_override(self) -> bool: ) def one_line_report(self) -> str: - metric_thresholds = ( - f"{'🔶 ' if not self.absolute_eps.is_default else ''}Absolute E(<{self.absolute_eps.value:.2e}) " - ) + metric_thresholds = f"{'🔶 ' if not self.absolute_eps.is_default else ''}Absolute E(<{self.absolute_eps.value:.2e}) " metric_thresholds += f"{'🔶 ' if not self.relative_fraction.is_default else ''}Relative E(<{self.relative_fraction.value * 100:.2e}%) " - metric_thresholds += ( - f"{'🔶 ' if not self.ulp_threshold.is_default else ''}ULP E(<{self.ulp_threshold.value})" - ) + metric_thresholds += f"{'🔶 ' if not self.ulp_threshold.is_default else ''}ULP E(<{self.ulp_threshold.value})" if self.check and self._has_override(): return f"🔶 No numerical differences with threshold override - metric: {metric_thresholds}" elif self.check: @@ -320,9 +336,7 @@ def one_line_report(self) -> str: else: failed_indices = len(np.logical_not(self.success).nonzero()[0]) all_indices = len(self.references.flatten()) - return ( - f"❌ Numerical failures: {failed_indices}/{all_indices} failed - metric: {metric_thresholds}" - ) + return f"❌ Numerical failures: {failed_indices}/{all_indices} failed - metric: {metric_thresholds}" def report(self, file_path: str | None = None) -> list[str]: report = [] @@ -332,7 +346,9 @@ def report(self, file_path: str | None = None) -> list[str]: bad_indices_count = len(failed_indices[0]) if self.changing_column_map is not None: if self.success.ndim == 3: - bad_column_count = (np.logical_not(self.success).any(axis=2) & self.changing_column_map).sum() + bad_column_count = ( + np.logical_not(self.success).any(axis=2) & self.changing_column_map + ).sum() total_column_count = self.changing_column_map.sum() bad_column_pct = round(bad_column_count / total_column_count * 100, 2) else: @@ -357,9 +373,7 @@ def report(self, file_path: str | None = None) -> list[str]: ) report_local_failures = f"Failures: (changing columns, chainging points, all points) | {bad_column_count}/{total_column_count} - {bad_column_pct}%, {bad_indices_count}/{self.number_changing_values} - {failures_of_changing_gridpoint_pct}%, {bad_indices_count}/{full_count} - {failures_of_all_grid_points_pct}%\n" else: - report_local_failures = ( - f"all grid points: {bad_indices_count}/{full_count} - {failures_of_all_grid_points_pct}%\n" - ) + report_local_failures = f"all grid points: {bad_indices_count}/{full_count} - {failures_of_all_grid_points_pct}%\n" report = [ f"{report_local_failures}" f"Index Computed Reference " @@ -377,13 +391,17 @@ def report(self, file_path: str | None = None) -> list[str]: elif self.sort_report == "index": indices_flatten = list(range(self.ulp_distance.size - 1, -1, -1)) else: - RuntimeError(f"[Translate test] Unknown {self.sort_report} report sorting option.") + RuntimeError( + f"[Translate test] Unknown {self.sort_report} report sorting option." + ) for iFlat in indices_flatten[::-1]: fi = np.unravel_index(iFlat, shape=self.ulp_distance.shape) if np.isnan(self.computed[fi]) and np.isnan(self.references[fi]): continue ulp_dist = ( - self.ulp_distance[fi] if np.isnan(self.ulp_distance[fi]) else int(self.ulp_distance[fi]) + self.ulp_distance[fi] + if np.isnan(self.ulp_distance[fi]) + else int(self.ulp_distance[fi]) ) index_as_string = "(" for i in fi: