Search code examples
pythonparticle-swarm

Python: PSO clumping <s>and code working only when mouse moves</s>


I'm trying to make a simple PSO (particle-swarm optimization) program.

Below is my current code, and I've been tweaking the weights only to find the "optimization" not working.

import random as ran
import math
# import threading as thr

import pygame as gm
# import pycuda as cuda
# import matplotlib.pyplot as plt

p_max = 10 # Maximum number of particles
rand_max = 100 # Maximum random vector value
# history = 10 # Number of iterations to record as history (including most current)
width = 600
height = 600

func_dict = {
            "sphere" : ( lambda x, y: ( (x-(width/2))**2 + (y-(height/2))**2 ) ),
            "booth" : ( lambda x, y: (((x-(width/2)) + 2*(y-(height/2)) - 7) ** 2) + ((2*(x-(width/2)) + (y-(height/2)) - 5) ** 2) ),
            "matyas" : ( lambda x, y: (0.26 * ((x-(width/2))**2 + (y-(height/2))**2) - 0.48*(x-(width/2))*(y-(height/2))) )
            } # (x-(width/2)) and (y-(height/2)) to shift the Zero to the display center
func = "sphere" # Choose function from func_dict

# Weights (0<w<1)
wLocal = 0.4 # Explore weight
wGlobal = 0.8 # Exploit weight
wRandom = 0.02 # Weight of random vector

global_best = [None, None, None] # Initial blank

class particle: # particles
    global global_best

    def __init__(self):
        global global_best
        global width, height
        global func_dict, func
        self.vel_x = 0
        self.vel_y = 0
        self.pos_x = ran.randint(0, width)
        self.pos_y = ran.randint(0, height)
        self.pos_z = func_dict[func](self.pos_x, self.pos_y)
        self.local_best = [self.pos_x, self.pos_y, self.pos_z]
        if (global_best[0] == None) or (global_best[1] == None) or (global_best[2] == None): # Is 1st particle
            global_best = self.local_best

    def update(self): # Update vectors
        global width, height
        global rand_max
        global wGlobal, wLocal, wRandom
        global global_best
        self.vel_x = (wGlobal * (global_best[0] - self.pos_x)) + (wLocal * (self.local_best[0] - self.pos_x)) + (wRandom * ran.randint(-rand_max, rand_max))
        self.vel_y = (wGlobal * (global_best[1] - self.pos_y)) + (wLocal * (self.local_best[1] - self.pos_y)) + (wRandom * ran.randint(-rand_max, rand_max))
        # self.pos_x = (self.pos_x + self.vel_x) % width
        # self.pos_y = (self.pos_y + self.vel_y) % height
        self.pos_x += self.vel_x
        self.pos_y += self.vel_y
        if self.pos_x < 0:
            self.pos_x = 0
        if self.pos_y < 0:
            self.pos_y = 0
        if self.pos_x > width:
            self.pos_x = width
        if self.pos_y > height:
            self.pos_y = height

        self.pos_z = func_dict[func](self.pos_x, self.pos_y)
        if self.pos_z < global_best[2]:
            global_best = [self.pos_x, self.pos_y, self.pos_z]

particles = [None for _ in range(p_max)]

def initialize():
    global_best = [None, None, None]
    for foo in range(p_max):
        particles[foo] = particle() # create new particles

# def dist(p1, p2): # distance
#     return(math.sqrt( ( (p1.pos_x - p2.pos_y)**2) + ((p1.pos_y - p2.pos_y)**2) ) )

# def update(this): # this = particle
#     this.vel_x = (wGlobal * (global_best[0] - this.pos_x)) + (wLocal * (this.local_best[0] - this.pos_x)) + (wRandom * ran.randint(0, rand_max))
#     this.vel_y = (wGlobal * (global_best[1] - this.pos_y)) + (wLocal * (this.local_best[1] - this.pos_y)) + (wRandom * ran.randint(0, rand_max))
#     # this.pos_x = (this.pos_x + this.vel_x) % width
#     # this.pos_y = (this.pos_y + this.vel_y) % height
#     this.pos_x += this.vel_x
#     this.pos_y += this.vel_y
#     if this.pos_x < 0:
#         this.pos_x = 0
#     if this.pos_y < 0:
#         this.pos_y = 0
#     if this.pos_x > width:
#         this.pos_x = width
#     if this.pos_y > height:
#         this.pos_y = height
#     # return this

# def update_multi(things): # things = list() of particles
#     these = things
#     for item in these:
#         item = update(item)
#     return these

gm.init()
main = gm.display.set_mode((width, height))
end_program = False
initialize()
main.fill((255, 255, 255))

while end_program == False:

    # main.fill((255, 255, 255)) #Comment/Uncomment to leave trace
    # plt.plot() # Plot functions

    for event in gm.event.get():
        if event.type == gm.QUIT:
            end_program = True

        for foo in range(len(particles)):
            particles[foo].update()

            gm.draw.circle(main, (0, 0, 0), (int(particles[foo].pos_x), int(particles[foo].pos_y)), 5, 0)

    gm.display.flip()

Problem 1: Program only runs when mouse is moving
I'm not sure why but the program only runs fairly quickly for a few iterations but seems to just stop afterwards, but continues when the mouse is moved.

Problem 2: Particles appear to stay local
As I move the mouse around, it kinda runs. What emerges is clumps of traces left when the 2nd # main.fill((255, 255, 255)) is uncommented. The 1st initial traces from before the program stops without the mouse's movement seem more spread out and I'm not sure if that's the global variables or the randoms at work.

Edit: I've fixed the problem where the program only runs when the mouse moves by unindenting:

    for foo in range(len(particles)):
        particles[foo].update()

        gm.draw.circle(main, (0, 0, 0), (int(particles[foo].pos_x), int(particles[foo].pos_y)), 5, 0)

However, the particles still seem to hardly move from their own positions, oscilating locally.


Solution

  • Problem 1 was solved thanks to Marius. I fixed it by simply putting update outside the event loop.

    Problem 2 was fixed by adding the local update that I forgot.

    if self.pos_z < global_best[2]: # This was not forgotten
                global_best = [self.pos_x, self.pos_y, self.pos_z]
    if self.pos_z < local_best[2]: # This was forgotten
                local_best = [self.pos_x, self.pos_y, self.pos_z]