Skip to content

Commit

Permalink
Add pySwarm code.
Browse files Browse the repository at this point in the history
  • Loading branch information
wojtekPi committed Apr 4, 2016
1 parent 49f7cef commit 9f33917
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 0 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ branch = true
omit =
*/env/*
*/test/*
*/grortir/externals/*
1 change: 1 addition & 0 deletions .landscape.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pep257:

ignore-paths:
- scent.py
- grortir/externals

python-targets:
- 3
2 changes: 2 additions & 0 deletions .pep257
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
# D202: No blank lines allowed *after* function docstring (personal preference)
# uncomment line below to ignore D202 rule.
# add-ignore = D202

match-dir='^((?!grortir\/externals).)*$'
2 changes: 2 additions & 0 deletions .pep8rc
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@

# E501: line too long (checked by PyLint)
ignore = E501

exclude=grortir/externals
4 changes: 4 additions & 0 deletions .privateWordDictionary
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ py
docstring
OK
tuple
pyswarm
tisimst
github
https
2 changes: 2 additions & 0 deletions grortir/externals/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""Package contains modified external modules."""
# pylint: skip-file
3 changes: 3 additions & 0 deletions grortir/externals/pyswarm/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Package with pyswarm.
https://github.com/tisimst/pyswarm
"""
235 changes: 235 additions & 0 deletions grortir/externals/pyswarm/pso.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
# pylint: skip-file
from functools import partial
import numpy as np

def _obj_wrapper(func, args, kwargs, x):
return func(x, *args, **kwargs)

def _is_feasible_wrapper(func, x):
return np.all(func(x)>=0)

def _cons_none_wrapper(x):
return np.array([0])

def _cons_ieqcons_wrapper(ieqcons, args, kwargs, x):
return np.array([y(x, *args, **kwargs) for y in ieqcons])

def _cons_f_ieqcons_wrapper(f_ieqcons, args, kwargs, x):
return np.array(f_ieqcons(x, *args, **kwargs))

def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={},
swarmsize=100, omega=0.5, phip=0.5, phig=0.5, maxiter=100,
minstep=1e-8, minfunc=1e-8, debug=False, processes=1,
particle_output=False):
"""
Perform a particle swarm optimization (PSO)
Parameters
==========
func : function
The function to be minimized
lb : array
The lower bounds of the design variable(s)
ub : array
The upper bounds of the design variable(s)
Optional
========
ieqcons : list
A list of functions of length n such that ieqcons[j](x,*args) >= 0.0 in
a successfully optimized problem (Default: [])
f_ieqcons : function
Returns a 1-D array in which each element must be greater or equal
to 0.0 in a successfully optimized problem. If f_ieqcons is specified,
ieqcons is ignored (Default: None)
args : tuple
Additional arguments passed to objective and constraint functions
(Default: empty tuple)
kwargs : dict
Additional keyword arguments passed to objective and constraint
functions (Default: empty dict)
swarmsize : int
The number of particles in the swarm (Default: 100)
omega : scalar
Particle velocity scaling factor (Default: 0.5)
phip : scalar
Scaling factor to search away from the particle's best known position
(Default: 0.5)
phig : scalar
Scaling factor to search away from the swarm's best known position
(Default: 0.5)
maxiter : int
The maximum number of iterations for the swarm to search (Default: 100)
minstep : scalar
The minimum stepsize of swarm's best position before the search
terminates (Default: 1e-8)
minfunc : scalar
The minimum change of swarm's best objective value before the search
terminates (Default: 1e-8)
debug : boolean
If True, progress statements will be displayed every iteration
(Default: False)
processes : int
The number of processes to use to evaluate objective function and
constraints (default: 1)
particle_output : boolean
Whether to include the best per-particle position and the objective
values at those.
Returns
=======
g : array
The swarm's best known position (optimal design)
f : scalar
The objective value at ``g``
p : array
The best known position per particle
pf: arrray
The objective values at each position in p
"""

assert len(lb)==len(ub), 'Lower- and upper-bounds must be the same length'
assert hasattr(func, '__call__'), 'Invalid function handle'
lb = np.array(lb)
ub = np.array(ub)
assert np.all(ub>lb), 'All upper-bound values must be greater than lower-bound values'

vhigh = np.abs(ub - lb)
vlow = -vhigh

# Initialize objective function
obj = partial(_obj_wrapper, func, args, kwargs)

# Check for constraint function(s) #########################################
if f_ieqcons is None:
if not len(ieqcons):
if debug:
print('No constraints given.')
cons = _cons_none_wrapper
else:
if debug:
print('Converting ieqcons to a single constraint function')
cons = partial(_cons_ieqcons_wrapper, ieqcons, args, kwargs)
else:
if debug:
print('Single constraint function given in f_ieqcons')
cons = partial(_cons_f_ieqcons_wrapper, f_ieqcons, args, kwargs)
is_feasible = partial(_is_feasible_wrapper, cons)

# Initialize the multiprocessing module if necessary
if processes > 1:
import multiprocessing
mp_pool = multiprocessing.Pool(processes)

# Initialize the particle swarm ############################################
S = swarmsize
D = len(lb) # the number of dimensions each particle has
x = np.random.rand(S, D) # particle positions
v = np.zeros_like(x) # particle velocities
p = np.zeros_like(x) # best particle positions
fx = np.zeros(S) # current particle function values
fs = np.zeros(S, dtype=bool) # feasibility of each particle
fp = np.ones(S)*np.inf # best particle function values
g = [] # best swarm position
fg = np.inf # best swarm position starting value

# Initialize the particle's position
x = lb + x*(ub - lb)

# Calculate objective and constraints for each particle
if processes > 1:
fx = np.array(mp_pool.map(obj, x))
fs = np.array(mp_pool.map(is_feasible, x))
else:
for i in range(S):
fx[i] = obj(x[i, :])
fs[i] = is_feasible(x[i, :])

# Store particle's best position (if constraints are satisfied)
i_update = np.logical_and((fx < fp), fs)
p[i_update, :] = x[i_update, :].copy()
fp[i_update] = fx[i_update]

# Update swarm's best position
i_min = np.argmin(fp)
if fp[i_min] < fg:
fg = fp[i_min]
g = p[i_min, :].copy()
else:
# At the start, there may not be any feasible starting point, so just
# give it a temporary "best" point since it's likely to change
g = x[0, :].copy()

# Initialize the particle's velocity
v = vlow + np.random.rand(S, D)*(vhigh - vlow)

# Iterate until termination criterion met ##################################
it = 1
while it <= maxiter:
rp = np.random.uniform(size=(S, D))
rg = np.random.uniform(size=(S, D))

# Update the particles velocities
v = omega*v + phip*rp*(p - x) + phig*rg*(g - x)
# Update the particles' positions
x = x + v
# Correct for bound violations
maskl = x < lb
masku = x > ub
x = x*(~np.logical_or(maskl, masku)) + lb*maskl + ub*masku

# Update objectives and constraints
if processes > 1:
fx = np.array(mp_pool.map(obj, x))
fs = np.array(mp_pool.map(is_feasible, x))
else:
for i in range(S):
fx[i] = obj(x[i, :])
fs[i] = is_feasible(x[i, :])

# Store particle's best position (if constraints are satisfied)
i_update = np.logical_and((fx < fp), fs)
p[i_update, :] = x[i_update, :].copy()
fp[i_update] = fx[i_update]

# Compare swarm's best position with global best position
i_min = np.argmin(fp)
if fp[i_min] < fg:
if debug:
print('New best for swarm at iteration {:}: {:} {:}'\
.format(it, p[i_min, :], fp[i_min]))

p_min = p[i_min, :].copy()
stepsize = np.sqrt(np.sum((g - p_min)**2))

if np.abs(fg - fp[i_min]) <= minfunc:
print('Stopping search: Swarm best objective change less than {:}'\
.format(minfunc))
if particle_output:
return p_min, fp[i_min], p, fp
else:
return p_min, fp[i_min]
elif stepsize <= minstep:
print('Stopping search: Swarm best position change less than {:}'\
.format(minstep))
if particle_output:
return p_min, fp[i_min], p, fp
else:
return p_min, fp[i_min]
else:
g = p_min.copy()
fg = fp[i_min]

if debug:
print('Best after iteration {:}: {:} {:}'.format(it, g, fg))
it += 1

print('Stopping search: maximum iterations reached --> {:}'.format(maxiter))

if not is_feasible(g):
print("However, the optimization couldn't find a feasible design. Sorry")
if particle_output:
return g, fg, p, fp
else:
return g, fg
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
networkx
numpy
wheel
pyenchant
testpackage == 2.26

0 comments on commit 9f33917

Please sign in to comment.