Coverage for moptipy / algorithms / so / hill_climber_with_restarts.py: 100%
36 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
1"""
2The implementation of the hill climbing algorithm with restarts `hcr`.
4This algorithm basically works like the normal hill climber `hc`
5(:class:`~moptipy.algorithms.so.hill_climber.HillClimber`), but it will
6restart automatically if no move was successful for
7`max_moves_without_improvement` iterative steps. It therefore maintains an
8internal counter `count` which is set to zero at the beginning of each restart
9and which is also set to zero again any time a move successfully improved the
10best-so-far solution of the current restart. If a search move, i.e., an
11application of the unary operator, yielded a new solution which is not better
12than the best-so-far solution of the current restart, `count` is incremented.
13If `count >= max_moves_without_improvement`, the algorithm begins a new
14restart with a new random solution.
161. Thomas Weise. *Optimization Algorithms.* 2021. Hefei, Anhui, China:
17 Institute of Applied Optimization (IAO), School of Artificial Intelligence
18 and Big Data, Hefei University. http://thomasweise.github.io/oa/
19"""
20from typing import Callable, Final
22from numpy.random import Generator
23from pycommons.types import check_int_range
25from moptipy.api.algorithm import Algorithm1
26from moptipy.api.operators import Op0, Op1
27from moptipy.api.process import Process
28from moptipy.utils.logger import KeyValueLogSection
31# start book
32class HillClimberWithRestarts(Algorithm1):
33 """
34 The stochastic hill climbing algorithm only accepts improving moves.
36 In each step, a hill climber creates a modified copy `new_x` of the
37 current best solution `best_x`. If `new_x` is better than `best_x`,
38 it becomes the new `best_x`. Otherwise, it is discarded. If no
39 improvement is made for `max_moves_without_improvement` steps, the
40 algorithm restarts.
41 """
43 def solve(self, process: Process) -> None:
44 """
45 Apply the hill climber with restarts to an optimization problem.
47 :param process: the black-box process object
48 """
49 # Create records for old and new point in the search space.
50 best_x = process.create() # record for best-so-far solution
51 new_x = process.create() # record for new solution
52 # Obtain the random number generator.
53 random: Final[Generator] = process.get_random()
55 # Put function references in variables to save time.
56 evaluate: Final[Callable] = process.evaluate # the objective
57 op1: Final[Callable] = self.op1.op1 # the unary operator
58 should_terminate: Final[Callable] = process.should_terminate
59 limit: Final[int] = self.max_moves_without_improvement
61 while not should_terminate(): # Until we need to quit....
62 self.op0.op0(random, best_x) # Create random solution and
63 best_f: int | float = evaluate(best_x)
64 count: int = 0 # The counter of unsuccessful moves = 0.
66 while not should_terminate(): # Until we need to quit...
67 op1(random, new_x, best_x) # new_x=neighbor of best_x
68 new_f: int | float = evaluate(new_x)
69 if new_f < best_f: # new_x is _better_ than best_x?
70 best_f = new_f # Store its objective value.
71 best_x, new_x = new_x, best_x # Swap best and new.
72 count = 0 # Reset unsuccessful move counter.
73 else: # The move did not lead to an improvement!?
74 count += 1 # Increment unsuccessful move counter.
75 if count >= limit: # Too many unsuccessful moves?
76 break # Break inner loop, start again randomly.
77# end book
79 def __init__(self, op0: Op0, op1: Op1,
80 max_moves_without_improvement: int) -> None:
81 """
82 Create the hill climber.
84 :param op0: the nullary search operator
85 :param op1: the unary search operator
86 :param max_moves_without_improvement: the maximum number of
87 moves without improvement before a restart
88 """
89 super().__init__(
90 f"hcr_{max_moves_without_improvement}", op0, op1)
91 #: the maximum moves without improvement
92 self.max_moves_without_improvement: Final[int] = \
93 check_int_range(
94 max_moves_without_improvement,
95 "max_moves_without_improvement", 1, 1_000_000_000_000)
97 def log_parameters_to(self, logger: KeyValueLogSection) -> None:
98 """
99 Log all parameters of this component as key-value pairs.
101 :param logger: the logger for the parameters
102 """
103 super().log_parameters_to(logger)
104 logger.key_value("maxMovesWithoutImprovement",
105 self.max_moves_without_improvement)