diff --git a/golem/visualisation/opt_history/fitness_box.py b/golem/visualisation/opt_history/fitness_box.py index 947935fa..461837b7 100644 --- a/golem/visualisation/opt_history/fitness_box.py +++ b/golem/visualisation/opt_history/fitness_box.py @@ -9,10 +9,10 @@ class FitnessBox(HistoryVisualization): - def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None, + def visualize(self, metric_id: int = 0, save_path: Optional[Union[os.PathLike, str]] = None, dpi: Optional[int] = None, best_fraction: Optional[float] = None): """ Visualizes fitness values across generations in the form of boxplot. - + :params metric_id: numeric index of the metric to visualize (for multi-objective opt-n) :param save_path: path to save the visualization. If set, then the image will be saved, and if not, it will be displayed. :param dpi: DPI of the output figure. @@ -23,7 +23,7 @@ def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None, dpi = dpi or self.get_predefined_value('dpi') best_fraction = best_fraction or self.get_predefined_value('best_fraction') - df_history = get_history_dataframe(self.history, best_fraction=best_fraction) + df_history = get_history_dataframe(self.history, metric_id, best_fraction=best_fraction) columns_needed = ['generation', 'individual', 'fitness'] df_history = df_history[columns_needed].drop_duplicates(ignore_index=True) # Get color palette by mean fitness per generation diff --git a/golem/visualisation/opt_history/fitness_line.py b/golem/visualisation/opt_history/fitness_line.py index 822dddb9..e60e1bbd 100644 --- a/golem/visualisation/opt_history/fitness_line.py +++ b/golem/visualisation/opt_history/fitness_line.py @@ -10,7 +10,6 @@ from matplotlib.widgets import Button from golem.core.log import default_log -from golem.core.optimisers.fitness import null_fitness from golem.core.optimisers.opt_history_objects.individual import Individual from golem.core.paths import default_data_dir from golem.visualisation.opt_history.history_visualization import HistoryVisualization @@ -44,10 +43,10 @@ def setup_fitness_plot(axis: plt.Axes, xlabel: str, title: Optional[str] = None, axis.grid(axis='y') -def plot_fitness_line_per_time(axis: plt.Axes, generations, label: Optional[str] = None, +def plot_fitness_line_per_time(axis: plt.Axes, generations, metric_id: int, label: Optional[str] = None, with_generation_limits: bool = True) \ -> Dict[int, Individual]: - best_fitness = null_fitness() + best_metric = np.inf # Assuming metric minimization gen_start_times = [] best_individuals = {} @@ -65,21 +64,22 @@ def plot_fitness_line_per_time(axis: plt.Axes, generations, label: Optional[str] if ind.native_generation != gen_num: continue evaluation_time = (datetime.fromisoformat(ind.metadata['evaluation_time_iso']) - start_time).seconds + target_metric = ind.fitness.values[metric_id] if evaluation_time < gen_start_times[gen_num]: gen_start_times[gen_num] = evaluation_time - if ind.fitness > best_fitness: + if target_metric <= best_metric: best_individuals[evaluation_time] = ind - best_fitness = ind.fitness + best_metric = target_metric best_eval_times, best_fitnesses = np.transpose( - [(evaluation_time, abs(individual.fitness.value)) + [(evaluation_time, individual.fitness.value) for evaluation_time, individual in best_individuals.items()]) best_eval_times = list(best_eval_times) best_fitnesses = list(best_fitnesses) if best_eval_times[-1] != end_time_seconds: - best_fitnesses.append(abs(best_fitness.value)) + best_fitnesses.append(best_metric) best_eval_times.append(end_time_seconds) gen_start_times.append(end_time_seconds) @@ -104,9 +104,9 @@ def plot_fitness_line_per_time(axis: plt.Axes, generations, label: Optional[str] return best_individuals -def plot_fitness_line_per_generations(axis: plt.Axes, generations, label: Optional[str] = None) \ +def plot_fitness_line_per_generations(axis: plt.Axes, generations, metric_id: int, label: Optional[str] = None) \ -> Dict[int, Individual]: - best_fitnesses, best_generations, best_individuals = find_best_running_fitness(generations, metric_id=0) + best_fitnesses, best_generations, best_individuals = find_best_running_fitness(generations, metric_id=metric_id) axis.step(best_generations, best_fitnesses, where='post', label=label) axis.set_xticks(range(len(generations))) axis.locator_params(nbins=10) @@ -114,9 +114,11 @@ def plot_fitness_line_per_generations(axis: plt.Axes, generations, label: Option class FitnessLine(HistoryVisualization): - def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None, dpi: Optional[int] = None, - per_time: Optional[bool] = None): + def visualize(self, metric_id: int = 0, save_path: Optional[Union[os.PathLike, str]] = None, + dpi: Optional[int] = None, per_time: Optional[bool] = None): """ Visualizes the best fitness values during the evolution in the form of line. + + :params metric_id: numeric index of the metric to visualize (for multi-objective opt-n). :param save_path: path to save the visualization. If set, then the image will be saved, and if not, it will be displayed. :param dpi: DPI of the output figure. @@ -129,10 +131,10 @@ def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None, dpi: Op fig, ax = plt.subplots(figsize=(6.4, 4.8), facecolor='w') if per_time: xlabel = 'Time, s' - plot_fitness_line_per_time(ax, self.history.generations) + plot_fitness_line_per_time(ax, self.history.generations, metric_id) else: xlabel = 'Generation' - plot_fitness_line_per_generations(ax, self.history.generations) + plot_fitness_line_per_generations(ax, self.history.generations, metric_id) setup_fitness_plot(ax, xlabel) show_or_save_figure(fig, save_path, dpi) @@ -140,10 +142,11 @@ def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None, dpi: Op class FitnessLineInteractive(HistoryVisualization): @with_alternate_matplotlib_backend - def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None, dpi: Optional[int] = None, + def visualize(self, metric_id: int = 0, save_path: Optional[Union[os.PathLike, str]] = None, dpi: Optional[int] = None, per_time: Optional[bool] = None, graph_show_kwargs: Optional[Dict[str, Any]] = None): """ Visualizes the best fitness values during the evolution in the form of line. Additionally, shows the structure of the best individuals and the moment of their discovering. + :params metric_id: numeric index of the metric to visualize (for multi-objective opt-n) :param save_path: path to save the visualization. If set, then the image will be saved, and if not, it will be displayed. :param dpi: DPI of the output figure. @@ -170,7 +173,7 @@ def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None, dpi: Op x_template = 'generation {}' plot_func = plot_fitness_line_per_generations - best_individuals = plot_func(ax_fitness, self.history.generations) + best_individuals = plot_func(ax_fitness, self.history.generations, metric_id) setup_fitness_plot(ax_fitness, x_label) ax_graph.axis('off') diff --git a/golem/visualisation/opt_history/multiple_fitness_line.py b/golem/visualisation/opt_history/multiple_fitness_line.py index 38c7267d..cd98d6c1 100644 --- a/golem/visualisation/opt_history/multiple_fitness_line.py +++ b/golem/visualisation/opt_history/multiple_fitness_line.py @@ -86,6 +86,8 @@ def visualize(self, def plot_multiple_fitness_lines(self, ax: plt.axis, metric_id: int = 0, with_confidence: bool = True): for histories, label in zip(list(self.historical_fitnesses.values()), list(self.historical_fitnesses.keys())): + if len(self.metric_names) == 1: + histories = [[gen_fit] for gen_fit in histories] plot_average_fitness_line_per_generations(ax, histories, label, with_confidence=with_confidence, metric_id=metric_id) diff --git a/golem/visualisation/opt_history/operations_animated_bar.py b/golem/visualisation/opt_history/operations_animated_bar.py index 2238398e..cc67b47e 100644 --- a/golem/visualisation/opt_history/operations_animated_bar.py +++ b/golem/visualisation/opt_history/operations_animated_bar.py @@ -77,7 +77,7 @@ def animate(frame_num): operation_column_name = 'Operation' column_for_operation = 'tag' if tags_map else 'node' - df_history = get_history_dataframe(self.history, best_fraction, tags_map) + df_history = get_history_dataframe(self.history, best_fraction=best_fraction, tags_map=tags_map) df_history = df_history.rename({ 'generation': generation_column_name, 'fitness': fitness_column_name, diff --git a/golem/visualisation/opt_history/operations_kde.py b/golem/visualisation/opt_history/operations_kde.py index 73654885..efea27d4 100644 --- a/golem/visualisation/opt_history/operations_kde.py +++ b/golem/visualisation/opt_history/operations_kde.py @@ -36,7 +36,7 @@ def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None, dpi: Op operation_column_name = 'Operation' column_for_operation = 'tag' if tags_map else 'node' - df_history = get_history_dataframe(self.history, best_fraction, tags_map) + df_history = get_history_dataframe(self.history, best_fraction=best_fraction, tags_map=tags_map) df_history = df_history.rename({'generation': generation_column_name, column_for_operation: operation_column_name}, axis='columns') operations_found = df_history[operation_column_name].unique() diff --git a/golem/visualisation/opt_history/utils.py b/golem/visualisation/opt_history/utils.py index c7c0d389..856e32af 100644 --- a/golem/visualisation/opt_history/utils.py +++ b/golem/visualisation/opt_history/utils.py @@ -19,7 +19,7 @@ TagOperationsMap = Dict[str, List[str]] -def get_history_dataframe(history: OptHistory, best_fraction: Optional[float] = None, +def get_history_dataframe(history: OptHistory, metric_id: int = 0, best_fraction: Optional[float] = None, tags_map: Optional[TagOperationsMap] = None): history_data = { 'generation': [], @@ -41,7 +41,7 @@ def get_history_dataframe(history: OptHistory, best_fraction: Optional[float] = for node in ind.graph.nodes: history_data['generation'].append(gen_num) history_data['individual'].append('_'.join([ind.uid, str(uid_counts[ind.uid])])) - fitness = abs(ind.fitness.value) + fitness = abs(ind.fitness.values[metric_id]) history_data['fitness'].append(fitness) history_data['node'].append(str(node)) if not tags_map: diff --git a/test/unit/optimizers/test_composing_history.py b/test/unit/optimizers/test_composing_history.py index d90642ab..34b9a6aa 100644 --- a/test/unit/optimizers/test_composing_history.py +++ b/test/unit/optimizers/test_composing_history.py @@ -265,15 +265,19 @@ def test_newly_generated_history(n_jobs: int): indirect=True) @pytest.mark.parametrize('plot_type', PlotTypesEnum) def test_history_show_saving_plots(tmp_path, plot_type: PlotTypesEnum, generate_history): - save_path = Path(tmp_path, plot_type.name) gif_plots = [PlotTypesEnum.operations_animated_bar, PlotTypesEnum.diversity_population] - save_path = save_path.with_suffix('.gif') if plot_type in gif_plots \ - else save_path.with_suffix('.png') + differ_by_metric_id_plots = [PlotTypesEnum.fitness_box, PlotTypesEnum.fitness_line, + PlotTypesEnum.fitness_line_interactive] history: OptHistory = generate_history visualizer = OptHistoryVisualizer(history) - visualization = plot_type.value(visualizer.history, visualizer.visuals_params) - visualization.visualize(save_path=str(save_path), best_fraction=0.1, dpi=100) + num_metrics_to_check = 2 if plot_type in differ_by_metric_id_plots else 1 + for metric_id in range(num_metrics_to_check): # check for the first two metrics + save_path = Path(tmp_path, f'{plot_type.name}_{metric_id}') + save_path = save_path.with_suffix('.gif') if plot_type in gif_plots \ + else save_path.with_suffix('.png') + visualization = plot_type.value(visualizer.history, visualizer.visuals_params) + visualization.visualize( metric_id= metric_id, save_path=str(save_path), best_fraction=0.1, dpi=100) if plot_type is not PlotTypesEnum.fitness_line_interactive: assert save_path.exists()