diff --git a/src/tranquilo_dev/config.py b/src/tranquilo_dev/config.py index bdcfc36..1fe0f58 100644 --- a/src/tranquilo_dev/config.py +++ b/src/tranquilo_dev/config.py @@ -71,6 +71,32 @@ def _n_evals_10(*args, **kwargs): # noqa: U100 return 10 +LABELS = { + # Tranquilo labels + "tranquilo": "Tranquilo-Scalar", + "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_experimental": "Tranquilo-Scalar-Experimental", + "tranquilo_ls_experimental": "Tranquilo-LS-Experimental", + # DFO-LS labels + "dfols": "DFO-LS", + "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", + "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/benchmark_plotting_functions.py b/src/tranquilo_dev/plotting/benchmark_plotting_functions.py new file mode 100644 index 0000000..695dc39 --- /dev/null +++ b/src/tranquilo_dev/plotting/benchmark_plotting_functions.py @@ -0,0 +1,269 @@ +"""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 +import matplotlib.pyplot as plt +from tranquilo_dev.config import LABELS + + +# ====================================================================================== +# Plot Config +# ====================================================================================== + +FIGURE_WIDTH_IN_CM = 14.69785 +FIGURE_HEIGHT_IN_CM = 10.0 + +AXIS_LABELS = { + "profile_plot": { + "xlabel": "Computational budget (normalized)", + "ylabel": "Share of solved problems", + }, + "deviation_plot": { + "xlabel": "Computational budget", + "ylabel": "Average distance to optimum (normalized)", + }, +} + +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" + +TABLEAU_10_COLORS = { + "blue": "#5778a4", + "green": "#6a9f58", + "orange": "#e49444", + "red": "#d1615d", + "teal": "#85b6b2", + "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 / + # 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", +} + + +BASE_COLORS = { + "Tranquilo-Scalar": TABLEAU_10_COLORS["teal"], + "Tranquilo-LS": TABLEAU_10_COLORS["blue"], + "DFO-LS": TABLEAU_10_COLORS["green"], + "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["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": [ + "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": [ + "DFO-LS (3 evals)", + "DFO-LS (5 evals)", + "DFO-LS (10 evals)", + "Tranquilo-LS", + ], + "scalar_vs_ls_benchmark": [ + "Tranquilo-LS", + "DFO-LS", + "Pounders", + "Tranquilo-Scalar", + "NlOpt-BOBYQA", + "NlOpt-Nelder-Mead", + ], +} + +# Font +# ====================================================================================== + +matplotlib.rcParams["font.size"] = 10 +matplotlib.rcParams["font.sans-serif"] = "Fira Sans" +matplotlib.rcParams["font.family"] = "sans-serif" + + +# ====================================================================================== +# Plotting function +# ====================================================================================== + + +def plot_benchmark(data, plot, benchmark): + """Create the base matplotlib figure. # noqa: D406, D407, D400 + + 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. + + """ + x_range = X_RANGE[plot][benchmark] + legend_label_order = LEGEND_LABEL_ORDER[benchmark] + + # 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_IN_CM), _cm_to_inch(FIGURE_HEIGHT_IN_CM)) + ) + for name, line in data.items(): + 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) + + # 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) + + # 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=3, + bbox_to_anchor=(0.5, -0.15), + 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/illustrations.py b/src/tranquilo_dev/plotting/illustrations.py new file mode 100644 index 0000000..43362fe --- /dev/null +++ b/src/tranquilo_dev/plotting/illustrations.py @@ -0,0 +1,388 @@ +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 = [] + + 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.append(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.append(fig1) # fig1.write_image("noise_plot_1.svg") + + plotting_data = [] + 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() + 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.append(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.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/plotting_functions.py b/src/tranquilo_dev/plotting/plotting_functions.py deleted file mode 100644 index 4137fab..0000000 --- a/src/tranquilo_dev/plotting/plotting_functions.py +++ /dev/null @@ -1,114 +0,0 @@ -import plotly.graph_objects as go - -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 -# ====================================================================================== - - -def update_profile_plot_scalar_benchmark(fig): - fig = _update_labels(fig) - fig = _update_legend(fig) - fig = _update_xrange(fig, 0, 50) - return fig - - -def update_profile_plot_ls_benchmark(fig): - fig = _update_labels(fig) - fig = _update_legend(fig) - fig = _update_xrange(fig, 0, 40) - return fig - - -def update_profile_plot_parallel_benchmark(fig): - fig = _update_legend(fig) - return fig - - -def update_profile_plot_noisy_benchmark(fig): - fig = _update_legend(fig) - return fig - - -def update_profile_plot_scalar_vs_ls_benchmark(fig): - fig = _update_legend(fig) - fig = _update_xrange(fig, 0, 50) - return fig - - -# ====================================================================================== -# Deviation plot updates -# ====================================================================================== - - -def update_deviation_plot_scalar_benchmark(fig): - fig = _update_labels(fig) - fig = _update_legend(fig) - fig = _update_xrange(fig, 0, 300) - return fig - - -def update_deviation_plot_ls_benchmark(fig): - fig = _update_labels(fig) - fig = _update_legend(fig) - fig = _update_xrange(fig, 0, 400) - return fig - - -def update_deviation_plot_parallel_benchmark(fig): - fig = _update_legend(fig) - fig = _update_xrange(fig, 0, 50) - return fig - - -def update_deviation_plot_noisy_benchmark(fig): - fig = _update_legend(fig) - return fig - - -def update_deviation_plot_scalar_vs_ls_benchmark(fig): - fig = _update_legend(fig) - fig = _update_xrange(fig, 0, 500) - return fig - - -# ====================================================================================== -# Shared functions -# ====================================================================================== - - -def _update_xrange(fig, lower, upper): - fig = go.Figure(fig) # makes a copy of the figure - fig.update_xaxes(range=[lower, upper]) - return fig - - -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_legend(fig): - fig = go.Figure(fig) # makes a copy of the figure - fig.update_layout( - legend_title="Algorithm", - legend_title_side="top", - ) - return fig 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..2779dab 100644 --- a/src/tranquilo_dev/plotting/task_create_benchmark_plots.py +++ b/src/tranquilo_dev/plotting/task_create_benchmark_plots.py @@ -1,13 +1,19 @@ +from copy import deepcopy + 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 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": {}} @@ -82,7 +88,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 +131,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_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_plots.py b/src/tranquilo_dev/plotting/task_create_publication_benchmark_plots.py similarity index 66% rename from src/tranquilo_dev/plotting/task_create_publication_plots.py rename to src/tranquilo_dev/plotting/task_create_publication_benchmark_plots.py index 050833c..80bff59 100644 --- a/src/tranquilo_dev/plotting/task_create_publication_plots.py +++ b/src/tranquilo_dev/plotting/task_create_publication_benchmark_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.benchmark_plotting_functions import plot_benchmark BLD_PAPER = BLD.joinpath("bld_paper") @@ -28,45 +28,58 @@ "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", {}) - 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, - "update_plot": update_plot, "problems": problems, + "plot_type": plot_type, + "benchmark": benchmark, } task_id = f"{plot_type}_{benchmark}" @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")) - def task_create_publication_plots( + @pytask.mark.produces(BLD_PAPER.joinpath(f"{plot_type}s", f"{benchmark}.pdf")) + def task_create_publication_benchmark_plots( depends_on, produces, plot_func, plot_kwargs, - update_plot, problems, + plot_type, + benchmark, ): results = {} 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) - updated.write_image(produces) + plotly_fig = plot_func(problems=problems, results=results, **plot_kwargs) + plotting_data = get_data_from_plotly_figure(plotly_fig) + + fig = plot_benchmark( + plotting_data, + plot=plot_type, + benchmark=benchmark, + ) + 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}