From 8a9cf64d2dee263a5ad83e0af43fb17ba7e1dc6b Mon Sep 17 00:00:00 2001 From: Tim Mensinger Date: Sat, 30 Dec 2023 14:25:19 +0100 Subject: [PATCH 1/9] Export images to PDF --- src/tranquilo_dev/plotting/task_copy_plots.py | 4 ++-- src/tranquilo_dev/plotting/task_create_benchmark_plots.py | 5 ++++- src/tranquilo_dev/plotting/task_create_publication_plots.py | 5 ++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/tranquilo_dev/plotting/task_copy_plots.py b/src/tranquilo_dev/plotting/task_copy_plots.py index d9718b9..c08ac53 100644 --- a/src/tranquilo_dev/plotting/task_copy_plots.py +++ b/src/tranquilo_dev/plotting/task_copy_plots.py @@ -9,8 +9,8 @@ for name in PLOT_CONFIG: for plot_type in ["profile", "convergence", "deviation"]: - source_file = BLD / "figures" / f"{plot_type}_plots" / f"{name}.eps" - dest_file = PUBLIC / "bld" / "figures" / f"{plot_type}_plots" / f"{name}.eps" + source_file = BLD / "figures" / f"{plot_type}_plots" / f"{name}.pdf" + dest_file = PUBLIC / "bld" / "figures" / f"{plot_type}_plots" / f"{name}.pdf" @pytask.mark.depends_on(source_file) @pytask.mark.produces(dest_file) diff --git a/src/tranquilo_dev/plotting/task_create_benchmark_plots.py b/src/tranquilo_dev/plotting/task_create_benchmark_plots.py index 9ae0ef0..1276073 100644 --- a/src/tranquilo_dev/plotting/task_create_benchmark_plots.py +++ b/src/tranquilo_dev/plotting/task_create_benchmark_plots.py @@ -1,5 +1,6 @@ import estimagic as em import pandas as pd +import plotly.io as pio import pytask from estimagic import convergence_plot from estimagic import profile_plot @@ -82,7 +83,7 @@ OUT = BLD / "figures" / f"{plot_type}_plots" @pytask.mark.depends_on(DEPS) - @pytask.mark.produces(OUT / f"{name}.eps") + @pytask.mark.produces(OUT / f"{name}.pdf") @pytask.mark.task(id=f"{plot_type}_plot_{name}") def task_create_benchmark_plots( depends_on, produces, info=info, plot_type=plot_type, name=name @@ -125,4 +126,6 @@ def task_create_benchmark_plots( if name == "scalar_and_ls": fig.update_xaxes(range=[trace.x[0], trace.x[-8]]) + # Deactivate warnings, that could otherwise be printed on the figure + pio.full_figure_for_development(fig, warn=False) fig.write_image(produces) diff --git a/src/tranquilo_dev/plotting/task_create_publication_plots.py b/src/tranquilo_dev/plotting/task_create_publication_plots.py index 050833c..865ffe9 100644 --- a/src/tranquilo_dev/plotting/task_create_publication_plots.py +++ b/src/tranquilo_dev/plotting/task_create_publication_plots.py @@ -1,5 +1,6 @@ import estimagic as em import pandas as pd +import plotly.io as pio import pytask import tranquilo_dev.plotting.plotting_functions as plotting_functions from estimagic import profile_plot @@ -54,7 +55,7 @@ @pytask.mark.task(id=task_id, kwargs=kwargs) @pytask.mark.depends_on(dependencies) - @pytask.mark.produces(BLD_PAPER.joinpath(f"{plot_type}s", f"{benchmark}.eps")) + @pytask.mark.produces(BLD_PAPER.joinpath(f"{plot_type}s", f"{benchmark}.pdf")) def task_create_publication_plots( depends_on, produces, @@ -69,4 +70,6 @@ def task_create_publication_plots( fig = plot_func(problems=problems, results=results, **plot_kwargs) updated = update_plot(fig) + # Deactivate warnings, that could otherwise be printed on the figure + pio.full_figure_for_development(fig, warn=False) updated.write_image(produces) From dc7bcc3e0aad696a0598f2cc3729f17fa77abfb8 Mon Sep 17 00:00:00 2001 From: Tim Mensinger Date: Sat, 30 Dec 2023 15:14:11 +0100 Subject: [PATCH 2/9] Move LABELS to config.py and work on publication figures --- src/tranquilo_dev/config.py | 28 +++++++++ .../plotting/plotting_functions.py | 57 +++++++++++++------ .../plotting/task_create_benchmark_plots.py | 7 ++- .../plotting/task_create_publication_plots.py | 6 +- 4 files changed, 78 insertions(+), 20 deletions(-) diff --git a/src/tranquilo_dev/config.py b/src/tranquilo_dev/config.py index 848a875..5a1c571 100644 --- a/src/tranquilo_dev/config.py +++ b/src/tranquilo_dev/config.py @@ -71,6 +71,34 @@ def _n_evals_10(*args, **kwargs): # noqa: U100 return 10 +LABELS = { + # Tranquilo labels + "tranquilo": "Tranquilo-Scalar", + "tranquilo_scalar_default": "Tranquilo-Scalar", + "tranquilo_default": "Tranquilo-Scalar", + "tranquilo_ls": "Tranquilo-LS", + "tranquilo_ls_default": "Tranquilo-LS", + "tranquilo_ls_parallel_2": "Tranquilo-LS (2)", + "tranquilo_ls_parallel_4": "Tranquilo-LS (4)", + "tranquilo_ls_parallel_8": "Tranquilo-LS (8)", + "tranquilo_experimental": "Tranquilo-Scalar-Experimental", + "tranquilo_scalar_experimental": "Tranquilo-Scalar-Experimental", + "tranquilo_ls_experimental": "Tranquilo-LS-Experimental", + # DFO-LS labels + "dfols": "DFO-LS", + "dfols_noisy_3": "DFO-LS (3)", + "dfols_noisy_5": "DFO-LS (5)", + "dfols_noisy_10": "DFO-LS (10)", + # Other labels + "nag_bobyqa": "NAG-BOBYQA", + "nlopt_bobyqa": "NlOpt-BOBYQA", + "nlopt_neldermead": "NlOpt-Nelder-Mead", + "scipy_neldermead": "SciPy-Nelder-Mead", + "tao_pounders": "TAO-Pounders", + "pounders": "Pounders", +} + + COMPETITION = { "nlopt_bobyqa": {"algorithm": "nlopt_bobyqa"}, "nag_bobyqa": {"algorithm": "nag_pybobyqa"}, diff --git a/src/tranquilo_dev/plotting/plotting_functions.py b/src/tranquilo_dev/plotting/plotting_functions.py index 4137fab..d279a3c 100644 --- a/src/tranquilo_dev/plotting/plotting_functions.py +++ b/src/tranquilo_dev/plotting/plotting_functions.py @@ -1,20 +1,14 @@ import plotly.graph_objects as go +from tranquilo_dev.config import LABELS + + +# ====================================================================================== +# Constants +# ====================================================================================== + +XAXIS_NAME = "Computational Budget" +XAXIS_NAME_NORMALIZED = "Computational Budget (Normalized)" -LABELS = { - "dfols": "DFO-LS", - "tranquilo": "Tranquilo-Scalar", - "tranquilo_default": "Tranquilo-Scalar", - "tranquilo_ls_default": "Tranquilo-LS", - "tranquilo_ls": "Tranquilo-LS", - "nag_bobyqa": "NAG-BOBYQA", - "nlopt_bobyqa": "NlOpt-BOBYQA", - "nlopt_neldermead": "NlOpt-Nelder-Mead", - "scipy_neldermead": "SciPy-Nelder-Mead", - "tao_pounders": "TAO-Pounders", - "pounders": "Pounders", - "tranquilo_experimental": "Tranquilo-Scalar-Experimental", - "tranquilo_ls_experimental": "Tranquilo-Experimental", -} # ====================================================================================== # Profile plot updates @@ -25,6 +19,7 @@ def update_profile_plot_scalar_benchmark(fig): fig = _update_labels(fig) fig = _update_legend(fig) fig = _update_xrange(fig, 0, 50) + fig = _update_axis_names(fig, xaxis=XAXIS_NAME_NORMALIZED) return fig @@ -32,22 +27,29 @@ def update_profile_plot_ls_benchmark(fig): fig = _update_labels(fig) fig = _update_legend(fig) fig = _update_xrange(fig, 0, 40) + fig = _update_axis_names(fig, xaxis=XAXIS_NAME_NORMALIZED) return fig def update_profile_plot_parallel_benchmark(fig): + fig = _update_labels(fig) fig = _update_legend(fig) + fig = _update_axis_names(fig, xaxis=XAXIS_NAME_NORMALIZED) return fig def update_profile_plot_noisy_benchmark(fig): + fig = _update_labels(fig) fig = _update_legend(fig) + fig = _update_axis_names(fig, xaxis=XAXIS_NAME_NORMALIZED) return fig def update_profile_plot_scalar_vs_ls_benchmark(fig): + fig = _update_labels(fig) fig = _update_legend(fig) fig = _update_xrange(fig, 0, 50) + fig = _update_axis_names(fig, xaxis=XAXIS_NAME_NORMALIZED) return fig @@ -60,6 +62,7 @@ def update_deviation_plot_scalar_benchmark(fig): fig = _update_labels(fig) fig = _update_legend(fig) fig = _update_xrange(fig, 0, 300) + fig = _update_axis_names(fig, xaxis=XAXIS_NAME) return fig @@ -67,23 +70,30 @@ def update_deviation_plot_ls_benchmark(fig): fig = _update_labels(fig) fig = _update_legend(fig) fig = _update_xrange(fig, 0, 400) + fig = _update_axis_names(fig, xaxis=XAXIS_NAME) return fig def update_deviation_plot_parallel_benchmark(fig): + fig = _update_labels(fig) fig = _update_legend(fig) fig = _update_xrange(fig, 0, 50) + fig = _update_axis_names(fig, xaxis=XAXIS_NAME) return fig def update_deviation_plot_noisy_benchmark(fig): + fig = _update_labels(fig) fig = _update_legend(fig) + fig = _update_axis_names(fig, xaxis=XAXIS_NAME) return fig def update_deviation_plot_scalar_vs_ls_benchmark(fig): + fig = _update_labels(fig) fig = _update_legend(fig) fig = _update_xrange(fig, 0, 500) + fig = _update_axis_names(fig, xaxis=XAXIS_NAME) return fig @@ -105,10 +115,23 @@ def _update_labels(fig): return fig +def _update_axis_names(fig, xaxis=None, yaxis=None): + fig = go.Figure(fig) # makes a copy of the figure + if xaxis is not None: + fig.update_xaxes(title_text=xaxis) + if yaxis is not None: + fig.update_yaxes(title_text=yaxis) + return fig + + def _update_legend(fig): fig = go.Figure(fig) # makes a copy of the figure fig.update_layout( - legend_title="Algorithm", - legend_title_side="top", + legend_title=None, + legend_orientation="h", + legend_yanchor="top", + legend_y=-0.5, + legend_xanchor="center", + legend_x=0.5, ) return fig diff --git a/src/tranquilo_dev/plotting/task_create_benchmark_plots.py b/src/tranquilo_dev/plotting/task_create_benchmark_plots.py index 1276073..2779dab 100644 --- a/src/tranquilo_dev/plotting/task_create_benchmark_plots.py +++ b/src/tranquilo_dev/plotting/task_create_benchmark_plots.py @@ -1,3 +1,5 @@ +from copy import deepcopy + import estimagic as em import pandas as pd import plotly.io as pio @@ -6,9 +8,12 @@ from estimagic import profile_plot from estimagic.visualization.deviation_plot import deviation_plot from tranquilo_dev.config import BLD +from tranquilo_dev.config import LABELS from tranquilo_dev.config import PLOT_CONFIG from tranquilo_dev.config import PROBLEM_SETS -from tranquilo_dev.plotting.plotting_functions import LABELS + +# Require a deepcopy since we will modify the labels +LABELS = deepcopy(LABELS) LINE_SETTINGS = {"parallelization_ls": {}, "noisy_ls": {}, "scalar_and_ls": {}} diff --git a/src/tranquilo_dev/plotting/task_create_publication_plots.py b/src/tranquilo_dev/plotting/task_create_publication_plots.py index 865ffe9..d0cb34c 100644 --- a/src/tranquilo_dev/plotting/task_create_publication_plots.py +++ b/src/tranquilo_dev/plotting/task_create_publication_plots.py @@ -29,7 +29,7 @@ "scalar_vs_ls_benchmark", ): # Retrieve plotting info and function - # ================================================================================== + # ============================================================================== info = PLOT_CONFIG[f"publication_{benchmark}"] problem_name = info["problem_name"] plot_kwargs = info.get(f"{plot_type}_options", {}) @@ -37,13 +37,15 @@ update_plot = getattr(plotting_functions, f"update_{plot_type}_{benchmark}") # Retrieve plotting data - # ================================================================================== + # ============================================================================== dependencies = [ BLD.joinpath("benchmarks", f"{problem_name}_{scenario}.pkl") for scenario in info["scenarios"] ] problems = em.get_benchmark_problems(**PROBLEM_SETS[problem_name]) + # Store variables in kwargs to pass to pytask + # ============================================================================== kwargs = { "plot_func": plot_func, "plot_kwargs": plot_kwargs, From 05e64218099fd049a476296384f0bc976450a429 Mon Sep 17 00:00:00 2001 From: Tim Mensinger Date: Tue, 2 Jan 2024 18:57:52 +0100 Subject: [PATCH 3/9] Remove tranquilo-scalar labels --- src/tranquilo_dev/config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tranquilo_dev/config.py b/src/tranquilo_dev/config.py index d2a8efa..10f2522 100644 --- a/src/tranquilo_dev/config.py +++ b/src/tranquilo_dev/config.py @@ -74,7 +74,6 @@ def _n_evals_10(*args, **kwargs): # noqa: U100 LABELS = { # Tranquilo labels "tranquilo": "Tranquilo-Scalar", - "tranquilo_scalar_default": "Tranquilo-Scalar", "tranquilo_default": "Tranquilo-Scalar", "tranquilo_ls": "Tranquilo-LS", "tranquilo_ls_default": "Tranquilo-LS", @@ -82,7 +81,6 @@ def _n_evals_10(*args, **kwargs): # noqa: U100 "tranquilo_ls_parallel_4": "Tranquilo-LS (4)", "tranquilo_ls_parallel_8": "Tranquilo-LS (8)", "tranquilo_experimental": "Tranquilo-Scalar-Experimental", - "tranquilo_scalar_experimental": "Tranquilo-Scalar-Experimental", "tranquilo_ls_experimental": "Tranquilo-LS-Experimental", # DFO-LS labels "dfols": "DFO-LS", From f1e98d2a5732e39fe64463649221be0da28e7f31 Mon Sep 17 00:00:00 2001 From: Tim Mensinger Date: Wed, 3 Jan 2024 19:56:37 +0100 Subject: [PATCH 4/9] Switch to matplotlib --- src/tranquilo_dev/config.py | 12 +- .../plotting/plotting_functions.py | 191 +++++++++++------- .../plotting/task_create_publication_plots.py | 16 +- 3 files changed, 131 insertions(+), 88 deletions(-) diff --git a/src/tranquilo_dev/config.py b/src/tranquilo_dev/config.py index 10f2522..72cb353 100644 --- a/src/tranquilo_dev/config.py +++ b/src/tranquilo_dev/config.py @@ -77,16 +77,16 @@ def _n_evals_10(*args, **kwargs): # noqa: U100 "tranquilo_default": "Tranquilo-Scalar", "tranquilo_ls": "Tranquilo-LS", "tranquilo_ls_default": "Tranquilo-LS", - "tranquilo_ls_parallel_2": "Tranquilo-LS (2)", - "tranquilo_ls_parallel_4": "Tranquilo-LS (4)", - "tranquilo_ls_parallel_8": "Tranquilo-LS (8)", + "tranquilo_ls_parallel_2": "Tranquilo-LS (2 Cores)", + "tranquilo_ls_parallel_4": "Tranquilo-LS (4 Cores)", + "tranquilo_ls_parallel_8": "Tranquilo-LS (8 Cores)", "tranquilo_experimental": "Tranquilo-Scalar-Experimental", "tranquilo_ls_experimental": "Tranquilo-LS-Experimental", # DFO-LS labels "dfols": "DFO-LS", - "dfols_noisy_3": "DFO-LS (3)", - "dfols_noisy_5": "DFO-LS (5)", - "dfols_noisy_10": "DFO-LS (10)", + "dfols_noisy_3": "DFO-LS (3 Evaluations)", + "dfols_noisy_5": "DFO-LS (5 Evaluations)", + "dfols_noisy_10": "DFO-LS (10 Evaluations)", # Other labels "nag_bobyqa": "NAG-BOBYQA", "nlopt_bobyqa": "NlOpt-BOBYQA", diff --git a/src/tranquilo_dev/plotting/plotting_functions.py b/src/tranquilo_dev/plotting/plotting_functions.py index d279a3c..64245e5 100644 --- a/src/tranquilo_dev/plotting/plotting_functions.py +++ b/src/tranquilo_dev/plotting/plotting_functions.py @@ -1,13 +1,43 @@ -import plotly.graph_objects as go +import matplotlib +import matplotlib.pyplot as plt from tranquilo_dev.config import LABELS # ====================================================================================== # Constants # ====================================================================================== +XAXIS_DEVIATION_PLOT = "Computational budget" +XAXIS_PROFILE_PLOT = "Computational budget (normalized)" -XAXIS_NAME = "Computational Budget" -XAXIS_NAME_NORMALIZED = "Computational Budget (Normalized)" +YAXIS_DEVIATION_PLOT = "Average distance to optimum (normalized)" +YAXIS_PROFILE_PLOT = "Share of solved problems" + +FIGURE_WIDTH = 14.69785 # textwidth of paper in cm +FIGURE_HEIGHT = 10.0 + +AXIS_LABEL_COLOR = "#605752" + +TABLEAU_10_COLORS = { + "blue": "#5778a4", + "orange": "#e49444", + "red": "#d1615d", + "teal": "#85b6b2", + "green": "#6a9f58", + "yellow": "#e7ca60", + "purple": "#a87c9f", + "pink": "#f1a2a9", + "brown": "#967662", + "gray": "#b8b0ac", +} + + +# ====================================================================================== +# Global matplotlib settings +# ====================================================================================== + +matplotlib.rcParams["font.size"] = 10 +matplotlib.rcParams["font.sans-serif"] = "Fira Sans" +matplotlib.rcParams["font.family"] = "sans-serif" # ====================================================================================== @@ -15,41 +45,41 @@ # ====================================================================================== -def update_profile_plot_scalar_benchmark(fig): - fig = _update_labels(fig) - fig = _update_legend(fig) - fig = _update_xrange(fig, 0, 50) - fig = _update_axis_names(fig, xaxis=XAXIS_NAME_NORMALIZED) +def update_profile_plot_scalar_benchmark(data): + fig, ax = _create_base_plot(data) + _update_xrange(ax, 1, 50) + _update_legend(ax) + _update_axes(ax, xname=XAXIS_PROFILE_PLOT, yname=YAXIS_PROFILE_PLOT) return fig -def update_profile_plot_ls_benchmark(fig): - fig = _update_labels(fig) - fig = _update_legend(fig) - fig = _update_xrange(fig, 0, 40) - fig = _update_axis_names(fig, xaxis=XAXIS_NAME_NORMALIZED) +def update_profile_plot_ls_benchmark(data): + fig, ax = _create_base_plot(data) + _update_xrange(ax, 1, 40) + _update_legend(ax) + _update_axes(ax, xname=XAXIS_PROFILE_PLOT, yname=YAXIS_PROFILE_PLOT) return fig -def update_profile_plot_parallel_benchmark(fig): - fig = _update_labels(fig) - fig = _update_legend(fig) - fig = _update_axis_names(fig, xaxis=XAXIS_NAME_NORMALIZED) +def update_profile_plot_parallel_benchmark(data): + fig, ax = _create_base_plot(data) + _update_legend(ax) + _update_axes(ax, xname=XAXIS_PROFILE_PLOT, yname=YAXIS_PROFILE_PLOT) return fig -def update_profile_plot_noisy_benchmark(fig): - fig = _update_labels(fig) - fig = _update_legend(fig) - fig = _update_axis_names(fig, xaxis=XAXIS_NAME_NORMALIZED) +def update_profile_plot_noisy_benchmark(data): + fig, ax = _create_base_plot(data) + _update_legend(ax) + _update_axes(ax, xname=XAXIS_PROFILE_PLOT, yname=YAXIS_PROFILE_PLOT) return fig -def update_profile_plot_scalar_vs_ls_benchmark(fig): - fig = _update_labels(fig) - fig = _update_legend(fig) - fig = _update_xrange(fig, 0, 50) - fig = _update_axis_names(fig, xaxis=XAXIS_NAME_NORMALIZED) +def update_profile_plot_scalar_vs_ls_benchmark(data): + fig, ax = _create_base_plot(data) + _update_xrange(ax, 1, 50) + _update_legend(ax) + _update_axes(ax, xname=XAXIS_PROFILE_PLOT, yname=YAXIS_PROFILE_PLOT) return fig @@ -58,80 +88,89 @@ def update_profile_plot_scalar_vs_ls_benchmark(fig): # ====================================================================================== -def update_deviation_plot_scalar_benchmark(fig): - fig = _update_labels(fig) - fig = _update_legend(fig) - fig = _update_xrange(fig, 0, 300) - fig = _update_axis_names(fig, xaxis=XAXIS_NAME) +def update_deviation_plot_scalar_benchmark(data): + fig, ax = _create_base_plot(data) + _update_xrange(ax, 0, 300) + _update_legend(ax) + _update_axes(ax, xname=XAXIS_DEVIATION_PLOT, yname=YAXIS_DEVIATION_PLOT) return fig -def update_deviation_plot_ls_benchmark(fig): - fig = _update_labels(fig) - fig = _update_legend(fig) - fig = _update_xrange(fig, 0, 400) - fig = _update_axis_names(fig, xaxis=XAXIS_NAME) +def update_deviation_plot_ls_benchmark(data): + fig, ax = _create_base_plot(data) + _update_xrange(ax, 0, 400) + _update_legend(ax) + _update_axes(ax, xname=XAXIS_DEVIATION_PLOT, yname=YAXIS_DEVIATION_PLOT) return fig -def update_deviation_plot_parallel_benchmark(fig): - fig = _update_labels(fig) - fig = _update_legend(fig) - fig = _update_xrange(fig, 0, 50) - fig = _update_axis_names(fig, xaxis=XAXIS_NAME) +def update_deviation_plot_parallel_benchmark(data): + fig, ax = _create_base_plot(data) + _update_xrange(ax, 0, 50) + _update_legend(ax) + _update_axes(ax, xname=XAXIS_DEVIATION_PLOT, yname=YAXIS_DEVIATION_PLOT) return fig -def update_deviation_plot_noisy_benchmark(fig): - fig = _update_labels(fig) - fig = _update_legend(fig) - fig = _update_axis_names(fig, xaxis=XAXIS_NAME) +def update_deviation_plot_noisy_benchmark(data): + fig, ax = _create_base_plot(data) + _update_legend(ax) + _update_axes(ax, xname=XAXIS_DEVIATION_PLOT, yname=YAXIS_DEVIATION_PLOT) return fig -def update_deviation_plot_scalar_vs_ls_benchmark(fig): - fig = _update_labels(fig) - fig = _update_legend(fig) - fig = _update_xrange(fig, 0, 500) - fig = _update_axis_names(fig, xaxis=XAXIS_NAME) +def update_deviation_plot_scalar_vs_ls_benchmark(data): + fig, ax = _create_base_plot(data) + _update_xrange(ax, 0, 500) + _update_legend(ax) + _update_axes(ax, xname=XAXIS_DEVIATION_PLOT, yname=YAXIS_DEVIATION_PLOT) return fig # ====================================================================================== # Shared functions # ====================================================================================== +def _create_base_plot(data): + """Figure updates that are required by all plots.""" + # Update label names + data = {LABELS[name]: line for name, line in data.items()} + + # Create matplotlib base figure + fig, ax = plt.subplots( + figsize=(_cm_to_inch(FIGURE_WIDTH), _cm_to_inch(FIGURE_HEIGHT)) + ) + for name, line in data.items(): + ax.plot(line["x"], line["y"], label=name) + # Remove top and right border (spine) + ax.spines[["right", "top"]].set_visible(False) -def _update_xrange(fig, lower, upper): - fig = go.Figure(fig) # makes a copy of the figure - fig.update_xaxes(range=[lower, upper]) - return fig + # Update ticks + ax.tick_params(direction="in", width=0.5) + return fig, ax -def _update_labels(fig): - fig = go.Figure(fig) # makes a copy of the figure - for trace in fig.data: - trace.update(name=LABELS[trace.name]) - return fig +def _update_xrange(ax, lower, upper): + ax.set_xlim(lower, upper) -def _update_axis_names(fig, xaxis=None, yaxis=None): - fig = go.Figure(fig) # makes a copy of the figure - if xaxis is not None: - fig.update_xaxes(title_text=xaxis) - if yaxis is not None: - fig.update_yaxes(title_text=yaxis) - return fig +def _update_axes(ax, xname=None, yname=None): + if xname is not None: + ax.set_xlabel(xname, color=AXIS_LABEL_COLOR) + if yname is not None: + ax.set_ylabel(yname, color=AXIS_LABEL_COLOR) -def _update_legend(fig): - fig = go.Figure(fig) # makes a copy of the figure - fig.update_layout( - legend_title=None, - legend_orientation="h", - legend_yanchor="top", - legend_y=-0.5, - legend_xanchor="center", - legend_x=0.5, + +def _update_legend(ax, ncol=3): + ax.legend( + frameon=False, + loc="upper center", + ncol=ncol, + bbox_to_anchor=(0.5, -0.15), + labelcolor=AXIS_LABEL_COLOR, ) - return fig + + +def _cm_to_inch(cm): + return cm * 0.393701 diff --git a/src/tranquilo_dev/plotting/task_create_publication_plots.py b/src/tranquilo_dev/plotting/task_create_publication_plots.py index d0cb34c..6c2b5db 100644 --- a/src/tranquilo_dev/plotting/task_create_publication_plots.py +++ b/src/tranquilo_dev/plotting/task_create_publication_plots.py @@ -1,6 +1,5 @@ import estimagic as em import pandas as pd -import plotly.io as pio import pytask import tranquilo_dev.plotting.plotting_functions as plotting_functions from estimagic import profile_plot @@ -70,8 +69,13 @@ def task_create_publication_plots( for path in depends_on.values(): results = {**results, **pd.read_pickle(path)} - fig = plot_func(problems=problems, results=results, **plot_kwargs) - updated = update_plot(fig) - # Deactivate warnings, that could otherwise be printed on the figure - pio.full_figure_for_development(fig, warn=False) - updated.write_image(produces) + plotly_fig = plot_func(problems=problems, results=results, **plot_kwargs) + plotting_data = get_data_from_plotly_figure(plotly_fig) + + fig = update_plot(plotting_data) + fig.savefig(produces, bbox_inches="tight") + + +def get_data_from_plotly_figure(fig): + lines = fig.data + return {line.name: {"x": line.x, "y": line.y} for line in lines} From aa4da29e7da9ff4f48395ac3610ac5b12c350016 Mon Sep 17 00:00:00 2001 From: Tim Mensinger Date: Thu, 4 Jan 2024 13:27:47 +0100 Subject: [PATCH 5/9] Adjust colors and use a single plotting function --- src/tranquilo_dev/config.py | 12 +- .../plotting/plotting_functions.py | 262 ++++++++++-------- .../plotting/task_create_publication_plots.py | 16 +- 3 files changed, 165 insertions(+), 125 deletions(-) diff --git a/src/tranquilo_dev/config.py b/src/tranquilo_dev/config.py index 72cb353..1fe0f58 100644 --- a/src/tranquilo_dev/config.py +++ b/src/tranquilo_dev/config.py @@ -77,16 +77,16 @@ def _n_evals_10(*args, **kwargs): # noqa: U100 "tranquilo_default": "Tranquilo-Scalar", "tranquilo_ls": "Tranquilo-LS", "tranquilo_ls_default": "Tranquilo-LS", - "tranquilo_ls_parallel_2": "Tranquilo-LS (2 Cores)", - "tranquilo_ls_parallel_4": "Tranquilo-LS (4 Cores)", - "tranquilo_ls_parallel_8": "Tranquilo-LS (8 Cores)", + "tranquilo_ls_parallel_2": "Tranquilo-LS (2 cores)", + "tranquilo_ls_parallel_4": "Tranquilo-LS (4 cores)", + "tranquilo_ls_parallel_8": "Tranquilo-LS (8 cores)", "tranquilo_experimental": "Tranquilo-Scalar-Experimental", "tranquilo_ls_experimental": "Tranquilo-LS-Experimental", # DFO-LS labels "dfols": "DFO-LS", - "dfols_noisy_3": "DFO-LS (3 Evaluations)", - "dfols_noisy_5": "DFO-LS (5 Evaluations)", - "dfols_noisy_10": "DFO-LS (10 Evaluations)", + "dfols_noisy_3": "DFO-LS (3 evals)", + "dfols_noisy_5": "DFO-LS (5 evals)", + "dfols_noisy_10": "DFO-LS (10 evals)", # Other labels "nag_bobyqa": "NAG-BOBYQA", "nlopt_bobyqa": "NlOpt-BOBYQA", diff --git a/src/tranquilo_dev/plotting/plotting_functions.py b/src/tranquilo_dev/plotting/plotting_functions.py index 64245e5..59e7425 100644 --- a/src/tranquilo_dev/plotting/plotting_functions.py +++ b/src/tranquilo_dev/plotting/plotting_functions.py @@ -1,39 +1,124 @@ +import itertools + import matplotlib import matplotlib.pyplot as plt from tranquilo_dev.config import LABELS # ====================================================================================== -# Constants +# Config # ====================================================================================== -XAXIS_DEVIATION_PLOT = "Computational budget" -XAXIS_PROFILE_PLOT = "Computational budget (normalized)" -YAXIS_DEVIATION_PLOT = "Average distance to optimum (normalized)" -YAXIS_PROFILE_PLOT = "Share of solved problems" +AXIS_LABELS = { + "profile_plot": { + "xlabel": "Computational budget (normalized)", + "ylabel": "Share of solved problems", + }, + "deviation_plot": { + "xlabel": "Computational budget", + "ylabel": "Average distance to optimum (normalized)", + }, +} FIGURE_WIDTH = 14.69785 # textwidth of paper in cm FIGURE_HEIGHT = 10.0 -AXIS_LABEL_COLOR = "#605752" +DARK_GRAY = "#534a46" TABLEAU_10_COLORS = { "blue": "#5778a4", + "green": "#6a9f58", "orange": "#e49444", "red": "#d1615d", "teal": "#85b6b2", - "green": "#6a9f58", "yellow": "#e7ca60", "purple": "#a87c9f", "pink": "#f1a2a9", "brown": "#967662", "gray": "#b8b0ac", + # Additional shades. (Taken by plugging in the color code of blue and green at + # https://www.w3schools.com/colors/colors_picker.asp, and selecting a lighter shade) + "green-light": "#8ab77b", + "green-very-light": "#b6d2ad", + "blue-light": "#7995b9", + "blue-very-light": "#abbdd3", + "blue-very-very-light": "#dee4ed", } +COLORS = { + # Tranquilo colors + "Tranquilo-Scalar": TABLEAU_10_COLORS["teal"], + "Tranquilo-LS": TABLEAU_10_COLORS["blue"], + "Tranquilo-LS (2 cores)": TABLEAU_10_COLORS["blue-light"], + "Tranquilo-LS (4 cores)": TABLEAU_10_COLORS["blue-very-light"], + "Tranquilo-LS (8 cores)": TABLEAU_10_COLORS["blue-very-very-light"], + # DFO-LS colors + "DFO-LS": TABLEAU_10_COLORS["green"], + "DFO-LS (3 evals)": TABLEAU_10_COLORS["green"], + "DFO-LS (5 evals)": TABLEAU_10_COLORS["green-light"], + "DFO-LS (10 evals)": TABLEAU_10_COLORS["green-very-light"], + # Other colors + "NAG-BOBYQA": TABLEAU_10_COLORS["orange"], + "NlOpt-BOBYQA": TABLEAU_10_COLORS["red"], + "NlOpt-Nelder-Mead": TABLEAU_10_COLORS["purple"], + "SciPy-Nelder-Mead": TABLEAU_10_COLORS["pink"], + "Pounders": TABLEAU_10_COLORS["gray"], +} -# ====================================================================================== -# Global matplotlib settings -# ====================================================================================== + +LEGEND_LABEL_ORDER = { + "scalar_benchmark": [ + "Tranquilo-Scalar", + "NAG-BOBYQA", + "NlOpt-BOBYQA", + "NlOpt-Nelder-Mead", + "SciPy-Nelder-Mead", + ], + "ls_benchmark": [ + "Tranquilo-LS", + "DFO-LS", + "Pounders", + ], + "parallel_benchmark": [ + "Tranquilo-LS", + "Tranquilo-LS (2 cores)", + "Tranquilo-LS (4 cores)", + "Tranquilo-LS (8 cores)", + "DFO-LS", + ], + "noisy_benchmark": [ + "Tranquilo-LS", + "DFO-LS (3 evals)", + "DFO-LS (5 evals)", + "DFO-LS (10 evals)", + ], + "scalar_vs_ls_benchmark": [ + "Tranquilo-LS", + "DFO-LS", + "Pounders", + "Tranquilo-Scalar", + "NlOpt-BOBYQA", + "NlOpt-Nelder-Mead", + ], +} + + +X_RANGE = { + "profile_plot": { + "scalar_benchmark": (1, 50), + "ls_benchmark": (1, 40), + "parallel_benchmark": (1,), + "noisy_benchmark": (1,), + "scalar_vs_ls_benchmark": (1, 50), + }, + "deviation_plot": { + "scalar_benchmark": (0, 300), + "ls_benchmark": (0, 400), + "parallel_benchmark": (0, 50), + "noisy_benchmark": (0,), + "scalar_vs_ls_benchmark": (0, 500), + }, +} matplotlib.rcParams["font.size"] = 10 matplotlib.rcParams["font.sans-serif"] = "Fira Sans" @@ -41,97 +126,27 @@ # ====================================================================================== -# Profile plot updates +# Plotting function # ====================================================================================== -def update_profile_plot_scalar_benchmark(data): - fig, ax = _create_base_plot(data) - _update_xrange(ax, 1, 50) - _update_legend(ax) - _update_axes(ax, xname=XAXIS_PROFILE_PLOT, yname=YAXIS_PROFILE_PLOT) - return fig - +def plot_benchmark(data, plot, benchmark): + """Create the base matplotlib figure. # noqa: D406, D407, D400 -def update_profile_plot_ls_benchmark(data): - fig, ax = _create_base_plot(data) - _update_xrange(ax, 1, 40) - _update_legend(ax) - _update_axes(ax, xname=XAXIS_PROFILE_PLOT, yname=YAXIS_PROFILE_PLOT) - return fig + Args: + data (dict): Dictionary containing the data to plot. Keys represent a single + line in the plot. The values are dictionaries with keys "x" and "y". + plot (str): Name of the plot to create. Must be in {"deviation_plot", + "profile_plot"}. + benchmark (str): Name of the benchmark. + Returns: + matplotlib.figure.Figure: The matplotlib figure. -def update_profile_plot_parallel_benchmark(data): - fig, ax = _create_base_plot(data) - _update_legend(ax) - _update_axes(ax, xname=XAXIS_PROFILE_PLOT, yname=YAXIS_PROFILE_PLOT) - return fig + """ + x_range = X_RANGE[plot][benchmark] + legend_label_order = LEGEND_LABEL_ORDER[benchmark] - -def update_profile_plot_noisy_benchmark(data): - fig, ax = _create_base_plot(data) - _update_legend(ax) - _update_axes(ax, xname=XAXIS_PROFILE_PLOT, yname=YAXIS_PROFILE_PLOT) - return fig - - -def update_profile_plot_scalar_vs_ls_benchmark(data): - fig, ax = _create_base_plot(data) - _update_xrange(ax, 1, 50) - _update_legend(ax) - _update_axes(ax, xname=XAXIS_PROFILE_PLOT, yname=YAXIS_PROFILE_PLOT) - return fig - - -# ====================================================================================== -# Deviation plot updates -# ====================================================================================== - - -def update_deviation_plot_scalar_benchmark(data): - fig, ax = _create_base_plot(data) - _update_xrange(ax, 0, 300) - _update_legend(ax) - _update_axes(ax, xname=XAXIS_DEVIATION_PLOT, yname=YAXIS_DEVIATION_PLOT) - return fig - - -def update_deviation_plot_ls_benchmark(data): - fig, ax = _create_base_plot(data) - _update_xrange(ax, 0, 400) - _update_legend(ax) - _update_axes(ax, xname=XAXIS_DEVIATION_PLOT, yname=YAXIS_DEVIATION_PLOT) - return fig - - -def update_deviation_plot_parallel_benchmark(data): - fig, ax = _create_base_plot(data) - _update_xrange(ax, 0, 50) - _update_legend(ax) - _update_axes(ax, xname=XAXIS_DEVIATION_PLOT, yname=YAXIS_DEVIATION_PLOT) - return fig - - -def update_deviation_plot_noisy_benchmark(data): - fig, ax = _create_base_plot(data) - _update_legend(ax) - _update_axes(ax, xname=XAXIS_DEVIATION_PLOT, yname=YAXIS_DEVIATION_PLOT) - return fig - - -def update_deviation_plot_scalar_vs_ls_benchmark(data): - fig, ax = _create_base_plot(data) - _update_xrange(ax, 0, 500) - _update_legend(ax) - _update_axes(ax, xname=XAXIS_DEVIATION_PLOT, yname=YAXIS_DEVIATION_PLOT) - return fig - - -# ====================================================================================== -# Shared functions -# ====================================================================================== -def _create_base_plot(data): - """Figure updates that are required by all plots.""" # Update label names data = {LABELS[name]: line for name, line in data.items()} @@ -140,37 +155,58 @@ def _create_base_plot(data): figsize=(_cm_to_inch(FIGURE_WIDTH), _cm_to_inch(FIGURE_HEIGHT)) ) for name, line in data.items(): - ax.plot(line["x"], line["y"], label=name) + ax.plot(line["x"], line["y"], label=name, color=COLORS[name]) # Remove top and right border (spine) ax.spines[["right", "top"]].set_visible(False) - # Update ticks - ax.tick_params(direction="in", width=0.5) - - return fig, ax - - -def _update_xrange(ax, lower, upper): - ax.set_xlim(lower, upper) - - -def _update_axes(ax, xname=None, yname=None): - if xname is not None: - ax.set_xlabel(xname, color=AXIS_LABEL_COLOR) - if yname is not None: - ax.set_ylabel(yname, color=AXIS_LABEL_COLOR) + # Set color of left and bottom spine + ax.spines[["left", "bottom"]].set_color(DARK_GRAY) + # Update ticks + ax.tick_params(direction="in", width=0.75, colors=DARK_GRAY) -def _update_legend(ax, ncol=3): + # Update legend + handles, labels = _get_sorted_legend_handles_and_labels(ax, legend_label_order) ax.legend( + handles=_row_to_col_ordering(handles, ncol=3), + labels=_row_to_col_ordering(labels, ncol=3), frameon=False, loc="upper center", - ncol=ncol, + ncol=3, bbox_to_anchor=(0.5, -0.15), - labelcolor=AXIS_LABEL_COLOR, + labelcolor=DARK_GRAY, ) + # Update axes + ax.set_xlabel(AXIS_LABELS[plot]["xlabel"], color=DARK_GRAY) + ax.set_ylabel(AXIS_LABELS[plot]["ylabel"], color=DARK_GRAY) + ax.xaxis.label.set_color(DARK_GRAY) + ax.yaxis.label.set_color(DARK_GRAY) + ax.set_xlim(*x_range) + ax.set_ylim(0, 1) + + return fig + def _cm_to_inch(cm): return cm * 0.393701 + + +def _get_sorted_legend_handles_and_labels(ax, sorted_labels=None): + handles, labels = ax.get_legend_handles_labels() + sorted_labels = labels if sorted_labels is None else sorted_labels + sorted_handles = [handles[labels.index(label)] for label in sorted_labels] + return sorted_handles, sorted_labels + + +def _row_to_col_ordering(items, ncol): + """Reorder frm row-major to column-major ordering. + + Transforms a list of items that are designed to be filled in a row-major order + into a list of items that are designed to be filled in a column-major order. + + Taken from: https://stackoverflow.com/a/10101532. + + """ + return itertools.chain(*[items[i::ncol] for i in range(ncol)]) diff --git a/src/tranquilo_dev/plotting/task_create_publication_plots.py b/src/tranquilo_dev/plotting/task_create_publication_plots.py index 6c2b5db..c005b9a 100644 --- a/src/tranquilo_dev/plotting/task_create_publication_plots.py +++ b/src/tranquilo_dev/plotting/task_create_publication_plots.py @@ -1,12 +1,12 @@ import estimagic as em import pandas as pd import pytask -import tranquilo_dev.plotting.plotting_functions as plotting_functions from estimagic import profile_plot from estimagic.visualization.deviation_plot import deviation_plot from tranquilo_dev.config import BLD from tranquilo_dev.config import PLOT_CONFIG from tranquilo_dev.config import PROBLEM_SETS +from tranquilo_dev.plotting.plotting_functions import plot_benchmark BLD_PAPER = BLD.joinpath("bld_paper") @@ -33,8 +33,6 @@ problem_name = info["problem_name"] plot_kwargs = info.get(f"{plot_type}_options", {}) - update_plot = getattr(plotting_functions, f"update_{plot_type}_{benchmark}") - # Retrieve plotting data # ============================================================================== dependencies = [ @@ -48,8 +46,9 @@ kwargs = { "plot_func": plot_func, "plot_kwargs": plot_kwargs, - "update_plot": update_plot, "problems": problems, + "plot_type": plot_type, + "benchmark": benchmark, } task_id = f"{plot_type}_{benchmark}" @@ -62,8 +61,9 @@ def task_create_publication_plots( produces, plot_func, plot_kwargs, - update_plot, problems, + plot_type, + benchmark, ): results = {} for path in depends_on.values(): @@ -72,7 +72,11 @@ def task_create_publication_plots( plotly_fig = plot_func(problems=problems, results=results, **plot_kwargs) plotting_data = get_data_from_plotly_figure(plotly_fig) - fig = update_plot(plotting_data) + fig = plot_benchmark( + plotting_data, + plot=plot_type, + benchmark=benchmark, + ) fig.savefig(produces, bbox_inches="tight") From d9c5c68d7a2266df9be8794ca9cca06e4bf69134 Mon Sep 17 00:00:00 2001 From: Tim Mensinger Date: Thu, 4 Jan 2024 15:18:56 +0100 Subject: [PATCH 6/9] Adjust colors, legend ordering, and add line width argument --- .../plotting/plotting_functions.py | 137 +++++++++++++----- 1 file changed, 97 insertions(+), 40 deletions(-) diff --git a/src/tranquilo_dev/plotting/plotting_functions.py b/src/tranquilo_dev/plotting/plotting_functions.py index 59e7425..695dc39 100644 --- a/src/tranquilo_dev/plotting/plotting_functions.py +++ b/src/tranquilo_dev/plotting/plotting_functions.py @@ -1,3 +1,14 @@ +"""Plotting function that create the publication ready figures. + +Most of the plotting behavior can be configured by changing some global dictionaries: + +- `AXIS_LABELS`: Contains the axis labels for each plot type. +- `X_RANGE`: Contains the x-axis range given each benchmark. +- `COLORS`: Contains the colors for each line given each benchmark. +- `LINE_WIDTH_UPDATES`: Contains the line width for each line given each benchmark. +- `LEGEND_LABEL_ORDER`: Contains the order of the legend labels given each benchmark. + +""" import itertools import matplotlib @@ -6,9 +17,12 @@ # ====================================================================================== -# Config +# Plot Config # ====================================================================================== +FIGURE_WIDTH_IN_CM = 14.69785 +FIGURE_HEIGHT_IN_CM = 10.0 + AXIS_LABELS = { "profile_plot": { "xlabel": "Computational budget (normalized)", @@ -20,8 +34,25 @@ }, } -FIGURE_WIDTH = 14.69785 # textwidth of paper in cm -FIGURE_HEIGHT = 10.0 +X_RANGE = { + "profile_plot": { + "scalar_benchmark": (1, 50), + "ls_benchmark": (1, 40), + "parallel_benchmark": (1,), + "noisy_benchmark": (1,), + "scalar_vs_ls_benchmark": (1, 50), + }, + "deviation_plot": { + "scalar_benchmark": (0, 300), + "ls_benchmark": (0, 400), + "parallel_benchmark": (0, 50), + "noisy_benchmark": (0,), + "scalar_vs_ls_benchmark": (0, 500), + }, +} + +# Colors +# ====================================================================================== DARK_GRAY = "#534a46" @@ -37,34 +68,68 @@ "brown": "#967662", "gray": "#b8b0ac", # Additional shades. (Taken by plugging in the color code of blue and green at - # https://www.w3schools.com/colors/colors_picker.asp, and selecting a lighter shade) - "green-light": "#8ab77b", - "green-very-light": "#b6d2ad", - "blue-light": "#7995b9", - "blue-very-light": "#abbdd3", - "blue-very-very-light": "#dee4ed", + # https://www.w3schools.com/colors/colors_picker.asp, and selecting a lighter / + # darker color given some percentage.) + "green-25": "#b7d2ad", + "green-50": "#6ea45b", + "green-75": "#37522d", + "blue-75": "#abbed4", + "blue-55": "#688ab1", + "blue-35": "#3d5776", + "blue-15": "#1a2532", } -COLORS = { - # Tranquilo colors + +BASE_COLORS = { "Tranquilo-Scalar": TABLEAU_10_COLORS["teal"], "Tranquilo-LS": TABLEAU_10_COLORS["blue"], - "Tranquilo-LS (2 cores)": TABLEAU_10_COLORS["blue-light"], - "Tranquilo-LS (4 cores)": TABLEAU_10_COLORS["blue-very-light"], - "Tranquilo-LS (8 cores)": TABLEAU_10_COLORS["blue-very-very-light"], - # DFO-LS colors "DFO-LS": TABLEAU_10_COLORS["green"], - "DFO-LS (3 evals)": TABLEAU_10_COLORS["green"], - "DFO-LS (5 evals)": TABLEAU_10_COLORS["green-light"], - "DFO-LS (10 evals)": TABLEAU_10_COLORS["green-very-light"], - # Other colors "NAG-BOBYQA": TABLEAU_10_COLORS["orange"], "NlOpt-BOBYQA": TABLEAU_10_COLORS["red"], "NlOpt-Nelder-Mead": TABLEAU_10_COLORS["purple"], - "SciPy-Nelder-Mead": TABLEAU_10_COLORS["pink"], + "SciPy-Nelder-Mead": TABLEAU_10_COLORS["brown"], "Pounders": TABLEAU_10_COLORS["gray"], } +PARALLEL_COLOR_UPDATES = { + "Tranquilo-LS": TABLEAU_10_COLORS["blue-75"], + "Tranquilo-LS (2 cores)": TABLEAU_10_COLORS["blue-55"], + "Tranquilo-LS (4 cores)": TABLEAU_10_COLORS["blue-35"], + "Tranquilo-LS (8 cores)": TABLEAU_10_COLORS["blue-15"], +} + +NOISY_COLOR_UPDATES = { + "DFO-LS (3 evals)": TABLEAU_10_COLORS["green-25"], + "DFO-LS (5 evals)": TABLEAU_10_COLORS["green-50"], + "DFO-LS (10 evals)": TABLEAU_10_COLORS["green-75"], +} + +COLORS = { + "scalar_benchmark": BASE_COLORS, + "ls_benchmark": BASE_COLORS, + "parallel_benchmark": {**BASE_COLORS, **PARALLEL_COLOR_UPDATES}, + "noisy_benchmark": {**BASE_COLORS, **NOISY_COLOR_UPDATES}, + "scalar_vs_ls_benchmark": BASE_COLORS, +} + +# Line width +# ====================================================================================== +DEFAULT_LINE_WIDTH = 1.5 + +LINE_WIDTH_UPDATES = { + "parallel_benchmark": { + "Tranquilo-LS (2 cores)": 1.6, + "Tranquilo-LS (4 cores)": 1.7, + "Tranquilo-LS (8 cores)": 1.8, + }, + "noisy_benchmark": { + "DFO-LS (5 evals)": 1.6, + "DFO-LS (10 evals)": 1.7, + }, +} + +# Legend +# ====================================================================================== LEGEND_LABEL_ORDER = { "scalar_benchmark": [ @@ -87,10 +152,10 @@ "DFO-LS", ], "noisy_benchmark": [ - "Tranquilo-LS", "DFO-LS (3 evals)", "DFO-LS (5 evals)", "DFO-LS (10 evals)", + "Tranquilo-LS", ], "scalar_vs_ls_benchmark": [ "Tranquilo-LS", @@ -102,23 +167,8 @@ ], } - -X_RANGE = { - "profile_plot": { - "scalar_benchmark": (1, 50), - "ls_benchmark": (1, 40), - "parallel_benchmark": (1,), - "noisy_benchmark": (1,), - "scalar_vs_ls_benchmark": (1, 50), - }, - "deviation_plot": { - "scalar_benchmark": (0, 300), - "ls_benchmark": (0, 400), - "parallel_benchmark": (0, 50), - "noisy_benchmark": (0,), - "scalar_vs_ls_benchmark": (0, 500), - }, -} +# Font +# ====================================================================================== matplotlib.rcParams["font.size"] = 10 matplotlib.rcParams["font.sans-serif"] = "Fira Sans" @@ -152,10 +202,17 @@ def plot_benchmark(data, plot, benchmark): # Create matplotlib base figure fig, ax = plt.subplots( - figsize=(_cm_to_inch(FIGURE_WIDTH), _cm_to_inch(FIGURE_HEIGHT)) + figsize=(_cm_to_inch(FIGURE_WIDTH_IN_CM), _cm_to_inch(FIGURE_HEIGHT_IN_CM)) ) for name, line in data.items(): - ax.plot(line["x"], line["y"], label=name, color=COLORS[name]) + lw = LINE_WIDTH_UPDATES.get(benchmark, {}).get(name, DEFAULT_LINE_WIDTH) + ax.plot( + line["x"], + line["y"], + label=name, + color=COLORS[benchmark][name], + linewidth=lw, + ) # Remove top and right border (spine) ax.spines[["right", "top"]].set_visible(False) From fb6e319c72af4d5a18dd8c2a642027ac6e139c3f Mon Sep 17 00:00:00 2001 From: Tim Mensinger Date: Thu, 4 Jan 2024 15:48:52 +0100 Subject: [PATCH 7/9] Rename publication plots to publication benchmark plots --- ...{plotting_functions.py => benchmark_plotting_functions.py} | 0 ...on_plots.py => task_create_publication_benchmark_plots.py} | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/tranquilo_dev/plotting/{plotting_functions.py => benchmark_plotting_functions.py} (100%) rename src/tranquilo_dev/plotting/{task_create_publication_plots.py => task_create_publication_benchmark_plots.py} (95%) diff --git a/src/tranquilo_dev/plotting/plotting_functions.py b/src/tranquilo_dev/plotting/benchmark_plotting_functions.py similarity index 100% rename from src/tranquilo_dev/plotting/plotting_functions.py rename to src/tranquilo_dev/plotting/benchmark_plotting_functions.py diff --git a/src/tranquilo_dev/plotting/task_create_publication_plots.py b/src/tranquilo_dev/plotting/task_create_publication_benchmark_plots.py similarity index 95% rename from src/tranquilo_dev/plotting/task_create_publication_plots.py rename to src/tranquilo_dev/plotting/task_create_publication_benchmark_plots.py index c005b9a..80bff59 100644 --- a/src/tranquilo_dev/plotting/task_create_publication_plots.py +++ b/src/tranquilo_dev/plotting/task_create_publication_benchmark_plots.py @@ -6,7 +6,7 @@ from tranquilo_dev.config import BLD from tranquilo_dev.config import PLOT_CONFIG from tranquilo_dev.config import PROBLEM_SETS -from tranquilo_dev.plotting.plotting_functions import plot_benchmark +from tranquilo_dev.plotting.benchmark_plotting_functions import plot_benchmark BLD_PAPER = BLD.joinpath("bld_paper") @@ -56,7 +56,7 @@ @pytask.mark.task(id=task_id, kwargs=kwargs) @pytask.mark.depends_on(dependencies) @pytask.mark.produces(BLD_PAPER.joinpath(f"{plot_type}s", f"{benchmark}.pdf")) - def task_create_publication_plots( + def task_create_publication_benchmark_plots( depends_on, produces, plot_func, From 8cbac954d6719e10e102860fc2e2bca9fd0fd863 Mon Sep 17 00:00:00 2001 From: Tim Mensinger Date: Thu, 4 Jan 2024 15:51:04 +0100 Subject: [PATCH 8/9] Add task to create noise illustrations --- src/tranquilo_dev/plotting/illustrations.py | 155 ++++++++++++++++++ .../task_create_publication_illustrations.py | 12 ++ 2 files changed, 167 insertions(+) create mode 100644 src/tranquilo_dev/plotting/illustrations.py create mode 100644 src/tranquilo_dev/plotting/task_create_publication_illustrations.py diff --git a/src/tranquilo_dev/plotting/illustrations.py b/src/tranquilo_dev/plotting/illustrations.py new file mode 100644 index 0000000..c0a50f7 --- /dev/null +++ b/src/tranquilo_dev/plotting/illustrations.py @@ -0,0 +1,155 @@ +from copy import deepcopy + +import numpy as np +import plotly.express as px +import plotly.graph_objects as go +from plotly.subplots import make_subplots +from tranquilo.visualize import _clean_legend_duplicates + + +def create_noise_plots(): + out = {} + + def func(x): + return x**4 + x**2 - x + + def model(x, beta): + return beta[0] + beta[1] * x + beta[2] * x**2 + + x_grid = np.linspace(-2, 1.5, 100) + y = func(x_grid) + rng = np.random.default_rng(4567) + noise_x_grid = np.linspace(-2, 1.5, 40) + noise_y = rng.normal(loc=func(noise_x_grid), scale=1.5) + fig = px.line(x=x_grid, y=y) + fig.data[0].name = "criterion function" + fig.data[0].showlegend = True + layout = go.Layout( + margin=go.layout.Margin( + l=10, # left margin + r=10, # right margin + b=10, # bottom margin + t=10, # top margin + ) + ) + fig.update_layout(layout) + fig.update_layout(height=500, width=600, template="plotly_white", showlegend=True) + fig.update_yaxes( + showgrid=False, + showline=True, + linewidth=1, + linecolor="black", + zeroline=False, + ) + fig.update_xaxes( + showgrid=False, + showline=True, + linewidth=1, + linecolor="black", + zeroline=False, + ) + fig.update_layout( + legend={ + "yanchor": "bottom", + "y": -0.2, + "xanchor": "center", + "x": 0.5, + "orientation": "h", + } + ) + out[0] = fig # fig.write_image("noise_plot_0.svg") + + fig1 = deepcopy(fig) + + fig1.add_trace( + go.Scatter( + x=noise_x_grid, + y=noise_y, + mode="markers", + marker_color="rgb(0,0,255)", + opacity=0.3, + showlegend=False, + ) + ) + out[1] = fig1 # fig1.write_image("noise_plot_1.svg") + + plotting_data = [] + for i, trustregion in enumerate([(-1.75, -0.5), (-0.75, 0.5)]): + in_region = (trustregion[0] <= noise_x_grid) & (noise_x_grid <= trustregion[1]) + xs = noise_x_grid.copy() + ys = noise_y.copy() + xs[in_region] = np.nan + ys[in_region] = np.nan + sample_xs = np.array([trustregion[0], np.mean(trustregion), trustregion[1]]) + sample_ys = func(sample_xs) + np.array([-3, 0.75, 1.5]) + model_xs = np.column_stack( + [ + np.ones(3), + sample_xs, + sample_xs**2, + ] + ) + beta, *_ = np.linalg.lstsq(model_xs, sample_ys, rcond=None) + model_grid = np.linspace(trustregion[0], trustregion[1], 15) + model_ys = np.array([model(x, beta) for x in model_grid]) + fig2 = deepcopy(fig) + fig2.add_trace( + go.Scatter( + x=xs, + y=ys, + mode="markers", + marker_color="rgb(0,0,255)", + showlegend=False, + opacity=0.3, + ) + ) + fig2.add_trace( + go.Scatter( + x=sample_xs, + y=sample_ys, + mode="markers", + marker_color="rgb(0,0,255)", + showlegend=False, + ) + ) + fig2.add_trace( + go.Scatter( + x=model_grid, + y=model_ys, + mode="lines", + name="model", + line_color="#F98900", + ) + ) + for x in trustregion: + fig2.add_vline(x=x, line_color="grey", line_width=1) + fig2.update_layout(width=600) + + plotting_data.append(fig2.data) + out[2 + i] = fig2 # fig2.write_image(f'noise_plot_{i+2}.svg') + + len(plotting_data) + fig3 = make_subplots(cols=2, rows=1) + for i in [1, 2]: + fig3.add_traces(plotting_data[i - 1], cols=i, rows=1) + fig3 = _clean_legend_duplicates(fig3) + fig3.update_layout( + legend={"yanchor": "bottom", "y": -0.2, "xanchor": "center", "x": 0.5} + ) + fig3.update_layout(height=700, width=800, template="plotly_white") + fig3.update_yaxes( + showgrid=False, + showline=True, + linewidth=1, + linecolor="black", + zeroline=False, + ) + fig3.update_xaxes( + showgrid=False, + showline=True, + linewidth=1, + linecolor="black", + zeroline=False, + ) + out[4] = fig3 + return out diff --git a/src/tranquilo_dev/plotting/task_create_publication_illustrations.py b/src/tranquilo_dev/plotting/task_create_publication_illustrations.py new file mode 100644 index 0000000..eb461cf --- /dev/null +++ b/src/tranquilo_dev/plotting/task_create_publication_illustrations.py @@ -0,0 +1,12 @@ +import pytask +from tranquilo_dev.config import BLD +from tranquilo_dev.plotting.illustrations import create_noise_plots + +BLD_SLIDEV = BLD.joinpath("bld_slidev") + + +@pytask.mark.produces({k: BLD_SLIDEV.joinpath(f"noise_plot_{k}.svg") for k in range(5)}) +def task_create_publication_noise_plots(produces): + figures = create_noise_plots() + for path, fig in zip(produces.values(), figures.values()): + fig.write_image(path) From dac98cfda20bc5e667bc3b96dab5530124f5fd51 Mon Sep 17 00:00:00 2001 From: Tim Mensinger Date: Thu, 4 Jan 2024 16:45:13 +0100 Subject: [PATCH 9/9] Add task to create other illustrations --- src/tranquilo_dev/plotting/illustrations.py | 245 +++++++++++++++++- .../task_create_presentation_illustrations.py | 33 +++ .../task_create_publication_illustrations.py | 12 - 3 files changed, 272 insertions(+), 18 deletions(-) create mode 100644 src/tranquilo_dev/plotting/task_create_presentation_illustrations.py delete mode 100644 src/tranquilo_dev/plotting/task_create_publication_illustrations.py diff --git a/src/tranquilo_dev/plotting/illustrations.py b/src/tranquilo_dev/plotting/illustrations.py index c0a50f7..43362fe 100644 --- a/src/tranquilo_dev/plotting/illustrations.py +++ b/src/tranquilo_dev/plotting/illustrations.py @@ -1,14 +1,16 @@ from copy import deepcopy +import estimagic as em import numpy as np import plotly.express as px import plotly.graph_objects as go from plotly.subplots import make_subplots from tranquilo.visualize import _clean_legend_duplicates +from tranquilo.visualize import _get_sample_points def create_noise_plots(): - out = {} + out = [] def func(x): return x**4 + x**2 - x @@ -57,7 +59,7 @@ def model(x, beta): "orientation": "h", } ) - out[0] = fig # fig.write_image("noise_plot_0.svg") + out.append(fig) # fig.write_image("noise_plot_0.svg") fig1 = deepcopy(fig) @@ -71,10 +73,10 @@ def model(x, beta): showlegend=False, ) ) - out[1] = fig1 # fig1.write_image("noise_plot_1.svg") + out.append(fig1) # fig1.write_image("noise_plot_1.svg") plotting_data = [] - for i, trustregion in enumerate([(-1.75, -0.5), (-0.75, 0.5)]): + for _, trustregion in enumerate([(-1.75, -0.5), (-0.75, 0.5)]): in_region = (trustregion[0] <= noise_x_grid) & (noise_x_grid <= trustregion[1]) xs = noise_x_grid.copy() ys = noise_y.copy() @@ -126,7 +128,7 @@ def model(x, beta): fig2.update_layout(width=600) plotting_data.append(fig2.data) - out[2 + i] = fig2 # fig2.write_image(f'noise_plot_{i+2}.svg') + out.append(fig2) # fig2.write_image(f'noise_plot_{i+2}.svg') len(plotting_data) fig3 = make_subplots(cols=2, rows=1) @@ -151,5 +153,236 @@ def model(x, beta): linecolor="black", zeroline=False, ) - out[4] = fig3 + out.append(fig3) return out + + +def create_other_illustration_plots(): + out = {} + + def sphere(x): + return {"root_contributions": x} + + res = em.minimize( + sphere, + params=np.array([1, 1]), + algorithm="tranquilo_ls", + ) + params_history = np.array(res.history["params"]) + states = res.algorithm_output["states"] + color_dict = { + "existing": "rgb(0,0,255)", + "new": "rgb(230,0,0)", + "discarded": "rgb(148, 148, 148)", + } + + for k in range(6): + out[f"animation_{k}.svg"] = _plot_sample_points( + params_history, states, k, color_dict + ) + + base_fig = _plot_sample_points(params_history, states, 1, color_dict) + fig = deepcopy(base_fig) + center = np.array([fig.data[0].x[0], fig.data[0].y[0]]) + candidate = np.array([fig.data[-1].x[0], fig.data[-1].y[0]]) + direction = candidate - center + line_points = [] + for i in [2, 4, 8]: + line_points.append(center + i * direction) + line_points = np.array(line_points) + fig.data[1].marker.color = fig.data[0].marker.color + fig.data[1].showlegend = False + fig.add_trace( + go.Scatter( + x=line_points[:, 0], + y=line_points[:, 1], + mode="markers", + marker_color="#805B87", + marker_size=9, + name="line search", + ) + ) + fig.update_xaxes(range=[0, 1.2]) + fig.update_yaxes(range=[0, 1.2]) + out["line_points_3.svg"] = deepcopy(fig) # fig.write_image("line_points_3.svg") + + fig.data[3].x = fig.data[3].x[:-1] + fig.data[3].y = fig.data[3].y[:-1] + out["line_points_2.svg"] = deepcopy(fig) # fig.write_image("line_points_2.svg") + + fig.data[3].x = fig.data[3].x[:-1] + fig.data[3].y = fig.data[3].y[:-1] + out["line_points_1.svg"] = deepcopy(fig) # fig.write_image("line_points_1.svg") + + fig.data = fig.data[:-1] + out["origin_plot.svg"] = deepcopy(fig) # fig.write_image("origin_plot.svg") + + center = candidate + radius = states[1].trustregion.radius + fig.add_shape( + type="circle", + xref="x", + yref="y", + x0=center[0] - radius, + y0=center[1] - radius, + x1=center[0] + radius, + y1=center[1] + radius, + line_width=0.5, + line_color="black", + line_dash="dash", + ) + out["empty_speculative_trustregion_small_scale.svg"] = deepcopy(fig) + + fig.update_yaxes(range=[0.5, 1.2]) + fig.update_xaxes(range=[0.5, 1.2]) + out["empty_speculative_trustregion_large_scale.svg"] = deepcopy(fig) + + fig.update_yaxes(range=[0, 1.2]) + fig.update_xaxes(range=[0, 1.2]) + + angle1 = 30 + angle2 = 55 + speculative1 = np.zeros((2, 2)) + speculative1[0, 0] = candidate[0] - np.cos(angle1 * np.pi / 180) * radius + speculative1[0, 1] = candidate[1] - np.sin(angle1 * np.pi / 180) * radius + speculative1[1, 0] = candidate[0] + np.sin(angle2 * np.pi / 180) * radius + speculative1[1, 1] = candidate[1] - np.cos(angle2 * np.pi / 180) * radius + fig.data = fig.data[:3] + fig.add_trace( + go.Scatter( + x=speculative1[:, 0], + y=speculative1[:, 1], + mode="markers", + marker_color="#F98900", + marker_size=9, + name="speculative", + ) + ) + out["sampled_speculative_trustregion_small_scale.svg"] = deepcopy(fig) + + fig.update_yaxes(range=[0.5, 1.2]) + fig.update_xaxes(range=[0.5, 1.2]) + out["sampled_speculative_trustregion_large_scale.svg"] = deepcopy(fig) + + fig.update_yaxes(range=[0, 1.2]) + fig.update_xaxes(range=[0, 1.2]) + angle1 = 15 + angle2 = 78 + speculative1[0, 0] = candidate[0] + np.sin(angle1 * np.pi / 180) * radius + speculative1[0, 1] = candidate[1] - np.cos(angle1 * np.pi / 180) * radius + speculative1[1, 0] = candidate[0] + np.sin(angle2 * np.pi / 180) * radius + speculative1[1, 1] = candidate[1] - np.cos(angle2 * np.pi / 180) * radius + + fig.data = fig.data[:3] + fig.add_trace( + go.Scatter( + x=speculative1[:, 0], + y=speculative1[:, 1], + mode="markers", + marker_color="#F98900", + marker_size=9, + name="speculative", + ) + ) + fig.add_trace( + go.Scatter( + x=line_points[:, 0], + y=line_points[:, 1], + mode="markers", + marker_color="#805B87", + marker_size=9, + name="line search", + ) + ) + + out["line_and_speculative_points.svg"] = deepcopy(fig) + fig.update_yaxes(scaleanchor="x", scaleratio=1) + fig.update_xaxes(scaleanchor="y", scaleratio=1) + + fig.update_layout(height=700, width=800, template="plotly_white") + out["checklayout.svg"] = deepcopy(fig) + + return out + + +# ====================================================================================== +# Shared functions +# ====================================================================================== + + +def _plot_sample_points(history, states, iteration, color_dict): + + state = states[iteration] + sample_points = _get_sample_points(state, history) + trustregion = state.trustregion + radius = trustregion.radius + center = trustregion.center + fig = go.Figure() + fig.add_shape( + type="circle", + xref="x", + yref="y", + x0=center[0] - radius, + y0=center[1] - radius, + x1=center[0] + radius, + y1=center[1] + radius, + line_width=0.5, + line_color="grey", + ) + + fig.add_traces( + px.scatter( + sample_points, + x=0, + y=1, + color="case", + color_discrete_map=color_dict, + opacity=0.7, + ).data, + ) + if iteration >= 1: + fig.add_trace( + go.Scatter( + x=[state.candidate_x[0]], + y=[state.candidate_x[1]], + mode="markers", + marker_symbol="star", + marker_color="#228b22", + name="candidate", + ), + ) + fig.update_traces( + marker_size=9, + ) + + fig = _clean_legend_duplicates(fig) + fig.update_layout(height=700, width=800, template="plotly_white") + fig.update_yaxes( + showgrid=False, + showline=True, + linewidth=1, + linecolor="black", + zeroline=False, + range=[-1.6, 1.6], + ) + fig.update_xaxes( + showgrid=False, + showline=True, + linewidth=1, + linecolor="black", + zeroline=False, + range=[-1.6, 1.6], + ) + fig.update_yaxes(scaleanchor="x", scaleratio=1) + fig.update_xaxes(scaleanchor="y", scaleratio=1) + + layout = go.Layout( + margin=go.layout.Margin( + l=10, # left margin + r=10, # right margin + b=10, # bottom margin + t=10, # top margin + ) + ) + fig = fig.update_layout(layout) + return fig diff --git a/src/tranquilo_dev/plotting/task_create_presentation_illustrations.py b/src/tranquilo_dev/plotting/task_create_presentation_illustrations.py new file mode 100644 index 0000000..ea9ef41 --- /dev/null +++ b/src/tranquilo_dev/plotting/task_create_presentation_illustrations.py @@ -0,0 +1,33 @@ +import pytask +from tranquilo_dev.config import BLD +from tranquilo_dev.plotting.illustrations import create_noise_plots +from tranquilo_dev.plotting.illustrations import create_other_illustration_plots + +BLD_SLIDEV = BLD.joinpath("bld_slidev") + + +@pytask.mark.produces({k: BLD_SLIDEV.joinpath(f"noise_plot_{k}.svg") for k in range(5)}) +def task_create_noise_plots(produces): + figures = create_noise_plots() + for path, fig in zip(produces.values(), figures): + fig.write_image(path) + + +OTHER_ILLUSTRATION_NAMES = [ + *[f"animation_{k}.svg" for k in range(6)], + *[f"line_points_{k}.svg" for k in range(1, 4)], + "origin_plot.svg", + "empty_speculative_trustregion_small_scale.svg", + "empty_speculative_trustregion_large_scale.svg", + "sampled_speculative_trustregion_small_scale.svg", + "sampled_speculative_trustregion_large_scale.svg", + "line_and_speculative_points.svg", + "checklayout.svg", +] + + +@pytask.mark.produces([BLD_SLIDEV.joinpath(name) for name in OTHER_ILLUSTRATION_NAMES]) +def task_create_other_illustration_plots(produces): + figures = create_other_illustration_plots() + for path in produces.values(): + figures[path.name].write_image(path) diff --git a/src/tranquilo_dev/plotting/task_create_publication_illustrations.py b/src/tranquilo_dev/plotting/task_create_publication_illustrations.py deleted file mode 100644 index eb461cf..0000000 --- a/src/tranquilo_dev/plotting/task_create_publication_illustrations.py +++ /dev/null @@ -1,12 +0,0 @@ -import pytask -from tranquilo_dev.config import BLD -from tranquilo_dev.plotting.illustrations import create_noise_plots - -BLD_SLIDEV = BLD.joinpath("bld_slidev") - - -@pytask.mark.produces({k: BLD_SLIDEV.joinpath(f"noise_plot_{k}.svg") for k in range(5)}) -def task_create_publication_noise_plots(produces): - figures = create_noise_plots() - for path, fig in zip(produces.values(), figures.values()): - fig.write_image(path)