Skip to content

Commit

Permalink
Merge pull request #117 from ljvmiranda921/add-topologies
Browse files Browse the repository at this point in the history
Add Topologies class
  • Loading branch information
Lj Miranda authored Jun 6, 2018
2 parents 8906ca9 + b6d24e1 commit 948e727
Show file tree
Hide file tree
Showing 12 changed files with 442 additions and 132 deletions.
97 changes: 3 additions & 94 deletions pyswarms/backend/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@

# Import modules
import numpy as np
from scipy.spatial import cKDTree

# Create a logger
logger = logging.getLogger(__name__)

def update_pbest(swarm):
def compute_pbest(swarm):
"""Takes a swarm instance and updates the personal best scores
You can use this method to update your personal best positions.
Expand Down Expand Up @@ -71,97 +70,7 @@ def update_pbest(swarm):
else:
return (new_pbest_pos, new_pbest_cost)

def update_gbest(swarm):
"""Updates the global best given the cost and the position
This method takes the current pbest_pos and pbest_cost, then returns
the minimum cost and position from the matrix. It should be used in
tandem with an if statement
.. code-block:: python
import pyswarms.backend as P
from pyswarms.backend.swarms import Swarm
my_swarm = P.create_swarm(n_particles, dimensions)
# If the minima of the pbest_cost is less than the best_cost
if np.min(pbest_cost) < best_cost:
# Update best_cost and position
swarm.best_pos, swarm.best_cost = P.update_gbest(my_swarm)
Parameters
----------
swarm : pyswarms.backend.swarm.Swarm
a Swarm instance
Returns
-------
numpy.ndarray
Best position of shape :code:`(n_dimensions, )`
float
Best cost
"""
try:
best_pos = swarm.pbest_pos[np.argmin(swarm.pbest_cost)]
best_cost = np.min(swarm.pbest_cost)
except AttributeError:
msg = 'Please pass a Swarm class. You passed {}'.format(type(swarm))
logger.error(msg)
raise
else:
return (best_pos, best_cost)

def update_gbest_neighborhood(swarm, p, k):
"""Updates the global best using a neighborhood approach
This uses the cKDTree method from :code:`scipy` to obtain the nearest
neighbours
Parameters
----------
swarm : pyswarms.backend.swarms.Swarm
a Swarm instance
k : int
number of neighbors to be considered. Must be a
positive integer less than :code:`n_particles`
p: int {1,2}
the Minkowski p-norm to use. 1 is the
sum-of-absolute values (or L1 distance) while 2 is
the Euclidean (or L2) distance.
Returns
-------
numpy.ndarray
Best position of shape :code:`(n_dimensions, )`
float
Best cost
"""
try:
# Obtain the nearest-neighbors for each particle
tree = cKDTree(swarm.position)
_, idx = tree.query(swarm.position, p=p, k=k)

# Map the computed costs to the neighbour indices and take the
# argmin. If k-neighbors is equal to 1, then the swarm acts
# independently of each other.
if k == 1:
# The minimum index is itself, no mapping needed.
best_neighbor = swarm.pbest_cost[idx][:, np.newaxis].argmin(axis=1)
else:
idx_min = swarm.pbest_cost[idx].argmin(axis=1)
best_neighbor = idx[np.arange(len(idx)), idx_min]
# Obtain best cost and position
best_cost = np.min(swarm.pbest_cost[best_neighbor])
best_pos = swarm.pbest_pos[np.argmin(swarm.pbest_cost[best_neighbor])]
except AttributeError:
msg = 'Please pass a Swarm class. You passed {}'.format(type(swarm))
logger.error(msg)
raise
else:
return (best_pos, best_cost)

def update_velocity(swarm, clamp):
def compute_velocity(swarm, clamp):
"""Updates the velocity matrix
This method updates the velocity matrix using the best and current
Expand Down Expand Up @@ -225,7 +134,7 @@ def update_velocity(swarm, clamp):
else:
return updated_velocity

def update_position(swarm, bounds):
def compute_position(swarm, bounds):
"""Updates the position matrix
This method updates the position matrix given the current position and
Expand Down
16 changes: 16 additions & 0 deletions pyswarms/backend/topology/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
The :code:`pyswarms.backend.topology` contains various topologies that dictate
particle behavior. These topologies implement three methods:
- compute_best_particle(): gets the position and cost of the best particle in the swarm
- update_velocity(): updates the velocity-matrix depending on the topology.
- update_position(): updates the position-matrix depending on the topology.
"""

from .star import Star
from .ring import Ring


__all__ = [
"Star",
"Ring"
]
24 changes: 24 additions & 0 deletions pyswarms/backend/topology/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-

"""
Base class for Topologies
"""

class Topology(object):

def __init__(self, **kwargs):
"""Initializes the class"""
pass

def compute_gbest(self, swarm):
"""Computes the best particle of the swarm and returns the cost and
position"""
raise NotImplementedError("Topology::compute_gbest()")

def compute_position(self, swarm):
"""Updates the swarm's position-matrix"""
raise NotImplementedError("Topology::compute_position()")

def compute_velocity(self, swarm):
"""Updates the swarm's velocity-matrix"""
raise NotImplementedError("Topology::compute_velocity()")
137 changes: 137 additions & 0 deletions pyswarms/backend/topology/ring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-

"""
A Ring Network Topology
This class implements a star topology where all particles are connected in a
ring-like fashion. This social behavior is often found in LocalBest PSO
optimizers.
"""

# Import from stdlib
import logging

# Import modules
import numpy as np
from scipy.spatial import cKDTree

# Import from package
from .. import operators as ops
from .base import Topology

# Create a logger
logger = logging.getLogger(__name__)

class Ring(Topology):

def __init__(self):
super(Ring, self).__init__()

def compute_gbest(self, swarm, p, k):
"""Updates the global best using a neighborhood approach
This uses the cKDTree method from :code:`scipy` to obtain the nearest
neighbours
Parameters
----------
swarm : pyswarms.backend.swarms.Swarm
a Swarm instance
k : int
number of neighbors to be considered. Must be a
positive integer less than :code:`n_particles`
p: int {1,2}
the Minkowski p-norm to use. 1 is the
sum-of-absolute values (or L1 distance) while 2 is
the Euclidean (or L2) distance.
Returns
-------
numpy.ndarray
Best position of shape :code:`(n_dimensions, )`
float
Best cost
"""
try:
# Obtain the nearest-neighbors for each particle
tree = cKDTree(swarm.position)
_, idx = tree.query(swarm.position, p=p, k=k)

# Map the computed costs to the neighbour indices and take the
# argmin. If k-neighbors is equal to 1, then the swarm acts
# independently of each other.
if k == 1:
# The minimum index is itself, no mapping needed.
best_neighbor = swarm.pbest_cost[idx][:, np.newaxis].argmin(axis=1)
else:
idx_min = swarm.pbest_cost[idx].argmin(axis=1)
best_neighbor = idx[np.arange(len(idx)), idx_min]
# Obtain best cost and position
best_cost = np.min(swarm.pbest_cost[best_neighbor])
best_pos = swarm.pbest_pos[np.argmin(swarm.pbest_cost[best_neighbor])]
except AttributeError:
msg = 'Please pass a Swarm class. You passed {}'.format(type(swarm))
logger.error(msg)
raise
else:
return (best_pos, best_cost)

def compute_velocity(self, swarm, clamp):
"""Computes the velocity matrix
This method updates the velocity matrix using the best and current
positions of the swarm. The velocity matrix is computed using the
cognitive and social terms of the swarm.
A sample usage can be seen with the following:
.. code-block :: python
import pyswarms.backend as P
from pyswarms.swarms.backend import Swarm
from pyswarms.backend.topology import Star
my_swarm = P.create_swarm(n_particles, dimensions)
my_topology = Ring()
for i in range(iters):
# Inside the for-loop
my_swarm.velocity = my_topology.update_velocity(my_swarm, clamp)
Parameters
----------
swarm : pyswarms.backend.swarms.Swarm
a Swarm instance
clamp : tuple of floats (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.
Returns
-------
numpy.ndarray
Updated velocity matrix
"""
return ops.compute_velocity(swarm, clamp)

def compute_position(self, swarm, bounds):
"""Updates the position matrix
This method updates the position matrix given the current position and
the velocity. If bounded, it waives updating the position.
Parameters
----------
swarm : pyswarms.backend.swarms.Swarm
a Swarm instance
bounds : tuple of :code:`np.ndarray` or list (default is :code:`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:`(dimensions,)`.
Returns
-------
numpy.ndarray
New position-matrix
"""
return ops.compute_position(swarm, bounds)
Loading

0 comments on commit 948e727

Please sign in to comment.