Skip to content

Commit

Permalink
Add velocity clamping feature
Browse files Browse the repository at this point in the history
This feature update introduces a velocity clamping feature
that is now inherent in the Base Class and has been implemented
in the local-best and global-best optimizers. In this commit,
the following were updated:
  - Base class now requires an additional argument v_clamp
  - All child classes GBestPSO and LBestPSO now requires v_clamp
  - Test cases now checks the validity of v_clamp

Why weren't velocity clamping implemented in the first iteration?
Isn't this a standard requirement?

Author: ljvmiranda921
  • Loading branch information
ljvmiranda921 committed Aug 4, 2017
1 parent 1dd917a commit 3666265
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 81 deletions.
6 changes: 3 additions & 3 deletions docs/examples/basic_optimization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ several variables at once.
.. code-block:: python
# Set-up hyperparameters
options = {'c1': 0.5, 'c2': 0.3, 'm':0.9}
options = {'c1': 0.5, 'c2': 0.3, 'w':0.9}
# Call instance of PSO
gbest_pso = ps.single.GBestPSO(n_particles=10, dims=2, **options)
Expand Down Expand Up @@ -90,7 +90,7 @@ Now, let's try this one using local-best PSO:
.. code-block:: python
# Set-up hyperparameters
options = {'c1': 0.5, 'c2': 0.3, 'm':0.9}
options = {'c1': 0.5, 'c2': 0.3, 'w':0.9}
# Call instance of PSO
lbest_pso = ps.single.LBestPSO(n_particles=10, dims=2, k=2,p=2, **options)
Expand Down Expand Up @@ -158,7 +158,7 @@ constant.
.. code-block:: python
# Initialize swarm
options = {'c1': 0.5, 'c2': 0.3, 'm':0.9}
options = {'c1': 0.5, 'c2': 0.3, 'w':0.9}
# Call instance of PSO with bounds argument
optimizer = ps.single.GBestPSO(n_particles=10, dims=2, bounds=bounds, **options)
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/train_neural_network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ parameters arbitrarily.
.. code-block:: python
# Initialize swarm
options = {'c1': 0.5, 'c2': 0.3, 'm':0.9}
options = {'c1': 0.5, 'c2': 0.3, 'w':0.9}
# Call instance of PSO with bounds argument
dims = (4 * 20) + (20 * 3) + 20 + 3
Expand Down
96 changes: 61 additions & 35 deletions pyswarms/base/bs.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,44 @@
# -*- coding: utf-8 -*-

""" :code:`bs.py`: base class for single-objective PSO """
r"""
Base class for single-objective Particle Swarm Optimization
implementations.
import numpy as np


class SwarmBase(object):
"""Base class for single-objective Particle Swarm Optimization
implementations.
All methods here are abstract and raises a :code:`NotImplementedError`
when not used. When defining your own swarm implementation,
create another class,
All methods here are abstract and raises a :code:`NotImplementedError`
when not used. When defining your own swarm implementation,
create another class,
>>> class MySwarm(SwarmBaseClass):
>>> def __init__(self):
>>> super(MySwarm, self).__init__()
and define all the necessary methods needed.
Take note that there is no velocity nor position update in this
base class. This enables this class to accommodate any variation
of the position or velocity update, without enforcing a specific
structure. As a guide, check the global best and local best
implementations in this package.
.. note:: Regarding :code:`**kwargs`, it is highly recommended to
include parameters used in position and velocity updates as
keyword arguments. For parameters that affect the topology of
the swarm, it may be much better to have them as positional
arguments.
See Also
--------
:mod:`pyswarms.single.gb`: global-best PSO implementation
:mod:`pyswarms.single.lb`: local-best PSO implementation
"""
and define all the necessary methods needed.
Take note that there is no velocity nor position update in this
base class. This enables this class to accommodate any variation
of the position or velocity update, without enforcing a specific
structure. As a guide, check the global best and local best
implementations in this package.
.. note:: Regarding :code:`**kwargs`, it is highly recommended to
include parameters used in position and velocity updates as
keyword arguments. For parameters that affect the topology of
the swarm, it may be much better to have them as positional
arguments.
See Also
--------
:mod:`pyswarms.single.gb`: global-best PSO implementation
:mod:`pyswarms.single.lb`: local-best PSO implementation
"""



import numpy as np


class SwarmBase(object):

def assertions(self):
"""Assertion method to check various inputs.
Expand All @@ -55,17 +58,26 @@ def assertions(self):
# Check setting of bounds
if self.bounds is not None:
if not type(self.bounds) == tuple:
raise TypeError('Variable `bound` must be a tuple.')
raise TypeError('Parameter `bound` must be a tuple.')
if not len(self.bounds) == 2:
raise IndexError('Variable `bound` must be of size 2.')
raise IndexError('Parameter `bound` must be of size 2.')
if not self.bounds[0].shape == self.bounds[1].shape:
raise IndexError('Arrays in `bound` must be of equal shapes')
if not self.bounds[0].shape[0] == self.bounds[1].shape[0] == self.dims:
raise IndexError('Variable `bound` must be the shape as dims.')
raise IndexError('Parameter `bound` must be the shape as dims.')
if not (self.bounds[1] > self.bounds[0]).all():
raise ValueError('Values of `bounds[1]` must be greater than `bounds[0]`.')

def __init__(self, n_particles, dims, bounds=None, **kwargs):
# Check clamp settings
if self.v_clamp is not None:
if not type(self.v_clamp) == tuple:
raise TypeError('Parameter `v_clamp` must be a tuple')
if not len(self.v_clamp) == 2:
raise IndexError('Parameter `v_clamp` must be of size 2')
if not self.v_clamp[0] < self.v_clamp[1]:
raise ValueError('Make sure that v_clamp is in the form (v_min, v_max)')

def __init__(self, n_particles, dims, bounds=None, v_clamp=None, **kwargs):
"""Initializes the swarm.
Creates a :code:`numpy.ndarray` of positions depending on the
Expand All @@ -83,6 +95,10 @@ def __init__(self, n_particles, dims, bounds=None, **kwargs):
a tuple of size 2 where the first entry is the minimum bound
while the second entry is the maximum bound. Each array must
be of shape :code:`(dims,)`.
v_clamp : tuple (default is :code:`None`)
a tuple of size 2 where the first entry is the minimum velocity
and the second entry is the maximum velocity. It
sets the limits for velocity clamping.
**kwargs: dict
a dictionary containing various keyword arguments for a
specific optimization technique
Expand All @@ -91,6 +107,7 @@ def __init__(self, n_particles, dims, bounds=None, **kwargs):
self.n_particles = n_particles
self.dims = dims
self.bounds = bounds
self.v_clamp = v_clamp
self.swarm_size = (n_particles, dims)
self.kwargs = kwargs

Expand Down Expand Up @@ -158,4 +175,13 @@ def reset(self):
high=self.max_bounds,
size=self.swarm_size)
else:
self.pos = np.random.uniform(size=self.swarm_size)
self.pos = np.random.uniform(size=self.swarm_size)

# Initialize velocity vectors
if self.v_clamp is not None:
v_min, v_max = self.v_clamp[0], self.v_clamp[1]
self.velocity = ((v_max - v_min)
* np.random.random_sample(size=self.swarm_size)
+ v_min)
else:
self.velocity = np.random.random_sample(size=self.swarm_size)
44 changes: 26 additions & 18 deletions pyswarms/single/gb.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
react given two choices: (1) to follow its *personal best* or (2) follow
the swarm's *global best* position. Overall, this dictates if the swarm
is explorative or exploitative in nature. In addition, a parameter
:math:`m` controls the inertia of the swarm's movement.
:math:`w` controls the inertia of the swarm's movement.
An example usage is as follows:
Expand Down Expand Up @@ -72,14 +72,10 @@ def assertions(self):
"""
super(GBestPSO, self).assertions()

if 'c1' not in self.kwargs:
raise KeyError('Missing c1 key in kwargs.')
if 'c2' not in self.kwargs:
raise KeyError('Missing c2 key in kwargs.')
if 'm' not in self.kwargs:
raise KeyError('Missing m key in kwargs.')

def __init__(self, n_particles, dims, bounds=None, **kwargs):
if not all (key in self.kwargs for key in ('c1', 'c2', 'w')):
raise KeyError('Missing either c1, c2, or w in kwargs')

def __init__(self, n_particles, dims, bounds=None, v_clamp=None, **kwargs):
"""Initializes the swarm.
Takes the same attributes as :code:`SwarmBase`, but also
Expand All @@ -96,17 +92,21 @@ def __init__(self, n_particles, dims, bounds=None, **kwargs):
a tuple of size 2 where the first entry is the minimum bound
while the second entry is the maximum bound. Each array must
be of shape :code:`(dims,)`.
v_clamp : tuple (default is :code:`None`)
a tuple of size 2 where the first entry is the minimum velocity
and the second entry is the maximum velocity. It
sets the limits for velocity clamping.
**kwargs : dict
Keyword argument that must contain the following dictionary
keys:
* c1 : float
cognitive parameter
* c2 : float
social parameter
* m : float
momentum parameter
* w : float
inertia parameter
"""
super(GBestPSO, self).__init__(n_particles, dims, bounds, **kwargs)
super(GBestPSO, self).__init__(n_particles, dims, bounds, v_clamp, **kwargs)

# Invoke assertions
self.assertions()
Expand Down Expand Up @@ -169,9 +169,6 @@ def reset(self):
"""Resets the attributes of the optimizer."""
super(GBestPSO, self).reset()

# Initialize velocity vectors
self.velocity = np.random.random_sample(size=self.swarm_size)

# Initialize the global best of the swarm
self.gbest_cost = np.inf
self.gbest_pos = None
Expand All @@ -187,14 +184,25 @@ def _update_velocity_position(self):
:code:`self.optimize()` method
"""
# Define the hyperparameters from kwargs dictionary
c1, c2, m = self.kwargs['c1'], self.kwargs['c2'], self.kwargs['m']
c1, c2, w = self.kwargs['c1'], self.kwargs['c2'], self.kwargs['w']

# Compute for cognitive and social terms
# Compute for cognitive and social terms and store it to a
# temporary velocity variable to be clamped later on
cognitive = (c1 * np.random.uniform(0,1,self.swarm_size)
* (self.pbest_pos - self.pos))
social = (c2 * np.random.uniform(0,1,self.swarm_size)
* (self.gbest_pos - self.pos))
self.velocity = (m * self.velocity) + cognitive + social
temp_velocity = (w * self.velocity) + cognitive + social

# Create a mask to clamp the velocities
if self.v_clamp is not None:
# Create a mask depending on the set boundaries
v_min, v_max = self.v_clamp[0], self.v_clamp[1]
_b = np.logical_and(temp_velocity >= v_min, temp_velocity <= v_max)
# Use the mask to finally clamp the velocities
self.velocity = np.where(~_b, self.velocity, temp_velocity)
else:
self.velocity = temp_velocity

# Update position and store it in a temporary variable
temp = self.pos.copy()
Expand Down
39 changes: 23 additions & 16 deletions pyswarms/single/lb.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,15 @@ def assertions(self):
"""
super(LBestPSO, self).assertions()

if 'c1' not in self.kwargs:
raise KeyError('Missing c1 key in kwargs.')
if 'c2' not in self.kwargs:
raise KeyError('Missing c2 key in kwargs.')
if 'm' not in self.kwargs:
raise KeyError('Missing m key in kwargs.')
if not all (key in self.kwargs for key in ('c1', 'c2', 'w')):
raise KeyError('Missing either c1, c2, or w in kwargs')

if not 0 <= self.k <= self.n_particles:
raise ValueError('No. of neighbors must be between 0 and no. of particles.')
if self.p not in [1,2]:
raise ValueError('p-value should either be 1 (for L1/Minkowski) or 2 (for L2/Euclidean).')

def __init__(self, n_particles, dims, bounds=None,
def __init__(self, n_particles, dims, bounds=None, v_clamp=None,
k=1, p=2, **kwargs):
"""Initializes the swarm.
Expand All @@ -116,6 +112,10 @@ def __init__(self, n_particles, dims, bounds=None,
a tuple of size 2 where the first entry is the minimum bound
while the second entry is the maximum bound. Each array must
be of shape :code:`(dims,)`.
v_clamp : tuple (default is :code:`None`)
a tuple of size 2 where the first entry is the minimum velocity
and the second entry is the maximum velocity. It
sets the limits for velocity clamping.
k: int (default is 1, must be less than :code:`n_particles`)
number of neighbors to be considered.
p: int {1,2} (default is 2)
Expand All @@ -128,15 +128,15 @@ def __init__(self, n_particles, dims, bounds=None,
cognitive parameter
* c2 : float
social parameter
* m : float
momentum parameter
* w : float
inertia parameter
"""

# Store n_neighbors and neighborhood type
self.k = k
self.p = p
super(LBestPSO, self).__init__(n_particles, dims, bounds, **kwargs)

super(LBestPSO, self).__init__(n_particles, dims, bounds, v_clamp, **kwargs)

# Invoke assertions
self.assertions()
Expand Down Expand Up @@ -243,9 +243,6 @@ def reset(self):
"""Resets the attributes of the optimizer."""
super(LBestPSO, self).reset()

# Initialize velocity vectors
self.velocity = np.random.random_sample(size=self.swarm_size)

# Initialize the local best of the swarm
self.lbest_cost = np.inf
self.lbest_pos = None
Expand All @@ -261,14 +258,24 @@ def _update_velocity_position(self):
:code:`self.optimize()` method
"""
# Define the hyperparameters from kwargs dictionary
c1, c2, m = self.kwargs['c1'], self.kwargs['c2'], self.kwargs['m']
c1, c2, w = self.kwargs['c1'], self.kwargs['c2'], self.kwargs['w']

# Compute for cognitive and social terms
cognitive = (c1 * np.random.uniform(0,1,self.swarm_size)
* (self.pbest_pos - self.pos))
social = (c2 * np.random.uniform(0,1,self.swarm_size)
* (self.lbest_pos - self.pos))
self.velocity = (m * self.velocity) + cognitive + social
temp_velocity = (w * self.velocity) + cognitive + social

# Create a mask to clamp the velocities
if self.v_clamp is not None:
# Create a mask depending on the set boundaries
v_min, v_max = self.v_clamp[0], self.v_clamp[1]
_b = np.logical_and(temp_velocity >= v_min, temp_velocity <= v_max)
# Use the mask to finally clamp the velocities
self.velocity = np.where(~_b, self.velocity, temp_velocity)
else:
self.velocity = temp_velocity

# Update position and store it in a temporary variable
temp = self.pos.copy()
Expand Down
Loading

0 comments on commit 3666265

Please sign in to comment.