From 385643151316a9396c0c30c9a6d7f81a353807dc Mon Sep 17 00:00:00 2001 From: r3kste <138380708+r3kste@users.noreply.github.com> Date: Tue, 23 Sep 2025 21:59:29 +0530 Subject: [PATCH 1/3] Add How-To guide for changing plotting backends. --- .../how_to_change_plotting_backend.ipynb | 156 ++++++++++++++++++ .../how_to/how_to_visualize_histories.ipynb | 46 ++++-- docs/source/how_to/index.md | 1 + 3 files changed, 193 insertions(+), 10 deletions(-) create mode 100644 docs/source/how_to/how_to_change_plotting_backend.ipynb diff --git a/docs/source/how_to/how_to_change_plotting_backend.ipynb b/docs/source/how_to/how_to_change_plotting_backend.ipynb new file mode 100644 index 000000000..0e04d2cf8 --- /dev/null +++ b/docs/source/how_to/how_to_change_plotting_backend.ipynb @@ -0,0 +1,156 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# How to change the plotting backend" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "optimagic supports various visualization libraries as plotting backends, which can be\n", + "selected using the `backend` argument." + ] + }, + { + "cell_type": "markdown", + "id": "2", + "metadata": {}, + "source": [ + "::::{tab-set}\n", + "\n", + ":::{tab-item} Plotly\n", + "\n", + "The default plotting library. To select the Plotly backend explicitly, set `backend=\"plotly\"`.\n", + "\n", + "The returned figure object is a [`plotly.graph_objects.Figure`](https://plotly.com/python-api-reference/generated/plotly.graph_objects.Figure.html).\n", + "\n", + ":::\n", + "\n", + ":::{tab-item} Matplotlib\n", + "\n", + "To select the Matplotlib backend, set `backend=\"matplotlib\"`.\n", + "\n", + "The returned figure object is a [`matplotlib.axes.Axes`](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html).\n", + "\n", + ":::\n", + "\n", + "::::" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "In the following guide, we showcase the criterion plot visualized using different\n", + "backends." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "import optimagic as om\n", + "\n", + "\n", + "def sphere(x):\n", + " return x @ x\n", + "\n", + "\n", + "results = {}\n", + "for algo in [\"scipy_lbfgsb\", \"scipy_neldermead\"]:\n", + " results[algo] = om.minimize(sphere, params=np.arange(5), algorithm=algo)" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Plotly" + ] + }, + { + "cell_type": "markdown", + "id": "6", + "metadata": {}, + "source": [ + ":::{note}\n", + "\n", + "**Choose the Plotly renderer according to your environment:**\n", + "\n", + "- Use `plotly.io.renderers.default = \"notebook_connected\"` in Jupyter notebooks for interactive plots.\n", + "- Use `plotly.io.renderers.default = \"browser\"` to open plots in your default web browser when running as a script.\n", + "\n", + "Refer to the [Plotly documentation](https://plotly.com/python/renderers/) for more details.\n", + "\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "import plotly.io as pio\n", + "\n", + "pio.renderers.default = \"notebook_connected\"\n", + "\n", + "fig = om.criterion_plot(results, backend=\"plotly\") # Also the default\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "## Matplotlib" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "ax = om.criterion_plot(results, backend=\"matplotlib\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.18" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/how_to/how_to_visualize_histories.ipynb b/docs/source/how_to/how_to_visualize_histories.ipynb index 144a35dbf..fbfdb58dd 100644 --- a/docs/source/how_to/how_to_visualize_histories.ipynb +++ b/docs/source/how_to/how_to_visualize_histories.ipynb @@ -97,7 +97,7 @@ "id": "8", "metadata": {}, "source": [ - "## Use some advanced options of criterion_plot" + "## Use some advanced options of criterion plot" ] }, { @@ -121,6 +121,32 @@ "cell_type": "markdown", "id": "10", "metadata": {}, + "source": [ + "## Criterion Plot with different plotting backend" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "Also see: [How to change the plotting backend](how_to_change_plotting_backend.ipynb) for a list of available backends, return types, and usage examples." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "ax = om.criterion_plot(results, backend=\"matplotlib\")" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, "source": [ "## Make a params plot" ] @@ -128,7 +154,7 @@ { "cell_type": "code", "execution_count": null, - "id": "11", + "id": "14", "metadata": {}, "outputs": [], "source": [ @@ -138,7 +164,7 @@ }, { "cell_type": "markdown", - "id": "12", + "id": "15", "metadata": {}, "source": [ "## Use advanced options of params plot" @@ -147,7 +173,7 @@ { "cell_type": "code", "execution_count": null, - "id": "13", + "id": "16", "metadata": {}, "outputs": [], "source": [ @@ -163,16 +189,16 @@ }, { "cell_type": "markdown", - "id": "14", + "id": "17", "metadata": {}, "source": [ - "## criterion_plot with multistart optimization" + "## Criterion plot with multistart optimization" ] }, { "cell_type": "code", "execution_count": null, - "id": "15", + "id": "18", "metadata": {}, "outputs": [], "source": [ @@ -192,7 +218,7 @@ { "cell_type": "code", "execution_count": null, - "id": "16", + "id": "19", "metadata": {}, "outputs": [], "source": [ @@ -203,7 +229,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -217,7 +243,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.17" } }, "nbformat": 4, diff --git a/docs/source/how_to/index.md b/docs/source/how_to/index.md index 911762e43..b8fbe8bbf 100644 --- a/docs/source/how_to/index.md +++ b/docs/source/how_to/index.md @@ -19,6 +19,7 @@ how_to_constraints how_to_globalization how_to_multistart how_to_visualize_histories +how_to_change_plotting_backend how_to_scaling how_to_logging how_to_errors_during_optimization From 1b3bbb8b4e4cd132edec0a2ceea1d6599bbb08b0 Mon Sep 17 00:00:00 2001 From: r3kste <138380708+r3kste@users.noreply.github.com> Date: Fri, 26 Sep 2025 20:38:27 +0530 Subject: [PATCH 2/3] Enable interactive mode for matplotlib in interactive environments. --- .../how_to/how_to_visualize_histories.ipynb | 56 +++++++------------ src/optimagic/visualization/backends.py | 6 ++ 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/docs/source/how_to/how_to_visualize_histories.ipynb b/docs/source/how_to/how_to_visualize_histories.ipynb index fbfdb58dd..e25d2a367 100644 --- a/docs/source/how_to/how_to_visualize_histories.ipynb +++ b/docs/source/how_to/how_to_visualize_histories.ipynb @@ -77,6 +77,18 @@ "cell_type": "markdown", "id": "6", "metadata": {}, + "source": [ + ":::{note}\n", + "\n", + "For details on using other plotting backends, see [How to change the plotting backend](how_to_change_plotting_backend.ipynb).\n", + "\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, "source": [ "## Compare two optimizations in a criterion plot" ] @@ -84,7 +96,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7", + "id": "8", "metadata": {}, "outputs": [], "source": [ @@ -94,7 +106,7 @@ }, { "cell_type": "markdown", - "id": "8", + "id": "9", "metadata": {}, "source": [ "## Use some advanced options of criterion plot" @@ -103,7 +115,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9", + "id": "10", "metadata": {}, "outputs": [], "source": [ @@ -117,36 +129,10 @@ "fig.show()" ] }, - { - "cell_type": "markdown", - "id": "10", - "metadata": {}, - "source": [ - "## Criterion Plot with different plotting backend" - ] - }, { "cell_type": "markdown", "id": "11", "metadata": {}, - "source": [ - "Also see: [How to change the plotting backend](how_to_change_plotting_backend.ipynb) for a list of available backends, return types, and usage examples." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "12", - "metadata": {}, - "outputs": [], - "source": [ - "ax = om.criterion_plot(results, backend=\"matplotlib\")" - ] - }, - { - "cell_type": "markdown", - "id": "13", - "metadata": {}, "source": [ "## Make a params plot" ] @@ -154,7 +140,7 @@ { "cell_type": "code", "execution_count": null, - "id": "14", + "id": "12", "metadata": {}, "outputs": [], "source": [ @@ -164,7 +150,7 @@ }, { "cell_type": "markdown", - "id": "15", + "id": "13", "metadata": {}, "source": [ "## Use advanced options of params plot" @@ -173,7 +159,7 @@ { "cell_type": "code", "execution_count": null, - "id": "16", + "id": "14", "metadata": {}, "outputs": [], "source": [ @@ -189,7 +175,7 @@ }, { "cell_type": "markdown", - "id": "17", + "id": "15", "metadata": {}, "source": [ "## Criterion plot with multistart optimization" @@ -198,7 +184,7 @@ { "cell_type": "code", "execution_count": null, - "id": "18", + "id": "16", "metadata": {}, "outputs": [], "source": [ @@ -218,7 +204,7 @@ { "cell_type": "code", "execution_count": null, - "id": "19", + "id": "17", "metadata": {}, "outputs": [], "source": [ diff --git a/src/optimagic/visualization/backends.py b/src/optimagic/visualization/backends.py index 541a39b30..9004aa9a5 100644 --- a/src/optimagic/visualization/backends.py +++ b/src/optimagic/visualization/backends.py @@ -9,6 +9,12 @@ if IS_MATPLOTLIB_INSTALLED: import matplotlib.pyplot as plt + # If running in an interactive environment, enable interactive mode + # to ensure that all figures are displayed correctly. + # See: https://github.com/matplotlib/matplotlib/issues/26716 + if plt.get_backend() == "module://matplotlib_inline.backend_inline": + plt.ion() + @runtime_checkable class LinePlotFunction(Protocol): From eee52d94792af93501a60ff3e49b77e6713a6ac8 Mon Sep 17 00:00:00 2001 From: r3kste <138380708+r3kste@users.noreply.github.com> Date: Mon, 29 Sep 2025 16:33:08 +0530 Subject: [PATCH 3/3] Move matplotlib import to within the line_plot function. --- src/optimagic/visualization/backends.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/optimagic/visualization/backends.py b/src/optimagic/visualization/backends.py index 9004aa9a5..402e1768e 100644 --- a/src/optimagic/visualization/backends.py +++ b/src/optimagic/visualization/backends.py @@ -1,4 +1,4 @@ -from typing import Any, Literal, Protocol, runtime_checkable +from typing import TYPE_CHECKING, Any, Literal, Protocol, runtime_checkable import plotly.graph_objects as go @@ -6,15 +6,9 @@ from optimagic.exceptions import InvalidPlottingBackendError, NotInstalledError from optimagic.visualization.plotting_utilities import LineData -if IS_MATPLOTLIB_INSTALLED: +if TYPE_CHECKING: import matplotlib.pyplot as plt - # If running in an interactive environment, enable interactive mode - # to ensure that all figures are displayed correctly. - # See: https://github.com/matplotlib/matplotlib/issues/26716 - if plt.get_backend() == "module://matplotlib_inline.backend_inline": - plt.ion() - @runtime_checkable class LinePlotFunction(Protocol): @@ -85,6 +79,16 @@ def _line_plot_matplotlib( width: int | None, legend_properties: dict[str, Any] | None, ) -> "plt.Axes": + import matplotlib.pyplot as plt + + # In interactive environments (like Jupyter), explicitly enable matplotlib's + # interactive mode. If it is not enabled, matplotlib's context manager will + # revert to non-interactive mode after creating the first figure, causing + # subsequent figures to not display inline. + # See: https://github.com/matplotlib/matplotlib/issues/26716 + if plt.get_backend() == "module://matplotlib_inline.backend_inline": + plt.ion() + if template is None: template = "default"