From fe9f5621de8adb619045a66e116d2d60d2e112f2 Mon Sep 17 00:00:00 2001 From: Nicolas Cellier Date: Fri, 8 Sep 2017 18:03:44 +0200 Subject: [PATCH 1/8] pep8 fix --- pyswarm/pso.py | 93 ++++++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/pyswarm/pso.py b/pyswarm/pso.py index 1811cd9..d807ee9 100644 --- a/pyswarm/pso.py +++ b/pyswarm/pso.py @@ -1,28 +1,34 @@ 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) + 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, + + +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 @@ -31,21 +37,21 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, 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 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, + 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 + Additional keyword arguments passed to objective and constraint functions (Default: empty dict) swarmsize : int The number of particles in the swarm (Default: 100) @@ -69,12 +75,12 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, If True, progress statements will be displayed every iteration (Default: False) processes : int - The number of processes to use to evaluate objective function and + 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 @@ -85,22 +91,23 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, 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' + + """ # noqa: docstring allowed to exceed 80 chars + + 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' - + 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) ######################################### + + # Check for constraint function(s) if f_ieqcons is None: if not len(ieqcons): if debug: @@ -120,8 +127,8 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, if processes > 1: import multiprocessing mp_pool = multiprocessing.Pool(processes) - - # Initialize the particle swarm ############################################ + + # Initialize the particle swarm S = swarmsize D = len(lb) # the number of dimensions each particle has x = np.random.rand(S, D) # particle positions @@ -129,12 +136,12 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, 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 + 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) + x = lb + x * (ub - lb) # Calculate objective and constraints for each particle if processes > 1: @@ -144,7 +151,7 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, 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() @@ -159,24 +166,24 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, # 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 ################################## + 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) + 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 + x = x * (~np.logical_or(maskl, masku)) + lb * maskl + ub * masku # Update objectives and constraints if processes > 1: @@ -196,22 +203,24 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, 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])) + 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)) + 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)) + print('Stopping search: ' + 'Swarm best position change less than {:}' + .format(minstep)) if particle_output: return p_min, fp[i_min], p, fp else: @@ -224,10 +233,12 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, print('Best after iteration {:}: {:} {:}'.format(it, g, fg)) it += 1 - print('Stopping search: maximum iterations reached --> {:}'.format(maxiter)) - + print( + 'Stopping search: maximum iterations reached --> {:}'.format(maxiter)) + if not is_feasible(g): - print("However, the optimization couldn't find a feasible design. Sorry") + print("However, the optimization couldn't " + "find a feasible design. Sorry") if particle_output: return g, fg, p, fp else: From ffcd342f7c561a116b1c3804369012216fd98c37 Mon Sep 17 00:00:00 2001 From: Nicolas Cellier Date: Fri, 8 Sep 2017 18:06:44 +0200 Subject: [PATCH 2/8] replace assert by proper Error assert should be used for debug (as tests) only, not for input validation. --- pyswarm/pso.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pyswarm/pso.py b/pyswarm/pso.py index d807ee9..8612834 100644 --- a/pyswarm/pso.py +++ b/pyswarm/pso.py @@ -94,12 +94,16 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, """ # noqa: docstring allowed to exceed 80 chars - assert len(lb) == len(ub), 'Lower- and upper-bounds must be the same length' - assert hasattr(func, '__call__'), 'Invalid function handle' + if len(lb) != len(ub): + raise ValueError('Lower- and upper-bounds must be the same length') + + if not hasattr(func, '__call__'): + raise TypeError('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' + if np.any(ub <= lb): + raise ValueError('All upper-bound values must be greater ' + 'than lower-bound values') vhigh = np.abs(ub - lb) vlow = -vhigh @@ -233,8 +237,8 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, print('Best after iteration {:}: {:} {:}'.format(it, g, fg)) it += 1 - print( - 'Stopping search: maximum iterations reached --> {:}'.format(maxiter)) + print('Stopping search: maximum ' + 'iterations reached --> {:}'.format(maxiter)) if not is_feasible(g): print("However, the optimization couldn't " From 3f01b9f2c1f5851a63d509f682c56bfa0898c3cf Mon Sep 17 00:00:00 2001 From: Nicolas Cellier Date: Fri, 8 Sep 2017 18:08:35 +0200 Subject: [PATCH 3/8] add shebangs --- pyswarm/pso.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyswarm/pso.py b/pyswarm/pso.py index 8612834..f2441a3 100644 --- a/pyswarm/pso.py +++ b/pyswarm/pso.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# coding=utf8 + from functools import partial import numpy as np From d415a31c94585a49bf7d4b210dcc440ccd1fad82 Mon Sep 17 00:00:00 2001 From: Nicolas Cellier Date: Fri, 8 Sep 2017 18:14:32 +0200 Subject: [PATCH 4/8] Use log instead of print. --- pyswarm/pso.py | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/pyswarm/pso.py b/pyswarm/pso.py index f2441a3..cf0ae10 100644 --- a/pyswarm/pso.py +++ b/pyswarm/pso.py @@ -1,9 +1,16 @@ #!/usr/bin/env python # coding=utf8 +import logging from functools import partial + import numpy as np +log = logging.getLogger(__name__) +log.handlers = [] +log.addHandler(logging.StreamHandler()) +log.setLevel("INFO") + def _obj_wrapper(func, args, kwargs, x): return func(x, *args, **kwargs) @@ -97,6 +104,9 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, """ # noqa: docstring allowed to exceed 80 chars + if debug: + log.setLevel("DEBUG") + if len(lb) != len(ub): raise ValueError('Lower- and upper-bounds must be the same length') @@ -117,16 +127,13 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, # Check for constraint function(s) if f_ieqcons is None: if not len(ieqcons): - if debug: - print('No constraints given.') + log.debug('No constraints given.') cons = _cons_none_wrapper else: - if debug: - print('Converting ieqcons to a single constraint function') + log.debug('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') + log.debug('Single constraint function given in f_ieqcons') cons = partial(_cons_f_ieqcons_wrapper, f_ieqcons, args, kwargs) is_feasible = partial(_is_feasible_wrapper, cons) @@ -209,25 +216,24 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, # 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 {:}: {:} {:}' + log.debug('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)) + log.info('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)) + log.info('Stopping search: ' + 'Swarm best position change less than {:}' + .format(minstep)) if particle_output: return p_min, fp[i_min], p, fp else: @@ -237,15 +243,15 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, fg = fp[i_min] if debug: - print('Best after iteration {:}: {:} {:}'.format(it, g, fg)) + log.info('Best after iteration {:}: {:} {:}'.format(it, g, fg)) it += 1 - print('Stopping search: maximum ' - 'iterations reached --> {:}'.format(maxiter)) + log.info('Stopping search: maximum ' + 'iterations reached --> {:}'.format(maxiter)) if not is_feasible(g): - print("However, the optimization couldn't " - "find a feasible design. Sorry") + log.info("However, the optimization couldn't " + "find a feasible design. Sorry") if particle_output: return g, fg, p, fp else: From 2c3ab61ff98b61e8c7bcfd66198ed2f6b32ed918 Mon Sep 17 00:00:00 2001 From: Nicolas Cellier Date: Fri, 8 Sep 2017 18:19:14 +0200 Subject: [PATCH 5/8] last clean-up --- pyswarm/pso.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pyswarm/pso.py b/pyswarm/pso.py index cf0ae10..97be118 100644 --- a/pyswarm/pso.py +++ b/pyswarm/pso.py @@ -125,7 +125,7 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, obj = partial(_obj_wrapper, func, args, kwargs) # Check for constraint function(s) - if f_ieqcons is None: + if not f_ieqcons: if not len(ieqcons): log.debug('No constraints given.') cons = _cons_none_wrapper @@ -242,8 +242,7 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, g = p_min.copy() fg = fp[i_min] - if debug: - log.info('Best after iteration {:}: {:} {:}'.format(it, g, fg)) + log.debug('Best after iteration {:}: {:} {:}'.format(it, g, fg)) it += 1 log.info('Stopping search: maximum ' @@ -254,5 +253,4 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, "find a feasible design. Sorry") if particle_output: return g, fg, p, fp - else: - return g, fg + return g, fg From e885ba63dfae77126d082c0bab2f7fe01abfe8e7 Mon Sep 17 00:00:00 2001 From: Nicolas Cellier Date: Fri, 8 Sep 2017 18:20:40 +0200 Subject: [PATCH 6/8] replace while by from --- pyswarm/pso.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyswarm/pso.py b/pyswarm/pso.py index 97be118..e98ce1b 100644 --- a/pyswarm/pso.py +++ b/pyswarm/pso.py @@ -185,8 +185,7 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, v = vlow + np.random.rand(S, D) * (vhigh - vlow) # Iterate until termination criterion met - it = 1 - while it <= maxiter: + for it in range(maxiter): rp = np.random.uniform(size=(S, D)) rg = np.random.uniform(size=(S, D)) @@ -243,7 +242,6 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, fg = fp[i_min] log.debug('Best after iteration {:}: {:} {:}'.format(it, g, fg)) - it += 1 log.info('Stopping search: maximum ' 'iterations reached --> {:}'.format(maxiter)) From 31c621324165a48c50215c54dd5f093209c3fb38 Mon Sep 17 00:00:00 2001 From: Nicolas Cellier Date: Fri, 8 Sep 2017 19:17:17 +0200 Subject: [PATCH 7/8] use a custom pool parameter. Allow other multiprocessing libraries to be used as multiprocess or dask-distributed --- pso_examples.py | 2 +- pyswarm/pso.py | 26 ++++++++++---------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/pso_examples.py b/pso_examples.py index c946788..14154b5 100644 --- a/pso_examples.py +++ b/pso_examples.py @@ -48,7 +48,7 @@ def weight(x, *args): H, d, t = x # all in inches B, rho, E, P = args return rho*2*np.pi*d*t*np.sqrt((B/2)**2 + H**2) - + def stress(x, *args): H, d, t = x # all in inches B, rho, E, P = args diff --git a/pyswarm/pso.py b/pyswarm/pso.py index e98ce1b..eb424de 100644 --- a/pyswarm/pso.py +++ b/pyswarm/pso.py @@ -2,6 +2,7 @@ # coding=utf8 import logging +import multiprocessing as mp from functools import partial import numpy as np @@ -35,6 +36,7 @@ def _cons_f_ieqcons_wrapper(f_ieqcons, args, kwargs, x): 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, + pool=mp.Pool, particle_output=False): """ Perform a particle swarm optimization (PSO) @@ -87,6 +89,7 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, processes : int The number of processes to use to evaluate objective function and constraints (default: 1) + pool : pool like object (default: multiprocessing.Pool) particle_output : boolean Whether to include the best per-particle position and the objective values at those. @@ -138,9 +141,10 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, is_feasible = partial(_is_feasible_wrapper, cons) # Initialize the multiprocessing module if necessary + map_function = map if processes > 1: - import multiprocessing - mp_pool = multiprocessing.Pool(processes) + pool = pool(processes) + map_function = pool.map # Initialize the particle swarm S = swarmsize @@ -158,13 +162,8 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, 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, :]) + fx = np.array(list(map_function(obj, x))) + fs = np.array(list(map_function(is_feasible, x))) # Store particle's best position (if constraints are satisfied) i_update = np.logical_and((fx < fp), fs) @@ -199,13 +198,8 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, 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, :]) + fx = np.array(list(map_function(obj, x))) + fs = np.array(list(map_function(is_feasible, x))) # Store particle's best position (if constraints are satisfied) i_update = np.logical_and((fx < fp), fs) From 73f78aab53d6d7c9999601b7dc6d6c26a89df6e2 Mon Sep 17 00:00:00 2001 From: Nicolas Cellier Date: Fri, 8 Sep 2017 19:30:16 +0200 Subject: [PATCH 8/8] fix implementation error --- pyswarm/pso.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyswarm/pso.py b/pyswarm/pso.py index eb424de..e79b87e 100644 --- a/pyswarm/pso.py +++ b/pyswarm/pso.py @@ -36,7 +36,7 @@ def _cons_f_ieqcons_wrapper(f_ieqcons, args, kwargs, x): 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, - pool=mp.Pool, + pool=None, particle_output=False): """ Perform a particle swarm optimization (PSO) @@ -143,7 +143,8 @@ def pso(func, lb, ub, ieqcons=[], f_ieqcons=None, args=(), kwargs={}, # Initialize the multiprocessing module if necessary map_function = map if processes > 1: - pool = pool(processes) + if not pool: + pool = mp.Pool(processes) map_function = pool.map # Initialize the particle swarm