Repository: fitbenchmarking/fitbenchmarking

Issue:
Divide by zero error when data is fit perfectly
![image](https://github.com/fitbenchmarking/fitbenchmarking/assets/48282025/29660985-09af-4bd6-be0d-311c039758c6)

When fitting a problem exactly, the normalised accuracy gives a div by zero exception.

Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/70640371/badge)](https://coveralls.io/builds/70640371)

coverage: 90.216% (+0.002%) from 90.214%
when pulling **44e07420bb0fd68f59fe87f55789f63965c7f074 on RabiyaF:1233-fix-perfect-fit-divide-by-zero**
into **cbbfa71754bb236973ac80ffdf357cf1229ca98c on fitbenchmarking:master**.

AndrewLister-STFC: @RabiyaF I just merged master in but now the tests aren't running. Is this related to the last PR you put in?

Patch:
diff --git a/fitbenchmarking/results_processing/base_table.py b/fitbenchmarking/results_processing/base_table.py
index 00a07564..1b5ef053 100644
--- a/fitbenchmarking/results_processing/base_table.py
+++ b/fitbenchmarking/results_processing/base_table.py
@@ -15,9 +15,11 @@ from fitbenchmarking.utils.misc import get_js
 
 FORMAT_DESCRIPTION = {
     "abs": "Absolute values are displayed in the table.",
-    "rel": "Relative values are displayed in the table.",
+    "rel": "Relative values are displayed in the table. Incase "
+    "of a perfect fit on a problem ``rel = abs / 1e-10``.",
     "both": "Absolute and relative values are displayed in "
-    "the table in the format ``abs (rel)``",
+    "the table in the format ``abs (rel)``. Incase "
+    "of a perfect fit on a problem ``rel = abs / 1e-10``.",
 }
 CONTRAST_RATIO_AAA = 7.0
 
diff --git a/fitbenchmarking/templates/table_descriptions.rst b/fitbenchmarking/templates/table_descriptions.rst
index 98a71a25..467ca604 100644
--- a/fitbenchmarking/templates/table_descriptions.rst
+++ b/fitbenchmarking/templates/table_descriptions.rst
@@ -58,6 +58,6 @@ rel: End
 
 both: Start
 
-Absolute and relative values are displayed in the table in the format: ``abs (rel)``, for example ``5.1 (1)`` where 5.1 is abs and 1 is rel values respectively
+Absolute and relative values are displayed in the table in the format: ``abs (rel)``, for example ``5.1 (1)`` where 5.1 is abs and 1 is rel values respectively. Incase of a perfect fit on a problem ``rel = abs / 1e-10``.
 
 both: End
diff --git a/fitbenchmarking/utils/fitbm_result.py b/fitbenchmarking/utils/fitbm_result.py
index 1ee722de..3de00c28 100644
--- a/fitbenchmarking/utils/fitbm_result.py
+++ b/fitbenchmarking/utils/fitbm_result.py
@@ -13,6 +13,9 @@ from fitbenchmarking.cost_func.base_cost_func import CostFunc
 from fitbenchmarking.cost_func.nlls_base_cost_func import BaseNLLSCostFunc
 from fitbenchmarking.parsing.fitting_problem import FittingProblem
 from fitbenchmarking.utils.debug import get_printable_table
+from fitbenchmarking.utils.log import get_logger
+
+LOGGER = get_logger()
 
 
 class FittingResult:
@@ -356,6 +359,13 @@ class FittingResult:
         if self._norm_acc is None:
             if self.min_accuracy in [np.nan, np.inf]:
                 self._norm_acc = np.inf
+            elif self.min_accuracy in [0.0, 0]:
+                LOGGER.warning(
+                    "The min accuracy of the dataset is 0. "
+                    "The relative performance will be "
+                    "approximated using a min of 1e-10."
+                )
+                self._norm_acc = self.accuracy / 1e-10
             else:
                 self._norm_acc = self.accuracy / self.min_accuracy
         return self._norm_acc


Test patch:
diff --git a/fitbenchmarking/utils/tests/test_fitbm_result.py b/fitbenchmarking/utils/tests/test_fitbm_result.py
index 8ba7543e..656ac3c4 100644
--- a/fitbenchmarking/utils/tests/test_fitbm_result.py
+++ b/fitbenchmarking/utils/tests/test_fitbm_result.py
@@ -20,11 +20,14 @@ from fitbenchmarking.hessian.analytic_hessian import (
 from fitbenchmarking.jacobian.scipy_jacobian import Scipy
 from fitbenchmarking.parsing.parser_factory import parse_problem_file
 from fitbenchmarking.utils.fitbm_result import FittingResult
+from fitbenchmarking.utils.log import get_logger
 from fitbenchmarking.utils.options import Options
 
 if TYPE_CHECKING:
     from fitbenchmarking.parsing.fitting_problem import FittingProblem
 
+LOGGER = get_logger()
+
 
 class FitbmResultTests(unittest.TestCase):
     """
@@ -200,21 +203,33 @@ class FitbmResultTests(unittest.TestCase):
         )
         self.assertAlmostEqual(expected, result.runtime, places=5)
 
-    def test_norm_acc_finite_min(self):
-        """
-        Test that norm_acc is correct when min_acc is finite.
-        """
-        expected = self.accuracy / self.min_accuracy
-        self.assertEqual(self.result.norm_acc, expected)
-
-    def test_norm_acc_infinite_min(self):
+    @parameterized.expand(
+        [
+            (np.inf, np.inf, np.inf, False),
+            (np.nan, np.nan, np.inf, False),
+            (1, 0.01, 100, False),
+            (1e-10, 0, 1, True),
+            (2e-10, 0, 2, True),
+            (1e-5, 0, 1e5, True),
+        ]
+    )
+    def test_norm_acc(self, acc, min_acc, expected, warn):
         """
-        Test that norm_acc is correct when min_acc is infinite.
+        Test norm_acc returns the expected results and raises the warning.
         """
-        expected = np.inf
-        self.result.accuracy = np.inf
-        self.result.min_accuracy = np.inf
-        self.assertEqual(self.result.norm_acc, expected)
+        self.result.accuracy = acc
+        self.result.min_accuracy = min_acc
+        if warn:
+            with self.assertLogs(LOGGER, level="WARNING") as log:
+                self.assertEqual(self.result.norm_acc, expected)
+                self.assertIn(
+                    "The min accuracy of the dataset is 0. The "
+                    "relative performance will be approximated "
+                    "using a min of 1e-10.",
+                    log.output[0],
+                )
+        else:
+            self.assertEqual(self.result.norm_acc, expected)
 
     @parameterized.expand(
         [
@@ -245,10 +260,9 @@ class FitbmResultTests(unittest.TestCase):
         """
         Test that norm_runtime is correct when min_runtime is infinite.
         """
-        expected = np.inf
         self.result.runtime = np.inf
         self.result.min_runtime = np.inf
-        self.assertEqual(self.result.norm_runtime, expected)
+        self.assertEqual(self.result.norm_runtime, np.inf)
 
     def test_sanitised_name(self):
         """