Skip to content

Commit

Permalink
[WIP] Update general optimizer (#244)
Browse files Browse the repository at this point in the history
  • Loading branch information
ljvmiranda921 authored Sep 12, 2018
1 parent a04fb9a commit 0d2b11c
Show file tree
Hide file tree
Showing 12 changed files with 73 additions and 240 deletions.
16 changes: 9 additions & 7 deletions pyswarms/backend/topology/random.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,23 @@


class Random(Topology):
def __init__(self, static=False):
def __init__(self, k, static=False):
"""Initializes the class
Parameters
----------
k : int
number of neighbors to be considered. Must be a
positive integer less than :code:`n_particles-1`
static : bool (Default is :code:`False`)
a boolean that decides whether the topology
is static or dynamic"""
is static or dynamic
"""
super(Random, self).__init__(static)
self.k = k
self.rep = Reporter(logger=logging.getLogger(__name__))

def compute_gbest(self, swarm, k):
def compute_gbest(self, swarm):
"""Update the global best using a random neighborhood approach
This uses random class from :code:`numpy` to give every particle k
Expand All @@ -46,9 +51,6 @@ def compute_gbest(self, swarm, k):
----------
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-1`
Returns
-------
Expand All @@ -60,7 +62,7 @@ def compute_gbest(self, swarm, k):
try:
# Check if the topology is static or dynamic and assign neighbors
if (self.static and self.neighbor_idx is None) or not self.static:
adj_matrix = self.__compute_neighbors(swarm, k)
adj_matrix = self.__compute_neighbors(swarm, self.k)
self.neighbor_idx = np.array(
[
adj_matrix[i].nonzero()[0]
Expand Down
28 changes: 16 additions & 12 deletions pyswarms/backend/topology/ring.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,27 @@


class Ring(Topology):
def __init__(self, static=False):
def __init__(self, p, k, static=False):
"""Initializes the class
Parameters
----------
static : bool (Default is :code:`False`)
a boolean that decides whether the topology
is static or dynamic"""
is static or dynamic
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.
k : int
number of neighbors to be considered. Must be a
positive integer less than :code:`n_particles`
"""
super(Ring, self).__init__(static)
self.p, self.k = p, k
self.rep = Reporter(logger=logging.getLogger(__name__))

def compute_gbest(self, swarm, p, k):
def compute_gbest(self, swarm):
"""Update the global best using a ring-like neighborhood approach
This uses the cKDTree method from :code:`scipy` to obtain the nearest
Expand All @@ -41,13 +50,6 @@ def compute_gbest(self, swarm, p, k):
----------
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
-------
Expand All @@ -61,12 +63,14 @@ def compute_gbest(self, swarm, p, k):
if (self.static and self.neighbor_idx is None) or not self.static:
# Obtain the nearest-neighbors for each particle
tree = cKDTree(swarm.position)
_, self.neighbor_idx = tree.query(swarm.position, p=p, k=k)
_, self.neighbor_idx = tree.query(
swarm.position, p=self.p, k=self.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:
if self.k == 1:
# The minimum index is itself, no mapping needed.
best_neighbor = swarm.pbest_cost[self.neighbor_idx][
:, np.newaxis
Expand Down
11 changes: 6 additions & 5 deletions pyswarms/backend/topology/von_neumann.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@


class VonNeumann(Ring):
def __init__(self):
super(VonNeumann, self).__init__(static=True)
def __init__(self, p, r):
super(VonNeumann, self).__init__(static=True, p=p, k=None)
self.r = r
self.rep = Reporter(logger=logging.getLogger(__name__))

def compute_gbest(self, swarm, p, r):
def compute_gbest(self, swarm):
"""Updates the global best using a neighborhood approach
The Von Neumann topology inherits from the Ring topology and uses
Expand All @@ -43,8 +44,8 @@ def compute_gbest(self, swarm, p, r):
float
Best cost
"""
neighbors = VonNeumann.delannoy(swarm.dimensions, r)
return super(VonNeumann, self).compute_gbest(swarm, p, neighbors)
self.k = VonNeumann.delannoy(swarm.dimensions, self.r)
return super(VonNeumann, self).compute_gbest(swarm)

@staticmethod
def delannoy(d, r):
Expand Down
4 changes: 2 additions & 2 deletions pyswarms/discrete/binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def __init__(
# Initialize the resettable attributes
self.reset()
# Initialize the topology
self.top = Ring(static=False)
self.top = Ring(static=False, p=self.p, k=self.k)
self.name = __name__

def optimize(self, objective_func, iters, fast=False, **kwargs):
Expand Down Expand Up @@ -192,7 +192,7 @@ def optimize(self, objective_func, iters, fast=False, **kwargs):
best_cost_yet_found = np.min(self.swarm.best_cost)
# Update gbest from neighborhood
self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest(
self.swarm, self.p, self.k
self.swarm
)
# Print to console
self.rep.hook(best_cost=self.swarm.best_cost)
Expand Down
78 changes: 3 additions & 75 deletions pyswarms/single/general_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,52 +167,6 @@ def __init__(
raise TypeError("Parameter `topology` must be a Topology object")
else:
self.top = topology

# Case for the Ring topology
if isinstance(topology, (Ring, VonNeumann)):
# Assign p-value as attributes
self.p = options["p"]
# Exceptions for the p value
if "p" not in self.options:
raise KeyError("Missing p in options")
if self.p not in [1, 2]:
raise ValueError(
"p-value should either be 1 (for L1/Minkowski) "
"or 2 (for L2/Euclidean)."
)

# Case for Random, VonNeumann and Ring topologies
if isinstance(topology, (Random, Ring, VonNeumann)):
if not isinstance(topology, VonNeumann):
self.k = options["k"]
if not isinstance(self.k, int):
raise ValueError(
"No. of neighbors must be an integer between"
"0 and no. of particles."
)
if not 0 <= self.k <= self.n_particles - 1:
raise ValueError(
"No. of neighbors must be between 0 and no. "
"of particles."
)
if "k" not in self.options:
raise KeyError("Missing k in options")
else:
# Assign range r as attribute
self.r = options["r"]
if not isinstance(self.r, int):
raise ValueError("The range must be a positive integer")
if (
self.r <= 0
or not 0
<= VonNeumann.delannoy(self.swarm.dimensions, self.r)
<= self.n_particles - 1
):
raise ValueError(
"The range must be set such that the computed"
"Delannoy number (number of neighbours) is"
"between 0 and the no. of particles."
)
self.name = __name__

def optimize(self, objective_func, iters, fast=False, **kwargs):
Expand Down Expand Up @@ -257,37 +211,11 @@ def optimize(self, objective_func, iters, fast=False, **kwargs):
self.swarm
)
best_cost_yet_found = self.swarm.best_cost
# If the topology is a ring topology just use the local minimum
# TODO
if isinstance(self.top, Ring) and not isinstance(
self.top, VonNeumann
):
# Update gbest from neighborhood
self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest(
self.swarm, self.p, self.k
)
# If the topology is a VonNeumann topology pass the neighbour and range attribute to compute_gbest()
# TODO
if isinstance(self.top, VonNeumann):
# Update gbest from neighborhood
# Update swarm
if np.min(self.swarm.pbest_cost) < self.swarm.best_cost:
self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest(
self.swarm, self.p, self.r
self.swarm
)
# If the topology is a random topology pass the neighbor attribute to compute_gbest()
# TODO
elif isinstance(self.top, Random):
# Get minima of pbest and check if it's less than gbest
if np.min(self.swarm.pbest_cost) < self.swarm.best_cost:
self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest(
self.swarm, self.k
)
else:
# Get minima of pbest and check if it's less than gbest
# TODO
if np.min(self.swarm.pbest_cost) < self.swarm.best_cost:
self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest(
self.swarm
)
# Print to console
self.rep.hook(best_cost=self.swarm.best_cost)
hist = self.ToHistory(
Expand Down
4 changes: 2 additions & 2 deletions pyswarms/single/local_best.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def __init__(
# Initialize the resettable attributes
self.reset()
# Initialize the topology
self.top = Ring(static=static)
self.top = Ring(static=static, p=self.p, k=self.k)
self.name = __name__

def optimize(self, objective_func, iters, fast=False, **kwargs):
Expand Down Expand Up @@ -223,7 +223,7 @@ def optimize(self, objective_func, iters, fast=False, **kwargs):
best_cost_yet_found = np.min(self.swarm.best_cost)
# Update gbest from neighborhood
self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest(
self.swarm, self.p, self.k
self.swarm
)
self.rep.hook(best_cost=np.min(self.swarm.best_cost))
# Save to history
Expand Down
10 changes: 8 additions & 2 deletions pyswarms/utils/decorators/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,15 @@ def cost_func(x):
cost_dec : callable
The vectorized output for all particles as defined by :code:`cost_func`
"""

def cost_dec(particles, **kwargs):
n_particles = particles.shape[0]
vector = np.array([cost_func(particles[i], **kwargs) for i in range(n_particles)])
assert vector.shape == (n_particles, ), "The cost function should return a single value."
vector = np.array(
[cost_func(particles[i], **kwargs) for i in range(n_particles)]
)
assert vector.shape == (
n_particles,
), "The cost function should return a single value."
return vector

return cost_dec
20 changes: 10 additions & 10 deletions tests/backend/topology/test_random.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
@pytest.mark.parametrize("k", [1, 2])
def test_update_gbest_neighborhood(swarm, k, static):
"""Test if update_gbest_neighborhood gives the expected return values"""
topology = Random(static=static)
pos, cost = topology.compute_gbest(swarm, k=k)
topology = Random(static=static, k=k)
pos, cost = topology.compute_gbest(swarm)
expected_pos = np.array([9.90438476e-01, 2.50379538e-03, 1.87405987e-05])
expected_cost = 1.0002528364353296
assert cost == pytest.approx(expected_cost)
Expand All @@ -25,7 +25,7 @@ def test_update_gbest_neighborhood(swarm, k, static):
@pytest.mark.parametrize("clamp", [None, (0, 1), (-1, 1)])
def test_compute_velocity_return_values(swarm, clamp, static):
"""Test if compute_velocity() gives the expected shape and range"""
topology = Random(static=static)
topology = Random(static=static, k=1)
v = topology.compute_velocity(swarm, clamp)
assert v.shape == swarm.position.shape
if clamp is not None:
Expand All @@ -39,7 +39,7 @@ def test_compute_velocity_return_values(swarm, clamp, static):
)
def test_compute_position_return_values(swarm, bounds, static):
"""Test if compute_position() gives the expected shape and range"""
topology = Random(static=static)
topology = Random(static=static, k=1)
p = topology.compute_position(swarm, bounds)
assert p.shape == swarm.velocity.shape
if bounds is not None:
Expand All @@ -50,8 +50,8 @@ def test_compute_position_return_values(swarm, bounds, static):
@pytest.mark.parametrize("k", [1, 2])
def test_compute_neighbors_return_values(swarm, k, static):
"""Test if __compute_neighbors() gives the expected shape and symmetry"""
topology = Random(static=static)
adj_matrix = topology._Random__compute_neighbors(swarm, k)
topology = Random(static=static, k=k)
adj_matrix = topology._Random__compute_neighbors(swarm, k=k)
assert adj_matrix.shape == (swarm.n_particles, swarm.n_particles)
assert np.allclose(adj_matrix, adj_matrix.T, atol=1e-8) # Symmetry test

Expand All @@ -61,8 +61,8 @@ def test_compute_neighbors_return_values(swarm, k, static):
def test_compute_neighbors_adjacency_matrix(swarm, k, static):
"""Test if __compute_neighbors() gives the expected matrix"""
np.random.seed(1)
topology = Random(static=static)
adj_matrix = topology._Random__compute_neighbors(swarm, k)
topology = Random(static=static, k=k)
adj_matrix = topology._Random__compute_neighbors(swarm, k=k)
# fmt: off
comparison_matrix = np.array([[1, 1, 1, 0, 1, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
Expand All @@ -82,6 +82,6 @@ def test_compute_neighbors_adjacency_matrix(swarm, k, static):
@pytest.mark.parametrize("k", [1])
def test_neighbor_idx(swarm, k, static):
"""Test if the neighbor_idx attribute is assigned"""
topology = Random(static=static)
topology.compute_gbest(swarm, k)
topology = Random(static=static, k=k)
topology.compute_gbest(swarm)
assert topology.neighbor_idx is not None
12 changes: 6 additions & 6 deletions tests/backend/topology/test_ring.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
@pytest.mark.parametrize("p", [1, 2])
def test_update_gbest_neighborhood(swarm, p, k, static):
"""Test if update_gbest_neighborhood gives the expected return values"""
topology = Ring(static=static)
pos, cost = topology.compute_gbest(swarm, p=p, k=k)
topology = Ring(static=static, p=p, k=k)
pos, cost = topology.compute_gbest(swarm)
expected_pos = np.array([9.90438476e-01, 2.50379538e-03, 1.87405987e-05])
expected_cost = 1.0002528364353296
assert cost == pytest.approx(expected_cost)
Expand All @@ -26,7 +26,7 @@ def test_update_gbest_neighborhood(swarm, p, k, static):
@pytest.mark.parametrize("clamp", [None, (0, 1), (-1, 1)])
def test_compute_velocity_return_values(swarm, clamp, static):
"""Test if compute_velocity() gives the expected shape and range"""
topology = Ring(static=static)
topology = Ring(static=static, p=1, k=2)
v = topology.compute_velocity(swarm, clamp)
assert v.shape == swarm.position.shape
if clamp is not None:
Expand All @@ -40,7 +40,7 @@ def test_compute_velocity_return_values(swarm, clamp, static):
)
def test_compute_position_return_values(swarm, bounds, static):
"""Test if compute_position() gives the expected shape and range"""
topology = Ring(static=static)
topology = Ring(static=static, p=1, k=2)
p = topology.compute_position(swarm, bounds)
assert p.shape == swarm.velocity.shape
if bounds is not None:
Expand All @@ -52,6 +52,6 @@ def test_compute_position_return_values(swarm, bounds, static):
@pytest.mark.parametrize("p", [1, 2])
def test_neighbor_idx(swarm, static, p, k):
"""Test if the neighbor_idx attribute is assigned"""
topology = Ring(static=static)
topology.compute_gbest(swarm, p=p, k=k)
topology = Ring(static=static, p=p, k=k)
topology.compute_gbest(swarm)
assert topology.neighbor_idx is not None
Loading

0 comments on commit 0d2b11c

Please sign in to comment.