Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Topologies #117

Merged
merged 8 commits into from
Jun 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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