Skip to content

Commit e3196c9

Browse files
author
Sarah Krebs
committed
Better cost-over-time hoover text
1 parent 46f490e commit e3196c9

File tree

3 files changed

+94
-70
lines changed

3 files changed

+94
-70
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# Version 1.2.1
22

33
## Quality of Life
4-
- Runs now get displayed with their parent directory for better distinguishability
4+
- Runs now get displayed with their parent directory for better distinguishability.
55
- Increase plot font sizes.
66
- Add a simple loading bar functionality for longer runs.
7+
- Show a run's hoover-text for the actual budget of a trial in Cost over Time with Combined budget (#154).
8+
- Adapt trajectory calculation for Group to be able to display hoover-text for the actual budget of a trial in Cost over Time.
9+
- Use highest budget as default budget for Cost over Time instead of Combined.
710

811
## General
912
- Seed is now required in the Recorder.

deepcave/plugins/objective/cost_over_time.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,9 @@ def load_inputs(self) -> Dict[str, Any]:
225225
},
226226
"budget_id": {
227227
"options": self.budget_options,
228-
"value": self.budget_options[-1]["value"],
228+
"value": self.budget_options[0]["value"]
229+
if len(self.budget_options) == 1
230+
else self.budget_options[-2]["value"],
229231
},
230232
"xaxis": {
231233
"options": [
@@ -344,8 +346,7 @@ def load_outputs(runs, inputs, outputs) -> go.Figure: # type: ignore
344346
continue
345347

346348
objective = run.get_objective(inputs["objective_id"])
347-
budget = run.get_budget(inputs["budget_id"])
348-
config_ids = outputs[run.id]["config_ids"]
349+
ids = outputs[run.id]["ids"]
349350
x = outputs[run.id]["times"]
350351
if inputs["xaxis"] == "trials":
351352
x = outputs[run.id]["ids"]
@@ -360,9 +361,11 @@ def load_outputs(runs, inputs, outputs) -> go.Figure: # type: ignore
360361
hoverinfo = "skip"
361362
symbol = None
362363
mode = "lines"
363-
if len(config_ids) > 0:
364+
if len(run.history) > 0:
364365
hovertext = [
365-
get_hovertext_from_config(run, config_id, budget) for config_id in config_ids
366+
get_hovertext_from_config(run, trial.config_id, trial.budget)
367+
for id, trial in enumerate(run.history)
368+
if id in ids
366369
]
367370
hoverinfo = "text"
368371
symbol = "circle"

deepcave/runs/group.py

Lines changed: 82 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
- Group: Can group and manage a group of runs.
1010
"""
1111

12-
from typing import Any, Dict, Iterator, List, Optional, Tuple
12+
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
1313

1414
from copy import deepcopy
1515

1616
import numpy as np
1717

1818
from deepcave.runs import AbstractRun, NotMergeableError, check_equality
19+
from deepcave.runs.objective import Objective
1920
from deepcave.utils.hash import string_to_hash
2021

2122

@@ -286,74 +287,91 @@ def get_model(self, config_id: int) -> Optional[Any]:
286287
run_id, config_id = self._original_config_mapping[config_id]
287288
return self.runs[run_id].get_model(config_id)
288289

289-
# Types dont match superclass
290-
def get_trajectory(self, *args, **kwargs): # type: ignore
290+
def get_trajectory(
291+
self,
292+
objective: Objective,
293+
budget: Optional[Union[int, float]] = None,
294+
seed: Optional[int] = None,
295+
) -> Tuple[List[float], List[float], List[float], List[int], List[int]]:
291296
"""
292-
Calculate the trajectory of the given objective and budget.
293-
294-
This includes the times, the mean costs, and the standard deviation of the costs.
297+
Calculate the trajectory of the given objective, budget, and seed.
295298
296299
Parameters
297300
----------
298-
*args
299-
Should be the objective to calculate the trajectory from.
300-
**kwargs
301-
Should be the budget to calculate the trajectory for.
301+
objective : Objective
302+
Objective to calculate the trajectory for.
303+
budget : Optional[Union[int, float]]
304+
Budget to calculate the trajectory for. If no budget is given, then the highest budget
305+
is chosen. By default None.
306+
seed : Optional[int], optional
307+
Seed to calculate the trajectory for. If no seed is given, then all seeds are
308+
considered. By default None.
302309
303310
Returns
304311
-------
305-
times : List[float]
306-
Times of the trajectory.
307-
costs_mean : List[float]
308-
Costs of the trajectory.
309-
costs_std : List[float]
310-
Standard deviation of the costs of the trajectory.
311-
ids : List[int]
312-
The "global" ids of the selected trial.
313-
config_ids : List[int]
314-
The configuration ids of the selected trials.
312+
Tuple[List[float], List[float], List[float], List[int], List[int]]
313+
times : List[float]
314+
Times of the trajectory.
315+
costs_mean : List[float]
316+
Costs of the trajectory.
317+
costs_std : List[float]
318+
Standard deviation of the costs of the trajectory. This is particularly useful for
319+
grouped runs.
320+
ids : List[int]
321+
The "global" ids of the selected trials.
322+
config_ids : List[int]
323+
Config ids of the selected trials.
315324
"""
316-
# Cache costs
317-
run_costs = []
318-
run_times = []
319-
320-
# All x values on which y values are needed
321-
all_times = []
322-
323-
for _, run in enumerate(self.runs):
324-
times, costs_mean, _, _, _ = run.get_trajectory(*args, **kwargs)
325-
326-
# Cache s.t. calculate it is not calculated multiple times
327-
run_costs.append(costs_mean)
328-
run_times.append(times)
329-
330-
# Add all times
331-
# Standard deviation needs to be calculated on all times
332-
for time in times:
333-
if time not in all_times:
334-
all_times.append(time)
335-
336-
all_times.sort()
337-
338-
# Now look for corresponding y values
339-
all_costs = []
340-
341-
for time in all_times:
342-
y = []
343-
344-
# Iterate over all runs
345-
for costs, times in zip(run_costs, run_times):
346-
# Find closest x value
347-
idx = min(range(len(times)), key=lambda i: abs(times[i] - time))
348-
y.append(costs[idx])
349-
350-
all_costs.append(y)
351-
352-
# Make numpy arrays
353-
all_costs_array = np.array(all_costs)
354-
355-
times = all_times
356-
costs_mean = np.mean(all_costs_array, axis=1)
357-
costs_std = np.std(all_costs_array, axis=1)
358-
359-
return times, list(costs_mean), list(costs_std), [], []
325+
if budget is None:
326+
budget = self.get_highest_budget()
327+
328+
costs_mean = []
329+
costs_std = []
330+
ids = []
331+
config_ids = []
332+
times = []
333+
334+
order = []
335+
336+
# Sort self.history by end-time
337+
for id, trial in enumerate(self.history):
338+
order.append((id, trial.end_time))
339+
order.sort(key=lambda tup: tup[1])
340+
341+
# Important: Objective can be minimized or maximized
342+
if objective.optimize == "lower":
343+
current_cost = np.inf
344+
else:
345+
current_cost = -np.inf
346+
347+
# Iterate over the history ordered by end-time and calculate the current incumbent
348+
for i, (id, _) in enumerate(order):
349+
trial = self.history[id]
350+
351+
# Get the incumbent over all trials up to this point
352+
try:
353+
_, cost = self.get_incumbent(
354+
objectives=objective,
355+
budget=budget,
356+
seed=seed,
357+
selected_ids=[selected_id for selected_id, _ in order[: i + 1]],
358+
)
359+
except RuntimeError:
360+
continue
361+
362+
# Now it's important to check whether the cost was minimized or maximized
363+
if objective.optimize == "lower":
364+
improvement = cost < current_cost
365+
else:
366+
improvement = cost > current_cost
367+
368+
if improvement:
369+
current_cost = cost
370+
371+
costs_mean.append(cost)
372+
costs_std.append(0.0)
373+
times.append(trial.end_time)
374+
ids.append(id)
375+
config_ids.append(trial.config_id)
376+
377+
return times, costs_mean, costs_std, ids, config_ids

0 commit comments

Comments
 (0)