How to use logging#

optimagic can keep a persistent log of the parameter and criterion values tried out by an optimizer in a sqlite database.

Turn logging on or off#

To enable logging, it suffices to provide a path to an sqlite database when calling maximize or minimize. The database does not have to exist, optimagic will generate it for you.

from pathlib import Path

import numpy as np

import optimagic as om
def sphere(params):
    return params @ params
# Remove the log file if it exists (just needed for the example)
log_file = Path("my_log.db")
if log_file.exists():
    log_file.unlink()

res = om.minimize(
    fun=sphere,
    params=np.arange(5),
    algorithm="scipy_lbfgsb",
    logging="my_log.db",
)

In case the SQLite file already exists, this will raise a FileExistsError to prevent from accidentally polluting an existing database. If you want to reuse an existing database on purpose, you must explicitly provide the corresponding option for if_database_exists:

log_options = om.SQLiteLogOptions(
    "my_log.db", if_database_exists=om.ExistenceStrategy.EXTEND
)

res = om.minimize(
    fun=sphere,
    params=np.arange(5),
    algorithm="scipy_lbfgsb",
    logging=log_options,
)

Make logging faster#

By default, we use a very safe mode of sqlite that makes it almost impossible to corrupt the database. Even if your computer is suddenly shut down or unplugged.

However, this makes writing logs rather slow, which becomes notable when the criterion function is very fast.

In that case, you can enable fast_logging, which is still quite safe!

log_options = om.SQLiteLogOptions(
    "my_log.db",
    fast_logging=True,
    if_database_exists=om.ExistenceStrategy.REPLACE,
)

res = om.minimize(
    fun=sphere,
    params=np.arange(5),
    algorithm="scipy_lbfgsb",
    logging=log_options,
)

Reading the log#

To read the log after an optimization, extract the logger from the optimization result:

reader = res.logger

Alternatively, you can create the reader like this:

reader = om.SQLiteLogReader("my_log.db")

Read the start params

reader.read_start_params()
array([0, 1, 2, 3, 4])

Read a specific iteration (use -1 for the last)

reader.read_iteration(-1)
IterationStateWithId(params=array([ 0.00000000e+00, -2.19792136e-07, -4.01986529e-08, -1.26862247e-07,
       -2.06263028e-07]), timestamp=2010.070966616, scalar_fun=1.08562981500731e-13, valid=True, raw_fun={'value': np.float64(1.08562981500731e-13), 'info': None}, step=1, exceptions=None, rowid=3)

Read the full history

reader.read_history().keys()
dict_keys(['params', 'fun', 'time'])

Plot the history from a log#

fig = om.criterion_plot("my_log.db")
fig.show(renderer="png")
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[11], line 1
----> 1 fig = om.criterion_plot("my_log.db")
      2 fig.show(renderer="png")

File ~/checkouts/readthedocs.org/user_builds/estimagic/conda/stable/lib/python3.10/site-packages/optimagic/visualization/history_plots.py:75, in criterion_plot(results, names, max_evaluations, template, palette, stack_multistart, monotone, show_exploration)
     71     _data = _extract_plotting_data_from_results_object(
     72         res, stack_multistart, show_exploration, plot_name="criterion_plot"
     73     )
     74 elif isinstance(res, (str, Path)):
---> 75     _data = _extract_plotting_data_from_database(
     76         res, stack_multistart, show_exploration
     77     )
     78 else:
     79     msg = "results must be (or contain) an OptimizeResult or a path to a log"

File ~/checkouts/readthedocs.org/user_builds/estimagic/conda/stable/lib/python3.10/site-packages/optimagic/visualization/history_plots.py:385, in _extract_plotting_data_from_database(res, stack_multistart, show_exploration)
    363 def _extract_plotting_data_from_database(res, stack_multistart, show_exploration):
    364     """Extract data for plotting from database.
    365 
    366     Args:
   (...)
    383 
    384     """
--> 385     reader = LogReader.from_options(SQLiteLogOptions(res))
    386     _problem_table = reader.problem_df
    388     direction = _problem_table["direction"].tolist()[-1]

File ~/checkouts/readthedocs.org/user_builds/estimagic/conda/stable/lib/python3.10/site-packages/optimagic/logging/logger.py:95, in LogReader.from_options(cls, log_options)
     88 if log_reader_class is None:
     89     raise ValueError(
     90         f"No LogReader implementation found for type "
     91         f"{type(log_options)}. Available option types: "
     92         f"\n {list(_LOG_OPTION_LOG_READER_REGISTRY.keys())}"
     93     )
---> 95 return log_reader_class._create(log_options)

File ~/checkouts/readthedocs.org/user_builds/estimagic/conda/stable/lib/python3.10/site-packages/optimagic/logging/logger.py:470, in SQLiteLogReader._create(cls, log_options)
    458 @classmethod
    459 def _create(cls, log_options: SQLiteLogOptions) -> SQLiteLogReader:
    460     """Create an instance of SQLiteLogReader using the provided log options.
    461 
    462     Args:
   (...)
    468 
    469     """
--> 470     return cls(log_options.path)

File ~/checkouts/readthedocs.org/user_builds/estimagic/conda/stable/lib/python3.10/site-packages/optimagic/logging/logger.py:449, in SQLiteLogReader.__init__(self, path)
    447 def __init__(self, path: str | Path):
    448     if not os.path.exists(path):
--> 449         raise FileNotFoundError(f"No file found at {path=}")
    451     log_options = SQLiteLogOptions(
    452         path, fast_logging=True, if_database_exists=ExistenceStrategy.EXTEND
    453     )
    454     self._iteration_store = IterationStore(log_options)

FileNotFoundError: No file found at path='m'
fig = om.params_plot("my_log.db", selector=lambda x: x[1:3])
fig.show(renderer="png")