NiaOrg / NiaPy

Python microframework for building nature-inspired algorithms. Official docs: https://niapy.org
https://niapy.org
MIT License
254 stars 77 forks source link

Squirrel search algorithm implementation #338

Open firefly-cpp opened 3 years ago

firefly-cpp commented 3 years ago

Squirrel search algorithm publication is currently the most cited article in SWEVO.

It would be nice to have this algorithm in our collection.

Has anyone time to implement this algorithm?

altaregos commented 2 years ago

can you assign me on this?

firefly-cpp commented 2 years ago

@altaregos, thanks. Yeah. Of course.

zStupan commented 2 years ago

I've made an attempt:

import numpy as np
from niapy.algorithms import Algorithm
from niapy.util.random import levy_flight
from niapy.task import Task

class SquirrelSearchAlgorithm(Algorithm):
    Name = ['SquirrelSearchAlgorithm', 'SSA']

    def __init__(self, population_size=50, food_sources=4, prob_predation=0.1, gliding_constant=1.9, scale=18, *args, **kwargs):
        super().__init__(population_size, *args, **kwargs)
        self.food_sources = food_sources
        self.prob_predation = prob_predation
        self.gliding_constant = gliding_constant
        self.scale = scale

    def set_parameters(self, population_size=50, food_sources=4, prob_predation=0.1, gliding_constant=1.9, scale=18, *args, **kwargs):
        super().set_parameters(population_size, *args, **kwargs)
        self.food_sources = food_sources
        self.prob_predation = prob_predation
        self.gliding_constant = gliding_constant
        self.scale = scale

    def get_parameters(self):
        params = super().get_parameters()
        params.update({
            'food_sources': self.food_sources,
            'prob_predation': self.prob_predation,
            'gliding_constant': self.gliding_constant,
            'scale': self.scale,
        })
        return params

    def gliding_distance(self):
        lift = 0.9783723933835806 / self.uniform(0.675, 1.5)
        drag = 1.630620655639301
        return 8.0 / (self.scale * drag / lift)

    def run_iteration(self, task, population, population_fitness, best_x, best_fitness, **params):
        indices = np.argsort(population_fitness)
        ht = indices[0]
        at = indices[1:self.food_sources]
        nt = indices[self.food_sources:]

        new_population = population.copy()

        for index in at:
            if self.random() >= self.prob_predation:
                new_population[index] += self.gliding_distance() * self.gliding_constant * (population[ht] - population[index])
                new_population[index] = task.repair(new_population[index], rng=self.rng)
            else:
                new_population[index] = self.uniform(task.lower, task.upper)

        nt = self.rng.permutation(nt)
        nt_1 = nt[:len(nt) // 2] # half go to acorn trees
        nt_2 = nt[len(nt) // 2:] # other half go to hickory trees

        for index in nt_1:
            if self.random() >= self.prob_predation:
                a = self.rng.choice(at)
                new_population[index] += self.gliding_distance() * self.gliding_constant * (population[a] - population[index])
                new_population[index] = task.repair(new_population[index], rng=self.rng)
            else:
                new_population[index] = self.uniform(task.lower, task.upper)

        for index in nt_2:
            if self.random() >= self.prob_predation:
                new_population[index] += self.gliding_distance() * self.gliding_constant * (population[ht] - population[index])
                new_population[index] = task.repair(new_population[index], rng=self.rng)
            else:
                new_population[index] = self.uniform(task.lower, task.upper)

        s_min = 1e-5 / (365 ** ((task.iters + 1) / (task.max_iters / 2.5)))
        sc = np.sqrt(np.sum((new_population[at] - new_population[ht]) ** 2))

        if sc < s_min:
            new_population[nt_1] = task.lower + levy_flight(size=(len(nt_1), task.dimension), rng=self.rng) * task.range
            new_population[nt_1] = task.repair(new_population[nt_1], rng=self.rng)

        new_fitness = np.apply_along_axis(task.eval, 1, new_population)
        best_x, best_fitness = self.get_best(new_population, new_fitness, best_x, best_fitness)

        return new_population, new_fitness, best_x, best_fitness, {}

if __name__ == '__main__':
    fit = []
    for i in range(30):
        task = Task('sphere', dimension=30, lower=-10, upper=10, max_iters=500)
        algo = SquirrelSearchAlgorithm()
        _, best_fitness = algo.run(task)
        fit.append(best_fitness)

    print(f'Best: {np.min(fit)}')
    print(f'Worst: {np.max(fit)}')
    print(f'Mean: {np.mean(fit)}')
    print(f'Std: {np.std(fit)}')

But I don't get the same results as the ones in the article. For this example (30 runs, 30-D sphere on [-10, 10], max_iters=500) i get:

Best: 7.5371836448343156e-06
Worst: 8.828262744205666e-05
Mean: 4.013667587147927e-05
Std: 2.289286326787123e-05

and in the article they got:

Best: 7.9225e-20
Worst: 5.7411e-07
Mean: 4.1689E-08
Std: 1.4356E-07

The article is extremely unclear, in my opinion. At least it was to me. It doesn't say how to choose which squirrels on normal trees go towards acorn trees and which towards the hickory tree, it just says "do it randomly". I just chose half randomly. Also, I don't know if I'm calculating the season constant correctly, because the equation is formatted weirdly and there's no real explanation of it. And then if the seasons change, I don't know which squirrels to move via levy flights.

Any ideas what I'm doing wrong? Otherwise I'd say it's best just not to include it.

BrandonDuncan13 commented 3 months ago

@zStupan, I'm confused by what you mean. Aren't the SSA results going to be different each time you run it? I haven't looked into this much, but just putting ideas out there

zStupan commented 3 months ago

@BrandonDuncan13 yes the results are different each time, but I would expect that the average fitness over 30 runs would be of similar magnitude as the results in the paper.

BrandonDuncan13 commented 3 months ago

@zStupan Ohh, okay, I see what you're talking about now. Yeah I agree the average fitness should be a similar magnitude. I will spend time trying to improve your code within the next few weeks but I don't have much confidence in improving it haha

BrandonDuncan13 commented 3 months ago

@Nehazzi If you're looking to apply this optimization algorithm to a coding project, I would either look at the MatLab code that exists for SSA or look at how other optimization algorithms have been applied to coding projects. Then you can figure out how to apply the SSA code above to your coding project. Idk what you're asking but I think that's what you were looking for