Repository: fitbenchmarking/fitbenchmarking

Issue:
Revisit the way result objects are used for table generation
Add attributes within results object (normalised values, html colours, etc) to create pandas tables of the result object instead of chi_sq and runtime values.  

Discussion:

Patch:
diff --git a/.travis.yml b/.travis.yml
index 61aa7643..c6289a1e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -25,15 +25,16 @@ before_install:
   - sudo apt-get install lcov
   - sudo apt-get install libblas-dev
   - sudo apt-get install liblapack-dev
+  - sudo apt-get install pandoc
 
 install:
   # Builds RALfit
   - ./build_ralfit.sh
-  
+
   - pip install -r requirements.txt
   - python setup.py install
   - python setup.py externals
-  
+
   # To access RALfit
   - export LD_LIBRARY_PATH=$HOME/build/fitbenchmarking/fitbenchmarking/RALFit/libRALFit/build/src:$LD_LIBRARY_PATH
 
diff --git a/docs/source/users/getting_started.rst b/docs/source/users/getting_started.rst
index 16b71c43..7461768d 100644
--- a/docs/source/users/getting_started.rst
+++ b/docs/source/users/getting_started.rst
@@ -209,3 +209,9 @@ Installing RAlfit
 The fitbenchmarking setup does not currently support installing RALfit
 through it. RALfit can be downloaded and installed from
 `here <https://github.com/ralna/RALFit>`__.
+
+*****************
+Installing Pandoc
+*****************
+
+Pandoc is used to convert the HTML tables into RST format, see here `here <https://pandoc.org/installing.html>`__ for installation.
diff --git a/example_scripts/options_template.ini b/example_scripts/options_template.ini
index 126327dc..34271218 100644
--- a/example_scripts/options_template.ini
+++ b/example_scripts/options_template.ini
@@ -92,11 +92,11 @@ use_errors: no
 #              have the top ranking (brightest) and values over 3 will show as
 #              the worst ranking (deep red)
 # default thresholds are 1.1, 1.33, 1.75, 3, and inf
-#colour_scale: 1.1, ranking-top-1
-#              1.33, ranking-top-2
-#              1.75, ranking-med-3
-#              3, ranking-low-4
-#              inf, ranking-low-5
+#colour_scale: 1.1, #fef0d9
+#              1.33, #fdcc8a
+#              1.75, #fc8d59
+#              3, #e34a33
+#              inf, #b30000
 
 # comparison_mode selects the mode for displaying values in the resulting
 #                 table
@@ -110,4 +110,4 @@ use_errors: no
 
 # results_dir is used to select where the output should be saved
 # default is results
-#results_dir: results
\ No newline at end of file
+#results_dir: results
diff --git a/fitbenchmarking/fitbenchmark_one_problem.py b/fitbenchmarking/fitbenchmark_one_problem.py
index aab75b07..d632a2a9 100644
--- a/fitbenchmarking/fitbenchmark_one_problem.py
+++ b/fitbenchmarking/fitbenchmark_one_problem.py
@@ -68,8 +68,7 @@ def fitbm_one_prob(problem, options, directory):
             controller.parameter_set = i
             problem_result, best_fit = benchmark(controller=controller,
                                                  minimizers=minimizers,
-                                                 num_runs=options.num_runs)
-
+                                                 options=options)
             if best_fit is not None:
                 # Make the plot of the best fit
                 plots.make_plots(problem=problem,
@@ -81,7 +80,7 @@ def fitbm_one_prob(problem, options, directory):
     return results
 
 
-def benchmark(controller, minimizers, num_runs):
+def benchmark(controller, minimizers, options):
     """
     Fit benchmark one problem, with one function definition and all
     the selected minimizers, using the chosen fitting software.
@@ -90,9 +89,8 @@ def benchmark(controller, minimizers, num_runs):
     :type controller: Object derived from BaseSoftwareController
     :param minimizers: array of minimizers used in fitting
     :type minimizers: list
-    :param num_runs: number of times controller.fit() is run to
-                     generate an average runtime
-    :type num_runs: int
+    :param options: all the information specified by the user
+    :type options: fitbenchmarking.utils.options.Options
 
     :returns: tuple(results_problem, best_fit) nested array of
               result objects, per minimizer and data object for
@@ -101,7 +99,7 @@ def benchmark(controller, minimizers, num_runs):
     """
     min_chi_sq, best_fit = None, None
     results_problem = []
-
+    num_runs = options.num_runs
     for minimizer in minimizers:
         print("            Minimizer: {}".format(minimizer))
 
@@ -130,7 +128,7 @@ def benchmark(controller, minimizers, num_runs):
             params=controller.final_params)
 
         if not controller.success:
-            chi_sq = np.nan
+            chi_sq = np.inf
             status = 'failed'
         else:
             ratio = np.max(runtime_list) / np.min(runtime_list)
@@ -158,7 +156,8 @@ def benchmark(controller, minimizers, num_runs):
                                         sorted_index=index)
 
         individual_result = \
-            misc.create_result_entry(problem=controller.problem,
+            misc.create_result_entry(options=options,
+                                     problem=controller.problem,
                                      status=status,
                                      chi_sq=chi_sq,
                                      runtime=runtime,
diff --git a/fitbenchmarking/fitting/misc.py b/fitbenchmarking/fitting/misc.py
index 3120ebfc..428f9940 100644
--- a/fitbenchmarking/fitting/misc.py
+++ b/fitbenchmarking/fitting/misc.py
@@ -12,11 +12,15 @@ def compute_chisq(actual, fitted, errors=None):
     Simple function that calculates the sum of the differences squared
     between the data and the fit.
 
-    @param actual :: The values from the known data
-    @param fitted :: The values from the fitted data
-    @param errors :: The values of the errors (weights)
+    :param actual: y data given in the fitting problem
+    :type actual: numpy.array
+    :param fitted: fitted result
+    :type fitted: numpy.array
+    :param errors: array of error values
+    :type errors: numpy.array or None
 
-    @returns :: the sum of the square of each element in differences
+    :returns: weighted or unweighted chi_sq value
+    :rtype: float
     """
     r = fitted - actual
     if errors is not None:
@@ -26,21 +30,31 @@ def compute_chisq(actual, fitted, errors=None):
     return chi_sq
 
 
-def create_result_entry(problem, status, chi_sq, runtime, minimizer,
+def create_result_entry(options, problem, status, chi_sq, runtime, minimizer,
                         ini_function_def, fin_function_def):
     """
     Helper function that creates a result object after fitting a problem
     with a certain function and minimizer.
 
-    @param problem :: problem object containing info that was fitted
-    @param status :: status of the fit, i.e. success or failure
-    @param chi_sq :: the chi squared of the fit
-    @param runtime :: the runtime of the fit
-    @param minimizer :: the minimizer used for this particular fit
-    @param ini_function_def :: the initial function definition for the fit
-    @param fin_function_def :: the final function definition for the fit
+    :param options: all the information specified by the user
+    :type options: fitbenchmarking.utils.options.Options
+    :param problem: problem object containing info that was fitted
+    :type problem: fitbenchmarking/parsing/fitting_problem/FittingProblem
+    :param status: status of the fit, i.e. success or failure
+    :type status: bool
+    :param chi_sq: the chi squared of the fit
+    :type chi_sq: float
+    :param runtime: the runtime of the fit
+    :type runtime: float
+    :param minimizer: the minimizer used for this particular fit
+    :type minimizer: str
+    :param ini_function_def: the initial function definition for the fit
+    :type ini_function_def: str
+    :param fin_function_def: the final function definition for the fit
+    :type fin_function_def: str
 
-    @returns :: the result object
+    :returns: results object
+    :rtype: fitbenchmarking.utils.fitbm_result.FittingResult
     """
 
     if 'fitFunction' in ini_function_def:
@@ -53,6 +67,7 @@ def create_result_entry(problem, status, chi_sq, runtime, minimizer,
     result = fitbm_result.FittingResult()
 
     # Populate result object
+    result.options = options
     result.problem = problem
     result.fit_status = status
     result.chi_sq = chi_sq
diff --git a/fitbenchmarking/results_output.py b/fitbenchmarking/results_output.py
index 077992e9..f62380f0 100644
--- a/fitbenchmarking/results_output.py
+++ b/fitbenchmarking/results_output.py
@@ -4,12 +4,11 @@ Functions that creates the tables and the visual display pages.
 
 from __future__ import (absolute_import, division, print_function)
 from collections import OrderedDict
+import copy
 import logging
-import numpy as np
 import os
 import pandas as pd
-import pytablewriter
-import re
+import pypandoc
 
 from fitbenchmarking.resproc import visual_pages
 from fitbenchmarking.utils import create_dirs
@@ -18,10 +17,6 @@ from fitbenchmarking.utils.logging_setup import logger
 # Some naming conventions for the output files
 FILENAME_SUFFIX_ACCURACY = 'acc'
 FILENAME_SUFFIX_RUNTIME = 'runtime'
-FILENAME_EXT_TXT = 'txt'
-FILENAME_EXT_HTML = 'html'
-
-HTML_COLOUR_SCALE = ['#fef0d9', '#fdcc8a', '#fc8d59', '#e34a33', '#b30000']
 
 
 def save_results_tables(options, results, group_name):
@@ -42,11 +37,8 @@ def save_results_tables(options, results, group_name):
     minimizers = [options.minimizers[s] for s in software]
     minimizers = sum(minimizers, [])
 
-    comparison_mode = options.comparison_mode
-
     results_dir = options.results_dir
     use_errors = options.use_errors
-    colour_scale = options.colour_scale
 
     weighted_str = 'weighted' if use_errors else 'unweighted'
 
@@ -54,23 +46,24 @@ def save_results_tables(options, results, group_name):
     linked_problems = \
         visual_pages.create_linked_probs(results, group_name, results_dir)
 
+    table_suffix = [FILENAME_SUFFIX_ACCURACY, FILENAME_SUFFIX_RUNTIME]
     table_names = []
-    for x in [FILENAME_SUFFIX_ACCURACY, FILENAME_SUFFIX_RUNTIME]:
+    for suffix in table_suffix:
         table_names.append(os.path.join(tables_dir,
                                         '{0}_{1}_{2}_table.'.format(
                                             group_name,
-                                            x,
+                                            suffix,
                                             weighted_str)))
     generate_tables(results, minimizers,
-                    linked_problems, colour_scale,
-                    comparison_mode, table_names)
+                    linked_problems, table_names,
+                    table_suffix)
 
     logging.shutdown()
 
 
 def generate_tables(results_per_test, minimizers,
-                    linked_problems, colour_scale,
-                    comparison_mode, table_names):
+                    linked_problems, table_names,
+                    table_suffix):
     """
     Generates accuracy and runtime tables, with both normalised and absolute
     results, and summary tables in both rst and html.
@@ -81,24 +74,17 @@ def generate_tables(results_per_test, minimizers,
     :type minimizers : list
     :param linked_problems : rst links for supporting pages
     :type linked_problems : list[str]
-    :param colour_scale : colour the html table
-    :type colour_scale : list
-    :param comparison_mode : check whether to produced 'rel', 'abs' or 'both'
-                              tables
-    :type comparison_mode : str
-    :param table_names : list of table names
-    :type table_names : list
+    :param table_name : list of table names
+    :type table_name : list
+    :param table_suffix : set output to be runtime or accuracy table
+    :type table_suffix : str
     """
 
-    acc_dict, time_dict, html_links = create_results_dict(results_per_test,
-                                                          linked_problems)
-    acc_tbl = create_pandas_dataframe(acc_dict, minimizers)
-    runtime_tbl = create_pandas_dataframe(time_dict, minimizers)
-
-    create_pandas_html(acc_tbl[comparison_mode], runtime_tbl[comparison_mode],
-                       minimizers, colour_scale, html_links, table_names)
-    create_pandas_rst(acc_tbl[comparison_mode], runtime_tbl[comparison_mode],
-                      minimizers, colour_scale, linked_problems, table_names)
+    results_dict, html_links = create_results_dict(results_per_test,
+                                                   linked_problems)
+    preproccess_data(results_dict)
+    table = create_pandas_dataframe(results_dict, minimizers, table_suffix)
+    render_pandas_dataframe(table, minimizers, html_links, table_names)
 
 
 def create_results_dict(results_per_test, linked_problems):
@@ -110,17 +96,16 @@ def create_results_dict(results_per_test, linked_problems):
     :param linked_problems : rst links for supporting pages
     :type linked_problems : list[str]
 
-    :return : tuple(acc_results, time_results, html_links)
-               dictionary of accuracy and timing results and
+    :return : tuple(results, html_links)
+               dictionary of results objects and
                html links for rending
-    :rtype : tuple(dict, dict, list)
+    :rtype : tuple(dict, list)
     """
 
     count = 1
     prev_name = ''
     template = '<a target="_blank" href="{0}">{1}</a>'
-    acc_results = OrderedDict()
-    time_results = OrderedDict()
+    results = OrderedDict()
     html_links = []
 
     for prob_results, link in zip(results_per_test, linked_problems):
@@ -133,158 +118,94 @@ def create_results_dict(results_per_test, linked_problems):
         prob_name = name + ' ' + str(count)
         url = link.split('<')[1].split('>')[0]
         html_links.append(template.format(url, prob_name))
-        acc_results[prob_name] = [result.chi_sq for result in prob_results]
-        time_results[prob_name] = [result.runtime for result in prob_results]
-    return acc_results, time_results, html_links
+        results[prob_name] = [result for result in prob_results]
+    return results, html_links
+
+
+def preproccess_data(data):
+    """
+    Helper function that preprocesses data into the right format for printing
+
+    :param data: dictionary of results objects
+    :type data: dict
+    """
+    for results in data.values():
+        min_chi_sq = min([r.chi_sq for r in results])
+        min_runtime = min([r.runtime for r in results])
+        for r in results:
+            r.min_chi_sq = min_chi_sq
+            r.min_runtime = min_runtime
+            r.set_colour_scale()
 
 
-def create_pandas_dataframe(table_data, minimizers):
+def create_pandas_dataframe(table_data, minimizers, table_suffix):
     """
-    Generates pandas tables.
+    Generates pandas data frame.
 
     :param table_data : dictionary containing results, i.e.,
                             {'prob1': [result1, result2, ...],
                              'prob2': [result1, result2, ...], ...}
     :type table_data : dict
     :param minimizers : list of minimizers (column headers)
-    :type group_name : list
+    :type minimizers : list
+    :param table_suffix : set output to be runtime or accuracy table
+    :type table_suffix : list
 
 
     :return : dict(tbl, tbl_norm, tbl_combined) dictionary of
                pandas DataFrames containing results.
-    :rtype : dict{pandas DataFrame, pandas DataFrame,
-                   pandas DataFrame}
+    :rtype : dict{pandas DataFrame, pandas DataFrame}
     """
 
+    # This function is only used in the mapping, hence, it is defined here.
+    def select_table(value, table_suffix):
+        '''
+        Selects either accuracy or runtime table.
+        '''
+        value.table_type = table_suffix
+        value = copy.copy(value)
+        return value
+
     tbl = pd.DataFrame.from_dict(table_data, orient='index')
     tbl.columns = minimizers
-
-    tbl_norm = tbl.apply(lambda x: x / x.min(), axis=1)
-    tbl_norm = tbl_norm.applymap(lambda x: '{:.4g}'.format(x))
-    tbl = tbl.applymap(lambda x: '{:.4g}'.format(x))
-
-    tbl_combined = OrderedDict()
-    for table1, table2 in zip(tbl.iterrows(), tbl_norm.iterrows()):
-        tbl_combined[table1[0]] = []
-        for value1, value2 in zip(table1[1].array, table2[1].array):
-            tbl_combined[table1[0]].append('{} ({})'.format(value1, value2))
-    tbl_combined = pd.DataFrame.from_dict(tbl_combined, orient='index')
-    tbl_combined.columns = minimizers
-    results_table = {'abs': tbl, 'rel': tbl_norm, 'both': tbl_combined}
-    return results_table
-
-
-def check_normalised(data, colours, colour_bounds):
-    """
-    Loops through row data of pandas data frame and assigns
-    colours depending on size.
-
-    :param data : Pandas row data
-    :type data : Pandas Series
-    :param colours : rst or html colour definitions
-    :type colours : list[str]
-    :param colour_bounds : rst or html colour bounds
-    :type colour_bounds : list[str]
-
-    :return : list of colour mappings with respect to the
-               size of the elements in the row
-    :rtype : list
-    """
-
-    data_numpy = data.array.to_numpy()
-    data_list = []
-    for x in data_numpy:
-        x = x.replace('nan', 'inf')
-        norm_stripped = re.findall(r'\(([^)]+)', x)
-        if norm_stripped == []:
-            data_list.append(float(x))
-        else:
-            data_list.append(float(norm_stripped[0]))
-
-    data_list = data_list / np.min(data_list)
-    results = len(data_list) * [colours[-1]]
-    for i, x in enumerate(data_list):
-        for j, y in enumerate(colour_bounds):
-            if x <= y:
-                results[i] = colours[j]
-                break
+    results = {}
+    for suffix in table_suffix:
+        results[suffix] = tbl.applymap(lambda x: select_table(x, suffix))
     return results
 
 
-def create_pandas_html(acc_tbl, runtime_tbl, minimizers,
-                       colour_scale, html_links, table_names):
+def render_pandas_dataframe(table_dict, minimizers, html_links, table_names):
     """
-    Generates html page from pandas dataframes.
+    Generates html and rst page from pandas dataframes.
 
-    :param acc_tbl : DataFrame of the accuracy results
-    :type acc_tbl : pandas DataFrame
-    :param runtime_tbl : DataFrame of the timing results
-    :type runtime_tbl : pandas DataFrame
+    :param table_dict : dictionary of DataFrame of the results
+    :type table_dict : dict(pandas DataFrame, ...)
     :param minimizers : list of minimizers (column headers)
     :type minimizers : list
-    :param colour_scale : user defined colour scale
-    :type colour_scale : list
     :param html_links : html links used in pandas rendering
     :type html_links : list
     :param table_names : list of table names
     :type table_names : list
     """
-    colour_bounds = [colour[0] for colour in colour_scale]
 
-    acc_tbl.index = html_links
-    runtime_tbl.index = html_links
-
-    def colour_highlight(data):
+    def colour_highlight(value):
         '''
         Colour mapping for visualisation of table
         '''
-        data_list = check_normalised(data, HTML_COLOUR_SCALE, colour_bounds)
+        colour = value.colour
+
+        return 'background-color: {0}'.format(colour)
 
-        return ['background-color: {0}'.format(i) for i in data_list]
-    results = [acc_tbl, runtime_tbl]
-    for table, name in zip(results, table_names):
-        table_style = table.style.apply(colour_highlight, axis=1)
+    for name, table in zip(table_names, table_dict.values()):
+        table.index = html_links
+        table_style = table.style.applymap(colour_highlight)
         with open(name + 'html', "w") as f:
             f.write(table_style.render())
 
-
-def create_pandas_rst(acc_tbl, runtime_tbl, minimizers,
-                      colour_scale, rst_links, table_names):
-    """
-    Generates html page from pandas dataframes.
-
-    :param acc_tbl : DataFrame of the accuracy results
-    :type acc_tbl : pandas DataFrame
-    :param runtime_tbl : DataFrame of the timing results
-    :type runtime_tbl : pandas DataFrame
-    :param minimizers : list of minimizers (column headers)
-    :type minimizers : list
-    :param colour_scale : user defined colour scale
-    :type colour_scale : list
-    :param rst_links : rst links used in pandas rendering
-    :type rst_links : list
-    :param table_names : list of table names
-    :type table_names : list
-    """
-    colour_bounds = [colour[0] for colour in colour_scale]
-    rst_colours = [colour[1] for colour in colour_scale]
-
-    acc_tbl.index = rst_links
-    runtime_tbl.index = rst_links
-
-    def colour_highlight(data):
-        '''
-        Colour mapping for visualisation of table
-        '''
-        data_list = check_normalised(data, rst_colours, colour_bounds)
-        for i, x in enumerate(data):
-            data[i] = ':{}:`{}`'.format(data_list[i], x)
-
-        return data
-
-    results = [acc_tbl, runtime_tbl]
-    for table, name in zip(results, table_names):
-        table.apply(colour_highlight, axis=1)
-        writer = pytablewriter.RstGridTableWriter()
-        writer.from_dataframe(table, add_index_column=True)
-        writer.dump(name + "rst")
+        # pypandoc can be installed without pandoc
+        try:
+            output = pypandoc.convert_file(name + 'html', 'rst')
+            with open(name + 'rst', "w") as f:
+                f.write(output)
+        except ImportError:
+            print('RST tables require Pandoc to be installed')
diff --git a/fitbenchmarking/utils/default_options.ini b/fitbenchmarking/utils/default_options.ini
index 02567935..422b70d5 100644
--- a/fitbenchmarking/utils/default_options.ini
+++ b/fitbenchmarking/utils/default_options.ini
@@ -90,13 +90,14 @@ use_errors: yes
 # colour_scale lists thresholds for each colour in the html table
 #              In the below example, this means that values less than 1.1 will
 #              have the top ranking (brightest) and values over 3 will show as
-#              the worst ranking (deep red)
+#              the worst ranking (deep red). One can change the bounds (1.1,
+#              1.33, etc) and the colouring here or within the run script.
 # default thresholds are 1.1, 1.33, 1.75, 3, and inf
-colour_scale: 1.1, ranking-top-1
-              1.33, ranking-top-2
-              1.75, ranking-med-3
-              3, ranking-low-4
-              inf, ranking-low-5
+colour_scale: 1.1, #fef0d9
+              1.33, #fdcc8a
+              1.75, #fc8d59
+              3, #e34a33
+              inf, #b30000
 
 # comparison_mode selects the mode for displaying values in the resulting
 #                 table
@@ -110,4 +111,4 @@ comparison_mode: both
 
 # results_dir is used to select where the output should be saved
 # default is results
-results_dir: results
\ No newline at end of file
+results_dir: results
diff --git a/fitbenchmarking/utils/fitbm_result.py b/fitbenchmarking/utils/fitbm_result.py
index 8b949edb..0828bbf8 100644
--- a/fitbenchmarking/utils/fitbm_result.py
+++ b/fitbenchmarking/utils/fitbm_result.py
@@ -1,5 +1,7 @@
 from __future__ import (absolute_import, division, print_function)
 
+import numpy as np
+
 
 class FittingResult(object):
     """
@@ -8,10 +10,11 @@ class FittingResult(object):
     """
 
     def __init__(self):
+        self.options = None
         self.problem = None
         self.fit_status = None
         self.chi_sq = None
-
+        self._min_chi_sq = None
         # Workspace with data to fit
         self.fit_wks = None
         self.params = None
@@ -19,11 +22,83 @@ class FittingResult(object):
 
         # Time it took to run the Fit algorithm
         self.runtime = None
-
+        self._min_runtime = None
         # Best minimizer for a certain problem and its function definition
         self.minimizer = None
         self.ini_function_def = None
         self.fin_function_def = None
 
-    def __repr__(self):
-        return 'Fitting problem class: minimizer = {0}'.format(self.minimizer)
+        self.value = None
+        self.norm_value = None
+        self.colour = None
+        self.colour_runtime = None
+        self.colour_acc = None
+
+        # Defines the type of table to be produced
+        self._table_type = None
+
+    def __str__(self):
+        if self.table_type is not None:
+            if self.options.comparison_mode == "abs":
+                output = "{:.4g}".format(self.value)
+            elif self.options.comparison_mode == "rel":
+                output = "{:.4g}".format(self.value)
+            elif self.options.comparison_mode == "both":
+                output = "{:.4g} ({:.4g})".format(self.value, self.norm_value)
+        else:
+            output = 'Fitting problem class: minimizer = {0}'.format(
+                self.minimizer)
+        return output
+
+    @property
+    def table_type(self):
+        return self._table_type
+
+    @table_type.setter
+    def table_type(self, value):
+        self._table_type = value
+        if value == "runtime":
+            self.value = self.runtime
+            self.norm_value = self.norm_runtime
+            self.colour = self.colour_runtime
+        if value == "acc":
+            self.value = self.chi_sq
+            self.norm_value = self.norm_acc
+            self.colour = self.colour_acc
+
+    def set_colour_scale(self):
+        """
+        Utility function set colour rendering for html tables
+        """
+        colour_scale = self.options.colour_scale
+        colour_bounds = [colour[0] for colour in colour_scale]
+        # prepending 0 value for colour bound
+        colour_bounds = [0] + colour_bounds
+        html_colours = [colour[1] for colour in colour_scale]
+        self.colour_runtime = colour_scale[-1]
+        self.colour_acc = colour_scale[-1]
+        for i in range(len(colour_bounds) - 1):
+            if colour_bounds[i] < self.norm_runtime <= colour_bounds[i + 1]:
+                self.colour_runtime = html_colours[i]
+            if colour_bounds[i] < self.norm_acc <= colour_bounds[i + 1]:
+                self.colour_acc = html_colours[i]
+
+    @property
+    def min_chi_sq(self):
+        return self._min_chi_sq
+
+    @min_chi_sq.setter
+    def min_chi_sq(self, value):
+        self._min_chi_sq = value
+        if not self.chi_sq > 0:
+            self.chi_sq = np.inf
+        self.norm_acc = self.chi_sq / self.min_chi_sq
+
+    @property
+    def min_runtime(self):
+        return self._min_runtime
+
+    @min_runtime.setter
+    def min_runtime(self, value):
+        self._min_runtime = value
+        self.norm_runtime = self.runtime / self.min_runtime
diff --git a/setup.py b/setup.py
index 1b67d1a0..60114dbd 100644
--- a/setup.py
+++ b/setup.py
@@ -20,7 +20,7 @@ setup(name='FitBenchmarking',
                         'lxml',
                         'pandas<=0.24.2',
                         'jinja2',
-                        'pytablewriter<=0.46.1',
+                        'pypandoc',
                         'dfogn',
                         'iminuit',
                         'configparser'],
@@ -30,5 +30,4 @@ setup(name='FitBenchmarking',
           'externals': InstallExternals,
           'help': Help,
       },
-      package_data={'fitbenchmarking': ['resproc/color_definitions.txt']}
       )


Test patch:
diff --git a/fitbenchmarking/fitting/tests/test_misc.py b/fitbenchmarking/fitting/tests/test_misc.py
index 399d0819..0c5f1da9 100644
--- a/fitbenchmarking/fitting/tests/test_misc.py
+++ b/fitbenchmarking/fitting/tests/test_misc.py
@@ -9,6 +9,8 @@ from fitbenchmarking.parsing.fitting_problem import FittingProblem
 from fitbenchmarking.fitting.misc import compute_chisq
 from fitbenchmarking.fitting.misc import create_result_entry
 
+from fitbenchmarking.utils.options import Options
+
 
 class FitMiscTests(unittest.TestCase):
 
@@ -47,7 +49,7 @@ class FitMiscTests(unittest.TestCase):
         return prob
 
     def create_expected_result(self):
-
+        options = Options()
         result = fitbm_result.FittingResult()
         problem = self.setup_nist_expected_problem()
         result.problem = problem
@@ -91,10 +93,12 @@ class FitMiscTests(unittest.TestCase):
         minimizer = 'Test'
         ini_function_def = 'ini_def_test'
         fin_function_def = 'fin_def_test'
+        options = Options()
 
         result = \
-            create_result_entry(problem, status, chi_sq, runtime, minimizer,
-                                ini_function_def, fin_function_def)
+            create_result_entry(options, problem, status, chi_sq,
+                                runtime, minimizer, ini_function_def,
+                                fin_function_def)
         result_expected = self.create_expected_result()
 
         self.assertEqual(result_expected.fit_status, result.fit_status)
diff --git a/fitbenchmarking/utils/tests/test_options.py b/fitbenchmarking/utils/tests/test_options.py
index 95377bed..018012a7 100644
--- a/fitbenchmarking/utils/tests/test_options.py
+++ b/fitbenchmarking/utils/tests/test_options.py
@@ -31,8 +31,8 @@ class OptionsTests(unittest.TestCase):
                       bar
 
             [PLOTTING]
-            colour_scale: 17.1, a_string?
-                          inf, another_string
+            colour_scale: 17.1, b_string?
+                          inf, final_string
             comparison_mode: abs
             results_dir: new_results
             """
@@ -42,9 +42,8 @@ class OptionsTests(unittest.TestCase):
                 'FITTING': {'use_errors': False,
                             'num_runs': 2,
                             'software': ['foo', 'bar']},
-                'PLOTTING': {'colour_scale': [(17.1, 'a_string?'),
-                                              (float('inf'),
-                                               'another_string')],
+                'PLOTTING': {'colour_scale': [(17.1, 'b_string?'),
+                                              (float('inf'), 'final_string')],
                              'comparison_mode': 'abs',
                              'results_dir': 'new_results'}
                 }
@@ -53,7 +52,6 @@ class OptionsTests(unittest.TestCase):
             datetime.datetime.now())
         with open(opts_file, 'w') as f:
             f.write(config_str)
-
         self.options = opts
         self.options_file = opts_file