Repository: fitbenchmarking/fitbenchmarking

Issue:
Performance profiles for emissions
**Description of the error**
When looking at the emissions table I see a runtime pp

**Describe the expected result**
We should have a separate emissions pp

**Suggested fix**
Copy code for runtime and accuracy to make emissions a first class citizen


Discussion:
github-actions[bot]: ## Unit & System Test Results
  4 files  ±0    4 suites  ±0   25m 12s :stopwatch: -47s
454 tests ±0  446 :white_check_mark: ±0   8 :zzz: ±0  0 :x: ±0 
876 runs  ±0  821 :white_check_mark: ±0  55 :zzz: ±0  0 :x: ±0 

Results for commit 4ed3fc82. ± Comparison against base commit 8065db0f.

[test-results]:data:application/gzip;base64,H4sIALRRlGUC/03MTQ6DIBCG4asY1l0Igo69TAOjJJP60yCsmt69oxVqMpv3meR7C0/TuIl7pW+V2BLFEkMKNtK6cEojFQv/4vE1OtdjS4g76fZPT3oxQQFvaWKoC4whrOGUkJZ9E7r2jDwJShb5LRqT4bJ49HUQ13mmyCH0ODQeQaHqbWsa4PNQOytVz+odQgfoUIrPFybUtogHAQAA


Patch:
diff --git a/fitbenchmarking/results_processing/acc_table.py b/fitbenchmarking/results_processing/acc_table.py
index 2e5f350c..98fafe61 100644
--- a/fitbenchmarking/results_processing/acc_table.py
+++ b/fitbenchmarking/results_processing/acc_table.py
@@ -34,9 +34,8 @@ class AccTable(Table):
         :param group_dir: path to the directory where group results should be
                           stored
         :type group_dir: str
-        :param pp_locations: tuple containing the locations of the
-                             performance profiles (acc then runtime)
-        :type pp_locations: tuple(str,str)
+        :param pp_locations: the locations of the performance profiles
+        :type pp_locations: dict[str,str]
         :param table_name: Name of the table
         :type table_name: str
         """
@@ -49,7 +48,9 @@ class AccTable(Table):
 
         self.pp_dash_urls = [f'http://127.0.0.1:{port}/{group_dir_label}/'
                              'perf_prof_acc']
-        self.pp_filenames = [os.path.relpath(self.pp_locations[0], group_dir)]
+        self.pp_filenames = [
+            os.path.relpath(self.pp_locations['acc'], group_dir)
+        ]
         self.cbar_title = "Problem-Specific Cell Shading: Relative Accuracy"
 
     def get_value(self, result):
diff --git a/fitbenchmarking/results_processing/base_table.py b/fitbenchmarking/results_processing/base_table.py
index 2f93bd35..4d0c893f 100644
--- a/fitbenchmarking/results_processing/base_table.py
+++ b/fitbenchmarking/results_processing/base_table.py
@@ -50,9 +50,8 @@ class Table:
         :param group_dir: path to the directory where group results should be
                           stored
         :type group_dir: str
-        :param pp_locations: tuple containing the locations of the
-                             performance profiles (acc then runtime)
-        :type pp_locations: tuple(str,str)
+        :param pp_locations: the locations of the performance profiles
+        :type pp_locations: dict[str,str]
         :param table_name: Name of the table
         :type table_name: str
         """
diff --git a/fitbenchmarking/results_processing/compare_table.py b/fitbenchmarking/results_processing/compare_table.py
index f3e3666b..a8c530e8 100644
--- a/fitbenchmarking/results_processing/compare_table.py
+++ b/fitbenchmarking/results_processing/compare_table.py
@@ -32,9 +32,8 @@ class CompareTable(Table):
         :param group_dir: path to the directory where group results should be
                           stored
         :type group_dir: str
-        :param pp_locations: tuple containing the locations of the
-                             performance profiles (acc then runtime)
-        :type pp_locations: tuple(str,str)
+        :param pp_locations: the locations of the performance profiles
+        :type pp_locations: dict[str,str]
         :param table_name: Name of the table
         :type table_name: str
         """
@@ -46,7 +45,8 @@ class CompareTable(Table):
         group_dir_label = os.path.basename(group_dir)
 
         self.pp_filenames = \
-            [os.path.relpath(pp, group_dir) for pp in pp_locations]
+            [os.path.relpath(pp_locations[pp], group_dir)
+             for pp in ['acc', 'runtime']]
 
         self.pp_dash_urls = [f'http://127.0.0.1:{port}/{group_dir_label}/'
                              f'perf_prof_{metric}'
diff --git a/fitbenchmarking/results_processing/emissions_table.py b/fitbenchmarking/results_processing/emissions_table.py
index a99218f1..8b10cb00 100644
--- a/fitbenchmarking/results_processing/emissions_table.py
+++ b/fitbenchmarking/results_processing/emissions_table.py
@@ -40,9 +40,8 @@ class EmissionsTable(Table):
         :param group_dir: path to the directory where group results should be
                           stored
         :type group_dir: str
-        :param pp_locations: tuple containing the locations of the
-                             performance profiles (acc then runtime)
-        :type pp_locations: tuple(str,str)
+        :param pp_locations: the locations of the performance profiles
+        :type pp_locations: dict[str,str]
         :param table_name: Name of the table
         :type table_name: str
         """
@@ -51,7 +50,8 @@ class EmissionsTable(Table):
                          pp_locations, table_name)
         self.name = 'emissions'
         self.has_pp = True
-        self.pp_filenames = [os.path.relpath(self.pp_locations[1], group_dir)]
+        self.pp_filenames = [
+            os.path.relpath(self.pp_locations['emissions'], group_dir)]
         self.pp_dash_urls = ['None']
 
         self.cbar_title = "Problem-Specific Cell Shading: Relative Emissions"
diff --git a/fitbenchmarking/results_processing/local_min_table.py b/fitbenchmarking/results_processing/local_min_table.py
index 26bf6bba..0d1ad8eb 100644
--- a/fitbenchmarking/results_processing/local_min_table.py
+++ b/fitbenchmarking/results_processing/local_min_table.py
@@ -54,9 +54,8 @@ class LocalMinTable(Table):
         :param group_dir: path to the directory where group results should be
                           stored
         :type group_dir: str
-        :param pp_locations: tuple containing the locations of the
-                             performance profiles (acc then runtime)
-        :type pp_locations: tuple(str,str)
+        :param pp_locations: the locations of the performance profiles
+        :type pp_locations: dict[str,str]
         :param table_name: Name of the table
         :type table_name: str
         """
@@ -68,7 +67,8 @@ class LocalMinTable(Table):
 
         self.has_pp = True
         self.pp_filenames = \
-            [os.path.relpath(pp, group_dir) for pp in pp_locations]
+            [os.path.relpath(pp_locations[pp], group_dir)
+             for pp in ['acc', 'runtime']]
 
         self.pp_dash_urls = [f'http://127.0.0.1:{port}/{group_dir_label}/'
                              f'perf_prof_{metric}'
diff --git a/fitbenchmarking/results_processing/performance_profiler.py b/fitbenchmarking/results_processing/performance_profiler.py
index 5936d017..4f8f024a 100644
--- a/fitbenchmarking/results_processing/performance_profiler.py
+++ b/fitbenchmarking/results_processing/performance_profiler.py
@@ -23,31 +23,34 @@ def profile(results, fig_dir, options):
     :param options: The options for the run
     :type options: utils.options.Options
 
-    :return: Path to acc and runtime profile graphs,
+    :return: Path to performance profile graphs,
              data for plotting the performance profiles
-    :rtype: (str, str), dict[str, pandas.DataFrame]
+    :rtype: dict[str, str], dict[str, pandas.DataFrame]
     """
-    acc_bound, runtime_bound = prepare_profile_data(results)
-    plot_path, pp_dfs = get_plot_path_and_data(acc_bound,
-                                               runtime_bound,
-                                               fig_dir,
-                                               options)
-    return plot_path, pp_dfs
+    bounds = prepare_profile_data(results)
+    plot_paths, pp_dfs = get_plot_path_and_data(bounds,
+                                                fig_dir,
+                                                options)
+    return plot_paths, pp_dfs
 
 
 def prepare_profile_data(results):
     """
-    Helper function which generates acc and runtime dictionaries which
-    contain the values for each minimizer.
+    Helper function which generates dictionaries for each metric containing
+    names of solvers as the keys and a list of floats (one for each problem)
+    as the values.
 
     :param results: The sorted results grouped by row and category
     :type results: dict[str, dict[str, list[utils.fitbm_result.FittingResult]]]
 
     :return: dictionary containing number of occurrences
-    :rtype: tuple(dict, dict)
+    :rtype: dict[str, dict[str, list[float]]]
     """
-    acc_dict = {}
-    runtime_dict = {}
+    pp_data = {
+        'acc': {},
+        'runtime': {},
+        'emissions': {}
+    }
     minimizers = []
     to_remove = set()
 
@@ -57,46 +60,47 @@ def prepare_profile_data(results):
                 key = result.modified_minimizer_name(with_software=True)
                 if len(minimizers) <= i:
                     minimizers.append(key)
-                    acc_dict[key] = []
-                    runtime_dict[key] = []
+                    for pp in pp_data.values():
+                        pp[key] = []
                 elif len(key) > len(minimizers[i]):
                     to_remove.add(minimizers[i])
-                    acc_dict[key] = acc_dict[minimizers[i]].copy()
-                    runtime_dict[key] = runtime_dict[minimizers[i]].copy()
+                    for pp in pp_data.values():
+                        pp[key] = pp[minimizers[i]].copy()
                     minimizers[i] = key
-                acc_dict[minimizers[i]].append(result.norm_acc)
-                runtime_dict[minimizers[i]].append(result.norm_runtime)
+                pp_data['acc'][minimizers[i]].append(result.norm_acc)
+                pp_data['runtime'][minimizers[i]].append(result.norm_runtime)
+                pp_data['emissions'][minimizers[i]].append(
+                    result.norm_emissions)
 
     for key in to_remove:
-        del acc_dict[key]
-        del runtime_dict[key]
+        for pp in pp_data.values():
+            del pp[key]
 
-    return acc_dict, runtime_dict
+    return pp_data
 
 
-def get_plot_path_and_data(acc, runtime, fig_dir, options):
+def get_plot_path_and_data(bounds, fig_dir, options):
     """
     Function that generates profiler plots
 
-    :param acc: acc dictionary containing number of occurrences
-    :type acc: dict
-    :param runtime: runtime dictionary containing number of occurrences
-    :type runtime: dict
+    :param bounds: For each metric, a dictionary of solver names and the list
+                   of values (one for each problem)
+    :type bounds: dict[str, dict[str, list[float]]]
     :param fig_dir: path to directory containing the figures
     :type fig_dir: str
     :param options: The options for the run
     :type options: utils.options.Options
 
-    :return: path to acc and runtime profile graphs,
-             data for plotting the graphs
-    :rtype: list[str], dict[str, pandas.DataFrame]
+    :return: path to profile graphs for each metric,
+             data for plotting the graphs for each metric
+    :rtype: dict[str, str], dict[str, pandas.DataFrame]
     """
-    figure_path = []
+    figure_paths = {}
     pp_dfs = {}
-    for profile_plot, name in zip([acc, runtime], ["acc", "runtime"]):
+    for name, profile_plot in bounds.items():
         this_filename_html = os.path.join(fig_dir, f"{name}_profile.html")
 
-        figure_path.append(this_filename_html)
+        figure_paths[name] = this_filename_html
 
         step_values = []
         max_value = 0.0
@@ -130,7 +134,7 @@ def get_plot_path_and_data(acc, runtime, fig_dir, options):
                                            this_filename_html,
                                            options)
 
-    return figure_path, pp_dfs
+    return figure_paths, pp_dfs
 
 
 def update_fig(fig: go.Figure, name: str, use_log_plot: bool,
@@ -276,7 +280,7 @@ def create_plot_and_df(step_values: 'list[np.ndarray]',
                        name=solver,
                        type='scatter'
                        )
-            )
+        )
 
     pp_df = create_df(all_solvers,
                       all_solver_values,
@@ -364,18 +368,17 @@ class DashPerfProfile():
                        "font-size": "15px"}
             ),
             dcc.Graph(id=f"visual {self.identif}")
-            ],
+        ],
         )
         return layout
 
     def set_callbacks(self):
-
         """Calls callbacks on the function that creates the dash graph."""
 
         dash.callback(
             Output(f"visual {self.identif}", "figure"),
             Input(f"Log axis toggle {self.identif}", "value")
-            )(self.create_graph)
+        )(self.create_graph)
 
     def create_graph(self, x_axis_scale):
         """
diff --git a/fitbenchmarking/results_processing/runtime_table.py b/fitbenchmarking/results_processing/runtime_table.py
index 618d4486..e459dd92 100644
--- a/fitbenchmarking/results_processing/runtime_table.py
+++ b/fitbenchmarking/results_processing/runtime_table.py
@@ -32,9 +32,8 @@ class RuntimeTable(Table):
         :param group_dir: path to the directory where group results should be
                           stored
         :type group_dir: str
-        :param pp_locations: tuple containing the locations of the
-                             performance profiles (acc then runtime)
-        :type pp_locations: tuple(str,str)
+        :param pp_locations: the locations of the performance profiles
+        :type pp_locations: dict[str,str]
         :param table_name: Name of the table
         :type table_name: str
         """
@@ -48,7 +47,9 @@ class RuntimeTable(Table):
 
         self.pp_dash_urls = [f'http://127.0.0.1:{port}/{group_dir_label}/'
                              f'perf_prof_runtime']
-        self.pp_filenames = [os.path.relpath(self.pp_locations[1], group_dir)]
+        self.pp_filenames = [
+            os.path.relpath(self.pp_locations['runtime'], group_dir)
+        ]
 
         self.cbar_title = "Problem-Specific Cell Shading: Relative Runtime"
 
diff --git a/fitbenchmarking/results_processing/tables.py b/fitbenchmarking/results_processing/tables.py
index 71cf0237..3608179f 100644
--- a/fitbenchmarking/results_processing/tables.py
+++ b/fitbenchmarking/results_processing/tables.py
@@ -44,9 +44,8 @@ def create_results_tables(options, results, best_results, group_dir, fig_dir,
     :type group_dir: str
     :param fig_dir: path to the directory where figures should be stored
     :type fig_dir: str
-    :param pp_locations: tuple containing the locations of the
-                         performance profiles (acc then runtime)
-    :type pp_locations: tuple(str,str)
+    :param pp_locations: the locations of the performance profiles
+    :type pp_locations: dict[str,str]
     :param failed_problems: list of failed problems to be reported in the
                             html output
     :type failed_problems: list
@@ -104,7 +103,7 @@ def create_results_tables(options, results, best_results, group_dir, fig_dir,
 
             run_name = f"{options.run_name}: " if options.run_name else ""
 
-            with open(f'{table.file_path}csv', "w") as f:
+            with open(f'{table.file_path}csv', "w", encoding='utf-8') as f:
                 f.write(csv_table)
 
             report_failed_min = \
@@ -195,9 +194,8 @@ def generate_table(results, best_results, options, group_dir, fig_dir,
     :type group_dir: str
     :param fig_dir: path to the directory where figures should be stored
     :type fig_dir: str
-    :param pp_locations: tuple containing the locations of the
-                         performance profiles (acc then runtime)
-    :type pp_locations: tuple(str,str)
+    :param pp_locations: the locations of the performance profiles
+    :type pp_locations: dict[str,str]
     :param table_name: name of the table
     :type table_name: str
     :param suffix: table suffix


Test patch:
diff --git a/fitbenchmarking/results_processing/tests/test_base_table.py b/fitbenchmarking/results_processing/tests/test_base_table.py
index 11bc43c6..9c68abe9 100644
--- a/fitbenchmarking/results_processing/tests/test_base_table.py
+++ b/fitbenchmarking/results_processing/tests/test_base_table.py
@@ -72,7 +72,9 @@ class CreateResultsDictTests(TestCase):
                            best_results=best_results,
                            options=Options(),
                            group_dir='fake',
-                           pp_locations=('no', 'pp'),
+                           pp_locations={'acc': 'no',
+                                         'runtime': 'pp',
+                                         'emissions': 'available'},
                            table_name='A table!')
 
         results_dict = table.sorted_results
@@ -106,7 +108,9 @@ class DisplayStrTests(TestCase):
                                 best_results=best_results,
                                 options=Options(),
                                 group_dir='fake',
-                                pp_locations=('no', 'pp'),
+                                pp_locations={'acc': 'no',
+                                              'runtime': 'pp',
+                                              'emissions': 'available'},
                                 table_name='A table!')
 
     def test_display_str_abs(self):
@@ -147,7 +151,9 @@ class SaveColourbarTests(TestCase):
                                 best_results=best_results,
                                 options=Options(),
                                 group_dir=self.root_directory,
-                                pp_locations=('no', 'pp'),
+                                pp_locations={'acc': 'no',
+                                              'runtime': 'pp',
+                                              'emissions': 'available'},
                                 table_name='A table!')
 
     @mock.patch("fitbenchmarking.results_processing.base_table.plt.savefig")
@@ -173,17 +179,18 @@ class TestContrastRatio(TestCase):
     """
     Tests the three functions used for calculating the contrast ratio
     """
+
     def test_calculate_luminance(self):
         """
         Tests the luminance calculation using 6 subtests
         """
         luminance_test = (
-             {'case': 'black', 'rgb': [0, 0, 0], 'output': 0},
-             {'case': 'white', 'rgb': [1, 1, 1], 'output': 1},
-             {'case': 'red', 'rgb': [1, 0, 0], 'output': 0.2126},
-             {'case': 'green', 'rgb': [0, 1, 0], 'output': 0.7152},
-             {'case': 'blue', 'rgb': [0, 0, 1], 'output': 0.0722},
-             {'case': 'purple', 'rgb': [1, 0, 1], 'output': 0.2848}
+            {'case': 'black', 'rgb': [0, 0, 0], 'output': 0},
+            {'case': 'white', 'rgb': [1, 1, 1], 'output': 1},
+            {'case': 'red', 'rgb': [1, 0, 0], 'output': 0.2126},
+            {'case': 'green', 'rgb': [0, 1, 0], 'output': 0.7152},
+            {'case': 'blue', 'rgb': [0, 0, 1], 'output': 0.0722},
+            {'case': 'purple', 'rgb': [1, 0, 1], 'output': 0.2848}
         )
         for test in luminance_test:
             with self.subTest(test['case']):
@@ -195,18 +202,18 @@ class TestContrastRatio(TestCase):
         Tests the contrast ratio calculation using 6 subtests
         """
         contrast_test = (
-             {'case': '1', 'background': [0, 0, 0],
-              'foreground': [1, 1, 1], 'output': 21.00},
-             {'case': '2', 'background': [1, 0, 0],
-              'foreground': [0, 0, 0], 'output': 5.25},
-             {'case': '3', 'background': [0, 1, 0],
-              'foreground': [0, 0, 0], 'output': 15.30},
-             {'case': '4', 'background': [0, 1, 0],
-              'foreground': [1, 1, 1], 'output': 1.37},
-             {'case': '5', 'background': [0, 0, 1],
-              'foreground': [1, 1, 1], 'output': 8.59},
-             {'case': '6', 'background': [0, 0, 1],
-              'foreground': [0, 0, 0], 'output': 2.44}
+            {'case': '1', 'background': [0, 0, 0],
+             'foreground': [1, 1, 1], 'output': 21.00},
+            {'case': '2', 'background': [1, 0, 0],
+             'foreground': [0, 0, 0], 'output': 5.25},
+            {'case': '3', 'background': [0, 1, 0],
+             'foreground': [0, 0, 0], 'output': 15.30},
+            {'case': '4', 'background': [0, 1, 0],
+             'foreground': [1, 1, 1], 'output': 1.37},
+            {'case': '5', 'background': [0, 0, 1],
+             'foreground': [1, 1, 1], 'output': 8.59},
+            {'case': '6', 'background': [0, 0, 1],
+             'foreground': [0, 0, 0], 'output': 2.44}
         )
         for test in contrast_test:
             with self.subTest(test['case']):
@@ -219,21 +226,21 @@ class TestContrastRatio(TestCase):
         Tests the function that determines the text colour
         """
         test_cases = (
-             {'case': '1',
-              'background': [0, 0, 0],
-              'output': 'rgb(255,255,255)'},
-             {'case': '2',
-              'background': [1, 1, 1],
-              'output': 'rgb(0,0,0)'},
-             {'case': '3',
-              'background': [1, 0, 0],
-              'output': 'rgb(0,0,0)'},
-             {'case': '4',
-              'background': [0, 1, 0],
-              'output': 'rgb(0,0,0)'},
-             {'case': '4',
-              'background': [0, 0, 1],
-              'output': 'rgb(255,255,255)'},
+            {'case': '1',
+             'background': [0, 0, 0],
+             'output': 'rgb(255,255,255)'},
+            {'case': '2',
+             'background': [1, 1, 1],
+             'output': 'rgb(0,0,0)'},
+            {'case': '3',
+             'background': [1, 0, 0],
+             'output': 'rgb(0,0,0)'},
+            {'case': '4',
+             'background': [0, 1, 0],
+             'output': 'rgb(0,0,0)'},
+            {'case': '4',
+             'background': [0, 0, 1],
+             'output': 'rgb(255,255,255)'},
         )
         for test in test_cases:
             with self.subTest(test['case']):
diff --git a/fitbenchmarking/results_processing/tests/test_performance_profiler.py b/fitbenchmarking/results_processing/tests/test_performance_profiler.py
index 22748209..0f65296c 100644
--- a/fitbenchmarking/results_processing/tests/test_performance_profiler.py
+++ b/fitbenchmarking/results_processing/tests/test_performance_profiler.py
@@ -145,9 +145,22 @@ class PerformanceProfilerTests(unittest.TestCase):
             self.runtime_expected[k] = [
                 v/min_runtime for v in self.runtime_expected[k]]
 
+        min_emissions = 1.0
+        self.emissions_expected = {
+            'm00 [s0]: j:j0': [np.inf, np.inf],
+            'm00 [s0]: j:j1': [np.inf, np.inf],
+            'm01 [s0]: j:j0': [1e4, 10.0],
+            'm01 [s0]: j:j1': [1.0, 1e4],
+            'm10 [s1]: j:j0': [1e4, 1.0],
+            'm10 [s1]: j:j1': [1e2, 1e2],
+            'm11 [s1]: j:j0': [1e3, 1.0],
+            'm11 [s1]: j:j1': [1e2, 1e2],
+        }
+        for k in self.emissions_expected:
+            self.emissions_expected[k] = [
+                v/min_emissions for v in self.emissions_expected[k]]
+
         self.fig_dir = ''
-        self.acc_name = "acc_profile.html"
-        self.runtime_name = "runtime_profile.html"
 
         root = os.path.dirname(getfile(fitbenchmarking))
         self.expected_results_dir = os.path.join(
@@ -164,7 +177,7 @@ class PerformanceProfilerTests(unittest.TestCase):
                       15.4, 25.9, 600.]),
             np.array([0., 2., 3.5, 5.8, 7., 10.,
                       25.4, 45.9, 800.])
-            ]
+        ]
         self.solver_values = [
             np.array([0., 5.4, 11., 20., 59.1,
                       130.5, 300.1, 600.5, 1000]),
@@ -172,7 +185,7 @@ class PerformanceProfilerTests(unittest.TestCase):
                       130.5, 200.8, 300, 5000]),
             np.array([0., 3.5, 7.2, 17.1, 29.6, 50.1,
                       78.6, 230.5, 770.1]),
-            ]
+        ]
 
         # pylint: disable=consider-using-with
         self._dir = TemporaryDirectory()
@@ -181,42 +194,45 @@ class PerformanceProfilerTests(unittest.TestCase):
 
     def tearDown(self):
         """
-        Removes expected acc and runtime plots
+        Removes expected plots
         """
-        for name in [self.acc_name, self.runtime_name]:
-            if os.path.isfile(name):
-                os.remove(name)
+        for metric in ['acc', 'runtime', 'emissions']:
+            if os.path.isfile(f"{metric}_profile.html"):
+                os.remove(f"{metric}_profile.html")
 
     def test_correct_prepare_profile_data(self):
         """
         Test that prepare profile data gives the correct result.
         """
-        acc, runtime = performance_profiler.prepare_profile_data(self.results)
+        bounds = performance_profiler.prepare_profile_data(self.results)
 
         for k, v in self.accuracy_expected.items():
-            assert np.allclose(v, acc[k])
+            assert np.allclose(v, bounds['acc'][k])
         for k, v in self.runtime_expected.items():
-            assert np.allclose(v, runtime[k])
+            assert np.allclose(v, bounds['runtime'][k])
+        for k, v in self.emissions_expected.items():
+            assert np.allclose(v, bounds['emissions'][k])
 
     # pylint: disable=W0632
     def test_correct_profile_output_paths(self):
         """
         Test that the performance profiler returns the expected paths.
         """
-        (acc, runtime), _ = performance_profiler.profile(self.results,
-                                                         self.fig_dir,
-                                                         self.options)
-        assert acc == "acc_profile.html"
-        assert runtime == "runtime_profile.html"
+        pp_locations, _ = performance_profiler.profile(self.results,
+                                                       self.fig_dir,
+                                                       self.options)
+        assert pp_locations['acc'] == "acc_profile.html"
+        assert pp_locations['runtime'] == "runtime_profile.html"
+        assert pp_locations['emissions'] == "emissions_profile.html"
 
     def test_profile_returns_dict(self):
         """
         Test that the performance profiler returns a dictionary
         of dataframes for plotting the profiles.
         """
-        (_, _), pp_dfs = performance_profiler.profile(self.results,
-                                                      self.fig_dir,
-                                                      self.options)
+        _, pp_dfs = performance_profiler.profile(self.results,
+                                                 self.fig_dir,
+                                                 self.options)
         assert isinstance(pp_dfs, dict)
         for df in list(pp_dfs.values()):
             assert isinstance(df, pd.DataFrame)
@@ -291,9 +307,13 @@ class PerformanceProfilerTests(unittest.TestCase):
         runtime = {'migrad [minuit]': [4.6, 6.4, 1.3, 8.5, 51.6, 10.8, 15.2],
                    'simplex [minuit]': [6.9, 15.2, 6.5, 5.6, 7., 8.5, 6.5],
                    'dfogn [dfo]': [8.6, 7.4, 51.6, 6.9, 6.5,  28.3, 17.2]}
+        emissions = {'migrad [minuit]': [0.1, 0.4, 0.3, 0.5, 0.6, 0.8, 0.2],
+                     'simplex [minuit]': [0.9, 0.2, 0.5, 0.6, 0.0, 0.5, 0.5],
+                     'dfogn [dfo]': [0.6, 0.4, 0.6, 0.9, 0.5,  0.3, 0.2]}
 
+        bounds = {'acc': acc, 'runtime': runtime, 'emissions': emissions}
         _, pp_dfs = performance_profiler.\
-            get_plot_path_and_data(acc, runtime,
+            get_plot_path_and_data(bounds,
                                    self.fig_dir,
                                    self.options)
 
@@ -307,6 +327,7 @@ class DashPerfProfileTests(unittest.TestCase):
     """
     Test the DashPerfProfile object is correct.
     """
+
     def setUp(self):
 
         self.options = Options()
diff --git a/fitbenchmarking/results_processing/tests/test_tables.py b/fitbenchmarking/results_processing/tests/test_tables.py
index ddb9d13e..00eb6eaa 100644
--- a/fitbenchmarking/results_processing/tests/test_tables.py
+++ b/fitbenchmarking/results_processing/tests/test_tables.py
@@ -82,7 +82,9 @@ class GenerateTableTests(unittest.TestCase):
                 options=self.options,
                 group_dir="group_dir",
                 fig_dir=self.fig_dir,
-                pp_locations=["pp_1", "pp_2"],
+                pp_locations={'acc': "pp_1",
+                              'emissions': 'pp_3',
+                              'runtime': "pp_2"},
                 table_name="table_name",
                 suffix=suffix)
 
@@ -105,7 +107,9 @@ class GenerateTableTests(unittest.TestCase):
             options=self.options,
             group_dir="group_dir",
             fig_dir=self.fig_dir,
-            pp_locations=["pp_1", "pp_2"],
+            pp_locations={'acc': "pp_1",
+                          'emissions': 'pp_3',
+                          'runtime': "pp_2"},
             table_name="table_name",
             suffix="compare")
 
@@ -204,7 +208,9 @@ class CreateResultsTableTests(unittest.TestCase):
                               best_results=self.best_results,
                               group_dir=self.group_dir,
                               fig_dir=self.fig_dir,
-                              pp_locations=["pp_1", "pp_2"],
+                              pp_locations={'acc': "pp_1",
+                                            'emissions': 'pp_3',
+                                            'runtime': "pp_2"},
                               failed_problems=[],
                               unselected_minimzers={'min1': []})
         for suffix in SORTED_TABLE_NAMES: