Coverage for moptipy / api / _mo_process_ss_log.py: 91%
68 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-29 10:36 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-29 10:36 +0000
1"""A multi-objective process with solution space and logging."""
2from typing import Callable, Final, cast # pylint: disable=W0611
4import numpy as np
5from numpy import copyto
6from pycommons.io.path import Path
7from pycommons.types import type_error
9from moptipy.api._mo_process_ss import _MOProcessSS
10from moptipy.api._process_base import _TIME_IN_NS, _check_log_time
11from moptipy.api.algorithm import Algorithm
12from moptipy.api.encoding import Encoding
13from moptipy.api.improvement_logger import ImprovementLogger
14from moptipy.api.mo_archive import MOArchivePruner
15from moptipy.api.mo_problem import MOProblem
16from moptipy.api.space import Space
17from moptipy.utils.logger import Logger
20class _MOProcessSSLog(_MOProcessSS):
21 """A multi-objective process with solution space and logging."""
23 def __init__(self,
24 solution_space: Space,
25 objective: MOProblem,
26 algorithm: Algorithm,
27 pruner: MOArchivePruner,
28 archive_max_size: int,
29 archive_prune_limit: int,
30 log_file: Path | None = None,
31 search_space: Space | None = None,
32 encoding: Encoding | None = None,
33 rand_seed: int | None = None,
34 max_fes: int | None = None,
35 max_time_millis: int | None = None,
36 goal_f: int | float | None = None,
37 log_all_fes: bool = False,
38 improvement_logger: ImprovementLogger | None = None) -> None:
39 """
40 Perform the internal initialization. Do not call directly.
42 :param solution_space: the search- and solution space.
43 :param objective: the objective function
44 :param algorithm: the optimization algorithm
45 :param pruner: the archive pruner
46 :param archive_max_size: the maximum archive size after pruning
47 :param archive_prune_limit: the archive size above which pruning will
48 be performed
49 :param log_file: the optional log file
50 :param search_space: the search space.
51 :param encoding: the encoding
52 :param rand_seed: the optional random seed
53 :param max_fes: the maximum permitted function evaluations
54 :param max_time_millis: the maximum runtime in milliseconds
55 :param goal_f: the goal objective value. if it is reached, the process
56 is terminated
57 :param log_all_fes: should we log all FEs?
58 :param improvement_logger: an improvement logger, whose
59 :meth:`~ImprovementLogger.log_improvement` method will be invoked
60 whenever the process has registered an improvement
61 """
62 super().__init__(solution_space=solution_space,
63 objective=objective,
64 algorithm=algorithm,
65 pruner=pruner,
66 archive_max_size=archive_max_size,
67 archive_prune_limit=archive_prune_limit,
68 log_file=log_file,
69 search_space=search_space,
70 encoding=encoding,
71 rand_seed=rand_seed,
72 max_fes=max_fes,
73 max_time_millis=max_time_millis,
74 goal_f=goal_f,
75 improvement_logger=improvement_logger)
76 if not isinstance(log_file, str):
77 raise type_error(log_file, "log_file", str)
78 if not isinstance(log_all_fes, bool):
79 raise type_error(log_all_fes, "log_all_fes", bool)
81 #: `True` if all FEs are logged, `False` to only log improvements.
82 self.__log_all: Final[bool] = log_all_fes
83 #: The in-memory log
84 self.__log: list[list[int | float | np.ndarray]] = []
85 #: the quick access to the log appending method
86 self.__log_append = self.__log.append
88 def f_evaluate(self, x, fs: np.ndarray) -> float | int:
89 if self._terminated:
90 if self._knows_that_terminated:
91 raise ValueError("The process has been terminated and "
92 "the algorithm knows it.")
93 return self._current_best_f
95 current_y: Final = self._current_y
96 self._g(x, current_y)
97 result: Final[int | float] = self._f_evaluate(current_y, fs)
98 self._current_fes = current_fes = self._current_fes + 1
99 do_term: bool = current_fes >= self._end_fes
100 do_log: bool = self.__log_all
101 ctn: int = 0
103 improved: bool = False
104 if result < self._current_best_f:
105 improved = True
106 self._current_best_f = result
107 copyto(self._current_best_fs, fs)
108 self.copy(self._current_best_x, x)
109 self._current_y = self._current_best_y
110 self._current_best_y = current_y
111 do_term = do_term or (result <= self._end_f)
113 if self.check_in(x, fs, True) or improved:
114 self._last_improvement_fe = current_fes
115 self._current_time_nanos = ctn = _TIME_IN_NS()
116 self._last_improvement_time_nanos = ctn
117 do_log = True
118 if self._log_improvement:
119 self._log_improvement(
120 cast("Callable[[Logger], None]",
121 lambda lg, _x=x, _y=current_y, _f=result, _fs=fs:
122 self._write_improvement(lg, _x, _y, _f, _fs)))
124 if do_log:
125 if ctn <= 0:
126 self._current_time_nanos = ctn = _TIME_IN_NS()
127 self.__log_append([current_fes, ctn, result, fs.copy()])
129 if do_term:
130 self.terminate()
132 return result
134 def _check_timing(self) -> None:
135 super()._check_timing()
136 _check_log_time(self._start_time_nanos, self._current_time_nanos,
137 self.__log)
139 def _write_log(self, logger: Logger) -> None:
140 self._write_mo_log(self.__log, self._start_time_nanos,
141 self.__log_all, logger)
142 del self.__log
143 super()._write_log(logger)
145 def __str__(self) -> str:
146 return "MOLoggingProcessWithSearchSpace"