-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbinary_pso.py
More file actions
189 lines (145 loc) · 5.69 KB
/
Copy pathbinary_pso.py
File metadata and controls
189 lines (145 loc) · 5.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
"""
Description:
This module implements a (discrete) binary particle swarm optimization
variant as described in:
- Kennedy, J., and R. C. Eberhart 1997. “A Discrete Binary Version of the
Particle Swarm Algorithm.” IEEE International conference on systems, man
and cybernetics, 1997. Computational cybernetics and simulation, Vol. 5,
Orlando, FL, October 12–15, pp: 4104–4108.
Author:
Michail D. Vrettas, PhD
Email:
michail.vrettas@gmail.com
Metadata:
License: GPL-3
"""
import numpy as np
from numba import njit
from numpy.typing import NDArray
from star_pso.utils import VOptions
from star_pso.engines.generic_pso import GenericPSO
from star_pso.utils.auxiliary import (nb_clip_inplace,
nb_median_hamming_distance)
@njit(cache=True, nogil=True, fastmath=True)
def fast_logistic(x: NDArray) -> NDArray:
"""
Local auxiliary function that is used to compute
the logistic values of input array 'x'.
:param x: a numpy array with the input values.
:return: the numpy array with the logistic values.
"""
return 1.0 / (1.0 + np.exp(-x))
# _end_def_
# Public interface.
__all__ = ["BinaryPSO", "fast_logistic"]
class BinaryPSO(GenericPSO):
"""
Description:
This class implements the (discrete) binary particle swarm optimization variant
as described in:
- Kennedy, J., and R. C. Eberhart 1997. “A Discrete Binary Version of the Particle
Swarm Algorithm.” IEEE International conference on systems, man, and cybernetics,
1997. Computational cybernetics and simulation, Vol. 5, Orlando, FL, October 12–15,
pp: 4104–4108.
"""
def __init__(self, v_min: float = -10.0, v_max: float = 10.0, **kwargs) -> None:
"""
Default initializer of the BinaryPSO class.
:param v_min: (float) minimum value for the velocity parameter.
:param v_max: (float) maximum value for the velocity parameter.
:return: None.
"""
# Call the super initializer with default parameters.
super().__init__(lower_bound=v_min, upper_bound=v_max, **kwargs)
# Generate initial particle velocities.
self.generate_random_velocities()
# _end_def_
def update_velocities(self, params: VOptions) -> None:
"""
Performs the update on the velocity equations.
:param params: VOptions tuple with the PSO options.
:return: None.
"""
# Call the method of the parent class.
super().update_velocities(params)
# Clip velocities in [v_min, v_max].
nb_clip_inplace(self._velocities,
self.lower_bound,
self.upper_bound)
# _end_def_
def update_positions(self) -> None:
"""
Updates the positions of the particles in the swarm.
:return: None.
"""
# Generate random vectors in U(0, 1).
uniform_values: NDArray = GenericPSO.rng.random(
size=(self.n_rows, self.n_cols), dtype=float)
# Create a matrix with zeros.
new_positions: NDArray = np.zeros_like(uniform_values,
dtype=np.uint8)
# Compute the logistic values.
logistic_values = fast_logistic(self._velocities)
# Where the logistic function values are higher
# than the random values set to one.
new_positions[logistic_values > uniform_values] = 1
# Update all particle positions.
self.swarm.set_positions(new_positions)
# _end_def_
def generate_random_velocities(self) -> None:
"""
Generate the population of velocities by sampling uniformly
random numbers within predefined lower and upper bounds.
:return: None.
"""
# Generate uniform FLOAT positions U(x_min, x_max).
self._velocities: NDArray = GenericPSO.rng.uniform(
low=self.lower_bound, high=self.upper_bound,
size=(self.n_rows, self.n_cols)
)
# _end_def_
def generate_random_positions(self) -> None:
"""
Generate the population of particles positions by
sampling discrete binary random numbers within the
{0, 1} set.
:return: None.
"""
# Generate random BINARY positions Bin(0, 1).
binary_positions: NDArray = GenericPSO.rng.integers(
low=0, high=2, size=(self.n_rows, self.n_cols),
dtype=np.uint8
)
# Assign the new positions in the swarm.
self.swarm.set_positions(binary_positions)
# _end_def_
def reset_all(self) -> None:
"""
Resets the particle positions, velocities
and clear all the statistics dictionary.
:return: None.
"""
# Reset particle velocities.
self.generate_random_velocities()
# Generate random binary positions.
self.generate_random_positions()
# Clear all the internal bookkeeping.
self.clear_all()
# _end_def_
def calculate_spread(self) -> float:
"""
Calculates a spread measure for the particle positions
using the normalized median Hamming distance.
A value close to '0' indicates the swarm is converging
to a single value. On the contrary a value close to '1'
indicates the swarm is still spread around the search
space.
:return: an estimated measure (float) for the spread of
the particles.
"""
# Extract the positions in a 2D numpy array.
positions = self.swarm.positions_as_array()
# Normalized median Hamming distance.
return nb_median_hamming_distance(positions, normal=True)
# _end_def_
# _end_class_