diff --git a/Project.toml b/Project.toml index e4ad775..a35f758 100644 --- a/Project.toml +++ b/Project.toml @@ -1,4 +1,4 @@ -name = "FTN_PSI_PA" +name = "MetaheuristicAlgos" uuid = "0afddabe-dfaf-4480-bf01-29bfefedff8e" version = "0.1.2" authors = ["Nikola-Mircic"] diff --git a/README.md b/README.md index 2afa183..372b1de 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,17 @@ -# ftn_psi_pa -Sample codes for Applied algorithms course on Faculty of Technical Sciences, University of Novi Sad +# Metaheuristic algorithms + +### What are these algorithms? +Metaheuristic algorithms are optimization algorithms that are used to address complicated issues that cannot be solved using standard approaches. + +These algorithms are inspired by natural processes such as **genetics, swarm behavior, and evolution**, and they are used to explore a broad search space to identify the global optimum of a problem. + + +### Examples of some metaheuristic algorithms implemented in Julia: + +- [Genetic algorithm](/src/genetic_algorithm) +- [Particle swarm optimization](/src/pso) + +
+ +TO-DO: +- Ant colony optimization diff --git a/src/FTN_PSI_PA.jl b/src/MetaheuristicAlgos.jl similarity index 64% rename from src/FTN_PSI_PA.jl rename to src/MetaheuristicAlgos.jl index 65e4838..02fdef3 100644 --- a/src/FTN_PSI_PA.jl +++ b/src/MetaheuristicAlgos.jl @@ -1,7 +1,9 @@ -module FTN_PSI_PA +module MetaheuristicAlgos import Printf +export GeneticAlgorithm + include("genetic_algorithm/genetic_algorithm.jl") include("pso/pso.jl") diff --git a/src/genetic_algorithm/genetic_algorithm.jl b/src/genetic_algorithm/genetic_algorithm.jl index 5fc0b84..06a47b4 100644 --- a/src/genetic_algorithm/genetic_algorithm.jl +++ b/src/genetic_algorithm/genetic_algorithm.jl @@ -1,9 +1,27 @@ module GeneticAlgorithm -include("crossover.jl") +include("selection.jl") -function geneticAlgorithm(population::Vector{Entity}, elitePercent::Float64, mutationPercent::Float64, crossoverFunc!::Function, iter::Int) +export generatePopulation +export fitFunction + +export makeCrossoverFunc + +export elitistSelection +export rouletteWheelSelection + +export geneticAlgorithm + +export getAverage +export analyzeElitePercentage +export analyzeMutationPercentage +export analyzeNumberOfIteration +export analyzePopulationSize + +export printResult + +function geneticAlgorithm(population::Vector{Entity}, selectAndCross::Function, mutationPercent::Float64, crossoverFunc!::Function, iter::Int) bestFitnes = [] updatePopulationFitness!(population, fitFunction) @@ -11,20 +29,10 @@ function geneticAlgorithm(population::Vector{Entity}, elitePercent::Float64, mut push!(bestFitnes, population[1].fitness) while !shouldStop(iter, bestFitnes) - n = length(population) - - eliteNumber = Int(trunc(elitePercent*n)); - - eliteNumber = eliteNumber + (n-eliteNumber)%2 - - elite = deepcopy(population[1:eliteNumber]) - - population = crossover!(population[eliteNumber+1:end], crossoverFunc!) + population = selectAndCross(population, crossoverFunc!) mutatePopulation!(population, mutationPercent) - population = [population; elite] - updatePopulationFitness!(population, fitFunction) push!(bestFitnes, population[1].fitness) @@ -49,16 +57,16 @@ end # ANALYZATION -function getAverage(population::Vector{Entity}, elitePercent::Float64, mutationPercent::Float64, crossoverFunc!::Function, iter::Int) +function getAverage(population::Vector{Entity}, selectAndCross::Function, mutationPercent::Float64, crossoverFunc!::Function, iter::Int) avg_gen = 0 avg_best = 0 for i in 1:100 gen_i, best_i = geneticAlgorithm(population, - elitePercent, - mutationPercent, - crossoverFunc!, - numOfIterations) + selectAndCross, + mutationPercent, + crossoverFunc!, + iter) avg_gen += gen_i avg_best += best_i.fitness end @@ -66,7 +74,7 @@ function getAverage(population::Vector{Entity}, elitePercent::Float64, mutationP return (avg_gen/100, avg_best/100) end -function analyzeElitePercentage(values::Array{Float64}) +function analyzeElitePercentage(values::Array{Function}) results = zeros(length(values), 2) for i in 1:length(values) @@ -92,7 +100,7 @@ function analyzeMutationPercentage(values::Array{Float64}) population_i = generatePopulation(populationSize, genesLength, minGene, maxGene) gen_i, best_i = getAverage(population_i, - elitePercent, + elitistSelection(elitePercent), values[i], makeCrossoverFunc([1;3]), numOfIterations) @@ -111,7 +119,7 @@ function analyzePopulationSize(values::Vector{Int}) population_i = generatePopulation(values[i], genesLength, minGene, maxGene) gen_i, best_i = getAverage(population_i, - elitePercent, + elitistSelection(elitePercent), mutationPercent, makeCrossoverFunc([1;3]), numOfIterations) @@ -130,7 +138,7 @@ function analyzeNumberOfIteration(values::Vector{Int}) population_i = generatePopulation(populationSize, genesLength, minGene, maxGene) gen_i, best_i = getAverage(population_i, - elitePercent, + elitistSelection(elitePercent), mutationPercent, makeCrossoverFunc([1;3]), values[i]) @@ -150,13 +158,4 @@ function printResult(values, results) end end -export geneticAlgorithm -export getAverage -export analyzeElitePercentage -export analyzeMutationPercentage -export analyzeNumberOfIteration -export analyzePopulationSize - -export printResult - end \ No newline at end of file diff --git a/src/genetic_algorithm/selection.jl b/src/genetic_algorithm/selection.jl new file mode 100644 index 0000000..55e45b0 --- /dev/null +++ b/src/genetic_algorithm/selection.jl @@ -0,0 +1,65 @@ +include("crossover.jl") + +function elitistSelection(elitePercent::Float64) + function selectionMethod(population::Vector{Entity}, crossFunc!::Function) + n = length(population) + + eliteNumber = Int(trunc(elitePercent*n)); + + eliteNumber = eliteNumber + (n-eliteNumber)%2 + + elite = deepcopy(population[1:eliteNumber]) + + population = crossover!(population[eliteNumber+1:end], crossFunc!) + + population = [population; elite] + + return population + end +end + +function findIdx(arr, x) + l::Int = 1; + r::Int = length(arr) + + + s = l + div(r-l,2) + idx = s + while l <= r + if x <= arr[s] + idx = s + r = s-1 + else + l = s+1 + end + + s = l + div(r-l,2) + end + + return idx +end + +function rouletteWheelSelection() + function selectionMethod(population::Vector{Entity}, crossFunc!::Function) + n = length(population) + + px = population .|> fitFunction + + totalFit = sum(px) + + px = px .|> (x)->x/totalFit + + for i=2:length(px) + px[i] = px[i] + px[i-1] + end + + for i=1:div(n,2) + idx1 = findIdx(px, rand()) + idx2 = findIdx(px, rand()) + + crossFunc!(population[idx1], population[idx2]) + end + + return population + end +end \ No newline at end of file diff --git a/test/genetic_algorithm.jl b/test/genetic_algorithm.jl new file mode 100644 index 0000000..8b0d383 --- /dev/null +++ b/test/genetic_algorithm.jl @@ -0,0 +1,41 @@ +using .GeneticAlgorithm + +@testset "GeneticAlgorithm" begin + #= + | Find four numbers that add up to 143. + | + | x1 + x2 + x3 + x4 = 143 + | x1=?, x2=?, x3=?, x4=? + =# + + populationSize = 100 + genesLength = 4 + minGene = 0 + maxGene = 143 + + elitePercent = 0.15 + mutationPercent = 0.2 + numOfIterations = 100 + + population = generatePopulation(populationSize, genesLength, minGene, maxGene) + + @testset "Elitistic Selection: " begin + num_gen, best = getAverage(population, + elitistSelection(elitePercent), + mutationPercent, + makeCrossoverFunc([1;3]), + numOfIterations) + + @test best < 0.1 # Test if the absolute error is smaller than 0.1 + end + + @testset "Roulette Wheel Selection: " begin + num_gen, best = getAverage(population, + rouletteWheelSelection(), + mutationPercent, + makeCrossoverFunc([1;3]), + numOfIterations) + + @test best < 0.1 # Test if the absolute error is smaller than 0.1 + end +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 5d89c25..3c55526 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,6 @@ +using MetaheuristicAlgos using Test @testset "Tests" begin - a = 5 - @test a == 5 + include("genetic_algorithm.jl") end \ No newline at end of file