Skip to content

Commit

Permalink
Split position-velocity update into two separate methods
Browse files Browse the repository at this point in the history
Just as a better design practice, the position-velocity update
method `_update_velocity_position()` is now split into two
separate methods: `_update_position()` and `_update_velocity()`.
Although the state side-effect behavior is there, splitting these
two can enable a more flexible implementation of the two updates.

In this context, the following were changed:
  - Base class now has `_update_position()` and `_update_velocity()`
  - Child classes are now implementing the same design

Hopefully, this design practice gets carried over to the next
implementations.

Author: ljvmiranda921
  • Loading branch information
ljvmiranda921 committed Aug 4, 2017
1 parent 3666265 commit 6d5421a
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 47 deletions.
15 changes: 15 additions & 0 deletions docs/api/pyswarms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ PySwarms package
Base Classes
------------

The base classes are inherited by various PSO implementations throughout the library.
It supports a simple skeleton to construct a customized PSO algorithm.

.. toctree::

pyswarms.base
Expand All @@ -15,6 +18,14 @@ Base Classes
Optimizers
-----------

The optimizers include the actual PSO implementations for various tasks. Generally,
there are two ways to implement an optimizer from this library: (1) as an easy
off-the-shelf algorithm, and (2) as an experimental custom-made algorithm.

* Easy off-the-shelf implementations include those that are already considered as standard in literature. This may include the classics such as global-best and local-best. Their topologies are hardcoded already, and there is no need for prior set-up in order to use. This is useful for quick-and-easy optimization problems.

* Experimental PSO algorithms are like standard PSO algorithms but without a defined topology. Instead, an object that inherits from a :code:`Topology` class is passed to an optimizer to define swarm behavior. Although the standard PSO implementations can be done through this, this is more experimental.

.. toctree::

pyswarms.single
Expand All @@ -23,6 +34,10 @@ Optimizers
Utilities
----------

This includes various utilities to help in optimization. In the future,
parameter search and plotting techniques will be incoroporated in this
module.

.. toctree::

pyswarms.utils.functions
26 changes: 23 additions & 3 deletions docs/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@
Features
========

There are two ways in which optimizers are implemented in PySwarms. The first involves
quick-and-easy implementations of classic PSO algorithms. Here, the topologies (or the way
a swarm behaves) is hardcoded in the source code. This is useful for fast implementations that
doesn't need prior set-up.

The second involves a set of experimental classes where topology is not defined. Instead, one
should create an object that inherits from a :code:`Topology` class, and pass it as a parameter
in the experimental PSO classes. There are some topologies that are already implemented, but it's also possible
to define a custom-made one. This is perfect for researchers who wanted to try out various swarm
behaviours and movements.

Single-Objective Optimizers
---------------------------
Expand All @@ -12,11 +21,22 @@ These are standard optimization techniques that aims to find the optima of a sin
Continuous
~~~~~~~~~~

Single-objective optimization where the search space is continuous.
Single-objective optimization where the search-space is continuous. Perfect for optimizing various
functions.

* :mod:`pyswarms.single.gb` - global-best Particle Swarm Optimization algorithm with a star-topology. Every particle compares itself with the best-performing particle in the swarm.
* :mod:`pyswarms.single.gb` - classic global-best Particle Swarm Optimization algorithm with a star-topology. Every particle compares itself with the best-performing particle in the swarm.

* :mod:`pyswarms.single.lb` - local-best Particle Swarm Optimization algorithm with a ring-topology. Every particle compares itself only with its nearest-neighbours as computed by a distance metric.
* :mod:`pyswarms.single.lb` - classic local-best Particle Swarm Optimization algorithm with a ring-topology. Every particle compares itself only with its nearest-neighbours as computed by a distance metric.

* :mod:`pyswarms.single.exp` - experimental Particle Swarm Optimization algorithm.

Discrete
~~~~~~~~

Single-objective optimization where the search-space is discrete. Useful for job-scheduling, traveling
salesman, or any other sequence-based problems.

* :mod:`pyswarms.discrete.bn` - classic binary Particle Swarm Optimization algorithm without mutation. Uses a ring topology to choose its neighbours (but can be set to global).


Utilities
Expand Down
6 changes: 5 additions & 1 deletion pyswarms/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@
"""

from .bs import SwarmBase
from .dbs import DiscreteSwarmBase

__all__ = ["SwarmBase"]
__all__ = [
"SwarmBase",
"DiscreteSwarmBase"
]
33 changes: 27 additions & 6 deletions pyswarms/base/bs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,14 @@
when not used. When defining your own swarm implementation,
create another class,
>>> class MySwarm(SwarmBaseClass):
>>> class MySwarm(SwarmBase):
>>> 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.
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
Expand Down Expand Up @@ -77,6 +74,10 @@ def assertions(self):
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)')

# Required keys in keyword arguments
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.
Expand Down Expand Up @@ -141,6 +142,26 @@ def optimize(self, f, iters, print_step=1, verbose=1):
"""
raise NotImplementedError("SwarmBase::optimize()")

def _update_velocity(self):
"""Updates the velocity matrix.
Raises
------
NotImplementedError
When this method is not implemented.
"""
raise NotImplementedError("SwarmBase::_update_velocity()")

def _update_position(self):
"""Updates the position matrix.
Raises
------
NotImplementedError
When this method is not implemented.
"""
raise NotImplementedError("SwarmBase::_update_position()")

def reset(self):
"""Resets the attributes of the optimizer.
Expand Down
6 changes: 4 additions & 2 deletions pyswarms/single/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@
from .gb import GBestPSO
from .lb import LBestPSO

__all__ = ["GBestPSO",
"LBestPSO"]
__all__ = [
"GBestPSO",
"LBestPSO"
]
31 changes: 16 additions & 15 deletions pyswarms/single/gb.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,10 @@ def assertions(self):
"""
super(GBestPSO, self).assertions()

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
initializes a velocity component by sampling from a random
distribution with range :code:`[0,1]`.
Attributes
----------
n_particles : int
Expand Down Expand Up @@ -160,7 +154,8 @@ def optimize(self, f, iters, print_step=1, verbose=1):
(i+1, iters, self.gbest_cost), verbose, 2)

# Perform velocity and position updates
self._update_velocity_position()
self._update_velocity()
self._update_position()

end_report(self.gbest_cost, self.gbest_pos, verbose)
return (self.gbest_cost, self.gbest_pos)
Expand All @@ -176,18 +171,17 @@ def reset(self):
# Initialize the personal best of each particle
self.pbest_pos = self.pos

def _update_velocity_position(self):
"""Updates the velocity and position of the swarm.
def _update_velocity(self):
"""Updates the velocity matrix of the swarm.
Specifically, it updates the attributes :code:`self.velocity`
and :code:`self.pos`. This function is being called by the
:code:`self.optimize()` method
This method updates the attribute :code:`self.velocity` of
the instantiated object. It is called by the
:code:`self.optimize()` method.
"""
# Define the hyperparameters from kwargs dictionary
c1, c2, w = self.kwargs['c1'], self.kwargs['c2'], self.kwargs['w']

# Compute for cognitive and social terms and store it to a
# temporary velocity variable to be clamped later on
# 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)
Expand All @@ -204,6 +198,13 @@ def _update_velocity_position(self):
else:
self.velocity = temp_velocity

def _update_position(self):
"""Updates the position matrix of the swarm.
This method updates the attribute :code:`self.pos` of
the instantiated object. It is called by the
:code:`self.optimize()` method.
"""
# Update position and store it in a temporary variable
temp = self.pos.copy()
temp += self.velocity
Expand Down
36 changes: 16 additions & 20 deletions pyswarms/single/lb.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,6 @@ def assertions(self):
"""
super(LBestPSO, self).assertions()

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]:
Expand All @@ -98,10 +95,6 @@ def __init__(self, n_particles, dims, bounds=None, v_clamp=None,
k=1, p=2, **kwargs):
"""Initializes the swarm.
Takes the same attributes as SwarmBase, but also initializes
a velocity component by sampling from a random distribution
with range :code:`[0,1]`.
Attributes
----------
n_particles : int
Expand Down Expand Up @@ -131,7 +124,6 @@ def __init__(self, n_particles, dims, bounds=None, v_clamp=None,
* w : float
inertia parameter
"""

# Store n_neighbors and neighborhood type
self.k = k
self.p = p
Expand Down Expand Up @@ -192,14 +184,11 @@ def optimize(self, f, iters, print_step=1, verbose=1):
(i+1, iters, np.min(self.lbest_cost)), verbose, 2)

# Perform position velocity update
self._update_velocity_position()

# Because we have multiple neighbor spaces, we are reporting the
# local-best for each neighbour, thus giving us multiple values
# for the local-best cost and positions. What we'll do is that
# we are going to obtain only the minimum of all these local
# positions and then report it.
self._update_velocity()
self._update_position()

# Only obtain the minimum of all these local positions and
# then return it.
self.best_neighbor_cost = np.argmin(self.lbest_cost)
self.best_neighbor_pos = self.lbest_pos[self.best_neighbor_cost]

Expand Down Expand Up @@ -250,12 +239,12 @@ def reset(self):
# Initialize the personal best of each particle
self.pbest_pos = self.pos

def _update_velocity_position(self):
"""Updates the velocity and position of the swarm.
def _update_velocity(self):
"""Updates the velocity matrix of the swarm.
Specifically, it updates the attributes :code:`self.velocity`
and :code:`self.pos`. This function is being called by the
:code:`self.optimize()` method
This method updates the attribute :code:`self.velocity` of
the instantiated object. It is called by the
:code:`self.optimize()` method.
"""
# Define the hyperparameters from kwargs dictionary
c1, c2, w = self.kwargs['c1'], self.kwargs['c2'], self.kwargs['w']
Expand All @@ -277,6 +266,13 @@ def _update_velocity_position(self):
else:
self.velocity = temp_velocity

def _update_position(self):
"""Updates the position matrix of the swarm.
This method updates the attribute :code:`self.pos` of
the instantiated object. It is called by the
:code:`self.optimize()` method.
"""
# Update position and store it in a temporary variable
temp = self.pos.copy()
temp += self.velocity
Expand Down
15 changes: 15 additions & 0 deletions tests/optimizers/test_gb.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,20 @@ def test_reset(self):
self.assertEqual(optimizer.gbest_cost, np.inf)
self.assertIsNone(optimizer.gbest_pos)

class Run(Base):
"""Perform a single run of the algorithm to see if something breaks."""

def test_run(self):
"""Perform a single run."""
optimizer = GBestPSO(10,2, **self.options)
try:
optimizer.optimize(sphere_func, 1000, verbose=0)
trigger = True
except:
print('Execution failed.')
trigger = False

self.assertTrue(trigger)

if __name__ == '__main__':
unittest.main()
15 changes: 15 additions & 0 deletions tests/optimizers/test_lb.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,20 @@ def test_reset(self):
self.assertEqual(optimizer.lbest_cost, np.inf)
self.assertIsNone(optimizer.lbest_pos)

class Run(Base):
"""Perform a single run of the algorithm to see if something breaks."""

def test_run(self):
"""Perform a single run."""
optimizer = LBestPSO(10,2, **self.options)
try:
optimizer.optimize(sphere_func, 1000, verbose=0)
trigger = True
except:
print('Execution failed.')
trigger = False

self.assertTrue(trigger)

if __name__ == '__main__':
unittest.main()

0 comments on commit 6d5421a

Please sign in to comment.