Skip to content

Commit 93428bb

Browse files
authored
Merge pull request #123 from automl/bug/warning_message_runs_not_comparable
Bug/warning message runs not comparable
2 parents 25fb549 + a410f5b commit 93428bb

File tree

10 files changed

+144
-70
lines changed

10 files changed

+144
-70
lines changed

.flake8

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ extend-exclude =
77
build
88
extend-ignore =
99
# No whitespace before ':' in [x : y]
10-
E203
10+
E203,
1111
# No lambdas — too strict
12-
E731
12+
E731,

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Plugins
44
- Add symbolic explanations plugin (#46).
5+
- It is now possible to view multiple unequal runs at once in Cost over Time and Pareto (#93).
6+
- Runs with unequal objectives cannot be displayed together.
7+
- Added an enum for displaying according warning messages.
58

69
## Enhancements
710
- Fix lower bounds of dependency versions.
@@ -19,6 +22,9 @@
1922
- Reset inputs to fix error when subsequently selecting runs with different configspaces, objectives or budgets (#106).
2023
- Fix errors due to changing inputs before runselection (#64).
2124
- For fANOVA, remove constant hyperparameters from configspace (#9).
25+
- When getting budget, objectives etc from multiple runs in Cost over Time and Pareto Front:
26+
- Instead of taking the first run as comparative value,
27+
- take the one with the lowest budget, else the index for the budgets could be out of bounds.
2228

2329
## Documentation
2430
- Add How to Contribute section.
@@ -68,7 +74,7 @@
6874
- SMAC 2.0
6975

7076
## Dependencies
71-
- Remove SMAC dependency by adding required function directly
77+
- Remove SMAC dependency by adding required function directly.
7278

7379
# Version 1.0.1
7480

deepcave/plugins/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -917,7 +917,6 @@ def __call__(self, render_button: bool = False) -> List[Component]:
917917
]
918918
else:
919919
components += [html.H1(self.name)]
920-
921920
try:
922921
self.check_runs_compatibility(self.all_runs)
923922
except NotMergeableError as message:

deepcave/plugins/dynamic.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def plugin_output_update(_: Any, *inputs_list: str) -> Any:
9595
runs = self.get_selected_runs(inputs)
9696

9797
raw_outputs = {}
98+
rc.clear()
9899
for run in runs:
99100
run_outputs = rc.get(run, self.id, inputs_key)
100101
if run_outputs is None:

deepcave/plugins/objective/cost_over_time.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
from dash import dcc, html
2020
from dash.exceptions import PreventUpdate
2121

22+
from deepcave import notification
2223
from deepcave.config import Config
2324
from deepcave.plugins.dynamic import DynamicPlugin
2425
from deepcave.runs import AbstractRun, check_equality
26+
from deepcave.runs.exceptions import NotMergeableError, RunInequality
2527
from deepcave.utils.layout import get_select_options, help_button
2628
from deepcave.utils.styled_plotty import (
2729
get_color,
@@ -56,6 +58,9 @@ def check_runs_compatibility(self, runs: List[AbstractRun]) -> None:
5658
Since this function is called before the layout is created,
5759
it can be also used to set common values for the plugin.
5860
61+
If the runs are not mergeable, they still should be displayed
62+
but with a corresponding warning message.
63+
5964
Parameters
6065
----------
6166
runs : List[AbstractRun]
@@ -69,18 +74,36 @@ def check_runs_compatibility(self, runs: List[AbstractRun]) -> None:
6974
If the budgets of the runs are not equal.
7075
If the objective of the runs are not equal.
7176
"""
72-
check_equality(runs, objectives=True, budgets=True)
77+
try:
78+
check_equality(runs, objectives=True, budgets=True)
79+
except NotMergeableError as e:
80+
run_inequality = e.args[1]
81+
if run_inequality == RunInequality.INEQ_BUDGET:
82+
notification.update("The budgets of the runs are not equal.", color="warning")
83+
elif run_inequality == RunInequality.INEQ_CONFIGSPACE:
84+
notification.update(
85+
"The configuration spaces of the runs are not equal.", color="warning"
86+
)
87+
elif run_inequality == RunInequality.INEQ_META:
88+
notification.update("The meta data of the runs is not equal.", color="warning")
89+
elif run_inequality == RunInequality.INEQ_OBJECTIVE:
90+
raise NotMergeableError("The objectives of the selected runs cannot be merged.")
7391

7492
# Set some attributes here
75-
run = runs[0]
76-
77-
objective_names = run.get_objective_names()
78-
objective_ids = run.get_objective_ids()
79-
self.objective_options = get_select_options(objective_names, objective_ids)
80-
81-
budgets = run.get_budgets(human=True)
82-
budget_ids = run.get_budget_ids()
83-
self.budget_options = get_select_options(budgets, budget_ids)
93+
# It is necessary to get the run with the smallest budget and objective options
94+
# as first comparative value, else there is gonna be an index problem
95+
objective_options = []
96+
budget_options = []
97+
for run in runs:
98+
objective_names = run.get_objective_names()
99+
objective_ids = run.get_objective_ids()
100+
objective_options.append(get_select_options(objective_names, objective_ids))
101+
102+
budgets = run.get_budgets(human=True)
103+
budget_ids = run.get_budget_ids()
104+
budget_options.append(get_select_options(budgets, budget_ids))
105+
self.objective_options = min(objective_options, key=len)
106+
self.budget_options = min(budget_options, key=len)
84107

85108
@staticmethod
86109
def get_input_layout(register: Callable) -> List[dbc.Row]:

deepcave/plugins/objective/pareto_front.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
import plotly.graph_objs as go
1818
from dash import dcc, html
1919

20+
from deepcave import notification
2021
from deepcave.config import Config
2122
from deepcave.plugins.dynamic import DynamicPlugin
2223
from deepcave.runs import AbstractRun, Status, check_equality
24+
from deepcave.runs.exceptions import NotMergeableError, RunInequality
2325
from deepcave.utils.layout import get_select_options, help_button
2426
from deepcave.utils.styled_plot import plt
2527
from deepcave.utils.styled_plotty import (
@@ -55,6 +57,9 @@ def check_runs_compatibility(self, runs: List[AbstractRun]) -> None:
5557
Since this function is called before the layout is created,
5658
it can be also used to set common values for the plugin.
5759
60+
If the runs are not mergeable, they still should be displayed
61+
but with a corresponding warning message
62+
5863
Parameters
5964
----------
6065
runs : List[AbstractRun]
@@ -68,18 +73,36 @@ def check_runs_compatibility(self, runs: List[AbstractRun]) -> None:
6873
If the budgets of the runs are not equal.
6974
If the objective of the runs are not equal.
7075
"""
71-
check_equality(runs, objectives=True, budgets=True)
76+
try:
77+
check_equality(runs, objectives=True, budgets=True)
78+
except NotMergeableError as e:
79+
run_inequality = e.args[1]
80+
if run_inequality == RunInequality.INEQ_BUDGET:
81+
notification.update("The budgets of the runs are not equal.", color="warning")
82+
elif run_inequality == RunInequality.INEQ_CONFIGSPACE:
83+
notification.update(
84+
"The configuration spaces of the runs are not equal.", color="warning"
85+
)
86+
elif run_inequality == RunInequality.INEQ_META:
87+
notification.update("The meta data of the runs is not equal.", color="warning")
88+
elif run_inequality == RunInequality.INEQ_OBJECTIVE:
89+
raise NotMergeableError("The objectives of the selected runs cannot be merged.")
7290

7391
# Set some attributes here
74-
run = runs[0]
75-
76-
objective_names = run.get_objective_names()
77-
objective_ids = run.get_objective_ids()
78-
self.objective_options = get_select_options(objective_names, objective_ids)
79-
80-
budgets = run.get_budgets(human=True)
81-
budget_ids = run.get_budget_ids()
82-
self.budget_options = get_select_options(budgets, budget_ids)
92+
# It is necessary to get the run with the smallest budget and objective options
93+
# as first comparative value, else there is gonna be an index problem
94+
objective_options = []
95+
budget_options = []
96+
for run in runs:
97+
objective_names = run.get_objective_names()
98+
objective_ids = run.get_objective_ids()
99+
objective_options.append(get_select_options(objective_names, objective_ids))
100+
101+
budgets = run.get_budgets(human=True)
102+
budget_ids = run.get_budget_ids()
103+
budget_options.append(get_select_options(budgets, budget_ids))
104+
self.objective_options = min(objective_options, key=len)
105+
self.budget_options = min(budget_options, key=len)
83106

84107
@staticmethod
85108
def get_input_layout(register: Callable) -> List[Any]:

deepcave/runs/__init__.py

Lines changed: 53 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
CONSTANT_VALUE,
3333
NAN_VALUE,
3434
)
35-
from deepcave.runs.exceptions import NotMergeableError
35+
from deepcave.runs.exceptions import NotMergeableError, RunInequality
3636
from deepcave.runs.objective import Objective
3737
from deepcave.runs.status import Status
3838
from deepcave.runs.trial import Trial
@@ -1236,68 +1236,79 @@ def check_equality(
12361236
if len(runs) == 0:
12371237
return result
12381238

1239-
# Check meta
1240-
if meta:
1241-
ignore = ["objectives", "budgets", "wallclock_limit"]
1242-
1243-
m1 = runs[0].get_meta()
1239+
# Check if objectives are mergeable
1240+
if objectives:
1241+
o1 = None
12441242
for run in runs:
1245-
m2 = run.get_meta()
1246-
1247-
for k, v in m1.items():
1248-
# Don't check on objectives or budgets
1249-
if k in ignore:
1250-
continue
1243+
o2 = run.get_objectives()
12511244

1252-
if k not in m2 or m2[k] != v:
1253-
raise NotMergeableError("Meta data of runs are not equal.")
1245+
if o1 is None:
1246+
o1 = o2
1247+
continue
12541248

1255-
result["meta"] = m1
1249+
if len(o1) != len(o2):
1250+
raise NotMergeableError(
1251+
"Objectives of runs are not equal.", RunInequality.INEQ_OBJECTIVE
1252+
)
12561253

1257-
# Make sure the same configspace is used
1258-
# Otherwise it does not make sense to merge
1259-
# the histories
1260-
if configspace:
1261-
cs1 = runs[0].configspace
1262-
for run in runs:
1263-
cs2 = run.configspace
1264-
if cs1 != cs2:
1265-
raise NotMergeableError("Configspace of runs are not equal.")
1254+
for o1_, o2_ in zip(o1, o2):
1255+
try:
1256+
o1_.merge(o2_)
1257+
except NotMergeableError:
1258+
raise NotMergeableError(
1259+
"Objectives of runs are not equal.", RunInequality.INEQ_OBJECTIVE
1260+
)
12661261

1267-
result["configspace"] = cs1
1262+
assert o1 is not None
1263+
serialized_objectives = [o.to_json() for o in o1]
1264+
result["objectives"] = serialized_objectives
1265+
if meta:
1266+
result["meta"]["objectives"] = serialized_objectives
12681267

12691268
# Also check if budgets are the same
12701269
if budgets:
12711270
b1 = runs[0].get_budgets(include_combined=False)
12721271
for run in runs:
12731272
b2 = run.get_budgets(include_combined=False)
12741273
if b1 != b2:
1275-
raise NotMergeableError("Budgets of runs are not equal.")
1274+
raise NotMergeableError("Budgets of runs are not equal.", RunInequality.INEQ_BUDGET)
12761275

12771276
result["budgets"] = b1
12781277
if meta:
12791278
result["meta"]["budgets"] = b1
12801279

1281-
# And if objectives are the same
1282-
if objectives:
1283-
o1 = None
1280+
# Make sure the same configspace is used
1281+
# Otherwise it does not make sense to merge
1282+
# the histories
1283+
if configspace:
1284+
cs1 = runs[0].configspace
12841285
for run in runs:
1285-
o2 = run.get_objectives()
1286+
cs2 = run.configspace
1287+
if cs1 != cs2:
1288+
raise NotMergeableError(
1289+
"Configspace of runs are not equal.", RunInequality.INEQ_CONFIGSPACE
1290+
)
12861291

1287-
if o1 is None:
1288-
o1 = o2
1289-
continue
1292+
result["configspace"] = cs1
12901293

1291-
if len(o1) != len(o2):
1292-
raise NotMergeableError("Objectives of runs are not equal.")
1294+
# Check meta
1295+
if meta:
1296+
ignore = ["objectives", "budgets", "wallclock_limit"]
12931297

1294-
for o1_, o2_ in zip(o1, o2):
1295-
o1_.merge(o2_)
1298+
m1 = runs[0].get_meta()
1299+
for run in runs:
1300+
m2 = run.get_meta()
12961301

1297-
assert o1 is not None
1298-
serialized_objectives = [o.to_json() for o in o1]
1299-
result["objectives"] = serialized_objectives
1300-
if meta:
1301-
result["meta"]["objectives"] = serialized_objectives
1302+
for k, v in m1.items():
1303+
# Don't check on objectives or budgets
1304+
if k in ignore:
1305+
continue
1306+
1307+
if k not in m2 or m2[k] != v:
1308+
raise NotMergeableError(
1309+
"Meta data of runs are not equal.", RunInequality.INEQ_META
1310+
)
1311+
1312+
result["meta"] = m1
13021313

13031314
return result

deepcave/runs/exceptions.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
- NotMergeableError: Raised if two or more runs are not mergeable.
1313
"""
1414

15+
from enum import Enum
16+
1517

1618
class NotValidRunError(Exception):
1719
"""Raised if directory is not a valid run."""
@@ -23,3 +25,12 @@ class NotMergeableError(Exception):
2325
"""Raised if two or more runs are not mergeable."""
2426

2527
pass
28+
29+
30+
class RunInequality(Enum):
31+
"""Check why runs were not compatible."""
32+
33+
INEQ_META = 1
34+
INEQ_OBJECTIVE = 2
35+
INEQ_BUDGET = 3
36+
INEQ_CONFIGSPACE = 4

docs/plugins/cost_over_time.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ Since multiple runs are supported, you directly see which run performs best to w
99
If you decide to display groups (which are combined runs), you will see the mean and standard
1010
deviation too.
1111

12-
.. note::
13-
The configuration spaces of the selected runs have to be equal. Otherwise, a good comparison
14-
is not possible.
12+
.. note::
13+
The configuration spaces of the selected runs should be equal. Otherwise, a good comparison
14+
is not possible. They can, however, still be displayed in the same graph.
1515

1616
This plugin is capable of answering following questions:
1717

docs/plugins/pareto_front.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ configurations for two given objectives.
99

1010
.. note::
1111
You can enable or disable specific runs if you click on the name right to the plot.
12-
If you click on a configuration you a redirected to the configuration plugin to see
12+
If you click on a configuration you are redirected to the configuration plugin to see
1313
the configuration in detail.
1414

1515
This plugin is capable of answering following questions:

0 commit comments

Comments
 (0)