Skip to content

Commit 40a9e23

Browse files
committed
added safea hybrids and improved documentation
1 parent 64f270a commit 40a9e23

File tree

14 files changed

+812
-13
lines changed

14 files changed

+812
-13
lines changed

moptipy/algorithms/so/ffa/eafea.py

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
"""
22
The EAFEA is hybrid of the (1+1)FEA and the (1+1)EA without Solution Transfer.
33
4+
The algorithm has two branches: (1) the EA branch, which performs randomized
5+
local search (RLS), which is in some contexts also called (1+1) EA. (2) the
6+
FEA branch, which performs RLS but uses frequency fitness assignment (FFA)
7+
as optimization criterion. No flow of information takes place between the two
8+
branches.
9+
410
1. Thomas Weise, Zhize Wu, Xinlu Li, Yan Chen, and Jörg Lässig. Frequency
511
Fitness Assignment: Optimization without Bias for Good Solutions can be
612
Efficient. *IEEE Transactions on Evolutionary Computation (TEVC)*.

moptipy/algorithms/so/ffa/eafea_a.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
"""
22
The EAFEA-A is hybrid of the (1+1)FEA and the (1+1)EA with Solution Transfer.
33
4-
The solution is transferred from the FEA branch to the EA branch if its
5-
H-value is 1, i.e., if it represents a completely new objective value.
4+
The algorithm combines frequency fitness assignment based local search, i.e.,
5+
the FEA, with randomized local search (RLS, also called (1+1) EA in some
6+
contexts). Both algorithms get assigned alternating objective function
7+
evaluations (FEs). The FEA branch remains unchanged, it is never disturbed and
8+
no information flows from the RLS branch over to it. However, solutions are
9+
copied from time to time from the FEA branch to the RLS branch. The solution
10+
is transferred from the FEA branch to the EA branch if its H-value is 1, i.e.,
11+
if it represents a completely new objective value.
612
713
1. Tianyu Liang, Zhize Wu, Jörg Lässig, Daan van den Berg, Sarah Louise
814
Thomson, and Thomas Weise. Addressing the Traveling Salesperson Problem

moptipy/algorithms/so/ffa/eafea_b.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
"""
22
The EAFEA-B is hybrid of the (1+1)FEA and the (1+1)EA with Solution Transfer.
33
4-
The solution is transferred from the FEA branch to the EA branch if its
5-
better than the current solution in that branch.
4+
The algorithm combines frequency fitness assignment based local search, i.e.,
5+
the FEA, with randomized local search (RLS, also called (1+1) EA in some
6+
contexts). Both algorithms get assigned alternating objective function
7+
evaluations (FEs). The FEA branch remains unchanged, it is never disturbed and
8+
no information flows from the RLS branch over to it. However, solutions are
9+
copied from time to time from the FEA branch to the RLS branch. The solution
10+
is transferred from the FEA branch to the EA branch if its better than the
11+
current solution in that branch.
612
713
1. Tianyu Liang, Zhize Wu, Jörg Lässig, Daan van den Berg, Sarah Louise
814
Thomson, and Thomas Weise. Addressing the Traveling Salesperson Problem

moptipy/algorithms/so/ffa/eafea_c.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"""
22
A Hybrid EA-FEA Algorithm: the `EAFEA-C`.
33
4-
This hybrid algorithm has the following features:
4+
The algorithm has two branches: (1) the EA branch, which performs randomized
5+
local search (RLS), which is in some contexts also called (1+1) EA. (2) the
6+
FEA branch, which performs RLS but uses frequency fitness assignment (FFA)
7+
as optimization criterion. This hybrid algorithm has the following features:
58
69
- The new solution of the FEA strand is copied to the EA strand if it has an
710
H-value which is not worse than the H-value of the current solution.

moptipy/algorithms/so/ffa/eafea_n.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"""
22
A Hybrid EA-FEA Algorithm: the `EAFEA-N`.
33
4-
This hybrid algorithm has the following features:
4+
The algorithm has two branches: (1) the EA branch, which performs randomized
5+
local search (RLS), which is in some contexts also called (1+1) EA. (2) the
6+
FEA branch, which performs RLS but uses frequency fitness assignment (FFA)
7+
as optimization criterion. This hybrid algorithm has the following features:
58
69
- The new solution of the FEA strand is always copied to the EA strand.
710
- The new solution of the EA strand is copied over to the FEA strand if it is

moptipy/algorithms/so/ffa/safea_a.py

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""
2+
The SAFEA-A is hybrid of the (1+1)FEA and the SA with Solution Transfer.
3+
4+
The algorithm combines frequency fitness assignment based local search, i.e.,
5+
the FEA, with simulated annealing (SA). Both algorithms get assigned
6+
alternating objective function evaluations (FEs). The FEA branch remains
7+
unchanged, it is never disturbed and no information flows from the simulated
8+
annealing branch over to it. However, solutions are copied from time to time
9+
from the FEA branch to the SA branch. The solution is transferred from the FEA
10+
branch to the SA branch if its H-value is 1, i.e., if it represents a
11+
completely new objective value.
12+
13+
1. Tianyu Liang, Zhize Wu, Jörg Lässig, Daan van den Berg, Sarah Louise
14+
Thomson, and Thomas Weise. Addressing the Traveling Salesperson Problem
15+
with Frequency Fitness Assignment and Hybrid Algorithms. *Soft Computing.*
16+
2024. https://dx.doi.org/10.1007/s00500-024-09718-8
17+
"""
18+
from collections import Counter
19+
from math import exp
20+
from typing import Callable, Final
21+
22+
from numpy.random import Generator
23+
from pycommons.types import type_error
24+
25+
from moptipy.algorithms.modules.temperature_schedule import TemperatureSchedule
26+
from moptipy.algorithms.so.ffa.ffa_h import create_h, log_h
27+
from moptipy.api.algorithm import Algorithm1
28+
from moptipy.api.operators import Op0, Op1
29+
from moptipy.api.process import Process
30+
from moptipy.utils.logger import KeyValueLogSection
31+
32+
33+
class SAFEAA(Algorithm1):
34+
"""An implementation of the SAFEA-A."""
35+
36+
def __init__(self, op0: Op0, op1: Op1, schedule: TemperatureSchedule,
37+
log_h_tbl: bool = False) -> None:
38+
"""
39+
Create the SAFEA-A.
40+
41+
:param op0: the nullary search operator
42+
:param op1: the unary search operator
43+
:param schedule: the temperature schedule to use
44+
:param log_h_tbl: should we log the H table?
45+
"""
46+
if not isinstance(schedule, TemperatureSchedule):
47+
raise type_error(schedule, "schedule", TemperatureSchedule)
48+
if not isinstance(log_h_tbl, bool):
49+
raise type_error(log_h_tbl, "log_h_tbl", bool)
50+
super().__init__(f"safeaA_{schedule}", op0, op1)
51+
#: True if we should log the H table, False otherwise
52+
self.__log_h_tbl: Final[bool] = log_h_tbl
53+
#: the temperature schedule
54+
self.schedule: Final[TemperatureSchedule] = schedule
55+
56+
def solve(self, process: Process) -> None:
57+
"""
58+
Apply the SAFEA-A to an optimization problem.
59+
60+
:param process: the black-box process object
61+
"""
62+
# Create records for old and new point in the search space.
63+
x_c = process.create() # record for current solution of the SA
64+
x_d = process.create() # record for current solution of the FEA
65+
x_n = process.create() # record for new solution
66+
67+
# Obtain the random number generator.
68+
random: Final[Generator] = process.get_random()
69+
70+
# Put function references in variables to save time.
71+
evaluate: Final[Callable] = process.evaluate # the objective
72+
should_terminate: Final[Callable] = process.should_terminate
73+
temperature: Final[Callable[[int], float]] = self.schedule.temperature
74+
r01: Final[Callable[[], float]] = random.random # random from [0, 1)
75+
xcopy: Final[Callable] = process.copy # copy(dest, source)
76+
op0: Final[Callable] = self.op0.op0 # the nullary operator
77+
op1: Final[Callable] = self.op1.op1 # the unary operator
78+
79+
h, ofs = create_h(process) # Allocate the h-table
80+
81+
# Start at a random point in the search space and evaluate it.
82+
op0(random, x_c) # Create 1 solution randomly and
83+
y_c: int | float = evaluate(x_c) + ofs # evaluate it.
84+
xcopy(x_d, x_c)
85+
y_d: int | float = y_c
86+
use_ffa: bool = True
87+
tau: int = 0 # The iteration index, needs to be 0 at first cmp.
88+
89+
while not should_terminate(): # Until we need to quit...
90+
use_ffa = not use_ffa # toggle use of FFA
91+
op1(random, x_n, x_d if use_ffa else x_c)
92+
y_n: int | float = evaluate(x_n) + ofs
93+
94+
if use_ffa: # the FEA branch
95+
h[y_n] += 1 # type: ignore # Increase the frequency
96+
h[y_d] += 1 # type: ignore # of new_f and cur_f.
97+
h_n = h[y_n] # type: ignore
98+
if h_n <= h[y_d]: # type: ignore
99+
y_d = y_n # Store its objective value.
100+
x_d, x_n = x_n, x_d # Swap best and new.
101+
if h_n <= 1: # if solution is new, then transfer it to
102+
xcopy(x_c, x_d) # the SA branch
103+
elif (y_n <= y_c) or ( # Accept if <= or if SA criterion
104+
r01() < exp((y_c - y_n) / temperature(tau))): # the SA
105+
y_c = y_n
106+
x_c, x_n = x_n, x_c
107+
tau = tau + 1 # Step the iteration index.
108+
109+
if not self.__log_h_tbl:
110+
return # we are done here
111+
112+
# After we are done, we want to print the H-table.
113+
if h[y_c] == 0: # type: ignore # Fix the H-table for the case
114+
h = Counter() # that only one FE was performed: In this case,
115+
h[y_c] = 1 # make Counter with only a single 1 value inside.
116+
117+
log_h(process, h, ofs) # log the H-table
118+
119+
def log_parameters_to(self, logger: KeyValueLogSection) -> None:
120+
"""
121+
Log all parameters of the SAFEA-A algorithm.
122+
123+
:param logger: the logger for the parameters
124+
"""
125+
super().log_parameters_to(logger)
126+
with logger.scope("ts") as ts:
127+
self.schedule.log_parameters_to(ts)

moptipy/algorithms/so/ffa/safea_b.py

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"""
2+
The SAFEA-B is hybrid of the (1+1)FEA and the SA with Solution Transfer.
3+
4+
The algorithm combines frequency fitness assignment based local search, i.e.,
5+
the FEA, with simulated annealing (SA). Both algorithms get assigned
6+
alternating objective function evaluations (FEs). The FEA branch remains
7+
unchanged, it is never disturbed and no information flows from the simulated
8+
annealing branch over to it. However, solutions are copied from time to time
9+
from the FEA branch to the SA branch. The solution is transferred from the FEA
10+
branch to the SA branch if its better than the current solution in that
11+
branch.
12+
13+
1. Tianyu Liang, Zhize Wu, Jörg Lässig, Daan van den Berg, Sarah Louise
14+
Thomson, and Thomas Weise. Addressing the Traveling Salesperson Problem
15+
with Frequency Fitness Assignment and Hybrid Algorithms. *Soft Computing.*
16+
2024. https://dx.doi.org/10.1007/s00500-024-09718-8
17+
"""
18+
from collections import Counter
19+
from math import exp
20+
from typing import Callable, Final
21+
22+
from numpy.random import Generator
23+
from pycommons.types import type_error
24+
25+
from moptipy.algorithms.modules.temperature_schedule import TemperatureSchedule
26+
from moptipy.algorithms.so.ffa.ffa_h import create_h, log_h
27+
from moptipy.api.algorithm import Algorithm1
28+
from moptipy.api.operators import Op0, Op1
29+
from moptipy.api.process import Process
30+
from moptipy.utils.logger import KeyValueLogSection
31+
32+
33+
class SAFEAB(Algorithm1):
34+
"""An implementation of the SAFEA-B."""
35+
36+
def __init__(self, op0: Op0, op1: Op1, schedule: TemperatureSchedule,
37+
log_h_tbl: bool = False) -> None:
38+
"""
39+
Create the SAFEA-B.
40+
41+
:param op0: the nullary search operator
42+
:param op1: the unary search operator
43+
:param schedule: the temperature schedule to use
44+
:param log_h_tbl: should we log the H table?
45+
"""
46+
if not isinstance(schedule, TemperatureSchedule):
47+
raise type_error(schedule, "schedule", TemperatureSchedule)
48+
if not isinstance(log_h_tbl, bool):
49+
raise type_error(log_h_tbl, "log_h_tbl", bool)
50+
super().__init__(f"safeaB_{schedule}", op0, op1)
51+
#: True if we should log the H table, False otherwise
52+
self.__log_h_tbl: Final[bool] = log_h_tbl
53+
#: the temperature schedule
54+
self.schedule: Final[TemperatureSchedule] = schedule
55+
56+
def solve(self, process: Process) -> None:
57+
"""
58+
Apply the SAFEA-B to an optimization problem.
59+
60+
:param process: the black-box process object
61+
"""
62+
# Create records for old and new point in the search space.
63+
x_c = process.create() # record for current solution of the SA
64+
x_d = process.create() # record for current solution of the FEA
65+
x_n = process.create() # record for new solution
66+
67+
# Obtain the random number generator.
68+
random: Final[Generator] = process.get_random()
69+
70+
# Put function references in variables to save time.
71+
evaluate: Final[Callable] = process.evaluate # the objective
72+
should_terminate: Final[Callable] = process.should_terminate
73+
temperature: Final[Callable[[int], float]] = self.schedule.temperature
74+
r01: Final[Callable[[], float]] = random.random # random from [0, 1)
75+
xcopy: Final[Callable] = process.copy # copy(dest, source)
76+
op0: Final[Callable] = self.op0.op0 # the nullary operator
77+
op1: Final[Callable] = self.op1.op1 # the unary operator
78+
79+
h, ofs = create_h(process) # Allocate the h-table
80+
81+
# Start at a random point in the search space and evaluate it.
82+
op0(random, x_c) # Create 1 solution randomly and
83+
y_c: int | float = evaluate(x_c) + ofs # evaluate it.
84+
xcopy(x_d, x_c)
85+
y_d: int | float = y_c
86+
use_ffa: bool = True
87+
tau: int = 0 # The iteration index, needs to be 0 at first cmp.
88+
89+
while not should_terminate(): # Until we need to quit...
90+
use_ffa = not use_ffa # toggle use of FFA
91+
op1(random, x_n, x_d if use_ffa else x_c)
92+
y_n: int | float = evaluate(x_n) + ofs
93+
94+
if use_ffa: # the FEA branch
95+
h[y_n] += 1 # type: ignore # Increase the frequency
96+
h[y_d] += 1 # type: ignore # of new_f and cur_f.
97+
if h[y_n] <= h[y_d]: # type: ignore
98+
y_d = y_n # Store its objective value.
99+
x_d, x_n = x_n, x_d # Swap best and new.
100+
if y_n <= y_c: # check transfer if solution was
101+
y_c = y_n # accepted
102+
xcopy(x_c, x_d) # copy the solution over
103+
continue # skip rest of loop body
104+
if (y_n <= y_c) or ( # Accept if <= or if SA criterion
105+
(not use_ffa) and ( # Only for SA, not FEA
106+
r01() < exp((y_c - y_n) / temperature(tau)))):
107+
y_c = y_n
108+
x_c, x_n = x_n, x_c
109+
tau = tau + 1 # Step the iteration index.
110+
111+
if not self.__log_h_tbl:
112+
return # we are done here
113+
114+
# After we are done, we want to print the H-table.
115+
if h[y_c] == 0: # type: ignore # Fix the H-table for the case
116+
h = Counter() # that only one FE was performed: In this case,
117+
h[y_c] = 1 # make Counter with only a single 1 value inside.
118+
119+
log_h(process, h, ofs) # log the H-table
120+
121+
def log_parameters_to(self, logger: KeyValueLogSection) -> None:
122+
"""
123+
Log all parameters of the SAFEA-B algorithm.
124+
125+
:param logger: the logger for the parameters
126+
"""
127+
super().log_parameters_to(logger)
128+
with logger.scope("ts") as ts:
129+
self.schedule.log_parameters_to(ts)

0 commit comments

Comments
 (0)