Source code for pymoo.algorithms.soo.nonconvex.pso

"""Particle Swarm Optimization (PSO) with adaptive parameters."""

import numpy as np

from pymoo.algorithms.soo.nonconvex.ga import FitnessSurvival
from pymoo.core.algorithm import Algorithm
from pymoo.core.individual import Individual
from pymoo.core.initialization import Initialization
from pymoo.core.population import Population
from pymoo.core.repair import NoRepair
from pymoo.core.replacement import ImprovementReplacement
from pymoo.docs import parse_doc_string
from pymoo.operators.crossover.dex import repair_random_init
from pymoo.operators.mutation.pm import PM
from pymoo.operators.repair.bounds_repair import is_out_of_bounds_by_problem
from pymoo.operators.repair.to_bound import set_to_bounds_if_outside
from pymoo.operators.sampling.lhs import LHS
from pymoo.util.display.column import Column
from pymoo.util.display.single import SingleObjectiveOutput
from pymoo.util.misc import norm_eucl_dist
from pymoo.util import default_random_state


# =========================================================================================================
# Display
# =========================================================================================================


class PSOFuzzyOutput(SingleObjectiveOutput):
    def __init__(self):
        super().__init__()

        self.f = Column(name="f", width=8)
        self.S = Column(name="S", width=7)
        self.w = Column(name="w", width=7)
        self.c1 = Column(name="c1", width=8)
        self.c2 = Column(name="c2", width=8)

        self.columns += [self.f, self.S, self.w, self.c1, self.c2]

    def update(self, algorithm):
        super().update(algorithm)

        self.f.set(algorithm.f)
        self.S.set(algorithm.strategy)
        self.w.set(algorithm.w)
        self.c1.set(algorithm.c1)
        self.c2.set(algorithm.c2)


# =========================================================================================================
# Adaptation Constants
# =========================================================================================================


def S1_exploration(f):
    if f <= 0.4:
        return 0
    elif 0.4 < f <= 0.6:
        return 5 * f - 2
    elif 0.6 < f <= 0.7:
        return 1
    elif 0.7 < f <= 0.8:
        return -10 * f + 8
    elif 0.8 < f:
        return 0


def S2_exploitation(f):
    if f <= 0.2:
        return 0
    elif 0.2 < f <= 0.3:
        return 10 * f - 2
    elif 0.3 < f <= 0.4:
        return 1
    elif 0.4 < f <= 0.6:
        return -5 * f + 3
    elif 0.6 < f:
        return 0


def S3_convergence(f):
    if f <= 0.1:
        return 1
    elif 0.1 < f <= 0.3:
        return -5 * f + 1.5
    elif 0.3 < f:
        return 0


def S4_jumping_out(f):
    if f <= 0.7:
        return 0
    elif 0.7 < f <= 0.9:
        return 5 * f - 3.5
    elif 0.9 < f:
        return 1


# =========================================================================================================
# Equation
# =========================================================================================================


@default_random_state
def pso_equation(X, P_X, S_X, V, V_max, w, c1, c2, r1=None, r2=None, random_state=None):
    n_particles, n_var = X.shape

    if r1 is None:
        r1 = random_state.random((n_particles, n_var))

    if r2 is None:
        r2 = random_state.random((n_particles, n_var))

    inerta = w * V
    cognitive = c1 * r1 * (P_X - X)
    social = c2 * r2 * (S_X - X)

    # calculate the velocity vector
    Vp = inerta + cognitive + social
    Vp = set_to_bounds_if_outside(Vp, -V_max, V_max)

    Xp = X + Vp

    return Xp, Vp


# =========================================================================================================
# Implementation
# =========================================================================================================


[docs] class PSO(Algorithm): def __init__( self, pop_size=25, sampling=LHS(), w=0.9, c1=2.0, c2=2.0, adaptive=True, initial_velocity="random", max_velocity_rate=0.20, pertube_best=True, repair=NoRepair(), output=PSOFuzzyOutput(), **kwargs, ): """Particle Swarm Optimization algorithm. Args: pop_size: The size of the swarm being used. sampling: Sampling strategy. adaptive: Whether w, c1, and c2 are changed dynamically over time. w: Inertia weight for velocity update. c1: Cognitive impact (personal best) during velocity update. c2: Social impact (global best) during velocity update. initial_velocity: How to initialize particle velocities ('random' or 'zero'). max_velocity_rate: Maximum velocity rate normalized by problem bounds. pertube_best: Whether to mutate the global best solution. repair: Repair strategy for handling constraints. output: Output display strategy. **kwargs: Additional algorithm parameters. """ super().__init__(output=output, **kwargs) self.initialization = Initialization(sampling) self.pop_size = pop_size self.adaptive = adaptive self.pertube_best = pertube_best self.V_max = None self.initial_velocity = initial_velocity self.max_velocity_rate = max_velocity_rate self.repair = repair self.w = w self.c1 = c1 self.c2 = c2 self.particles = None self.sbest = None def _setup(self, problem, **kwargs): self.V_max = self.max_velocity_rate * (problem.xu - problem.xl) self.f, self.strategy = None, None def _initialize_infill(self): return self.initialization.do( self.problem, self.pop_size, algorithm=self, random_state=self.random_state ) def _initialize_advance(self, infills=None, **kwargs): particles = self.pop if self.initial_velocity == "random": init_V = ( self.random_state.random((len(particles), self.problem.n_var)) * self.V_max[None, :] ) elif self.initial_velocity == "zero": init_V = np.zeros((len(particles), self.problem.n_var)) else: raise Exception("Unknown velocity initialization.") particles.set("V", init_V) self.particles = particles super()._initialize_advance(infills=infills, **kwargs) def _infill(self): problem, particles, pbest = self.problem, self.particles, self.pop (X, V) = particles.get("X", "V") P_X = pbest.get("X") sbest = self._social_best() S_X = sbest.get("X") Xp, Vp = pso_equation( X, P_X, S_X, V, self.V_max, self.w, self.c1, self.c2, random_state=self.random_state, ) # if the problem has boundaries to be considered if problem.has_bounds(): for k in range(20): # find the individuals which are still infeasible m = is_out_of_bounds_by_problem(problem, Xp) if len(m) == 0: break # actually execute the differential equation Xp[m], Vp[m] = pso_equation( X[m], P_X[m], S_X[m], V[m], self.V_max, self.w, self.c1, self.c2, random_state=self.random_state, ) # if still infeasible do a random initialization Xp = repair_random_init( Xp, X, *problem.bounds(), random_state=self.random_state ) # create the offspring population off = Population.new(X=Xp, V=Vp) # try to improve the current best with a pertubation if self.pertube_best: k = FitnessSurvival().do(problem, pbest, n_survive=1, return_indices=True)[ 0 ] mut = PM( prob=0.9, eta=self.random_state.uniform(5, 30), at_least_once=False ) mutant = mut( problem, Population(Individual(X=pbest[k].X)), random_state=self.random_state, )[0] off[k].set("X", mutant.X) self.repair(problem, off) self.sbest = sbest return off def _advance(self, infills=None, **kwargs): assert infills is not None, ( "This algorithms uses the AskAndTell interface thus 'infills' must to be provided." ) # set the new population to be equal to the offsprings self.particles = infills # if an offspring has improved the personal store that index has_improved = ImprovementReplacement().do( self.problem, self.pop, infills, return_indices=True ) # set the personal best which have been improved self.pop[has_improved] = infills[has_improved] if self.adaptive: self._adapt() def _social_best(self): return Population([self.opt[0]] * len(self.pop)) def _adapt(self): pop = self.pop X, F = pop.get("X", "F") sbest = self.sbest ( w, c1, c2, ) = self.w, self.c1, self.c2 # get the average distance from one to another for normalization D = norm_eucl_dist(self.problem, X, X) mD = D.sum(axis=1) / (len(pop) - 1) _min, _max = mD.min(), mD.max() # get the average distance to the best g_D = norm_eucl_dist(self.problem, sbest.get("X"), X).mean() f = (g_D - _min) / (_max - _min + 1e-32) S = np.array( [ S1_exploration(f), S2_exploitation(f), S3_convergence(f), S4_jumping_out(f), ] ) strategy = S.argmax() + 1 delta = 0.05 + (self.random_state.random() * 0.05) if strategy == 1: c1 += delta c2 -= delta elif strategy == 2: c1 += 0.5 * delta c2 -= 0.5 * delta elif strategy == 3: c1 += 0.5 * delta c2 += 0.5 * delta elif strategy == 4: c1 -= delta c2 += delta c1 = max(1.5, min(2.5, c1)) c2 = max(1.5, min(2.5, c2)) if c1 + c2 > 4.0: c1 = 4.0 * (c1 / (c1 + c2)) c2 = 4.0 * (c2 / (c1 + c2)) w = 1 / (1 + 1.5 * np.exp(-2.6 * f)) self.f = f self.strategy = strategy self.c1 = c1 self.c2 = c2 self.w = w
parse_doc_string(PSO.__init__)