From e2e45b191fcf159813080423588cde1604f003e7 Mon Sep 17 00:00:00 2001 From: r3kste <138380708+r3kste@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:42:57 +0530 Subject: [PATCH 1/4] Implementation of `bokeh` backend. --- .tools/envs/testenv-linux.yml | 1 + .tools/envs/testenv-nevergrad.yml | 1 + .tools/envs/testenv-numpy.yml | 1 + .tools/envs/testenv-others.yml | 1 + .tools/envs/testenv-pandas.yml | 1 + .tools/envs/testenv-plotly.yml | 1 + docs/rtd_environment.yml | 1 + .../how_to_change_plotting_backend.ipynb | 36 ++++ environment.yml | 1 + src/optimagic/config.py | 2 +- src/optimagic/visualization/backends.py | 196 +++++++++++++++++- .../visualization/convergence_plot.py | 26 ++- src/optimagic/visualization/history_plots.py | 13 +- src/optimagic/visualization/profile_plot.py | 11 +- src/optimagic/visualization/slice_plot.py | 8 +- .../visualization/test_convergence_plot.py | 3 + .../visualization/test_profile_plot.py | 1 + 17 files changed, 284 insertions(+), 20 deletions(-) diff --git a/.tools/envs/testenv-linux.yml b/.tools/envs/testenv-linux.yml index eebe90d0d..87e08d82b 100644 --- a/.tools/envs/testenv-linux.yml +++ b/.tools/envs/testenv-linux.yml @@ -20,6 +20,7 @@ dependencies: - pandas # run, tests - plotly>=6.2 # run, tests - matplotlib # tests + - bokeh # tests - pybaum>=0.1.2 # run, tests - scipy>=1.2.1 # run, tests - sqlalchemy # run, tests diff --git a/.tools/envs/testenv-nevergrad.yml b/.tools/envs/testenv-nevergrad.yml index 15bd1322f..d369e0c55 100644 --- a/.tools/envs/testenv-nevergrad.yml +++ b/.tools/envs/testenv-nevergrad.yml @@ -18,6 +18,7 @@ dependencies: - pandas # run, tests - plotly>=6.2 # run, tests - matplotlib # tests + - bokeh # tests - pybaum>=0.1.2 # run, tests - scipy>=1.2.1 # run, tests - sqlalchemy # run, tests diff --git a/.tools/envs/testenv-numpy.yml b/.tools/envs/testenv-numpy.yml index b9d35ef74..ed78d560c 100644 --- a/.tools/envs/testenv-numpy.yml +++ b/.tools/envs/testenv-numpy.yml @@ -18,6 +18,7 @@ dependencies: - joblib # run, tests - plotly>=6.2 # run, tests - matplotlib # tests + - bokeh # tests - pybaum>=0.1.2 # run, tests - scipy>=1.2.1 # run, tests - sqlalchemy # run, tests diff --git a/.tools/envs/testenv-others.yml b/.tools/envs/testenv-others.yml index 7d7f416c4..5b8a953cc 100644 --- a/.tools/envs/testenv-others.yml +++ b/.tools/envs/testenv-others.yml @@ -18,6 +18,7 @@ dependencies: - pandas # run, tests - plotly>=6.2 # run, tests - matplotlib # tests + - bokeh # tests - pybaum>=0.1.2 # run, tests - scipy>=1.2.1 # run, tests - sqlalchemy # run, tests diff --git a/.tools/envs/testenv-pandas.yml b/.tools/envs/testenv-pandas.yml index be1beff35..a0418ff9c 100644 --- a/.tools/envs/testenv-pandas.yml +++ b/.tools/envs/testenv-pandas.yml @@ -18,6 +18,7 @@ dependencies: - joblib # run, tests - plotly>=6.2 # run, tests - matplotlib # tests + - bokeh # tests - pybaum>=0.1.2 # run, tests - scipy>=1.2.1 # run, tests - sqlalchemy # run, tests diff --git a/.tools/envs/testenv-plotly.yml b/.tools/envs/testenv-plotly.yml index 29720829a..abf4dd211 100644 --- a/.tools/envs/testenv-plotly.yml +++ b/.tools/envs/testenv-plotly.yml @@ -18,6 +18,7 @@ dependencies: - numpy >= 2 # run, tests - pandas # run, tests - matplotlib # tests + - bokeh # tests - pybaum>=0.1.2 # run, tests - scipy>=1.2.1 # run, tests - sqlalchemy # run, tests diff --git a/docs/rtd_environment.yml b/docs/rtd_environment.yml index 58f6d8549..5ff33f75c 100644 --- a/docs/rtd_environment.yml +++ b/docs/rtd_environment.yml @@ -19,6 +19,7 @@ dependencies: - furo - pybaum - matplotlib + - bokeh - seaborn - numpy - pandas diff --git a/docs/source/how_to/how_to_change_plotting_backend.ipynb b/docs/source/how_to/how_to_change_plotting_backend.ipynb index 14b86277c..c8e07237f 100644 --- a/docs/source/how_to/how_to_change_plotting_backend.ipynb +++ b/docs/source/how_to/how_to_change_plotting_backend.ipynb @@ -44,6 +44,19 @@ "\n", ":::\n", "\n", + ":::{tab-item} Bokeh\n", + "\n", + "To select the Bokeh backend, set `backend=\"bokeh\"`.\n", + "\n", + "The returned figure object is a [`bokeh.plotting.figure`](https://docs.bokeh.org/en/latest/docs/reference/plotting/figure.html).\n", + "\n", + "```{note}\n", + "In case of grid plots (such as `convergence_plot` or `slice_plot`), the returned object is a [`bokeh.models.GridPlot`](https://docs.bokeh.org/en/latest/docs/reference/models/plots.html#bokeh.models.GridPlot) object.\n", + "```\n", + "\n", + "```{warning}\n", + "Bokeh applies themes globally. Passing the `template` parameter to a plotting function updates the theme for all existing and future Bokeh plots. If you do not pass `template`, a default template is applied, which will also change the global theme.\n", + "```\n", "::::" ] }, @@ -134,6 +147,29 @@ "source": [ "ax = om.criterion_plot(results, backend=\"matplotlib\")" ] + }, + { + "cell_type": "markdown", + "id": "10", + "metadata": {}, + "source": [ + "## Bokeh" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11", + "metadata": {}, + "outputs": [], + "source": [ + "from bokeh.io import output_notebook, show\n", + "\n", + "output_notebook()\n", + "\n", + "p = om.criterion_plot(results, backend=\"bokeh\")\n", + "show(p)" + ] } ], "metadata": { diff --git a/environment.yml b/environment.yml index d4b4f9c74..d62a675b0 100644 --- a/environment.yml +++ b/environment.yml @@ -22,6 +22,7 @@ dependencies: - pandas # run, tests - plotly>=6.2 # run, tests - matplotlib # tests + - bokeh # tests - pybaum>=0.1.2 # run, tests - scipy>=1.2.1 # run, tests - sqlalchemy # run, tests diff --git a/src/optimagic/config.py b/src/optimagic/config.py index 0aeb98b6b..def8302f7 100644 --- a/src/optimagic/config.py +++ b/src/optimagic/config.py @@ -62,7 +62,7 @@ def _is_installed(module_name: str) -> bool: # ====================================================================================== IS_MATPLOTLIB_INSTALLED = _is_installed("matplotlib") - +IS_BOKEH_INSTALLED = _is_installed("bokeh") # ====================================================================================== # Check if pandas version is newer or equal to version 2.1.0 diff --git a/src/optimagic/visualization/backends.py b/src/optimagic/visualization/backends.py index 4a35a51c7..7c45aa8ab 100644 --- a/src/optimagic/visualization/backends.py +++ b/src/optimagic/visualization/backends.py @@ -4,11 +4,12 @@ import numpy as np import plotly.graph_objects as go -from optimagic.config import IS_MATPLOTLIB_INSTALLED +from optimagic.config import IS_BOKEH_INSTALLED, IS_MATPLOTLIB_INSTALLED from optimagic.exceptions import InvalidPlottingBackendError, NotInstalledError from optimagic.visualization.plotting_utilities import LineData, MarkerData if TYPE_CHECKING: + import bokeh import matplotlib.pyplot as plt @@ -409,9 +410,189 @@ def _grid_line_plot_matplotlib( return axes +def _line_plot_bokeh( + lines: list[LineData], + *, + title: str | None, + xlabel: str | None, + xrange: tuple[float, float] | None, + ylabel: str | None, + yrange: tuple[float, float] | None, + template: str | None, + height: int | None, + width: int | None, + legend_properties: dict[str, Any] | None, + margin_properties: dict[str, Any] | None, + horizontal_line: float | None, + marker: MarkerData | None, + subplot: Any | None = None, +) -> "bokeh.plotting.figure": + from bokeh.io import curdoc + from bokeh.models import Legend, LegendItem, Scatter, Span + from bokeh.plotting import figure + + if template is None: + template = "light_minimal" + curdoc().theme = template + + if subplot is not None: + p = subplot + else: + p = figure() + + if title is not None: + p.title.text = title + if xlabel is not None: + p.xaxis.axis_label = xlabel.format(linebreak="\n") + if xrange is not None: + p.x_range.start, p.x_range.end = xrange + if ylabel is not None: + p.yaxis.axis_label = ylabel.format(linebreak="\n") + if yrange is not None: + p.y_range.start, p.y_range.end = yrange + if height is not None: + p.height = height + if width is not None: + p.width = width + + _legend_items = [] + for line in lines: + glyph = p.line( + line.x, + line.y, + line_color=line.color, + line_width=2, + ) + + if line.show_in_legend: + _legend_items.append(LegendItem(label=line.name, renderers=[glyph])) + + if horizontal_line is not None: + glyph = Span( + location=horizontal_line, + dimension="width", + line_color=p.yaxis.axis_line_color or "gray", + line_width=p.yaxis.axis_line_width or 2, + ) + p.add_layout(glyph) + + if marker is not None: + glyph = Scatter( + x=marker.x, + y=marker.y, + marker="dot", + fill_color=marker.color, + line_color=marker.color, + size=30, + ) + p.add_glyph(glyph) + + if _legend_items: + legend_kwargs = legend_properties.copy() if legend_properties else {} + place = legend_kwargs.pop("place", "center") + text = legend_kwargs.pop("title", None) + + legend = Legend(items=_legend_items, **(legend_kwargs)) + p.add_layout(legend, place=place) + p.legend.title = text + + return p + + +def _grid_line_plot_bokeh( + lines_list: list[list[LineData]], + *, + n_rows: int, + n_cols: int, + titles: list[str] | None, + xlabels: list[str] | None, + xrange: tuple[float, float] | None, + share_x: bool, + ylabels: list[str] | None, + yrange: tuple[float, float] | None, + share_y: bool, + template: str | None, + height: int | None, + width: int | None, + legend_properties: dict[str, Any] | None, + margin_properties: dict[str, Any] | None, + plot_title: str | None, + marker_list: list[MarkerData] | None, + make_subplot_kwargs: dict[str, Any] | None = None, +) -> "bokeh.models.GridPlot": + """Create a grid of line plots using Bokeh. + + Args: + ...: All other argument descriptions can be found in the docstring of the + `grid_line_plot` function. + + Returns: + A Bokeh gridplot object. + + """ + + from bokeh.layouts import gridplot + from bokeh.plotting import figure + + plots: list[list[figure]] = [] + + for row in range(n_rows): + subplot_row: list[figure] = [] + for col in range(n_cols): + idx = row * n_cols + col + if idx >= len(lines_list): + break + + p = figure() + + if share_x: + if row > 0: + # Share x-range with the top-most subplot in the same column + p.x_range = plots[0][col].x_range + if row < n_rows - 1: + # Hide tick labels except for subplots in the last row + p.xaxis.major_label_text_font_size = "0pt" + if share_y: + if col > 0: + # Share y-range with the left-most subplot in the same row + p.y_range = subplot_row[0].y_range + + # Hide tick labels except for subplots in the first column + p.yaxis.major_label_text_font_size = "0pt" + + _line_plot_bokeh( + lines_list[idx], + title=titles[idx] if titles else None, + xlabel=xlabels[idx] if xlabels else None, + xrange=xrange, + ylabel=ylabels[idx] if ylabels else None, + yrange=yrange, + template=template, + height=None, + width=None, + legend_properties=legend_properties, + margin_properties=None, + horizontal_line=None, + marker=marker_list[idx] if marker_list else None, + subplot=p, + ) + + subplot_row.append(p) + plots.append(subplot_row) + + grid = gridplot( + plots, + height=height // n_rows if height else None, + width=width // n_cols if width else None, + toolbar_location="right", + ) + + return grid + + def line_plot( lines: list[LineData], - backend: Literal["plotly", "matplotlib"] = "plotly", + backend: Literal["plotly", "matplotlib", "bokeh"] = "plotly", *, title: str | None = None, xlabel: str | None = None, @@ -474,7 +655,7 @@ def line_plot( def grid_line_plot( lines_list: list[list[LineData]], - backend: Literal["plotly", "matplotlib"] = "plotly", + backend: Literal["plotly", "matplotlib", "bokeh"] = "plotly", *, n_rows: int, n_cols: int, @@ -562,18 +743,23 @@ def grid_line_plot( _line_plot_matplotlib, _grid_line_plot_matplotlib, ), + "bokeh": ( + IS_BOKEH_INSTALLED, + _line_plot_bokeh, + _grid_line_plot_bokeh, + ), } @overload def _get_plot_function( - backend: Literal["plotly", "matplotlib"], grid_plot: Literal[False] + backend: Literal["plotly", "matplotlib", "bokeh"], grid_plot: Literal[False] ) -> LinePlotFunction: ... @overload def _get_plot_function( - backend: Literal["plotly", "matplotlib"], grid_plot: Literal[True] + backend: Literal["plotly", "matplotlib", "bokeh"], grid_plot: Literal[True] ) -> GridLinePlotFunction: ... diff --git a/src/optimagic/visualization/convergence_plot.py b/src/optimagic/visualization/convergence_plot.py index 286cfa650..3287a9b4d 100644 --- a/src/optimagic/visualization/convergence_plot.py +++ b/src/optimagic/visualization/convergence_plot.py @@ -14,6 +14,11 @@ BACKEND_TO_CONVERGENCE_PLOT_LEGEND_PROPERTIES: dict[str, dict[str, Any]] = { "plotly": {}, "matplotlib": {"loc": "outside right upper", "fontsize": "x-small"}, + "bokeh": { + "location": "top_right", + "place": "right", + "label_text_font_size": "8pt", + }, } BACKEND_TO_CONVERGENCE_PLOT_MARGIN_PROPERTIES: dict[str, dict[str, int]] = { @@ -70,7 +75,7 @@ def convergence_plot( x_precision: float = 1e-4, y_precision: float = 1e-4, combine_plots_in_grid: bool = True, - backend: Literal["plotly", "matplotlib"] = "plotly", + backend: Literal["plotly", "matplotlib", "bokeh"] = "plotly", template: str | None = None, palette: list[str] | str = DEFAULT_PALETTE, ) -> Any: @@ -124,7 +129,8 @@ def convergence_plot( for each factor pair or a dictionary of individual plots. Default is True. backend: The backend to use for plotting. Default is "plotly". template: The template for the figure. If not specified, the default template of - the backend is used. + the backend is used. For the 'bokeh' backend, this changes the global theme, + which affects all Bokeh plots in the session. palette: The coloring palette for traces. Default is the D3 qualitative palette. Returns: @@ -173,6 +179,7 @@ def convergence_plot( outcome=outcome, palette=palette, combine_plots_in_grid=combine_plots_in_grid, + backend=backend, ) n_rows = int(np.ceil(len(lines_list) / n_cols)) @@ -238,7 +245,8 @@ def _extract_convergence_plot_lines( runtime_measure: str, outcome: str, palette: list[str] | str, - combine_plots_in_grid: bool = True, + combine_plots_in_grid: bool, + backend: str, ) -> tuple[list[list[LineData]], list[str]]: lines_list = [] # container for all subplots titles = [] @@ -255,6 +263,14 @@ def _extract_convergence_plot_lines( else: to_plot = _prob_data + show_in_legend = True + if combine_plots_in_grid: + # If combining plots, only show in legend of first subplot + # For 'bokeh' backend, show in legend for all subplots + # as it does not support single legend on grid plots. + # See: https://github.com/bokeh/bokeh/issues/7607 + show_in_legend = (i == 0) or (backend == "bokeh") + for alg, group in to_plot.groupby("algorithm", sort=False): line_data = LineData( x=group[runtime_measure].to_numpy(), @@ -262,7 +278,7 @@ def _extract_convergence_plot_lines( name=str(alg), color=next(palette_cycle), # if combining plots, only show legend in first subplot - show_in_legend=(not combine_plots_in_grid) or (i == 0), + show_in_legend=show_in_legend, ) subplot_lines.append(line_data) @@ -274,7 +290,7 @@ def _extract_convergence_plot_lines( name="true solution", color=next(palette_cycle), # if combining plots, only show legend in first subplot - show_in_legend=(not combine_plots_in_grid) or (i == 0), + show_in_legend=show_in_legend, ) subplot_lines.append(line_data) diff --git a/src/optimagic/visualization/history_plots.py b/src/optimagic/visualization/history_plots.py index d3aa440cd..d14d8412b 100644 --- a/src/optimagic/visualization/history_plots.py +++ b/src/optimagic/visualization/history_plots.py @@ -27,6 +27,9 @@ "matplotlib": { "loc": "upper right", }, + "bokeh": { + "location": "top_right", + }, } @@ -37,7 +40,7 @@ def criterion_plot( results: ResultOrPath | list[ResultOrPath] | dict[str, ResultOrPath], names: list[str] | str | None = None, max_evaluations: int | None = None, - backend: Literal["plotly", "matplotlib"] = "plotly", + backend: Literal["plotly", "matplotlib", "bokeh"] = "plotly", template: str | None = None, palette: list[str] | str = DEFAULT_PALETTE, stack_multistart: bool = False, @@ -53,7 +56,8 @@ def criterion_plot( max_evaluations: Clip the criterion history after that many entries. backend: The backend to use for plotting. Default is "plotly". template: The template for the figure. If not specified, the default template of - the backend is used. + the backend is used. For the 'bokeh' backend, this changes the global theme, + which affects all Bokeh plots in the session. palette: The coloring palette for traces. Default is the D3 qualitative palette. stack_multistart: Whether to combine multistart histories into a single history. Default is False. @@ -154,7 +158,7 @@ def params_plot( result: ResultOrPath, selector: Callable[[PyTree], PyTree] | None = None, max_evaluations: int | None = None, - backend: Literal["plotly", "matplotlib"] = "plotly", + backend: Literal["plotly", "matplotlib", "bokeh"] = "plotly", template: str | None = None, palette: list[str] | str = DEFAULT_PALETTE, show_exploration: bool = False, @@ -169,7 +173,8 @@ def params_plot( max_evaluations: Clip the criterion history after that many entries. backend: The backend to use for plotting. Default is "plotly". template: The template for the figure. If not specified, the default template of - the backend is used. + the backend is used. For the 'bokeh' backend, this changes the global theme, + which affects all Bokeh plots in the session. palette: The coloring palette for traces. Default is the D3 qualitative palette. show_exploration: If True, exploration samples of a multistart optimization are visualized. Default is False. diff --git a/src/optimagic/visualization/profile_plot.py b/src/optimagic/visualization/profile_plot.py index 8d828b6a3..cf9482aba 100644 --- a/src/optimagic/visualization/profile_plot.py +++ b/src/optimagic/visualization/profile_plot.py @@ -19,6 +19,12 @@ "fontsize": "x-small", "title": "algorithm", }, + "bokeh": { + "location": "top_right", + "place": "right", + "label_text_font_size": "8pt", + "title": "algorithm", + }, } BACKEND_TO_PROFILE_PLOT_MARGIN_PROPERTIES: dict[str, dict[str, Any]] = { @@ -38,7 +44,7 @@ def profile_plot( stopping_criterion: Literal["x", "y", "x_and_y", "x_or_y"] = "y", x_precision: float = 1e-4, y_precision: float = 1e-4, - backend: Literal["plotly", "matplotlib"] = "plotly", + backend: Literal["plotly", "matplotlib", "bokeh"] = "plotly", template: str | None = None, palette: list[str] | str = DEFAULT_PALETTE, ) -> Any: @@ -82,7 +88,8 @@ def profile_plot( value) before the criterion for clipping and convergence is fulfilled. backend: The backend to use for plotting. Default is "plotly". template: The template for the figure. If not specified, the default template of - the backend is used. + the backend is used. For the 'bokeh' backend, this changes the global theme, + which affects all Bokeh plots in the session. palette: The coloring palette for traces. Default is the D3 qualitative palette. Returns: diff --git a/src/optimagic/visualization/slice_plot.py b/src/optimagic/visualization/slice_plot.py index 2ef2aacd1..206f6a8ec 100644 --- a/src/optimagic/visualization/slice_plot.py +++ b/src/optimagic/visualization/slice_plot.py @@ -44,7 +44,7 @@ def slice_plot( share_y: bool = True, expand_yrange: float = 0.02, share_x: bool = False, - backend: Literal["plotly", "matplotlib"] = "plotly", + backend: Literal["plotly", "matplotlib", "bokeh"] = "plotly", template: str | None = None, color: str | None = DEFAULT_PALETTE[0], title: str | None = None, @@ -91,9 +91,11 @@ def slice_plot( x axis for all plots in one column. backend: The backend to use for plotting. Default is "plotly". template: The template for the figure. If not specified, the default template of - the backend is used. + the backend is used. For the 'bokeh' backend, this changes the global theme, + which affects all Bokeh plots in the session. color: The line color. - title: The figure title. + title: The figure title. This is not used for the `bokeh` backend, as it does + not support title for grid plot. return_dict: If True, return dictionary with individual plots of each parameter, else, combine individual plots into a figure with subplots. batch_evaluator: See :ref:`batch_evaluators`. diff --git a/tests/optimagic/visualization/test_convergence_plot.py b/tests/optimagic/visualization/test_convergence_plot.py index a5a77fe04..469503bf7 100644 --- a/tests/optimagic/visualization/test_convergence_plot.py +++ b/tests/optimagic/visualization/test_convergence_plot.py @@ -53,6 +53,7 @@ def test_convergence_plot_default_options(benchmark_results): {"x_precision": 1e-5}, {"y_precision": 1e-5}, {"backend": "matplotlib"}, + {"backend": "bokeh"}, ] @@ -111,6 +112,8 @@ def test_extract_convergence_plot_lines(benchmark_results): runtime_measure="n_evaluations", outcome="criterion_normalized", palette=["red", "green", "blue"], + combine_plots_in_grid=True, + backend="bla", ) assert isinstance(lines_list, list) and isinstance(titles, list) diff --git a/tests/optimagic/visualization/test_profile_plot.py b/tests/optimagic/visualization/test_profile_plot.py index c6ed62d55..c8fd1817c 100644 --- a/tests/optimagic/visualization/test_profile_plot.py +++ b/tests/optimagic/visualization/test_profile_plot.py @@ -181,6 +181,7 @@ def test_extract_profile_plot_lines(): {"runtime_measure": "n_batches"}, {"stopping_criterion": "x_or_y"}, {"backend": "matplotlib"}, + {"backend": "bokeh"}, ] From a4178e5d345168368cd00c27eaaf8313c82486e8 Mon Sep 17 00:00:00 2001 From: r3kste <138380708+r3kste@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:17:27 +0530 Subject: [PATCH 2/4] Fix some mypy issues --- src/optimagic/visualization/backends.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/optimagic/visualization/backends.py b/src/optimagic/visualization/backends.py index 7c45aa8ab..deb874283 100644 --- a/src/optimagic/visualization/backends.py +++ b/src/optimagic/visualization/backends.py @@ -427,13 +427,15 @@ def _line_plot_bokeh( marker: MarkerData | None, subplot: Any | None = None, ) -> "bokeh.plotting.figure": + from bokeh import themes from bokeh.io import curdoc - from bokeh.models import Legend, LegendItem, Scatter, Span + from bokeh.models import Scatter + from bokeh.models.annotations import Legend, LegendItem, Span from bokeh.plotting import figure if template is None: template = "light_minimal" - curdoc().theme = template + curdoc().theme = themes.built_in_themes[template] if subplot is not None: p = subplot @@ -534,10 +536,10 @@ def _grid_line_plot_bokeh( from bokeh.layouts import gridplot from bokeh.plotting import figure - plots: list[list[figure]] = [] + plots: list[list[Any]] = [] for row in range(n_rows): - subplot_row: list[figure] = [] + subplot_row: list[Any] = [] for col in range(n_cols): idx = row * n_cols + col if idx >= len(lines_list): From c723c88564bc91e093811aba78954af9d4f6e0aa Mon Sep 17 00:00:00 2001 From: r3kste <138380708+r3kste@users.noreply.github.com> Date: Thu, 27 Nov 2025 13:54:02 +0530 Subject: [PATCH 3/4] Use `figure` as type hint in the bokeh backend --- .pre-commit-config.yaml | 1 + src/optimagic/visualization/backends.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8c0599032..e25f32c5b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -153,6 +153,7 @@ repos: - types-cffi - types-openpyxl - types-jinja2 + - bokeh ci: autoupdate_schedule: monthly skip: diff --git a/src/optimagic/visualization/backends.py b/src/optimagic/visualization/backends.py index deb874283..c8195cc55 100644 --- a/src/optimagic/visualization/backends.py +++ b/src/optimagic/visualization/backends.py @@ -536,7 +536,7 @@ def _grid_line_plot_bokeh( from bokeh.layouts import gridplot from bokeh.plotting import figure - plots: list[list[Any]] = [] + plots: list[list[figure]] = [] for row in range(n_rows): subplot_row: list[Any] = [] @@ -582,7 +582,7 @@ def _grid_line_plot_bokeh( subplot_row.append(p) plots.append(subplot_row) - grid = gridplot( + grid = gridplot( # type: ignore[call-overload] plots, height=height // n_rows if height else None, width=width // n_cols if width else None, From 1c5c9b6f480a8cd5cd265929391d1e7f455a226e Mon Sep 17 00:00:00 2001 From: r3kste <138380708+r3kste@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:34:16 +0530 Subject: [PATCH 4/4] Skip mypy stubtest in CI due to size limitations. --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e25f32c5b..d073ebc02 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -158,3 +158,8 @@ ci: autoupdate_schedule: monthly skip: - update-algo-selection-code + # Skip mypy stubtest on pre-commit.ci due to maximum size limitations. This is + # unlikely to get better in the future as dependencies keep growing. Local runs + # of pre-commit would still execute stubtest. For CI, we have a separate GitHub + # Action that runs stubtest. + - mypy