|
1
|
|
|
# encoding=utf8 |
|
2
|
|
|
# pylint: disable=mixed-indentation, trailing-whitespace, multiple-statements, attribute-defined-outside-init, logging-not-lazy, redefined-builtin, line-too-long, no-self-use, arguments-differ, no-else-return, bad-continuation |
|
3
|
|
|
import logging |
|
4
|
|
|
from numpy import argsort, sum, apply_along_axis, where, pi, ceil, isinf |
|
5
|
|
|
from numpy.random import exponential |
|
6
|
|
|
import numpy as np |
|
7
|
|
|
from NiaPy.algorithms.algorithm import Algorithm |
|
8
|
|
|
|
|
9
|
|
|
__all__ = ['MonarchButterflyOptimization'] |
|
10
|
|
|
|
|
11
|
|
|
logging.basicConfig() |
|
12
|
|
|
logger = logging.getLogger('NiaPy.algorithms.basic') |
|
13
|
|
|
logger.setLevel('INFO') |
|
14
|
|
|
|
|
15
|
|
|
|
|
16
|
|
|
class MonarchButterflyOptimization(Algorithm): |
|
17
|
|
|
r"""Implementation of Monarch Butterfly Optimization. |
|
18
|
|
|
|
|
19
|
|
|
Algorithm: |
|
20
|
|
|
Monarch Butterfly Optimization |
|
21
|
|
|
|
|
22
|
|
|
Date: |
|
23
|
|
|
2019 |
|
24
|
|
|
|
|
25
|
|
|
Authors: |
|
26
|
|
|
Jan Banko |
|
27
|
|
|
|
|
28
|
|
|
License: |
|
29
|
|
|
MIT |
|
30
|
|
|
|
|
31
|
|
|
Reference paper: |
|
32
|
|
|
Wang, Gai-Ge & Deb, Suash & Cui, Zhihua. (2015). Monarch Butterfly Optimization. Neural Computing and Applications. 10.1007/s00521-015-1923-y. , https://www.researchgate.net/publication/275964443_Monarch_Butterfly_Optimization. |
|
33
|
|
|
|
|
34
|
|
|
Attributes: |
|
35
|
|
|
Name (List[str]): List of strings representing algorithm name. |
|
36
|
|
|
PAR (float): Partition. |
|
37
|
|
|
PER (float): Period. |
|
38
|
|
|
|
|
39
|
|
|
See Also: |
|
40
|
|
|
* :class:`NiaPy.algorithms.Algorithm` |
|
41
|
|
|
""" |
|
42
|
|
|
Name = ['MonarchButterflyOptimization', 'MBO'] |
|
43
|
|
|
|
|
44
|
|
|
@staticmethod |
|
45
|
|
|
def algorithmInfo(): |
|
46
|
|
|
return r""" |
|
47
|
|
|
Description: Monarch butterfly optimization algorithm is inspired by the migration behaviour of the monarch butterflies in nature. |
|
48
|
|
|
Authors: Wang, Gai-Ge & Deb, Suash & Cui, Zhihua. |
|
49
|
|
|
Year: 2015 |
|
50
|
|
|
Main reference: Wang, Gai-Ge & Deb, Suash & Cui, Zhihua. (2015). Monarch Butterfly Optimization. Neural Computing and Applications. 10.1007/s00521-015-1923-y. , https://www.researchgate.net/publication/275964443_Monarch_Butterfly_Optimization. |
|
51
|
|
|
""" |
|
52
|
|
|
|
|
53
|
|
|
@staticmethod |
|
54
|
|
|
def typeParameters(): |
|
55
|
|
|
r"""Get dictionary with functions for checking values of parameters. |
|
56
|
|
|
|
|
57
|
|
|
Returns: |
|
58
|
|
|
Dict[str, Callable]: |
|
59
|
|
|
* PAR (Callable[[float], bool]): Checks if partition parameter has a proper value. |
|
60
|
|
|
* PER (Callable[[float], bool]): Checks if period parameter has a proper value. |
|
61
|
|
|
See Also: |
|
62
|
|
|
* :func:`NiaPy.algorithms.algorithm.Algorithm.typeParameters` |
|
63
|
|
|
""" |
|
64
|
|
|
d = Algorithm.typeParameters() |
|
65
|
|
|
d.update({ |
|
66
|
|
|
'PAR': lambda x: isinstance(x, float) and x > 0, |
|
67
|
|
|
'PER': lambda x: isinstance(x, float) and x > 0 |
|
68
|
|
|
}) |
|
69
|
|
|
return d |
|
70
|
|
|
|
|
71
|
|
|
def setParameters(self, NP=20, PAR=5.0 / 12.0, PER=1.2, **ukwargs): |
|
72
|
|
|
r"""Set the parameters of the algorithm. |
|
73
|
|
|
|
|
74
|
|
|
Args: |
|
75
|
|
|
NP (Optional[int]): Population size. |
|
76
|
|
|
PAR (Optional[int]): Partition. |
|
77
|
|
|
PER (Optional[int]): Period. |
|
78
|
|
|
ukwargs (Dict[str, Any]): Additional arguments. |
|
79
|
|
|
|
|
80
|
|
|
See Also: |
|
81
|
|
|
* :func:`NiaPy.algorithms.Algorithm.setParameters` |
|
82
|
|
|
""" |
|
83
|
|
|
Algorithm.setParameters(self, NP=NP) |
|
84
|
|
|
self.NP, self.PAR, self.PER, self.keep, self.BAR, self.NP1 = NP, PAR, PER, 2, PAR, int(ceil(PAR * NP)) |
|
85
|
|
|
self.NP2 = int(NP - self.NP1) |
|
86
|
|
|
if ukwargs: |
|
87
|
|
|
logger.info('Unused arguments: %s' % (ukwargs)) |
|
88
|
|
|
|
|
89
|
|
|
def repair(self, x, lower, upper): |
|
90
|
|
|
r"""Truncate exceeded dimensions to the limits. |
|
91
|
|
|
|
|
92
|
|
|
Args: |
|
93
|
|
|
x (numpy.ndarray): Individual to repair. |
|
94
|
|
|
lower (numpy.ndarray): Lower limits for dimensions. |
|
95
|
|
|
upper (numpy.ndarray): Upper limits for dimensions. |
|
96
|
|
|
|
|
97
|
|
|
Returns: |
|
98
|
|
|
numpy.ndarray: Repaired individual. |
|
99
|
|
|
""" |
|
100
|
|
|
ir = where(x < lower) |
|
101
|
|
|
x[ir] = lower[ir] |
|
102
|
|
|
ir = where(x > upper) |
|
103
|
|
|
x[ir] = upper[ir] |
|
104
|
|
|
return x |
|
105
|
|
|
|
|
106
|
|
|
def levy(self, step_size, D): |
|
107
|
|
|
r"""Calculate levy flight. |
|
108
|
|
|
|
|
109
|
|
|
Args: |
|
110
|
|
|
step_size (float): Size of the walk step. |
|
111
|
|
|
D (int): Number of dimensions. |
|
112
|
|
|
|
|
113
|
|
|
Returns: |
|
114
|
|
|
numpy.ndarray: Calculated values for levy flight. |
|
115
|
|
|
""" |
|
116
|
|
|
delataX = np.array( |
|
117
|
|
|
[sum(np.tan(pi * self.uniform(0.0, 1.0, 10))) for _ in range(0, D)]) |
|
118
|
|
|
return delataX |
|
119
|
|
|
|
|
120
|
|
|
def migrationOperator(self, D, NP1, NP2, Butterflies): |
|
121
|
|
|
r"""Apply the migration operator. |
|
122
|
|
|
|
|
123
|
|
|
Args: |
|
124
|
|
|
D (int): Number of dimensions. |
|
125
|
|
|
NP1 (int): Number of butterflies in Land 1. |
|
126
|
|
|
NP2 (int): Number of butterflies in Land 2. |
|
127
|
|
|
Butterflies (numpy.ndarray): Current butterfly population. |
|
128
|
|
|
|
|
129
|
|
|
Returns: |
|
130
|
|
|
numpy.ndarray: Adjusted butterfly population. |
|
131
|
|
|
""" |
|
132
|
|
|
pop1 = np.copy(Butterflies[:NP1]) |
|
133
|
|
|
pop2 = np.copy(Butterflies[NP1:]) |
|
134
|
|
|
for k1 in range(0, NP1): |
|
135
|
|
|
for parnum1 in range(0, D): |
|
136
|
|
|
r1 = self.uniform(0.0, 1.0) * self.PER |
|
137
|
|
|
if r1 <= self.PAR: |
|
138
|
|
|
r2 = self.randint(Nmin=0, Nmax=NP1 - 1) |
|
139
|
|
|
Butterflies[k1, parnum1] = pop1[r2, parnum1] |
|
140
|
|
|
else: |
|
141
|
|
|
r3 = self.randint(Nmin=0, Nmax=NP2 - 1) |
|
142
|
|
|
Butterflies[k1, parnum1] = pop2[r3, parnum1] |
|
143
|
|
|
return Butterflies |
|
144
|
|
|
|
|
145
|
|
|
def adjustingOperator(self, t, max_t, D, NP1, NP2, Butterflies, best): |
|
146
|
|
|
r"""Apply the adjusting operator. |
|
147
|
|
|
|
|
148
|
|
|
Args: |
|
149
|
|
|
t (int): Current generation. |
|
150
|
|
|
max_t (int): Maximum generation. |
|
151
|
|
|
D (int): Number of dimensions. |
|
152
|
|
|
NP1 (int): Number of butterflies in Land 1. |
|
153
|
|
|
NP2 (int): Number of butterflies in Land 2. |
|
154
|
|
|
Butterflies (numpy.ndarray): Current butterfly population. |
|
155
|
|
|
best (numpy.ndarray): The best butterfly currently. |
|
156
|
|
|
|
|
157
|
|
|
Returns: |
|
158
|
|
|
numpy.ndarray: Adjusted butterfly population. |
|
159
|
|
|
""" |
|
160
|
|
|
pop2 = np.copy(Butterflies[NP1:]) |
|
161
|
|
|
for k2 in range(NP1, NP1 + NP2): |
|
162
|
|
|
scale = 1.0 / ((t + 1)**2) |
|
163
|
|
|
step_size = ceil(exponential(2 * max_t)) |
|
164
|
|
|
delataX = self.levy(step_size, D) |
|
165
|
|
|
for parnum2 in range(0, D): |
|
166
|
|
|
if self.uniform(0.0, 1.0) >= self.PAR: |
|
167
|
|
|
Butterflies[k2, parnum2] = best[parnum2] |
|
168
|
|
|
else: |
|
169
|
|
|
r4 = self.randint(Nmin=0, Nmax=NP2 - 1) |
|
170
|
|
|
Butterflies[k2, parnum2] = pop2[r4, 1] |
|
171
|
|
|
if(self.uniform(0.0, 1.0) > self.BAR): |
|
172
|
|
|
Butterflies[k2, parnum2] += scale * \ |
|
173
|
|
|
(delataX[parnum2] - 0.5) |
|
174
|
|
|
return Butterflies |
|
175
|
|
|
|
|
176
|
|
|
def evaluateAndSort(self, task, Butterflies): |
|
177
|
|
|
r"""Evaluate and sort the butterfly population. |
|
178
|
|
|
|
|
179
|
|
|
Args: |
|
180
|
|
|
task (Task): Optimization task |
|
181
|
|
|
Butterflies (numpy.ndarray): Current butterfly population. |
|
182
|
|
|
|
|
183
|
|
|
Returns: |
|
184
|
|
|
numpy.ndarray: Tuple[numpy.ndarray, float, numpy.ndarray]: |
|
185
|
|
|
1. Best butterfly according to the evaluation. |
|
186
|
|
|
2. The best fitness value. |
|
187
|
|
|
3. Butterfly population. |
|
188
|
|
|
""" |
|
189
|
|
|
Fitness = apply_along_axis(task.eval, 1, Butterflies) |
|
190
|
|
|
indices = argsort(Fitness) |
|
191
|
|
|
Butterflies = Butterflies[indices] |
|
192
|
|
|
Fitness = Fitness[indices] |
|
193
|
|
|
|
|
194
|
|
|
return Fitness, Butterflies |
|
195
|
|
|
|
|
196
|
|
|
def initPopulation(self, task): |
|
197
|
|
|
r"""Initialize the starting population. |
|
198
|
|
|
|
|
199
|
|
|
Args: |
|
200
|
|
|
task (Task): Optimization task |
|
201
|
|
|
|
|
202
|
|
|
Returns: |
|
203
|
|
|
Tuple[numpy.ndarray, numpy.ndarray[float], Dict[str, Any]]: |
|
204
|
|
|
1. New population. |
|
205
|
|
|
2. New population fitness/function values. |
|
206
|
|
|
3. Additional arguments: |
|
207
|
|
|
* dx (float): A small value used in local seeding stage. |
|
208
|
|
|
|
|
209
|
|
|
See Also: |
|
210
|
|
|
* :func:`NiaPy.algorithms.Algorithm.initPopulation` |
|
211
|
|
|
""" |
|
212
|
|
|
Butterflies = self.uniform(task.Lower, task.Upper, [self.NP, task.D]) |
|
213
|
|
|
Fitness, Butterflies = self.evaluateAndSort(task, Butterflies) |
|
214
|
|
|
return Butterflies, Fitness, {'tmp_best': Butterflies[0]} |
|
215
|
|
|
|
|
216
|
|
|
def runIteration(self, task, Butterflies, Evaluations, xb, fxb, tmp_best, **dparams): |
|
217
|
|
|
r"""Core function of Forest Optimization Algorithm. |
|
218
|
|
|
|
|
219
|
|
|
Args: |
|
220
|
|
|
task (Task): Optimization task. |
|
221
|
|
|
Butterflies (numpy.ndarray): Current population. |
|
222
|
|
|
Evaluations (numpy.ndarray[float]): Current population function/fitness values. |
|
223
|
|
|
xb (numpy.ndarray): Global best individual. |
|
224
|
|
|
fxb (float): Global best individual fitness/function value. |
|
225
|
|
|
tmp_best (numpy.ndarray): Best individual currently. |
|
226
|
|
|
**dparams (Dict[str, Any]): Additional arguments. |
|
227
|
|
|
|
|
228
|
|
|
Returns: |
|
229
|
|
|
Tuple[numpy.ndarray, numpy.ndarray[float], Dict[str, Any]]: |
|
230
|
|
|
1. New population. |
|
231
|
|
|
2. New population fitness/function values. |
|
232
|
|
|
3. Additional arguments: |
|
233
|
|
|
* dx (float): A small value used in local seeding stage. |
|
234
|
|
|
""" |
|
235
|
|
|
tmpElite = np.copy(Butterflies[:self.keep]) |
|
236
|
|
|
max_t = task.nGEN if isinf(task.nGEN) is False else task.nFES / self.NP |
|
237
|
|
|
|
|
238
|
|
|
Butterflies = apply_along_axis(self.repair, 1, self.migrationOperator( |
|
239
|
|
|
task.D, self.NP1, self.NP2, Butterflies), task.Lower, task.Upper) |
|
240
|
|
|
Butterflies = apply_along_axis(self.repair, 1, self.adjustingOperator( |
|
241
|
|
|
task.Iters, max_t, task.D, self.NP1, self.NP2, Butterflies, tmp_best), task.Lower, task.Upper) |
|
242
|
|
|
Fitness, Butterflies = self.evaluateAndSort(task, Butterflies) |
|
243
|
|
|
tmp_best = Butterflies[0] |
|
244
|
|
|
Butterflies[-self.keep:] = tmpElite |
|
245
|
|
|
Fitness, Butterflies = self.evaluateAndSort(task, Butterflies) |
|
246
|
|
|
return Butterflies, Fitness, {'tmp_best': tmp_best} |
|
247
|
|
|
|