Repository: optuna/optuna

Issue:
Add best score reporting into progress bar
### Motivation

It becomes quite inconvenient to scroll through hundreds of optuna run logs in jupyter notebook to find what is the current best score(s). At the same time, progress bar always stays on  top of the output cell, and it would be easy to track current best score via progress bar description line. Also, it is already implemented in LightGBM integration: https://github.com/optuna/optuna/blob/2d6c15ee68065132070862b66de14894f35294ad/optuna/integration/_lightgbm_tuner/optimize.py#L205

### Description

Add the current best score(s) reporting to the progress bar's description or postfix string, when the progress bar is used.

### Alternatives (optional)

1. Look at the run logs, provided by optuna.
2. Save optimization runs info into some persistent storage - SQLite, Redis, RDBMS and track best scores via optuna-dashboard
3. Track scores by using MLFlow/WandB integration

### Additional context (optional)

_No response_


Discussion:
This issue has not seen any recent activity.
This issue has not seen any recent activity.

Resolving patch:
diff --git a/optuna/progress_bar.py b/optuna/progress_bar.py
--- a/optuna/progress_bar.py
+++ b/optuna/progress_bar.py
@@ -1,6 +1,7 @@
 import logging
 from typing import Any
 from typing import Optional
+from typing import TYPE_CHECKING
 
 from tqdm.auto import tqdm
 
@@ -8,6 +9,9 @@
 from optuna._experimental import experimental_func
 
 
+if TYPE_CHECKING:
+    from optuna.study import Study
+
 _tqdm_handler: Optional["_TqdmLoggingHandler"] = None
 
 
@@ -37,7 +41,10 @@ class _ProgressBar:
     """
 
     def __init__(
-        self, is_valid: bool, n_trials: Optional[int] = None, timeout: Optional[float] = None
+        self,
+        is_valid: bool,
+        n_trials: Optional[int] = None,
+        timeout: Optional[float] = None,
     ) -> None:
 
         self._is_valid = is_valid and (n_trials or timeout) is not None
@@ -57,15 +64,9 @@ def _init_valid(self) -> None:
             self._progress_bar = tqdm(total=self._n_trials)
 
         else:
-            fmt = "{percentage:3.0f}%|{bar}| {elapsed}/{desc}"
-            self._progress_bar = tqdm(total=self._timeout, bar_format=fmt)
-
-            # Using description string instead postfix string
-            # to display formatted timeout, since postfix carries
-            # extra comma space auto-format.
-            # https://github.com/tqdm/tqdm/issues/712
             total = tqdm.format_interval(self._timeout)
-            self._progress_bar.set_description_str(total)
+            fmt = "{desc} {percentage:3.0f}%|{bar}| {elapsed}/" + total
+            self._progress_bar = tqdm(total=self._timeout, bar_format=fmt)
 
         global _tqdm_handler
 
@@ -75,15 +76,29 @@ def _init_valid(self) -> None:
         optuna_logging.disable_default_handler()
         optuna_logging._get_library_root_logger().addHandler(_tqdm_handler)
 
-    def update(self, elapsed_seconds: float) -> None:
+    def update(self, elapsed_seconds: float, study: "Study") -> None:
         """Update the progress bars if ``is_valid`` is :obj:`True`.
 
         Args:
             elapsed_seconds:
                 The time past since :func:`~optuna.study.Study.optimize` started.
+            study:
+                The current study object.
         """
 
         if self._is_valid:
+            if not study._is_multi_objective():
+                # Not updating the progress bar when there are no complete trial.
+                try:
+                    msg = (
+                        f"Best trial: {study.best_trial.number}. "
+                        f"Best value: {study.best_value:.6g}"
+                    )
+
+                    self._progress_bar.set_description(msg)
+                except ValueError:
+                    pass
+
             if self._n_trials is not None:
                 self._progress_bar.update(1)
                 if self._timeout is not None:
diff --git a/optuna/study/_optimize.py b/optuna/study/_optimize.py
--- a/optuna/study/_optimize.py
+++ b/optuna/study/_optimize.py
@@ -174,7 +174,8 @@ def _optimize_sequential(
                 callback(study, frozen_trial)
 
         if progress_bar is not None:
-            progress_bar.update((datetime.datetime.now() - time_start).total_seconds())
+            elapsed_seconds = (datetime.datetime.now() - time_start).total_seconds()
+            progress_bar.update(elapsed_seconds, study)
 
     study._storage.remove_session()
 


Test patch:
diff --git a/tests/study_tests/test_study.py b/tests/study_tests/test_study.py
--- a/tests/study_tests/test_study.py
+++ b/tests/study_tests/test_study.py
@@ -846,6 +846,8 @@ def test_optimize_with_progbar(n_jobs: int, capsys: _pytest.capture.CaptureFixtu
     _, err = capsys.readouterr()
 
     # Search for progress bar elements in stderr.
+    assert "Best trial: 0" in err
+    assert "Best value: 1" in err
     assert "10/10" in err
     assert "100%" in err
 
@@ -857,6 +859,8 @@ def test_optimize_without_progbar(n_jobs: int, capsys: _pytest.capture.CaptureFi
     study.optimize(lambda _: 1.0, n_trials=10, n_jobs=n_jobs)
     _, err = capsys.readouterr()
 
+    assert "Best trial: 0" not in err
+    assert "Best value: 1" not in err
     assert "10/10" not in err
     assert "100%" not in err
 
@@ -867,6 +871,8 @@ def test_optimize_with_progbar_timeout(capsys: _pytest.capture.CaptureFixture) -
     study.optimize(lambda _: 1.0, timeout=2.0, show_progress_bar=True)
     _, err = capsys.readouterr()
 
+    assert "Best trial: 0" in err
+    assert "Best value: 1" in err
     assert "00:02/00:02" in err
     assert "100%" in err
 
@@ -912,6 +918,8 @@ def test_optimize_without_progbar_timeout(
     study.optimize(lambda _: 1.0, timeout=2.0, n_jobs=n_jobs)
     _, err = capsys.readouterr()
 
+    assert "Best trial: 0" not in err
+    assert "Best value: 1.0" not in err
     assert "00:02/00:02" not in err
     assert "100%" not in err
 
@@ -925,6 +933,8 @@ def test_optimize_progbar_n_trials_prioritized(
     study.optimize(lambda _: 1.0, n_trials=10, n_jobs=n_jobs, timeout=10.0, show_progress_bar=True)
     _, err = capsys.readouterr()
 
+    assert "Best trial: 0" in err
+    assert "Best value: 1" in err
     assert "10/10" in err
     assert "100%" in err
     assert "it" in err