Repository: fitbenchmarking/fitbenchmarking

Issue:
Add a default value for x in FittingProblem.eval_f
Every evaluation of this is going to occur at self.data_x.
This already wrapped by some controllers to give a function which only takes parameters as an argument.

We should make self.data_x a default, which will accommodate all of the controllers

Discussion:

Patch:
diff --git a/fitbenchmarking/fitting/controllers/dfogn_controller.py b/fitbenchmarking/fitting/controllers/dfogn_controller.py
index 328f62ae..ae6fc1cc 100644
--- a/fitbenchmarking/fitting/controllers/dfogn_controller.py
+++ b/fitbenchmarking/fitting/controllers/dfogn_controller.py
@@ -31,8 +31,7 @@ class DFOGNController(Controller):
         self._pinit = np.asarray(self.initial_params)
 
     def _prediction_error(self, p):
-        f = self.data_y - self.problem.eval_f(x=self.data_x,
-                                              params=p,
+        f = self.data_y - self.problem.eval_f(params=p,
                                               function_id=self.function_id)
         if self.use_errors:
             f = f/self.data_e
@@ -59,7 +58,6 @@ class DFOGNController(Controller):
         will be read from.
         """
         if self.success:
-            self.results = self.problem.eval_f(x=self.data_x,
-                                               params=self._popt,
+            self.results = self.problem.eval_f(params=self._popt,
                                                function_id=self.function_id)
             self.final_params = self._popt
diff --git a/fitbenchmarking/fitting/controllers/minuit_controller.py b/fitbenchmarking/fitting/controllers/minuit_controller.py
index cf5fe91a..640aefdb 100644
--- a/fitbenchmarking/fitting/controllers/minuit_controller.py
+++ b/fitbenchmarking/fitting/controllers/minuit_controller.py
@@ -43,8 +43,7 @@ class MinuitController(Controller):
                                                       errordef=1)
 
     def _prediction_error(self, p):
-        f = self.problem.eval_f(x=self.data_x,
-                                params=p,
+        f = self.problem.eval_f(params=p,
                                 function_id=self.function_id)
         f = f - self.data_y
         if self.use_errors:
@@ -68,7 +67,6 @@ class MinuitController(Controller):
         self.success = (self._popt is not None)
 
         if self.success:
-            self.results = self.problem.eval_f(x=self.data_x,
-                                               params=self._popt,
+            self.results = self.problem.eval_f(params=self._popt,
                                                function_id=self.function_id)
             self.final_params = self._popt
diff --git a/fitbenchmarking/fitting/controllers/ralfit_controller.py b/fitbenchmarking/fitting/controllers/ralfit_controller.py
index 951ab5af..8765e089 100644
--- a/fitbenchmarking/fitting/controllers/ralfit_controller.py
+++ b/fitbenchmarking/fitting/controllers/ralfit_controller.py
@@ -44,8 +44,7 @@ class RALFitController(Controller):
             raise RuntimeError("An undefined RALFit minmizer was selected")
 
     def _prediction_error(self, p):
-        f = self.problem.eval_f(x=self.data_x,
-                                params=p,
+        f = self.problem.eval_f(params=p,
                                 function_id=self.function_id)
         f = f - self.data_y
         if self.use_errors:
@@ -76,7 +75,6 @@ class RALFitController(Controller):
         will be read from.
         """
         if self.success:
-            self.results = self.problem.eval_f(x=self.data_x,
-                                               params=self._popt,
+            self.results = self.problem.eval_f(params=self._popt,
                                                function_id=self.function_id)
             self.final_params = self._popt
diff --git a/fitbenchmarking/fitting/controllers/scipy_controller.py b/fitbenchmarking/fitting/controllers/scipy_controller.py
index 33d1f955..38e5beb6 100644
--- a/fitbenchmarking/fitting/controllers/scipy_controller.py
+++ b/fitbenchmarking/fitting/controllers/scipy_controller.py
@@ -50,7 +50,6 @@ class ScipyController(Controller):
         will be read from.
         """
         if self.success:
-            self.results = self.problem.eval_f(x=self.data_x,
-                                               params=self._popt,
+            self.results = self.problem.eval_f(params=self._popt,
                                                function_id=self.function_id)
             self.final_params = self._popt
diff --git a/fitbenchmarking/parsing/fitting_problem.py b/fitbenchmarking/parsing/fitting_problem.py
index 43c05c14..b7365f22 100644
--- a/fitbenchmarking/parsing/fitting_problem.py
+++ b/fitbenchmarking/parsing/fitting_problem.py
@@ -50,16 +50,16 @@ class FittingProblem:
         # Executable param pairs
         self.functions = None
 
-    def eval_f(self, x, params, function_id):
+    def eval_f(self, params, function_id, x=None):
         """
         Function evaluation method
 
-        :param x: x data values
-        :type x: numpy array
         :param params: parameter value(s)
         :type params: list
         :param function_id: The index of the function in functions
         :type function_id: int
+        :param x: x data values or None, if None this uses self.data_x
+        :type x: numpy array
 
         :return: y data values evaluated from the function of the problem
         :rtype: numpy array
@@ -67,6 +67,9 @@ class FittingProblem:
         if self.functions is None:
             raise AttributeError('Cannot call function before setting'
                                  + 'functions in object.')
+
+        if x is None:
+            x = self.data_x
         function = self.functions[function_id][0]
         return function(x, *params)
 


Test patch:
diff --git a/fitbenchmarking/parsing/tests/test_fitting_problem.py b/fitbenchmarking/parsing/tests/test_fitting_problem.py
index f97a8e4b..b9cca333 100644
--- a/fitbenchmarking/parsing/tests/test_fitting_problem.py
+++ b/fitbenchmarking/parsing/tests/test_fitting_problem.py
@@ -70,6 +70,10 @@ class TestFittingProblem(TestCase):
                                              params=[5],
                                              function_id=1)
         self.assertTrue(all(eval_result == np.array([16])))
+        fitting_problem.data_x = np.array([20, 21, 22])
+        eval_result = fitting_problem.eval_f(params=[5],
+                                             function_id=0)
+        self.assertTrue(all(eval_result == np.array([25, 26, 27])))
 
     def test_eval_starting_params(self):
         """