Source code for saealib.callback.handlers

"""Built-in callback handlers for common logging use cases."""

from __future__ import annotations

import logging

import numpy as np

from saealib.callback.events import GenerationStartEvent, _EventContext
from saealib.comparators import non_dominated_sort
from saealib.utils.indicators import hypervolume

logger = logging.getLogger(__name__)


[docs] def logging_generation(event: GenerationStartEvent) -> None: """ Log the state at the start of each generation. For single-objective problems, logs the best objective value determined by the comparator. For multi-objective problems, logs the size of the first Pareto front and the per-objective value ranges of that front. Parameters ---------- event : GenerationStartEvent The generation-start event. """ if not logger.isEnabledFor(logging.INFO): return ctx: _EventContext = event.ctx if ctx.n_obj == 1: cmp = ctx.comparator sorted_idxs = cmp.sort_population(ctx.archive) best_f = ( ctx.archive.get_array("f")[sorted_idxs[0]] if len(sorted_idxs) > 0 else "n/a" ) logger.info( "Generation %d started. fe: %d. Best f: %s", ctx.gen, ctx.fe, best_f, ) else: f = ctx.archive.get_array("f") _, fronts = non_dominated_sort(f) front1_idxs = fronts[0] if fronts else [] front1_size = len(front1_idxs) if front1_size > 0: f_front1 = f[front1_idxs] f_min = np.min(f_front1, axis=0) f_max = np.max(f_front1, axis=0) ranges_str = ", ".join( f"f[{i}]=[{f_min[i]:.4g}, {f_max[i]:.4g}]" for i in range(ctx.n_obj) ) else: ranges_str = "n/a" logger.info( "Generation %d started. fe: %d. Front1 size: %d. %s", ctx.gen, ctx.fe, front1_size, ranges_str, )
[docs] def logging_generation_hv(reference_point: np.ndarray): """ Return a handler that logs the hypervolume per generation. Computes the hypervolume of the first Pareto front in the archive with respect to the given reference point (minimization convention). Parameters ---------- reference_point : np.ndarray Reference (nadir) point, shape (n_obj,). Each component should be strictly greater than the best achievable value per objective. Returns ------- Callable[[GenerationStartEvent], None] A handler compatible with ``CallbackManager.register``. Examples -------- >>> optimizer.cbmanager.register( ... GenerationStartEvent, ... logging_generation_hv(np.array([1.1, 1.1])) ... ) """ ref = np.asarray(reference_point, dtype=float) def _callback(event: GenerationStartEvent) -> None: if not logger.isEnabledFor(logging.INFO): return ctx: _EventContext = event.ctx f = ctx.archive.get_array("f") _, fronts = non_dominated_sort(f) if not fronts or not fronts[0]: return f_front1 = f[fronts[0]] hv = hypervolume(f_front1, ref) logger.info("Generation %d. fe: %d. HV: %.6g", ctx.gen, ctx.fe, hv) return _callback