From 85d682234e0b69149d74f94dabd6b47eca6252d0 Mon Sep 17 00:00:00 2001 From: Jesus Mejia Date: Thu, 16 May 2024 09:52:48 -0600 Subject: [PATCH 01/13] update file names --- src/Metaheuristics.jl | 51 +------ .../{ => combinatorial}/BRKGA/BRKGA.jl | 0 src/algorithms/combinatorial/GRASP/grasp.jl | 144 ++++++++++++++++++ .../combinatorial/LocalSearch/local_search.jl | 50 ++++++ .../LocalSearch/neighbourhood.jl | 66 ++++++++ src/algorithms/combinatorial/VNS/vnd.jl | 84 ++++++++++ src/algorithms/combinatorial/VNS/vns.jl | 76 +++++++++ src/algorithms/combinatorial/combinatorial.jl | 1 + .../{ => multiobjective}/CCMO/CCMO.jl | 0 .../{ => multiobjective}/MOEAD_DE/MOEAD_DE.jl | 0 .../MOEAD_DE/weights_and_ideal.jl | 0 .../{ => multiobjective}/NSGA2/NSGA2.jl | 0 .../NSGA2/crowding-distance.jl | 0 .../{ => multiobjective}/NSGA3/NSGA3.jl | 0 .../{ => multiobjective}/SMS_EMOA/SMS_EMOA.jl | 0 .../{ => multiobjective}/SMS_EMOA/calc-hv.jl | 0 .../SMS_EMOA/update-population.jl | 0 .../{ => multiobjective}/SPEA2/SPEA2.jl | 0 .../multiobjective/multiobjective.jl | 8 + .../{ => singleobjective}/ABC/ABC.jl | 0 .../{ => singleobjective}/ABC/bee_dynamics.jl | 0 .../{ => singleobjective}/CGSA/CGSA.jl | 0 .../{ => singleobjective}/CGSA/chaos.jl | 0 .../{ => singleobjective}/CGSA/physics.jl | 0 src/algorithms/{ => singleobjective}/DE/DE.jl | 0 .../{ => singleobjective}/DE/epsilonDE.jl | 0 .../{ => singleobjective}/ECA/CECA.jl | 0 .../{ => singleobjective}/ECA/ECA.jl | 0 .../ECA/adaptive_parameters.jl | 0 .../ECA/center_of_mass.jl | 0 src/algorithms/{ => singleobjective}/GA/GA.jl | 0 .../{ => singleobjective}/MCCGA/MCCGA.jl | 0 .../{ => singleobjective}/MCCGA/utils.jl | 0 .../{ => singleobjective}/PSO/PSO.jl | 0 .../{ => singleobjective}/PSO/velocity.jl | 0 .../{ => singleobjective}/Restart/Restart.jl | 0 src/algorithms/{ => singleobjective}/SA/SA.jl | 0 .../{ => singleobjective}/SA/new_solution.jl | 0 .../{ => singleobjective}/WOA/WOA.jl | 0 .../singleobjective/singleobjective.jl | 35 +++++ 40 files changed, 467 insertions(+), 48 deletions(-) rename src/algorithms/{ => combinatorial}/BRKGA/BRKGA.jl (100%) create mode 100644 src/algorithms/combinatorial/GRASP/grasp.jl create mode 100644 src/algorithms/combinatorial/LocalSearch/local_search.jl create mode 100644 src/algorithms/combinatorial/LocalSearch/neighbourhood.jl create mode 100644 src/algorithms/combinatorial/VNS/vnd.jl create mode 100644 src/algorithms/combinatorial/VNS/vns.jl create mode 100644 src/algorithms/combinatorial/combinatorial.jl rename src/algorithms/{ => multiobjective}/CCMO/CCMO.jl (100%) rename src/algorithms/{ => multiobjective}/MOEAD_DE/MOEAD_DE.jl (100%) rename src/algorithms/{ => multiobjective}/MOEAD_DE/weights_and_ideal.jl (100%) rename src/algorithms/{ => multiobjective}/NSGA2/NSGA2.jl (100%) rename src/algorithms/{ => multiobjective}/NSGA2/crowding-distance.jl (100%) rename src/algorithms/{ => multiobjective}/NSGA3/NSGA3.jl (100%) rename src/algorithms/{ => multiobjective}/SMS_EMOA/SMS_EMOA.jl (100%) rename src/algorithms/{ => multiobjective}/SMS_EMOA/calc-hv.jl (100%) rename src/algorithms/{ => multiobjective}/SMS_EMOA/update-population.jl (100%) rename src/algorithms/{ => multiobjective}/SPEA2/SPEA2.jl (100%) create mode 100644 src/algorithms/multiobjective/multiobjective.jl rename src/algorithms/{ => singleobjective}/ABC/ABC.jl (100%) rename src/algorithms/{ => singleobjective}/ABC/bee_dynamics.jl (100%) rename src/algorithms/{ => singleobjective}/CGSA/CGSA.jl (100%) rename src/algorithms/{ => singleobjective}/CGSA/chaos.jl (100%) rename src/algorithms/{ => singleobjective}/CGSA/physics.jl (100%) rename src/algorithms/{ => singleobjective}/DE/DE.jl (100%) rename src/algorithms/{ => singleobjective}/DE/epsilonDE.jl (100%) rename src/algorithms/{ => singleobjective}/ECA/CECA.jl (100%) rename src/algorithms/{ => singleobjective}/ECA/ECA.jl (100%) rename src/algorithms/{ => singleobjective}/ECA/adaptive_parameters.jl (100%) rename src/algorithms/{ => singleobjective}/ECA/center_of_mass.jl (100%) rename src/algorithms/{ => singleobjective}/GA/GA.jl (100%) rename src/algorithms/{ => singleobjective}/MCCGA/MCCGA.jl (100%) rename src/algorithms/{ => singleobjective}/MCCGA/utils.jl (100%) rename src/algorithms/{ => singleobjective}/PSO/PSO.jl (100%) rename src/algorithms/{ => singleobjective}/PSO/velocity.jl (100%) rename src/algorithms/{ => singleobjective}/Restart/Restart.jl (100%) rename src/algorithms/{ => singleobjective}/SA/SA.jl (100%) rename src/algorithms/{ => singleobjective}/SA/new_solution.jl (100%) rename src/algorithms/{ => singleobjective}/WOA/WOA.jl (100%) create mode 100644 src/algorithms/singleobjective/singleobjective.jl diff --git a/src/Metaheuristics.jl b/src/Metaheuristics.jl index be2991f7..99208ee5 100644 --- a/src/Metaheuristics.jl +++ b/src/Metaheuristics.jl @@ -76,57 +76,12 @@ include("optimize/optimize.jl") # template file include("algorithms/template.jl") -include("algorithms/GA/GA.jl") - -# ECA algorithm -include("algorithms/ECA/ECA.jl") -include("algorithms/ECA/CECA.jl") - -# Differential algorithm -include("algorithms/DE/DE.jl") - -# PSO algorithm -include("algorithms/PSO/PSO.jl") - -# The whale optimization algorithm -# S Mirjalili, A Lewis - Advances in Engineering Software, 2016 -include("algorithms/WOA/WOA.jl") - -# Mirjalili, Seyedali, and Amir H. Gandomi. -# "Chaotic gravitational constants for the gravitational search algorithm." -# Applied Soft Computing 53 (2017): 407-419. -include("algorithms/CGSA/CGSA.jl") - -# SA: Simulated Annealing -# Kirkpatrick, S., Gelatt, C.D., & Vecchi, M.P. (1983). Optimization by -# Simulated Annealing. _Science, 220_, 671-680. -include("algorithms/SA/SA.jl") - - -# Aritifical Bee colony -include("algorithms/ABC/ABC.jl") - -# MOEA decomposition based using Differential Evolution -include("algorithms/MOEAD_DE/MOEAD_DE.jl") - -# Non-dominate sorting Genetic Algorithm -include("algorithms/NSGA2/NSGA2.jl") -include("algorithms/NSGA3/NSGA3.jl") -include("algorithms/SMS_EMOA/SMS_EMOA.jl") -include("algorithms/SPEA2/SPEA2.jl") - -include("algorithms/CCMO/CCMO.jl") -include("algorithms/Restart/Restart.jl") - - -# genetic algorithm -include("algorithms/MCCGA/MCCGA.jl") -include("algorithms/BRKGA/BRKGA.jl") +include("algorithms/singleobjective/singleobjective.jl") +include("algorithms/multiobjective/multiobjective.jl") +include("algorithms/combinatorial/combinatorial.jl") include("algorithms/stop_criteria.jl") - include("DecisionMaking/DecisionMaking.jl") - include("precompile/precompile.jl") ####################################################### diff --git a/src/algorithms/BRKGA/BRKGA.jl b/src/algorithms/combinatorial/BRKGA/BRKGA.jl similarity index 100% rename from src/algorithms/BRKGA/BRKGA.jl rename to src/algorithms/combinatorial/BRKGA/BRKGA.jl diff --git a/src/algorithms/combinatorial/GRASP/grasp.jl b/src/algorithms/combinatorial/GRASP/grasp.jl new file mode 100644 index 00000000..21df5382 --- /dev/null +++ b/src/algorithms/combinatorial/GRASP/grasp.jl @@ -0,0 +1,144 @@ +abstract type AbstractGRASP <: MH.AbstractParameters end + +struct GRASP{I, T, L} <: AbstractGRASP + initial::I + constructor::T + local_search::L +end + +Base.@kwdef struct GreedyRandomizedContructor + candidates + instance = nothing + α::Float64 = 0.4 + rng = MH.default_rng_mh() +end + + +function construct(constructor) + @error "Define your own randomized greedy constructor" + status.stop = true + nothing +end + +function local_search(x, localsearch, problem) + @error "Define your own local search" + nothing +end + + +function compute_cost(candidates, constructor, instance) + @warn "Define compute_cost for\nconstructor=$constructor\ninstance=$instance" + zeros(length(candidates)) +end + + +function construct(constructor::GreedyRandomizedContructor) + candidates = constructor.candidates |> copy + α = constructor.α + # create empty solution S + S = empty(candidates) + # construct solution + while !isempty(candidates) + cost = compute_cost(candidates, constructor, constructor.instance) + cmin = minimum(cost) + cmax = maximum(cost) + # compute restricted candidate list + RCL = [i for i in eachindex(candidates) if cost[i] <= cmin + α*(cmax - cmin) ] + # select candidate at random and insert into solution + s = rand(constructor.rng, RCL) + push!(S, candidates[s]) + # update list of candidates + deleteat!(candidates, s) + end + S +end + +function GRASP(;initial=nothing, constructor=nothing, local_search=nothing, + options = MH.Options(), information=MH.Information()) + # TODO + if isnothing(constructor) || isnothing(local_search) + error("Provide a constructor and a local search") + end + grasp = GRASP(initial, constructor, local_search) + MH.Algorithm(grasp; options, information) +end + +MH.iscompatible(::MH.BitArraySpace, ::AbstractGRASP) = true +MH.iscompatible(::MH.PermutationSpace, ::AbstractGRASP) = true + +function MH.initialize!(status, parameters::AbstractGRASP, problem, information, options, args...; kargs...) + + if isnothing(parameters.initial) + x0 = rand(options.rng, problem.search_space) + else + x0 = parameters.initial + end + # set default budget + options.f_calls_limit = Inf + if options.iterations <= 0 + options.iterations = 500 + end + + + sol = MH.create_solution(x0, problem) + # TODO + MH.State(sol, [sol]) +end + +function MH.update_state!( + status, + parameters::AbstractGRASP, + problem, + information, + options, + args...; + kargs... + ) + # heuristic construction + x = construct(parameters.constructor) + # perform local search and evaluate solutions + x_improved = local_search(x, parameters.local_search, problem) + + # since local search can return a solution without evaluation + # it is necessary to evaluate objective function + if x_improved isa AbstractVector + # evaluate solution + sol = MH.create_solution(x_improved, problem) + elseif x_improved isa MH.AbstractSolution + sol = x_improved + else + # seems that local search returned something different to a vector + return + end + + # save best solutions + if MH.is_better(sol, status.best_sol) + status.best_sol = sol + end + + # update history (for convergence checking) + #= TODO + push!(status.population, sol) + max_history = 10 + if length(status.population) > 2max_history + offprings = status.population[max_history:end] + deleteat!(status.population, max_history:length(status.population)) + MH.environmental_selection!(status.population, offprings, MH.ElitistReplacement()) + end + =# + +end + +function MH.final_stage!( + status, + parameters::AbstractGRASP, + problem, + information, + options, + args...; + kargs... + ) + # TODO + nothing +end + diff --git a/src/algorithms/combinatorial/LocalSearch/local_search.jl b/src/algorithms/combinatorial/LocalSearch/local_search.jl new file mode 100644 index 00000000..a5f45427 --- /dev/null +++ b/src/algorithms/combinatorial/LocalSearch/local_search.jl @@ -0,0 +1,50 @@ +abstract type AbstractLocalSearch end +struct BestImprovingSearch <: AbstractLocalSearch end +struct FirstImprovingSearch <: AbstractLocalSearch end + +include("neighbourhood.jl") + +function local_search(x, neighbourhood::Neighbourhood, ls::AbstractLocalSearch, problem) + # creates an iterator and perform local search over the iterator + iter = NeighborhoodIterator(x, neighbourhood) + local_search(x, iter, ls, problem) +end + +function local_search(x, neighbourhood::InternalNeighbourhood, ::BestImprovingSearch, problem) + best = MH.create_solution(copy(x), problem) + for xnew in neighbourhood + sol = MH.create_solution(xnew, problem) + if MH.is_better(sol, best) + best = deepcopy(sol) + end + end + best +end + +function local_search(x, neighbourhood::InternalNeighbourhood, ::FirstImprovingSearch, problem) + initial = MH.create_solution(copy(x), problem) + for xnew in neighbourhood + sol = MH.create_solution(xnew, problem) + if MH.is_better(sol, initial) + return deepcopy(sol) + end + end + initial +end + +function local_search(x, ls::AbstractLocalSearch, problem) + if problem.search_space isa MH.PermutationSpace + neighbourhood = TwoOpt(;x) + elseif problem.search_space isa MH.BoxConstrained + neighbourhood = MH.GridSampler(problem.search_space) + elseif problem.search_space isa MH.BitArraySpace + # TODO + neighbourhood = nothing + else + @error "Definition of local_search for $(problem.search_space) is required." + return + end + local_search(x, neighbourhood, ls, problem) +end + + diff --git a/src/algorithms/combinatorial/LocalSearch/neighbourhood.jl b/src/algorithms/combinatorial/LocalSearch/neighbourhood.jl new file mode 100644 index 00000000..2646bb17 --- /dev/null +++ b/src/algorithms/combinatorial/LocalSearch/neighbourhood.jl @@ -0,0 +1,66 @@ +abstract type Neighbourhood end +abstract type InternalNeighbourhood <: Neighbourhood end + +struct NeighborhoodIterator{X, N} <: InternalNeighbourhood + x::X + neighborhood::N +end + +Base.@kwdef struct TwoOpt + x::Vector{Int} + k::Int = 2 +end + +function Base.iterate(two_opt::TwoOpt, state=1) + i, permutation, k = state, two_opt.x, two_opt.k-1 + if !(1 <= i <= length(permutation)-k && 1 <= k <= length(permutation)) + return nothing + end + reverse!(view(permutation, i:i+k)) + permutation, state+1 +end + + +function neighborhood_structure(x, s::Neighbourhood, i) + # The i-th neighbour in the k-th neighborhood around x + n = nameof(typeof(s)) + + println(""" + Define your neighborhood as follows: + + ` + function neighborhood_structure(x, s::$n, i) + # ... + end + ` + """ + ) +end + +function Base.iterate(iter::NeighborhoodIterator, state=(copy(iter.x), 1)) + # reuse same x from state + x, i = state + x .= iter.x + v = neighborhood_structure(x, iter.neighborhood, i) + if isnothing(v) + return v + end + # return to current solution + + v, (x, i+1) +end + + +#= +function local_search(x, neighborhood::TwoOpt, problem) + # FIXME: overwriting best.x by every xnew + best = MH.create_solution(x, problem) + for xnew in neighborhood + sol = MH.create_solution(xnew, problem) + if isnothing(best) || MH.is_better(sol, best) + best = sol + end + end + best +end +=# diff --git a/src/algorithms/combinatorial/VNS/vnd.jl b/src/algorithms/combinatorial/VNS/vnd.jl new file mode 100644 index 00000000..dbaa7d7b --- /dev/null +++ b/src/algorithms/combinatorial/VNS/vnd.jl @@ -0,0 +1,84 @@ +abstract type AbstractVNS <: MH.AbstractParameters end + + +Base.@kwdef struct VND{I, N, L} <: AbstractVNS + initial::I + neighborhood::N # neighborhood structures + local_search::L # local search strategy +end + +function VND(;initial = nothing, neighborhood = nothing, local_search = nothing, + options=MH.Options(), information=MH.Information()) + + parameters = VND(initial, neighborhood, local_search) + + MH.Algorithm(parameters; options, information) +end + + +MH.iscompatible(::MH.BitArraySpace, ::AbstractVNS) = true +MH.iscompatible(::MH.PermutationSpace, ::AbstractVNS) = true + +function MH.initialize!(status, parameters::AbstractVNS, problem, information, options, args...; kargs...) + + if isnothing(parameters.initial) + x0 = rand(options.rng, problem.search_space) + else + x0 = parameters.initial + end + # set default budget + options.f_calls_limit = Inf + if options.iterations <= 0 + options.iterations = 500 + end + + sol = MH.create_solution(x0, problem) + # TODO + MH.State(sol, [sol]) +end + +function MH.update_state!( + status, + parameters::VND, + problem, + information, + options, + args...; + kargs... + ) + + improvement = false + l = 1 + + # check if movement is required + while l <= length(parameters.neighborhood) + # current solution + x = MH.minimizer(status) + # exploration of the neighborhood + neighborhood = parameters.neighborhood[l] + # local search around x + sol = local_search(x, neighborhood, parameters.local_search, problem) + + # check for empty neighborhood + if isnothing(sol) + l += 1 + continue + end + + # move or not + if MH.is_better(sol, status.best_sol) + status.best_sol = sol + l = 1 + improvement = true + else + l += 1 + end + end + + # stop if no improvement is obtained + status.stop = !improvement +end + + +function MH.final_stage!(status, parameters::AbstractVNS, problem, information, options, args...; kargs...) +end diff --git a/src/algorithms/combinatorial/VNS/vns.jl b/src/algorithms/combinatorial/VNS/vns.jl new file mode 100644 index 00000000..ea24040b --- /dev/null +++ b/src/algorithms/combinatorial/VNS/vns.jl @@ -0,0 +1,76 @@ +include("vnd.jl") + + +Base.@kwdef struct VNS{I, S, L, C, N} <: AbstractVNS + initial::I + neighborhood_shaking::S + neighborhood_local::L + local_search::C + neighborhood_change::N +end + +# change neighborhood +struct SequentialChange end +struct CyclicChange end + +function VNS(;initial=nothing,neighborhood_shaking=nothing, neighborhood_local=nothing, + local_search=nothing, neighborhood_change=SequentialChange(), + options=MH.Options(), information=MH.Information()) + + parameters = VNS(initial, neighborhood_shaking, neighborhood_local, + local_search, neighborhood_change) + + MH.Algorithm(parameters; options, information) +end + +function shake(x, neighborhood, rng) + # select at random + neighborhood_structure(x, neighborhood, rng) +end + +function neighborhood_change(old, new, k, ::SequentialChange) + if MH.is_better(new, old) + return 1, new + end + k + 1, old +end + +function neighborhood_change(old, new, k, ::CyclicChange) + k += 1 + if MH.is_better(new, old) + return k, new + end + k, old +end + +function MH.update_state!(status, parameters::VNS, problem, information, options, args...; kargs...) + # current solution + sol = first(status.population) + k = 1 + while k <= length(parameters.neighborhood_shaking) + x = MH.get_position(sol) + neighborhood = parameters.neighborhood_shaking[k] + # select x at random from kth neighborhood + xp = shake(x, neighborhood, options.rng) + + # perform local search around xp using VND + # TODO: update this for considering other VNS variants (for the local search) + vnd = VND(;initial=xp, neighborhood=parameters.neighborhood_local, + local_search = FirstImprovingSearch()) + _res_local = MH.optimize(problem.f, problem.search_space, vnd) + sol_new = _res_local.best_sol + + # neighborhood change or not? + k, sol = neighborhood_change(sol, sol_new, k, parameters.neighborhood_change) + + # save best result so far (internal use only, VNS doesn't use it) + if MH.is_better(sol_new, status.best_sol) + status.best_sol = sol_new + end + problem.f_calls += _res_local.f_calls + + end + # save sol (x) for the next iteration of VNS + status.population = [sol] +end + diff --git a/src/algorithms/combinatorial/combinatorial.jl b/src/algorithms/combinatorial/combinatorial.jl new file mode 100644 index 00000000..46d3641a --- /dev/null +++ b/src/algorithms/combinatorial/combinatorial.jl @@ -0,0 +1 @@ +include("BRKGA/BRKGA.jl") diff --git a/src/algorithms/CCMO/CCMO.jl b/src/algorithms/multiobjective/CCMO/CCMO.jl similarity index 100% rename from src/algorithms/CCMO/CCMO.jl rename to src/algorithms/multiobjective/CCMO/CCMO.jl diff --git a/src/algorithms/MOEAD_DE/MOEAD_DE.jl b/src/algorithms/multiobjective/MOEAD_DE/MOEAD_DE.jl similarity index 100% rename from src/algorithms/MOEAD_DE/MOEAD_DE.jl rename to src/algorithms/multiobjective/MOEAD_DE/MOEAD_DE.jl diff --git a/src/algorithms/MOEAD_DE/weights_and_ideal.jl b/src/algorithms/multiobjective/MOEAD_DE/weights_and_ideal.jl similarity index 100% rename from src/algorithms/MOEAD_DE/weights_and_ideal.jl rename to src/algorithms/multiobjective/MOEAD_DE/weights_and_ideal.jl diff --git a/src/algorithms/NSGA2/NSGA2.jl b/src/algorithms/multiobjective/NSGA2/NSGA2.jl similarity index 100% rename from src/algorithms/NSGA2/NSGA2.jl rename to src/algorithms/multiobjective/NSGA2/NSGA2.jl diff --git a/src/algorithms/NSGA2/crowding-distance.jl b/src/algorithms/multiobjective/NSGA2/crowding-distance.jl similarity index 100% rename from src/algorithms/NSGA2/crowding-distance.jl rename to src/algorithms/multiobjective/NSGA2/crowding-distance.jl diff --git a/src/algorithms/NSGA3/NSGA3.jl b/src/algorithms/multiobjective/NSGA3/NSGA3.jl similarity index 100% rename from src/algorithms/NSGA3/NSGA3.jl rename to src/algorithms/multiobjective/NSGA3/NSGA3.jl diff --git a/src/algorithms/SMS_EMOA/SMS_EMOA.jl b/src/algorithms/multiobjective/SMS_EMOA/SMS_EMOA.jl similarity index 100% rename from src/algorithms/SMS_EMOA/SMS_EMOA.jl rename to src/algorithms/multiobjective/SMS_EMOA/SMS_EMOA.jl diff --git a/src/algorithms/SMS_EMOA/calc-hv.jl b/src/algorithms/multiobjective/SMS_EMOA/calc-hv.jl similarity index 100% rename from src/algorithms/SMS_EMOA/calc-hv.jl rename to src/algorithms/multiobjective/SMS_EMOA/calc-hv.jl diff --git a/src/algorithms/SMS_EMOA/update-population.jl b/src/algorithms/multiobjective/SMS_EMOA/update-population.jl similarity index 100% rename from src/algorithms/SMS_EMOA/update-population.jl rename to src/algorithms/multiobjective/SMS_EMOA/update-population.jl diff --git a/src/algorithms/SPEA2/SPEA2.jl b/src/algorithms/multiobjective/SPEA2/SPEA2.jl similarity index 100% rename from src/algorithms/SPEA2/SPEA2.jl rename to src/algorithms/multiobjective/SPEA2/SPEA2.jl diff --git a/src/algorithms/multiobjective/multiobjective.jl b/src/algorithms/multiobjective/multiobjective.jl new file mode 100644 index 00000000..65512bd3 --- /dev/null +++ b/src/algorithms/multiobjective/multiobjective.jl @@ -0,0 +1,8 @@ +# MOEA decomposition based using Differential Evolution +include("MOEAD_DE/MOEAD_DE.jl") +# Non-dominate sorting Genetic Algorithm +include("NSGA2/NSGA2.jl") +include("NSGA3/NSGA3.jl") +include("SMS_EMOA/SMS_EMOA.jl") +include("SPEA2/SPEA2.jl") +include("CCMO/CCMO.jl") diff --git a/src/algorithms/ABC/ABC.jl b/src/algorithms/singleobjective/ABC/ABC.jl similarity index 100% rename from src/algorithms/ABC/ABC.jl rename to src/algorithms/singleobjective/ABC/ABC.jl diff --git a/src/algorithms/ABC/bee_dynamics.jl b/src/algorithms/singleobjective/ABC/bee_dynamics.jl similarity index 100% rename from src/algorithms/ABC/bee_dynamics.jl rename to src/algorithms/singleobjective/ABC/bee_dynamics.jl diff --git a/src/algorithms/CGSA/CGSA.jl b/src/algorithms/singleobjective/CGSA/CGSA.jl similarity index 100% rename from src/algorithms/CGSA/CGSA.jl rename to src/algorithms/singleobjective/CGSA/CGSA.jl diff --git a/src/algorithms/CGSA/chaos.jl b/src/algorithms/singleobjective/CGSA/chaos.jl similarity index 100% rename from src/algorithms/CGSA/chaos.jl rename to src/algorithms/singleobjective/CGSA/chaos.jl diff --git a/src/algorithms/CGSA/physics.jl b/src/algorithms/singleobjective/CGSA/physics.jl similarity index 100% rename from src/algorithms/CGSA/physics.jl rename to src/algorithms/singleobjective/CGSA/physics.jl diff --git a/src/algorithms/DE/DE.jl b/src/algorithms/singleobjective/DE/DE.jl similarity index 100% rename from src/algorithms/DE/DE.jl rename to src/algorithms/singleobjective/DE/DE.jl diff --git a/src/algorithms/DE/epsilonDE.jl b/src/algorithms/singleobjective/DE/epsilonDE.jl similarity index 100% rename from src/algorithms/DE/epsilonDE.jl rename to src/algorithms/singleobjective/DE/epsilonDE.jl diff --git a/src/algorithms/ECA/CECA.jl b/src/algorithms/singleobjective/ECA/CECA.jl similarity index 100% rename from src/algorithms/ECA/CECA.jl rename to src/algorithms/singleobjective/ECA/CECA.jl diff --git a/src/algorithms/ECA/ECA.jl b/src/algorithms/singleobjective/ECA/ECA.jl similarity index 100% rename from src/algorithms/ECA/ECA.jl rename to src/algorithms/singleobjective/ECA/ECA.jl diff --git a/src/algorithms/ECA/adaptive_parameters.jl b/src/algorithms/singleobjective/ECA/adaptive_parameters.jl similarity index 100% rename from src/algorithms/ECA/adaptive_parameters.jl rename to src/algorithms/singleobjective/ECA/adaptive_parameters.jl diff --git a/src/algorithms/ECA/center_of_mass.jl b/src/algorithms/singleobjective/ECA/center_of_mass.jl similarity index 100% rename from src/algorithms/ECA/center_of_mass.jl rename to src/algorithms/singleobjective/ECA/center_of_mass.jl diff --git a/src/algorithms/GA/GA.jl b/src/algorithms/singleobjective/GA/GA.jl similarity index 100% rename from src/algorithms/GA/GA.jl rename to src/algorithms/singleobjective/GA/GA.jl diff --git a/src/algorithms/MCCGA/MCCGA.jl b/src/algorithms/singleobjective/MCCGA/MCCGA.jl similarity index 100% rename from src/algorithms/MCCGA/MCCGA.jl rename to src/algorithms/singleobjective/MCCGA/MCCGA.jl diff --git a/src/algorithms/MCCGA/utils.jl b/src/algorithms/singleobjective/MCCGA/utils.jl similarity index 100% rename from src/algorithms/MCCGA/utils.jl rename to src/algorithms/singleobjective/MCCGA/utils.jl diff --git a/src/algorithms/PSO/PSO.jl b/src/algorithms/singleobjective/PSO/PSO.jl similarity index 100% rename from src/algorithms/PSO/PSO.jl rename to src/algorithms/singleobjective/PSO/PSO.jl diff --git a/src/algorithms/PSO/velocity.jl b/src/algorithms/singleobjective/PSO/velocity.jl similarity index 100% rename from src/algorithms/PSO/velocity.jl rename to src/algorithms/singleobjective/PSO/velocity.jl diff --git a/src/algorithms/Restart/Restart.jl b/src/algorithms/singleobjective/Restart/Restart.jl similarity index 100% rename from src/algorithms/Restart/Restart.jl rename to src/algorithms/singleobjective/Restart/Restart.jl diff --git a/src/algorithms/SA/SA.jl b/src/algorithms/singleobjective/SA/SA.jl similarity index 100% rename from src/algorithms/SA/SA.jl rename to src/algorithms/singleobjective/SA/SA.jl diff --git a/src/algorithms/SA/new_solution.jl b/src/algorithms/singleobjective/SA/new_solution.jl similarity index 100% rename from src/algorithms/SA/new_solution.jl rename to src/algorithms/singleobjective/SA/new_solution.jl diff --git a/src/algorithms/WOA/WOA.jl b/src/algorithms/singleobjective/WOA/WOA.jl similarity index 100% rename from src/algorithms/WOA/WOA.jl rename to src/algorithms/singleobjective/WOA/WOA.jl diff --git a/src/algorithms/singleobjective/singleobjective.jl b/src/algorithms/singleobjective/singleobjective.jl new file mode 100644 index 00000000..47a81a36 --- /dev/null +++ b/src/algorithms/singleobjective/singleobjective.jl @@ -0,0 +1,35 @@ +include("GA/GA.jl") + +# ECA algorithm +include("ECA/ECA.jl") +include("ECA/CECA.jl") + +# Differential algorithm +include("DE/DE.jl") + +# PSO algorithm +include("PSO/PSO.jl") + +# The whale optimization algorithm +# S Mirjalili, A Lewis - Advances in Engineering Software, 2016 +include("WOA/WOA.jl") + +# Mirjalili, Seyedali, and Amir H. Gandomi. +# "Chaotic gravitational constants for the gravitational search algorithm." +# Applied Soft Computing 53 (2017): 407-419. +include("CGSA/CGSA.jl") + +# SA: Simulated Annealing +# Kirkpatrick, S., Gelatt, C.D., & Vecchi, M.P. (1983). Optimization by +# Simulated Annealing. _Science, 220_, 671-680. +include("SA/SA.jl") + + +# Aritifical Bee colony +include("ABC/ABC.jl") + + +# genetic algorithm +include("MCCGA/MCCGA.jl") + +include("Restart/Restart.jl") From fed12bed868c1f9298ab305980acf2406a83a2c0 Mon Sep 17 00:00:00 2001 From: Jesus Mejia Date: Thu, 16 May 2024 12:38:15 -0600 Subject: [PATCH 02/13] fix issue on neighborhood framework --- .../GRASP/{grasp.jl => GRASP.jl} | 61 ++++++++++--------- .../{local_search.jl => LocalSearchUtils.jl} | 22 +++---- .../LocalSearch/neighbourhood.jl | 35 +++++------ .../combinatorial/VNS/{vnd.jl => VND.jl} | 24 ++++---- .../combinatorial/VNS/{vns.jl => VNS.jl} | 19 +++--- src/algorithms/combinatorial/combinatorial.jl | 5 ++ 6 files changed, 87 insertions(+), 79 deletions(-) rename src/algorithms/combinatorial/GRASP/{grasp.jl => GRASP.jl} (68%) rename src/algorithms/combinatorial/LocalSearch/{local_search.jl => LocalSearchUtils.jl} (67%) rename src/algorithms/combinatorial/VNS/{vnd.jl => VND.jl} (68%) rename src/algorithms/combinatorial/VNS/{vns.jl => VNS.jl} (80%) diff --git a/src/algorithms/combinatorial/GRASP/grasp.jl b/src/algorithms/combinatorial/GRASP/GRASP.jl similarity index 68% rename from src/algorithms/combinatorial/GRASP/grasp.jl rename to src/algorithms/combinatorial/GRASP/GRASP.jl index 21df5382..cf3dc7d6 100644 --- a/src/algorithms/combinatorial/GRASP/grasp.jl +++ b/src/algorithms/combinatorial/GRASP/GRASP.jl @@ -1,4 +1,4 @@ -abstract type AbstractGRASP <: MH.AbstractParameters end +abstract type AbstractGRASP <: AbstractParameters end struct GRASP{I, T, L} <: AbstractGRASP initial::I @@ -9,8 +9,8 @@ end Base.@kwdef struct GreedyRandomizedContructor candidates instance = nothing - α::Float64 = 0.4 - rng = MH.default_rng_mh() + α::Float64 = 0.6 + rng = default_rng_mh() end @@ -44,6 +44,11 @@ function construct(constructor::GreedyRandomizedContructor) cmax = maximum(cost) # compute restricted candidate list RCL = [i for i in eachindex(candidates) if cost[i] <= cmin + α*(cmax - cmin) ] + if isempty(RCL) + @error "RCL is empty. Try increasing α or check your `compute_cost` method." + return + end + # select candidate at random and insert into solution s = rand(constructor.rng, RCL) push!(S, candidates[s]) @@ -54,19 +59,19 @@ function construct(constructor::GreedyRandomizedContructor) end function GRASP(;initial=nothing, constructor=nothing, local_search=nothing, - options = MH.Options(), information=MH.Information()) + options = Options(), information=Information()) # TODO if isnothing(constructor) || isnothing(local_search) error("Provide a constructor and a local search") end grasp = GRASP(initial, constructor, local_search) - MH.Algorithm(grasp; options, information) + Algorithm(grasp; options, information) end -MH.iscompatible(::MH.BitArraySpace, ::AbstractGRASP) = true -MH.iscompatible(::MH.PermutationSpace, ::AbstractGRASP) = true +iscompatible(::BitArraySpace, ::AbstractGRASP) = true +iscompatible(::PermutationSpace, ::AbstractGRASP) = true -function MH.initialize!(status, parameters::AbstractGRASP, problem, information, options, args...; kargs...) +function initialize!(status, parameters::AbstractGRASP, problem, information, options, args...; kargs...) if isnothing(parameters.initial) x0 = rand(options.rng, problem.search_space) @@ -79,13 +84,11 @@ function MH.initialize!(status, parameters::AbstractGRASP, problem, information, options.iterations = 500 end - - sol = MH.create_solution(x0, problem) - # TODO - MH.State(sol, [sol]) + sol = create_solution(x0, problem) + State(sol, [sol]) end -function MH.update_state!( +function update_state!( status, parameters::AbstractGRASP, problem, @@ -96,6 +99,16 @@ function MH.update_state!( ) # heuristic construction x = construct(parameters.constructor) + if isnothing(x) || isempty(x) + status.stop = true + options.debug && @error """ + Constructor is returning empty solutions. Early stopping optimization. + """ + return + elseif options.debug + @info "Solution generated by `constructor`:\n$x\nPerforming local search..." + end + # perform local search and evaluate solutions x_improved = local_search(x, parameters.local_search, problem) @@ -103,33 +116,23 @@ function MH.update_state!( # it is necessary to evaluate objective function if x_improved isa AbstractVector # evaluate solution - sol = MH.create_solution(x_improved, problem) - elseif x_improved isa MH.AbstractSolution + sol = create_solution(x_improved, problem) + elseif x_improved isa AbstractSolution sol = x_improved else # seems that local search returned something different to a vector return end + options.debug && @info "Local search returned:\n$(get_position(sol))" # save best solutions - if MH.is_better(sol, status.best_sol) + if is_better(sol, status.best_sol) status.best_sol = sol + options.debug && @info "A better solution was found." end - - # update history (for convergence checking) - #= TODO - push!(status.population, sol) - max_history = 10 - if length(status.population) > 2max_history - offprings = status.population[max_history:end] - deleteat!(status.population, max_history:length(status.population)) - MH.environmental_selection!(status.population, offprings, MH.ElitistReplacement()) - end - =# - end -function MH.final_stage!( +function final_stage!( status, parameters::AbstractGRASP, problem, diff --git a/src/algorithms/combinatorial/LocalSearch/local_search.jl b/src/algorithms/combinatorial/LocalSearch/LocalSearchUtils.jl similarity index 67% rename from src/algorithms/combinatorial/LocalSearch/local_search.jl rename to src/algorithms/combinatorial/LocalSearch/LocalSearchUtils.jl index a5f45427..973b90bb 100644 --- a/src/algorithms/combinatorial/LocalSearch/local_search.jl +++ b/src/algorithms/combinatorial/LocalSearch/LocalSearchUtils.jl @@ -11,10 +11,10 @@ function local_search(x, neighbourhood::Neighbourhood, ls::AbstractLocalSearch, end function local_search(x, neighbourhood::InternalNeighbourhood, ::BestImprovingSearch, problem) - best = MH.create_solution(copy(x), problem) + best = create_solution(copy(x), problem) for xnew in neighbourhood - sol = MH.create_solution(xnew, problem) - if MH.is_better(sol, best) + sol = create_solution(xnew, problem) + if is_better(sol, best) best = deepcopy(sol) end end @@ -22,10 +22,10 @@ function local_search(x, neighbourhood::InternalNeighbourhood, ::BestImprovingSe end function local_search(x, neighbourhood::InternalNeighbourhood, ::FirstImprovingSearch, problem) - initial = MH.create_solution(copy(x), problem) + initial = create_solution(copy(x), problem) for xnew in neighbourhood - sol = MH.create_solution(xnew, problem) - if MH.is_better(sol, initial) + sol = create_solution(xnew, problem) + if is_better(sol, initial) return deepcopy(sol) end end @@ -33,11 +33,11 @@ function local_search(x, neighbourhood::InternalNeighbourhood, ::FirstImprovingS end function local_search(x, ls::AbstractLocalSearch, problem) - if problem.search_space isa MH.PermutationSpace - neighbourhood = TwoOpt(;x) - elseif problem.search_space isa MH.BoxConstrained - neighbourhood = MH.GridSampler(problem.search_space) - elseif problem.search_space isa MH.BitArraySpace + if problem.search_space isa PermutationSpace + neighbourhood = TwoOptNeighbourhood() + elseif problem.search_space isa BoxConstrained + neighbourhood = GridSampler(problem.search_space) + elseif problem.search_space isa BitArraySpace # TODO neighbourhood = nothing else diff --git a/src/algorithms/combinatorial/LocalSearch/neighbourhood.jl b/src/algorithms/combinatorial/LocalSearch/neighbourhood.jl index 2646bb17..11ac692a 100644 --- a/src/algorithms/combinatorial/LocalSearch/neighbourhood.jl +++ b/src/algorithms/combinatorial/LocalSearch/neighbourhood.jl @@ -6,11 +6,11 @@ struct NeighborhoodIterator{X, N} <: InternalNeighbourhood neighborhood::N end -Base.@kwdef struct TwoOpt - x::Vector{Int} +Base.@kwdef struct TwoOptNeighbourhood <: Neighbourhood k::Int = 2 end +#= function Base.iterate(two_opt::TwoOpt, state=1) i, permutation, k = state, two_opt.x, two_opt.k-1 if !(1 <= i <= length(permutation)-k && 1 <= k <= length(permutation)) @@ -19,6 +19,7 @@ function Base.iterate(two_opt::TwoOpt, state=1) reverse!(view(permutation, i:i+k)) permutation, state+1 end +=# function neighborhood_structure(x, s::Neighbourhood, i) @@ -37,6 +38,21 @@ function neighborhood_structure(x, s::Neighbourhood, i) ) end + +function neighborhood_structure(permutation, s::TwoOptNeighbourhood, i) + k = s.k-1 + if i isa Integer + if !(1 <= i <= length(permutation)-k && 1 <= k <= length(permutation)) + return nothing + end + else + i = rand(i, 1:length(permutation)-k) + end + reverse!(view(permutation, i:i+k)) + permutation +end + + function Base.iterate(iter::NeighborhoodIterator, state=(copy(iter.x), 1)) # reuse same x from state x, i = state @@ -46,21 +62,6 @@ function Base.iterate(iter::NeighborhoodIterator, state=(copy(iter.x), 1)) return v end # return to current solution - v, (x, i+1) end - -#= -function local_search(x, neighborhood::TwoOpt, problem) - # FIXME: overwriting best.x by every xnew - best = MH.create_solution(x, problem) - for xnew in neighborhood - sol = MH.create_solution(xnew, problem) - if isnothing(best) || MH.is_better(sol, best) - best = sol - end - end - best -end -=# diff --git a/src/algorithms/combinatorial/VNS/vnd.jl b/src/algorithms/combinatorial/VNS/VND.jl similarity index 68% rename from src/algorithms/combinatorial/VNS/vnd.jl rename to src/algorithms/combinatorial/VNS/VND.jl index dbaa7d7b..26d55bc6 100644 --- a/src/algorithms/combinatorial/VNS/vnd.jl +++ b/src/algorithms/combinatorial/VNS/VND.jl @@ -1,4 +1,4 @@ -abstract type AbstractVNS <: MH.AbstractParameters end +abstract type AbstractVNS <: AbstractParameters end Base.@kwdef struct VND{I, N, L} <: AbstractVNS @@ -8,18 +8,18 @@ Base.@kwdef struct VND{I, N, L} <: AbstractVNS end function VND(;initial = nothing, neighborhood = nothing, local_search = nothing, - options=MH.Options(), information=MH.Information()) + options=Options(), information=Information()) parameters = VND(initial, neighborhood, local_search) - MH.Algorithm(parameters; options, information) + Algorithm(parameters; options, information) end -MH.iscompatible(::MH.BitArraySpace, ::AbstractVNS) = true -MH.iscompatible(::MH.PermutationSpace, ::AbstractVNS) = true +iscompatible(::BitArraySpace, ::AbstractVNS) = true +iscompatible(::PermutationSpace, ::AbstractVNS) = true -function MH.initialize!(status, parameters::AbstractVNS, problem, information, options, args...; kargs...) +function initialize!(status, parameters::AbstractVNS, problem, information, options, args...; kargs...) if isnothing(parameters.initial) x0 = rand(options.rng, problem.search_space) @@ -32,12 +32,12 @@ function MH.initialize!(status, parameters::AbstractVNS, problem, information, o options.iterations = 500 end - sol = MH.create_solution(x0, problem) + sol = create_solution(x0, problem) # TODO - MH.State(sol, [sol]) + State(sol, [sol]) end -function MH.update_state!( +function update_state!( status, parameters::VND, problem, @@ -53,7 +53,7 @@ function MH.update_state!( # check if movement is required while l <= length(parameters.neighborhood) # current solution - x = MH.minimizer(status) + x = minimizer(status) # exploration of the neighborhood neighborhood = parameters.neighborhood[l] # local search around x @@ -66,7 +66,7 @@ function MH.update_state!( end # move or not - if MH.is_better(sol, status.best_sol) + if is_better(sol, status.best_sol) status.best_sol = sol l = 1 improvement = true @@ -80,5 +80,5 @@ function MH.update_state!( end -function MH.final_stage!(status, parameters::AbstractVNS, problem, information, options, args...; kargs...) +function final_stage!(status, parameters::AbstractVNS, problem, information, options, args...; kargs...) end diff --git a/src/algorithms/combinatorial/VNS/vns.jl b/src/algorithms/combinatorial/VNS/VNS.jl similarity index 80% rename from src/algorithms/combinatorial/VNS/vns.jl rename to src/algorithms/combinatorial/VNS/VNS.jl index ea24040b..b6b387bb 100644 --- a/src/algorithms/combinatorial/VNS/vns.jl +++ b/src/algorithms/combinatorial/VNS/VNS.jl @@ -1,5 +1,4 @@ -include("vnd.jl") - +include("VND.jl") Base.@kwdef struct VNS{I, S, L, C, N} <: AbstractVNS initial::I @@ -15,12 +14,12 @@ struct CyclicChange end function VNS(;initial=nothing,neighborhood_shaking=nothing, neighborhood_local=nothing, local_search=nothing, neighborhood_change=SequentialChange(), - options=MH.Options(), information=MH.Information()) + options=Options(), information=Information()) parameters = VNS(initial, neighborhood_shaking, neighborhood_local, local_search, neighborhood_change) - MH.Algorithm(parameters; options, information) + Algorithm(parameters; options, information) end function shake(x, neighborhood, rng) @@ -29,7 +28,7 @@ function shake(x, neighborhood, rng) end function neighborhood_change(old, new, k, ::SequentialChange) - if MH.is_better(new, old) + if is_better(new, old) return 1, new end k + 1, old @@ -37,18 +36,18 @@ end function neighborhood_change(old, new, k, ::CyclicChange) k += 1 - if MH.is_better(new, old) + if is_better(new, old) return k, new end k, old end -function MH.update_state!(status, parameters::VNS, problem, information, options, args...; kargs...) +function update_state!(status, parameters::VNS, problem, information, options, args...; kargs...) # current solution sol = first(status.population) k = 1 while k <= length(parameters.neighborhood_shaking) - x = MH.get_position(sol) + x = get_position(sol) neighborhood = parameters.neighborhood_shaking[k] # select x at random from kth neighborhood xp = shake(x, neighborhood, options.rng) @@ -57,14 +56,14 @@ function MH.update_state!(status, parameters::VNS, problem, information, options # TODO: update this for considering other VNS variants (for the local search) vnd = VND(;initial=xp, neighborhood=parameters.neighborhood_local, local_search = FirstImprovingSearch()) - _res_local = MH.optimize(problem.f, problem.search_space, vnd) + _res_local = optimize(problem.f, problem.search_space, vnd) sol_new = _res_local.best_sol # neighborhood change or not? k, sol = neighborhood_change(sol, sol_new, k, parameters.neighborhood_change) # save best result so far (internal use only, VNS doesn't use it) - if MH.is_better(sol_new, status.best_sol) + if is_better(sol_new, status.best_sol) status.best_sol = sol_new end problem.f_calls += _res_local.f_calls diff --git a/src/algorithms/combinatorial/combinatorial.jl b/src/algorithms/combinatorial/combinatorial.jl index 46d3641a..aa0dfecd 100644 --- a/src/algorithms/combinatorial/combinatorial.jl +++ b/src/algorithms/combinatorial/combinatorial.jl @@ -1 +1,6 @@ include("BRKGA/BRKGA.jl") + +# classical algorithms +include("LocalSearch/LocalSearchUtils.jl") +include("GRASP/GRASP.jl") +#include("VNS/VNS.jl") From d5e63492f4e0ec265f5517f92e024fb647c7c348 Mon Sep 17 00:00:00 2001 From: Jesus Mejia Date: Thu, 16 May 2024 12:38:31 -0600 Subject: [PATCH 03/13] minor update --- src/optimize/utils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/optimize/utils.jl b/src/optimize/utils.jl index c11c7b6e..cd685076 100644 --- a/src/optimize/utils.jl +++ b/src/optimize/utils.jl @@ -79,7 +79,7 @@ end function show_status(status, parameters, options) !options.debug && (return show_status_oneline(status, parameters, options)) status.final_time = time() - msg = "Current Status of " * string(typeof(parameters)) + msg = "Current Status of " * string(nameof(typeof(parameters))) @info msg display(status) end From a980c6c5c71fd9113f9348e5434356e24749cdb8 Mon Sep 17 00:00:00 2001 From: Jesus Mejia Date: Thu, 16 May 2024 12:52:20 -0600 Subject: [PATCH 04/13] rename to Neighborhood --- .../combinatorial/LocalSearch/LocalSearchUtils.jl | 10 +++++----- .../{neighbourhood.jl => neighborhood.jl} | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) rename src/algorithms/combinatorial/LocalSearch/{neighbourhood.jl => neighborhood.jl} (78%) diff --git a/src/algorithms/combinatorial/LocalSearch/LocalSearchUtils.jl b/src/algorithms/combinatorial/LocalSearch/LocalSearchUtils.jl index 973b90bb..d01610e8 100644 --- a/src/algorithms/combinatorial/LocalSearch/LocalSearchUtils.jl +++ b/src/algorithms/combinatorial/LocalSearch/LocalSearchUtils.jl @@ -2,15 +2,15 @@ abstract type AbstractLocalSearch end struct BestImprovingSearch <: AbstractLocalSearch end struct FirstImprovingSearch <: AbstractLocalSearch end -include("neighbourhood.jl") +include("neighborhood.jl") -function local_search(x, neighbourhood::Neighbourhood, ls::AbstractLocalSearch, problem) +function local_search(x, neighbourhood::Neighborhood, ls::AbstractLocalSearch, problem) # creates an iterator and perform local search over the iterator iter = NeighborhoodIterator(x, neighbourhood) local_search(x, iter, ls, problem) end -function local_search(x, neighbourhood::InternalNeighbourhood, ::BestImprovingSearch, problem) +function local_search(x, neighbourhood::InternalNeighborhood, ::BestImprovingSearch, problem) best = create_solution(copy(x), problem) for xnew in neighbourhood sol = create_solution(xnew, problem) @@ -21,7 +21,7 @@ function local_search(x, neighbourhood::InternalNeighbourhood, ::BestImprovingSe best end -function local_search(x, neighbourhood::InternalNeighbourhood, ::FirstImprovingSearch, problem) +function local_search(x, neighbourhood::InternalNeighborhood, ::FirstImprovingSearch, problem) initial = create_solution(copy(x), problem) for xnew in neighbourhood sol = create_solution(xnew, problem) @@ -34,7 +34,7 @@ end function local_search(x, ls::AbstractLocalSearch, problem) if problem.search_space isa PermutationSpace - neighbourhood = TwoOptNeighbourhood() + neighbourhood = TwoOptNeighborhood() elseif problem.search_space isa BoxConstrained neighbourhood = GridSampler(problem.search_space) elseif problem.search_space isa BitArraySpace diff --git a/src/algorithms/combinatorial/LocalSearch/neighbourhood.jl b/src/algorithms/combinatorial/LocalSearch/neighborhood.jl similarity index 78% rename from src/algorithms/combinatorial/LocalSearch/neighbourhood.jl rename to src/algorithms/combinatorial/LocalSearch/neighborhood.jl index 11ac692a..639e5b32 100644 --- a/src/algorithms/combinatorial/LocalSearch/neighbourhood.jl +++ b/src/algorithms/combinatorial/LocalSearch/neighborhood.jl @@ -1,12 +1,12 @@ -abstract type Neighbourhood end -abstract type InternalNeighbourhood <: Neighbourhood end +abstract type Neighborhood end +abstract type InternalNeighborhood <: Neighborhood end -struct NeighborhoodIterator{X, N} <: InternalNeighbourhood +struct NeighborhoodIterator{X, N} <: InternalNeighborhood x::X neighborhood::N end -Base.@kwdef struct TwoOptNeighbourhood <: Neighbourhood +Base.@kwdef struct TwoOptNeighborhood <: Neighborhood k::Int = 2 end @@ -22,7 +22,7 @@ end =# -function neighborhood_structure(x, s::Neighbourhood, i) +function neighborhood_structure(x, s::Neighborhood, i) # The i-th neighbour in the k-th neighborhood around x n = nameof(typeof(s)) @@ -39,7 +39,7 @@ function neighborhood_structure(x, s::Neighbourhood, i) end -function neighborhood_structure(permutation, s::TwoOptNeighbourhood, i) +function neighborhood_structure(permutation, s::TwoOptNeighborhood, i) k = s.k-1 if i isa Integer if !(1 <= i <= length(permutation)-k && 1 <= k <= length(permutation)) From 6fd505a138be7b71234f4e934c9896e13a5c2aed Mon Sep 17 00:00:00 2001 From: Jesus Mejia Date: Thu, 16 May 2024 12:52:45 -0600 Subject: [PATCH 05/13] minor update --- .../combinatorial/LocalSearch/neighborhood.jl | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/algorithms/combinatorial/LocalSearch/neighborhood.jl b/src/algorithms/combinatorial/LocalSearch/neighborhood.jl index 639e5b32..3ac9ee2b 100644 --- a/src/algorithms/combinatorial/LocalSearch/neighborhood.jl +++ b/src/algorithms/combinatorial/LocalSearch/neighborhood.jl @@ -10,18 +10,6 @@ Base.@kwdef struct TwoOptNeighborhood <: Neighborhood k::Int = 2 end -#= -function Base.iterate(two_opt::TwoOpt, state=1) - i, permutation, k = state, two_opt.x, two_opt.k-1 - if !(1 <= i <= length(permutation)-k && 1 <= k <= length(permutation)) - return nothing - end - reverse!(view(permutation, i:i+k)) - permutation, state+1 -end -=# - - function neighborhood_structure(x, s::Neighborhood, i) # The i-th neighbour in the k-th neighborhood around x n = nameof(typeof(s)) From 3b8156b55481781aa024e603f63b0789e33e5794 Mon Sep 17 00:00:00 2001 From: Jesus Mejia Date: Thu, 16 May 2024 14:10:29 -0600 Subject: [PATCH 06/13] minor update --- src/algorithms/combinatorial/LocalSearch/neighborhood.jl | 6 ++---- src/algorithms/combinatorial/VNS/VND.jl | 2 +- src/algorithms/combinatorial/VNS/VNS.jl | 2 +- src/algorithms/combinatorial/combinatorial.jl | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/algorithms/combinatorial/LocalSearch/neighborhood.jl b/src/algorithms/combinatorial/LocalSearch/neighborhood.jl index 3ac9ee2b..5a689cf4 100644 --- a/src/algorithms/combinatorial/LocalSearch/neighborhood.jl +++ b/src/algorithms/combinatorial/LocalSearch/neighborhood.jl @@ -10,18 +10,16 @@ Base.@kwdef struct TwoOptNeighborhood <: Neighborhood k::Int = 2 end -function neighborhood_structure(x, s::Neighborhood, i) +function neighborhood_structure(x, s, i) # The i-th neighbour in the k-th neighborhood around x n = nameof(typeof(s)) - println(""" + error(""" Define your neighborhood as follows: - ` function neighborhood_structure(x, s::$n, i) # ... end - ` """ ) end diff --git a/src/algorithms/combinatorial/VNS/VND.jl b/src/algorithms/combinatorial/VNS/VND.jl index 26d55bc6..734615b5 100644 --- a/src/algorithms/combinatorial/VNS/VND.jl +++ b/src/algorithms/combinatorial/VNS/VND.jl @@ -1,7 +1,7 @@ abstract type AbstractVNS <: AbstractParameters end -Base.@kwdef struct VND{I, N, L} <: AbstractVNS +struct VND{I, N, L} <: AbstractVNS initial::I neighborhood::N # neighborhood structures local_search::L # local search strategy diff --git a/src/algorithms/combinatorial/VNS/VNS.jl b/src/algorithms/combinatorial/VNS/VNS.jl index b6b387bb..0f0d6de5 100644 --- a/src/algorithms/combinatorial/VNS/VNS.jl +++ b/src/algorithms/combinatorial/VNS/VNS.jl @@ -1,6 +1,6 @@ include("VND.jl") -Base.@kwdef struct VNS{I, S, L, C, N} <: AbstractVNS +struct VNS{I, S, L, C, N} <: AbstractVNS initial::I neighborhood_shaking::S neighborhood_local::L diff --git a/src/algorithms/combinatorial/combinatorial.jl b/src/algorithms/combinatorial/combinatorial.jl index aa0dfecd..d8323da4 100644 --- a/src/algorithms/combinatorial/combinatorial.jl +++ b/src/algorithms/combinatorial/combinatorial.jl @@ -3,4 +3,4 @@ include("BRKGA/BRKGA.jl") # classical algorithms include("LocalSearch/LocalSearchUtils.jl") include("GRASP/GRASP.jl") -#include("VNS/VNS.jl") +include("VNS/VNS.jl") From 3c31dac2f2e5186170f2d9643db19c720655ad26 Mon Sep 17 00:00:00 2001 From: Jesus Mejia Date: Tue, 21 May 2024 06:30:00 -0600 Subject: [PATCH 07/13] add some docstring --- docs/make.jl | 10 +- docs/src/algorithms.md | 185 ------------------ docs/src/algorithms/combinatorial.md | 50 +++++ docs/src/algorithms/index.md | 34 ++++ docs/src/algorithms/multiobjective.md | 51 +++++ docs/src/algorithms/singleobjective.md | 90 +++++++++ src/algorithms/combinatorial/GRASP/GRASP.jl | 61 ++---- .../combinatorial/GRASP/constructor.jl | 74 +++++++ src/algorithms/combinatorial/VNS/VND.jl | 18 +- src/algorithms/combinatorial/VNS/VNS.jl | 27 ++- src/algorithms/combinatorial/combinatorial.jl | 2 + 11 files changed, 368 insertions(+), 234 deletions(-) delete mode 100644 docs/src/algorithms.md create mode 100644 docs/src/algorithms/combinatorial.md create mode 100644 docs/src/algorithms/index.md create mode 100644 docs/src/algorithms/multiobjective.md create mode 100644 docs/src/algorithms/singleobjective.md create mode 100644 src/algorithms/combinatorial/GRASP/constructor.jl diff --git a/docs/make.jl b/docs/make.jl index 07f66f7f..4f5cc4e4 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,7 +1,7 @@ using Documenter, Metaheuristics using DocumenterCitations -bib = CitationBibliography(joinpath(@__DIR__, "references.bib")) +bib = CitationBibliography(joinpath(@__DIR__, "references.bib"), style=:authoryear) makedocs( bib, @@ -23,7 +23,13 @@ makedocs( "tutorials/n-queens.md", ], "Examples" => "examples.md", - "Algorithms" => "algorithms.md", + "Algorithms" => [ + "algorithms/index.md", + "algorithms/singleobjective.md", + "algorithms/multiobjective.md", + "algorithms/combinatorial.md", + ], + #"Algorithms" => "algorithms.md", "Problems" => "problems.md", "Performance Indicators" => "indicators.md", "Multi-Criteria Decision Making" => "mcdm.md", diff --git a/docs/src/algorithms.md b/docs/src/algorithms.md deleted file mode 100644 index 613344e9..00000000 --- a/docs/src/algorithms.md +++ /dev/null @@ -1,185 +0,0 @@ -# Algorithms - -List of implemented metaheuristics. The algorithms were implemented based on the -contributor's understanding of the algorithms detailed in the published paper. - -| Algorithm | Objective | Constraints | Large Scale | Batch Evaluation| Structure Name | -|-----------|:-----------|:-----------:|:-----------:| :----------:|------------------------| -| ECA | Single | ✅ | ➖ | ✅ | [`ECA`](@ref) | -| DE | Single | ✅ | ➖ | ✅ | [`DE`](@ref) | -| PSO | Single | ✅ | ➖ | ✅ | [`PSO`](@ref) | -| ABC | Single | ❌ | ➖ | ❌ | [`ABC`](@ref) | -| MOEA/D-DE | Multi | ➖ | ➖ | ❌ | [`MOEAD_DE`](@ref) | -| GSA | Single | ❌ | ❌ | ✅ | [`CGSA`](@ref) | -| SA | Single | ✅ | ➖ | ❌ | [`SA`](@ref) | -| WOA | Single | ✅ | ➖ | ✅ | [`WOA`](@ref) | -| NSGA-II | Multi | ✅ | ➖ | ✅ | [`NSGA2`](@ref) | -| NSGA-III | Many | ✅ | ➖ | ✅ | [`NSGA3`](@ref) | -| SMS-EMOA | Multi | ✅ | ➖ | ✅ | [`SMS_EMOA`](@ref) | -| SPEA2 | Multi | ✅ | ➖ | ✅ | [`SPEA2`](@ref) | -| BCA | Bilevel | ✅ | ❌ | ❌ | [`BCA`](https://jmejia8.github.io/BilevelHeuristics.jl/dev/algorithms/#BCA) | -| MCCGA | Single | ❌ | ❌ | ❌ | [`MCCGA`](@ref) | -| GA | Single | ✅ | ➖ | ✅ | [`GA`](@ref) | -| CCMO | Multi | ✅ | ➖ | ✅ | [`CCMO`](@ref) | -| $\varepsilon$DE | Single | ✅ | ➖ | ✅ | [`εDE`](@ref) | -| BRKGA | Single | ✅ | ➖ | ✅ | [`BRKGA`](@ref) | - - -✅ = supported, -❌ = not supported, -➖ = can be supported by changing default parameters. - -- **Batch Evaluation** = Simultaneous evaluation of multiple solutions (batch) see "[Batch Evaluation](@ref)". -- **Constraints** = Equality and inequality constraints. -- **Large Scale** = High dimensional problems (variables space). - -## Evolutionary Centers Algorithm - -ECA was proposed for solving global optimization problems. See [MejiaMezura2019](@cite) for more information. -```@docs -ECA -``` - -## Differential Evolution - -DE is an evolutionary algorithm based on vector differences. -See [Price2013](@cite) for more details. - -```@docs -DE -``` - -## Particle Swarm Optimization - -PSO is a population-based optimization technique inspired by the motion of bird flocks and schooling fish by [KennedyEberhart1995](@cite). - -```@docs -PSO -``` - -## Artificial Bee Colony - -A powerful and efficient algorithm for numerical function optimization: artificial bee colony (ABC) algorithm by [KarabogaBasturk2007](@cite). -```@docs -ABC -``` - - -## MOEA/D-DE - -Multiobjective optimization problems with complicated Pareto sets by [LiZhang2008](@cite). - -```@docs -MOEAD_DE -``` - - -## Gravitational Search Algorithm - -Chaotic gravitational constants for the gravitational search algorithm by -[MirjaliliGandomi2017](@cite) - -```@docs -CGSA -``` - - -## Simulated Annealing - -Physics-inspired algorithm for optimization by [Van1987](@cite). - -```@docs -SA -``` - -## Whale Optimization Algorithm - -The Whale Optimization Algorithm inspired by humpback whales proposed in [MirjaliliLewis2016](@cite). - -```@docs -WOA -``` - - -## NSGA-II - -A fast and elitist multiobjective genetic algorithm: NSGA-II by [Deb2002](@cite). - -```@docs -NSGA2 -``` - - -## NSGA-III - -An Evolutionary Many-Objective Optimization Algorithm Using Reference-Point-Based -Nondominated Sorting Approach, Part I: Solving Problems With Box Constraints by [DebJain2014](@cite). -```@docs -NSGA3 -``` - -## SMS-EMOA - -An EMO algorithm using the hypervolume measure as a selection criterion by [Emmerich2005](@cite). -```@docs -SMS_EMOA -``` - - -## SPEA2 - -Improved strength Pareto evolutionary algorithm by [Zitzler2001](@cite). -```@docs -SPEA2 -``` - -## BCA - -Bilevel Centers Algorithm has been proposed to solve bilevel optimization problems. -See [`BilevelHeuristics.BCA`](https://jmejia8.github.io/BilevelHeuristics.jl/dev/algorithms/#BCA) for -details. - - -## MCCGA - -Machine-coded Compact Genetic Algorithms for real-valued optimization problems by [SatmanAkadal2020mcga](@cite). - -```@docs -MCCGA -``` - -## GA - - -```@docs -GA -``` - -## CCMO - -A Coevolutionary Framework for Constrained Multiobjective Optimization Problems -proposed by [Tian2020](@cite). - -```@docs -CCMO -``` - -## $\varepsilon$DE - -``\varepsilon`` Constrained Differential Evolution with Gradient-Based Mutation and Feasible Elites by [Takahama2006Constrained](@cite). - -!!! warning "Gradient mutation" - Gradient mutation is not implemented here. - - -```@docs -εDE -``` - -## BRKGA - -Biased Random Key Genetic Algorithm by [Gonalves2010](@cite). - -```@docs -BRKGA -``` diff --git a/docs/src/algorithms/combinatorial.md b/docs/src/algorithms/combinatorial.md new file mode 100644 index 00000000..3245fabb --- /dev/null +++ b/docs/src/algorithms/combinatorial.md @@ -0,0 +1,50 @@ +# Combinatorial + +These algorithms can be used for solving combinatorial optimization. + +## BRKGA + +Biased Random Key Genetic Algorithm. + +```@docs +BRKGA +``` + +## GRASP + +Generalized Random Adaptive Search Procedure. + +```@docs +GRASP +``` + +```@docs +Metaheuristics.GreedyRandomizedContructor +``` + +```@docs +Metaheuristics.construct +``` + + +```@docs +Metaheuristics.compute_cost +``` + + +## VND + +Variational Neighborhood Descendent. + +```@docs +VND +``` + +## VNS + +General Variational Neighborhood Search. + +```@docs +VNS +``` + diff --git a/docs/src/algorithms/index.md b/docs/src/algorithms/index.md new file mode 100644 index 00000000..34aebaf0 --- /dev/null +++ b/docs/src/algorithms/index.md @@ -0,0 +1,34 @@ +# Index + +List of implemented metaheuristics. The algorithms were implemented based on the +contributor's understanding of the algorithms detailed in the published paper. + +| Algorithm | Objective | Constraints | Large Scale | Batch Evaluation| Structure Name | +|-----------|:-----------|:-----------:|:-----------:| :----------:|------------------------| +| ECA | Single | ✅ | ➖ | ✅ | [`ECA`](@ref) | +| DE | Single | ✅ | ➖ | ✅ | [`DE`](@ref) | +| PSO | Single | ✅ | ➖ | ✅ | [`PSO`](@ref) | +| ABC | Single | ❌ | ➖ | ❌ | [`ABC`](@ref) | +| MOEA/D-DE | Multi | ➖ | ➖ | ❌ | [`MOEAD_DE`](@ref) | +| GSA | Single | ❌ | ❌ | ✅ | [`CGSA`](@ref) | +| SA | Single | ✅ | ➖ | ❌ | [`SA`](@ref) | +| NSGA-II | Multi | ✅ | ➖ | ✅ | [`NSGA2`](@ref) | +| NSGA-III | Many | ✅ | ➖ | ✅ | [`NSGA3`](@ref) | +| SMS-EMOA | Multi | ✅ | ➖ | ✅ | [`SMS_EMOA`](@ref) | +| SPEA2 | Multi | ✅ | ➖ | ✅ | [`SPEA2`](@ref) | +| BCA | Bilevel | ✅ | ❌ | ❌ | [`BCA`](https://jmejia8.github.io/BilevelHeuristics.jl/dev/algorithms/#BCA) | +| MCCGA | Single | ❌ | ❌ | ❌ | [`MCCGA`](@ref) | +| GA | Single | ✅ | ➖ | ✅ | [`GA`](@ref) | +| CCMO | Multi | ✅ | ➖ | ✅ | [`CCMO`](@ref) | +| $\varepsilon$DE | Single | ✅ | ➖ | ✅ | [`εDE`](@ref) | +| BRKGA | Single | ✅ | ➖ | ✅ | [`BRKGA`](@ref) | + + +✅ = supported, +❌ = not supported, +➖ = can be supported by changing default parameters. + +- **Batch Evaluation** = Simultaneous evaluation of multiple solutions (batch) see "[Batch Evaluation](@ref)". +- **Constraints** = Equality and inequality constraints. +- **Large Scale** = High dimensional problems (variables space). + diff --git a/docs/src/algorithms/multiobjective.md b/docs/src/algorithms/multiobjective.md new file mode 100644 index 00000000..5b056538 --- /dev/null +++ b/docs/src/algorithms/multiobjective.md @@ -0,0 +1,51 @@ +# Multi-objective + +## MOEA/D-DE + +Multiobjective optimization problems with complicated Pareto sets by [LiZhang2008](@cite). + +```@docs +MOEAD_DE +``` + +## NSGA-II + +A fast and elitist multiobjective genetic algorithm: NSGA-II by [Deb2002](@cite). + +```@docs +NSGA2 +``` + + +## NSGA-III + +An Evolutionary Many-Objective Optimization Algorithm Using Reference-Point-Based +Nondominated Sorting Approach, Part I: Solving Problems With Box Constraints by [DebJain2014](@cite). +```@docs +NSGA3 +``` + +## SMS-EMOA + +An EMO algorithm using the hypervolume measure as a selection criterion by [Emmerich2005](@cite). +```@docs +SMS_EMOA +``` + + +## SPEA2 + +Improved strength Pareto evolutionary algorithm by [Zitzler2001](@cite). +```@docs +SPEA2 +``` + +## CCMO + +A Coevolutionary Framework for Constrained Multiobjective Optimization Problems +proposed by [Tian2020](@cite). + +```@docs +CCMO +``` + diff --git a/docs/src/algorithms/singleobjective.md b/docs/src/algorithms/singleobjective.md new file mode 100644 index 00000000..a567e8e9 --- /dev/null +++ b/docs/src/algorithms/singleobjective.md @@ -0,0 +1,90 @@ +# Single-objective + + +## Evolutionary Centers Algorithm + +ECA was proposed for solving global optimization problems. See [MejiaMezura2019](@cite) for more information. + +```@docs +ECA +``` + +## Differential Evolution + +DE is an evolutionary algorithm based on vector differences. +See [Price2013](@cite) for more details. + +```@docs +DE +``` + +## Particle Swarm Optimization + +PSO is a population-based optimization technique inspired by the motion of bird flocks and schooling fish by [KennedyEberhart1995](@cite). + +```@docs +PSO +``` + +## Artificial Bee Colony + +A powerful and efficient algorithm for numerical function optimization: artificial bee colony (ABC) algorithm by [KarabogaBasturk2007](@cite). +```@docs +ABC +``` + + +## Gravitational Search Algorithm + +Chaotic gravitational constants for the gravitational search algorithm by +[MirjaliliGandomi2017](@cite) + +```@docs +CGSA +``` + + +## Simulated Annealing + +Physics-inspired algorithm for optimization by [Van1987](@cite). + +```@docs +SA +``` + + + +## BCA + +Bilevel Centers Algorithm has been proposed to solve bilevel optimization problems. +See [`BilevelHeuristics.BCA`](https://jmejia8.github.io/BilevelHeuristics.jl/dev/algorithms/#BCA) for +details. + + +## MCCGA + +Machine-coded Compact Genetic Algorithms for real-valued optimization problems by [SatmanAkadal2020mcga](@cite). + +```@docs +MCCGA +``` + +## GA + + +```@docs +GA +``` + +## $\varepsilon$DE + +``\varepsilon`` Constrained Differential Evolution with Gradient-Based Mutation and Feasible Elites by [Takahama2006Constrained](@cite). + +!!! warning "Gradient mutation" + Gradient mutation is not implemented here. + + +```@docs +εDE +``` + diff --git a/src/algorithms/combinatorial/GRASP/GRASP.jl b/src/algorithms/combinatorial/GRASP/GRASP.jl index cf3dc7d6..f06f50d4 100644 --- a/src/algorithms/combinatorial/GRASP/GRASP.jl +++ b/src/algorithms/combinatorial/GRASP/GRASP.jl @@ -6,63 +6,36 @@ struct GRASP{I, T, L} <: AbstractGRASP local_search::L end -Base.@kwdef struct GreedyRandomizedContructor - candidates - instance = nothing - α::Float64 = 0.6 - rng = default_rng_mh() -end - - function construct(constructor) @error "Define your own randomized greedy constructor" status.stop = true nothing end -function local_search(x, localsearch, problem) - @error "Define your own local search" - nothing -end +include("constructor.jl") +""" + GRASP(;initial, constructor, local_search,...) -function compute_cost(candidates, constructor, instance) - @warn "Define compute_cost for\nconstructor=$constructor\ninstance=$instance" - zeros(length(candidates)) -end +Generalized Random Adaptive Search Procedure. +# Allowed parameters -function construct(constructor::GreedyRandomizedContructor) - candidates = constructor.candidates |> copy - α = constructor.α - # create empty solution S - S = empty(candidates) - # construct solution - while !isempty(candidates) - cost = compute_cost(candidates, constructor, constructor.instance) - cmin = minimum(cost) - cmax = maximum(cost) - # compute restricted candidate list - RCL = [i for i in eachindex(candidates) if cost[i] <= cmin + α*(cmax - cmin) ] - if isempty(RCL) - @error "RCL is empty. Try increasing α or check your `compute_cost` method." - return - end - - # select candidate at random and insert into solution - s = rand(constructor.rng, RCL) - push!(S, candidates[s]) - # update list of candidates - deleteat!(candidates, s) - end - S -end +- `initial`: an initial solution if necessary. +- `constructor` parameters for the greedy constructor. +- `local_search` the local search strategy `BestImprovingSearch()` (default) and `FirstImprovingSearch()`. + +See [`GreedyRandomizedContructor`](@ref) + +# Example: Knapsack Problem -function GRASP(;initial=nothing, constructor=nothing, local_search=nothing, +TODO +""" +function GRASP(;initial=nothing, constructor=nothing, local_search=BestImprovingSearch(), options = Options(), information=Information()) # TODO - if isnothing(constructor) || isnothing(local_search) - error("Provide a constructor and a local search") + if isnothing(constructor) + error("Provide a constructor.") end grasp = GRASP(initial, constructor, local_search) Algorithm(grasp; options, information) diff --git a/src/algorithms/combinatorial/GRASP/constructor.jl b/src/algorithms/combinatorial/GRASP/constructor.jl new file mode 100644 index 00000000..aab2203b --- /dev/null +++ b/src/algorithms/combinatorial/GRASP/constructor.jl @@ -0,0 +1,74 @@ +""" + GreedyRandomizedContructor(;candidates, instance, α, rng) + +This structure can be used to use the Greedy Randomized Contructor. + +- `candidates` vector of candidates (item to choose to form a solution). +- `instance` a user define structure for saving data on the problem instance. +- `α` controls the randomness (0 deterministic greedy heuristic, 1 for pure random). +- `rng` storages the random number generator. + +This constructor assumes overwriting the `compute_cost` function: + +```julia +import Metaheuristics as MH +struct MyInstance end +function MH.compute_cost(candidates, constructor, instance::MyInstance) + # ... +end +``` + +See also [`compute_cost`](@ref) and [`GRASP`](@ref) +""" +Base.@kwdef struct GreedyRandomizedContructor + candidates + instance = nothing + α::Float64 = 0.6 + rng = default_rng_mh() +end + +""" + compute_cost(candidates, constructor, instance) + +Compute the cost for each candidate in `candidates`, for given `constructor` and +provided `instance`. + +See also [`GreedyRandomizedContructor`](@ref) and [`GRASP`](@ref) +""" +function compute_cost(candidates, constructor, instance) + @warn "Define compute_cost for\nconstructor=$constructor\ninstance=$instance" + zeros(length(candidates)) +end + +""" + construct(constructor) + +Constructor procedure for GRASP. + +See also [`GreedyRandomizedContructor`](@ref), [`compute_cost`](@ref) and [`GRASP`](@ref) +""" +function construct(constructor::GreedyRandomizedContructor) + candidates = constructor.candidates |> copy + α = constructor.α + # create empty solution S + S = empty(candidates) + # construct solution + while !isempty(candidates) + cost = compute_cost(candidates, constructor, constructor.instance) + cmin = minimum(cost) + cmax = maximum(cost) + # compute restricted candidate list + RCL = [i for i in eachindex(candidates) if cost[i] <= cmin + α*(cmax - cmin) ] + if isempty(RCL) + @error "RCL is empty. Try increasing α or check your `compute_cost` method." + return + end + + # select candidate at random and insert into solution + s = rand(constructor.rng, RCL) + push!(S, candidates[s]) + # update list of candidates + deleteat!(candidates, s) + end + S +end diff --git a/src/algorithms/combinatorial/VNS/VND.jl b/src/algorithms/combinatorial/VNS/VND.jl index 734615b5..341ed657 100644 --- a/src/algorithms/combinatorial/VNS/VND.jl +++ b/src/algorithms/combinatorial/VNS/VND.jl @@ -7,7 +7,23 @@ struct VND{I, N, L} <: AbstractVNS local_search::L # local search strategy end -function VND(;initial = nothing, neighborhood = nothing, local_search = nothing, +""" + VND(;initial, neighborhood, local_search,...) + +Variational Neighborhood Descendent. + +# Allowed parameters + +- `initial`: Use this parameter to provide an initial solution (optional). +- `neighborhood`: Neighborhood structure. +- `local_search` the local search strategy `BestImprovingSearch()` (default) and `FirstImprovingSearch()`. + + +# Example: Knapsack Problem + +TODO +""" +function VND(;initial = nothing, neighborhood = nothing, local_search = BestImprovingSearch(), options=Options(), information=Information()) parameters = VND(initial, neighborhood, local_search) diff --git a/src/algorithms/combinatorial/VNS/VNS.jl b/src/algorithms/combinatorial/VNS/VNS.jl index 0f0d6de5..21e05a5f 100644 --- a/src/algorithms/combinatorial/VNS/VNS.jl +++ b/src/algorithms/combinatorial/VNS/VNS.jl @@ -12,10 +12,33 @@ end struct SequentialChange end struct CyclicChange end +""" + VNS(;initial, neighborhood_shaking, neighborhood_local, local_search, neighborhood_change) + +General Variational Neighborhood Search. + +# Allowed parameters + +- `initial`: Use this parameter to provide an initial solution (optional). +- `neighborhood_shaking`: Neighborhood structure for the shaking step. +- `neighborhood_local`: Neighborhood structure for the local search. +- `local_search`: the local search strategy `BestImprovingSearch()` (default) and `FirstImprovingSearch()`. +- `neighborhood_change`: The procedure for changing among neighborhood structures (default `SequentialChange()`). + + +# Example: Knapsack Problem + +TODO +""" function VNS(;initial=nothing,neighborhood_shaking=nothing, neighborhood_local=nothing, - local_search=nothing, neighborhood_change=SequentialChange(), + local_search=FirstImprovingSearch(), neighborhood_change=SequentialChange(), options=Options(), information=Information()) + # TODO + if isnothing(neighborhood_shaking) && isnothing(neighborhood_local) + error("Provide neighborhood_shaking and neighborhood_local.") + end + parameters = VNS(initial, neighborhood_shaking, neighborhood_local, local_search, neighborhood_change) @@ -55,7 +78,7 @@ function update_state!(status, parameters::VNS, problem, information, options, a # perform local search around xp using VND # TODO: update this for considering other VNS variants (for the local search) vnd = VND(;initial=xp, neighborhood=parameters.neighborhood_local, - local_search = FirstImprovingSearch()) + local_search = parameters.local_search) _res_local = optimize(problem.f, problem.search_space, vnd) sol_new = _res_local.best_sol diff --git a/src/algorithms/combinatorial/combinatorial.jl b/src/algorithms/combinatorial/combinatorial.jl index d8323da4..565c6939 100644 --- a/src/algorithms/combinatorial/combinatorial.jl +++ b/src/algorithms/combinatorial/combinatorial.jl @@ -4,3 +4,5 @@ include("BRKGA/BRKGA.jl") include("LocalSearch/LocalSearchUtils.jl") include("GRASP/GRASP.jl") include("VNS/VNS.jl") + +export GRASP, VND, VNS From f959aa67cd6f0505a5c01d925899ce9e08dfe05d Mon Sep 17 00:00:00 2001 From: Jesus Mejia Date: Tue, 21 May 2024 08:38:31 -0600 Subject: [PATCH 08/13] add examples --- src/algorithms/combinatorial/GRASP/GRASP.jl | 47 ++++++++++++++++++++- src/algorithms/combinatorial/VNS/VND.jl | 36 +++++++++++++++- src/algorithms/combinatorial/VNS/VNS.jl | 45 +++++++++++++++++++- 3 files changed, 125 insertions(+), 3 deletions(-) diff --git a/src/algorithms/combinatorial/GRASP/GRASP.jl b/src/algorithms/combinatorial/GRASP/GRASP.jl index f06f50d4..b545ac92 100644 --- a/src/algorithms/combinatorial/GRASP/GRASP.jl +++ b/src/algorithms/combinatorial/GRASP/GRASP.jl @@ -29,7 +29,52 @@ See [`GreedyRandomizedContructor`](@ref) # Example: Knapsack Problem -TODO +```julia +import Metaheuristics as MH + +# define type for saving knapsack problem instance +struct KPInstance + profit + weight + capacity +end + +function MH.compute_cost(candidates, constructor, instance::KPInstance) + # Ration profit / weight + ratio = instance.profit[candidates] ./ instance.weight[candidates] + # It is assumed minimizing non-negative costs + maximum(ratio) .- ratio +end + +function main() + # problem instance + profit = [55, 10,47, 5, 4, 50, 8, 61, 85, 87] + weight = [95, 4, 60, 32, 23, 72, 80, 62, 65, 46] + capacity = 269 + optimum = 295 + instance = KPInstance(profit, weight, capacity) + + # objective function and search space + f, search_space, _ = MH.TestProblems.knapsack(profit, weight, capacity) + candidates = rand(search_space) + + # define each GRASP component + constructor = MH.GreedyRandomizedContructor(;candidates, instance, α = 0.95) + local_search = MH.BestImprovingSearch() + neighborhood = MH.TwoOptNeighborhood() + grasp = MH.GRASP(;constructor, local_search) + + # optimize and display results + result = MH.optimize(f, search_space, grasp) + display(result) + # compute GAP + fx = -minimum(result) + GAP = (optimum - fx) / optimum + println("The GAP is ", GAP) +end + +main() +``` """ function GRASP(;initial=nothing, constructor=nothing, local_search=BestImprovingSearch(), options = Options(), information=Information()) diff --git a/src/algorithms/combinatorial/VNS/VND.jl b/src/algorithms/combinatorial/VNS/VND.jl index 341ed657..ca716068 100644 --- a/src/algorithms/combinatorial/VNS/VND.jl +++ b/src/algorithms/combinatorial/VNS/VND.jl @@ -21,7 +21,41 @@ Variational Neighborhood Descendent. # Example: Knapsack Problem -TODO +```julia +import Metaheuristics as MH + +struct MyKPNeighborhood <: MH.Neighborhood + k::Int +end + +function MH.neighborhood_structure(x, s::MyKPNeighborhood, i::Integer) + # return the i-th neighbor around x, regarding s.k structure + i > length(x) && return nothing + reverse!(view(x, i:min(length(x), i+s.k))) + x +end + + +function main() + profit = [55, 10,47, 5, 4, 50, 8, 61, 85, 87] + weight = [95, 4, 60, 32, 23, 72, 80, 62, 65, 46] + capacity = 269 + + # objective function and search space + f, search_space, _ = MH.TestProblems.knapsack(profit, weight, capacity) + + # list the neighborhood structures + neighborhood = [MyKPNeighborhood(1), MyKPNeighborhood(2), MyKPNeighborhood(3)] + local_search = MH.BestImprovingSearch() + # instantiate VNS + vnd = MH.VND(;neighborhood, local_search) + + res = MH.optimize(f, search_space, vnd) + display(res) +end + +main() +``` """ function VND(;initial = nothing, neighborhood = nothing, local_search = BestImprovingSearch(), options=Options(), information=Information()) diff --git a/src/algorithms/combinatorial/VNS/VNS.jl b/src/algorithms/combinatorial/VNS/VNS.jl index 21e05a5f..9c8be000 100644 --- a/src/algorithms/combinatorial/VNS/VNS.jl +++ b/src/algorithms/combinatorial/VNS/VNS.jl @@ -28,7 +28,50 @@ General Variational Neighborhood Search. # Example: Knapsack Problem -TODO +```julia +import Metaheuristics as MH + +struct MyKPNeighborhood <: MH.Neighborhood + k::Int +end + +function MH.neighborhood_structure(x, s::MyKPNeighborhood, rng) + # this is defined due to shaking procedure requires a random one + # not the i-th neighbor. + i = rand(rng, 1:length(x)) + reverse!(view(x, i:min(length(x), i+s.k))) + x +end + +function MH.neighborhood_structure(x, s::MyKPNeighborhood, i::Integer) + # return the i-th neighbor around x, regarding s.k structure + i > length(x) && return nothing + reverse!(view(x, i:min(length(x), i+s.k))) + x +end + +function main() + profit = [55, 10,47, 5, 4, 50, 8, 61, 85, 87] + weight = [95, 4, 60, 32, 23, 72, 80, 62, 65, 46] + capacity = 269 + nitems = length(weight) + + # objective function and search space + f, search_space, _ = MH.TestProblems.knapsack(profit, weight, capacity) + + # list the neighborhood structures + neighborhood_shaking = [MyKPNeighborhood(6), MyKPNeighborhood(5), MyKPNeighborhood(4)] + neighborhood_local = [MyKPNeighborhood(3), MyKPNeighborhood(2), MyKPNeighborhood(1)] + local_search = MH.BestImprovingSearch() + # instantiate VNS + vnd = MH.VNS(;neighborhood_shaking, neighborhood_local, local_search, options=MH.Options(verbose=true)) + + res = MH.optimize(f, search_space, vnd) + display(res) +end + +main() +``` """ function VNS(;initial=nothing,neighborhood_shaking=nothing, neighborhood_local=nothing, local_search=FirstImprovingSearch(), neighborhood_change=SequentialChange(), From 5af56e0901ff414fa6195ca89247a7da98a346ea Mon Sep 17 00:00:00 2001 From: Jesus Mejia Date: Tue, 21 May 2024 08:38:45 -0600 Subject: [PATCH 09/13] new tests --- src/TestProblems/TestProblems.jl | 3 ++ src/TestProblems/combinatorial.jl | 33 +++++++++++++ test/combinatorial.jl | 80 +++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 src/TestProblems/combinatorial.jl diff --git a/src/TestProblems/TestProblems.jl b/src/TestProblems/TestProblems.jl index a64f7eea..ffa1f53b 100644 --- a/src/TestProblems/TestProblems.jl +++ b/src/TestProblems/TestProblems.jl @@ -5,6 +5,7 @@ import ..generateChild, ..gen_ref_dirs, ..norm, ..get_non_dominated_solutions include("box-constrained.jl") include("constrained.jl") include("multiobjective.jl") +include("combinatorial.jl") const _problems_dict = Dict( :sphere => sphere, @@ -34,6 +35,8 @@ const _problems_dict = Dict( :C1_DTLZ3 => C1_DTLZ3, :C2_DTLZ2 => C2_DTLZ2, :C3_DTLZ4 => C3_DTLZ4, + ################################# + :knapsack => knapsack, ) """ diff --git a/src/TestProblems/combinatorial.jl b/src/TestProblems/combinatorial.jl new file mode 100644 index 00000000..536cced6 --- /dev/null +++ b/src/TestProblems/combinatorial.jl @@ -0,0 +1,33 @@ +import ..PermutationSpace, ..BitArraySpace + +""" + knapsack(profit, weight, capacity; encoding=:permutation) + +Return the Knapsack problem regarding the provided parameters. +""" +function knapsack(profit, weight, capacity; encoding=:permutation) + function _f(items) + p = w = 0.0 + for item in items + w += weight[item] + if w > capacity + return -p + end + p += profit[item] + end + -p + end + if encoding === :permutation + f = _f + search_space = PermutationSpace(length(profit)) + elseif encoding === :binary + f = x -> _f(findall(x)) + search_space = BitArraySpace(length(profit)) + else + error("Encoding $encoding is not valid.") + end + # TODO Try to handle optimums when provided when provided + optimum = nothing + + return f, search_space, optimum +end diff --git a/test/combinatorial.jl b/test/combinatorial.jl index 6008ff6a..e8e45fae 100644 --- a/test/combinatorial.jl +++ b/test/combinatorial.jl @@ -64,6 +64,11 @@ @test decode(minimizer(res)) == target_perm end + function grasp_vns() + + end + + rkga() #### Permutation @@ -98,3 +103,78 @@ end + +@testset "Combinatorial II" begin + struct MyKPNeighborhood <: Metaheuristics.Neighborhood + k::Int + end + + struct KPInstance + profit + weight + capacity + end + + function Metaheuristics.compute_cost(candidates, constructor, instance::KPInstance) + # Ration profit / weight + ratio = instance.profit[candidates] ./ instance.weight[candidates] + # It is assumed minimizing non-negative costs + maximum(ratio) .- ratio + end + + function Metaheuristics.neighborhood_structure(x, s::MyKPNeighborhood, rng) + # this is defined due to shaking procedure requires a random one + # not the i-th neighbor. + i = rand(rng, 1:length(x)) + reverse!(view(x, i:min(length(x), i+s.k))) + x + end + + function Metaheuristics.neighborhood_structure(x, s::MyKPNeighborhood, i::Integer) + # return the i-th neighbor around x, regarding s.k structure + i > length(x) && return nothing + reverse!(view(x, i:min(length(x), i+s.k))) + x + end + + function vns_grasp() + profit = [55, 10,47, 5, 4, 50, 8, 61, 85, 87] + weight = [95, 4, 60, 32, 23, 72, 80, 62, 65, 46] + capacity = 269 + optimum = 295 + + # objective function and search space + f, search_space, _ =Metaheuristics.TestProblems.knapsack(profit, weight, capacity) + options = Options(iterations=50, seed=1) + + ########################################### + # VND/VNS + ########################################### + # list the neighborhood structures + neighborhood_shaking = [MyKPNeighborhood(6), MyKPNeighborhood(5), MyKPNeighborhood(4)] + neighborhood_local = [MyKPNeighborhood(3), MyKPNeighborhood(2), MyKPNeighborhood(1)] + local_search = Metaheuristics.FirstImprovingSearch() + # instantiate VNS + vnd = Metaheuristics.VNS(;neighborhood_shaking, neighborhood_local, local_search, options) + + res = Metaheuristics.optimize(f, search_space, vnd) + @test -minimum(res) == optimum + + + ########################################### + # GRASP + ########################################### + candidates = rand(search_space) + instance = KPInstance(profit, weight, capacity) + constructor = Metaheuristics.GreedyRandomizedContructor(;candidates, instance, α = 0.95) + local_search = Metaheuristics.BestImprovingSearch() + neighborhood = Metaheuristics.TwoOptNeighborhood() + grasp = GRASP(;constructor, local_search) + + # optimize and display results + res = optimize(f, search_space, grasp) + @test -minimum(res) == optimum + end + + vns_grasp() +end From 4d1b5c487c5142a48e1c568f00aa41dda25c10a3 Mon Sep 17 00:00:00 2001 From: Jesus Mejia Date: Tue, 21 May 2024 08:49:00 -0600 Subject: [PATCH 10/13] fix naming --- docs/src/algorithms/combinatorial.md | 6 +++--- src/algorithms/combinatorial/GRASP/GRASP.jl | 2 +- src/algorithms/combinatorial/VNS/VND.jl | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/algorithms/combinatorial.md b/docs/src/algorithms/combinatorial.md index 3245fabb..e2405d6a 100644 --- a/docs/src/algorithms/combinatorial.md +++ b/docs/src/algorithms/combinatorial.md @@ -12,7 +12,7 @@ BRKGA ## GRASP -Generalized Random Adaptive Search Procedure. +Greedy randomized adaptive search procedure. ```@docs GRASP @@ -34,7 +34,7 @@ Metaheuristics.compute_cost ## VND -Variational Neighborhood Descendent. +Variable Neighborhood Descent. ```@docs VND @@ -42,7 +42,7 @@ VND ## VNS -General Variational Neighborhood Search. +General Variable Neighborhood Search. ```@docs VNS diff --git a/src/algorithms/combinatorial/GRASP/GRASP.jl b/src/algorithms/combinatorial/GRASP/GRASP.jl index b545ac92..26adf958 100644 --- a/src/algorithms/combinatorial/GRASP/GRASP.jl +++ b/src/algorithms/combinatorial/GRASP/GRASP.jl @@ -17,7 +17,7 @@ include("constructor.jl") """ GRASP(;initial, constructor, local_search,...) -Generalized Random Adaptive Search Procedure. +Greedy Randomized Adaptive Search Procedure. # Allowed parameters diff --git a/src/algorithms/combinatorial/VNS/VND.jl b/src/algorithms/combinatorial/VNS/VND.jl index ca716068..726f3363 100644 --- a/src/algorithms/combinatorial/VNS/VND.jl +++ b/src/algorithms/combinatorial/VNS/VND.jl @@ -10,7 +10,7 @@ end """ VND(;initial, neighborhood, local_search,...) -Variational Neighborhood Descendent. +Variable Neighborhood Descent. # Allowed parameters From a1124443b8862e9d95ddb8ba24a4884facb6861a Mon Sep 17 00:00:00 2001 From: Jesus Mejia Date: Tue, 21 May 2024 08:49:19 -0600 Subject: [PATCH 11/13] update list of algorithms --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ff7a77b5..a9324c65 100644 --- a/README.md +++ b/README.md @@ -36,18 +36,22 @@ Some representative metaheuristics are developed here, including those for singl multi-objective optimization. Moreover, some constraint handling techniques have been considered in most of the [implemented algorithms](https://jmejia8.github.io/Metaheuristics.jl/stable/algorithms/). +### Combinatorial Optimization + +- **GRASP**: Greedy randomized adaptive search procedure. +- **VND**: Variable Neighborhood Descent. +- **VNS**: Variable Neighborhood Search. +- **BRKGA**: Biased Random Key Genetic Algorithm. + ### Single-Objective Optimization - **ECA**: Evolutionary Centers Algorithm - **DE**: Differential Evolution - **PSO**: Particle Swarm Optimization -- **ABC**: Artificial Bee Colony -- **GSA**: Gravitational Search Algorithm - **SA**: Simulated Annealing -- **WOA**: Whale Optimization Algorithm - **MCCGA**: Machine-coded Compact Genetic Algorithm - **GA**: Genetic Algorithm -- **BRKGA**: Biased Random Key Genetic Algorithm +- [and more...](https://jmejia8.github.io/Metaheuristics.jl/stable/algorithms/) ### Multi-Objective Optimization From d504c54ab7c993ca3477bf998d91e6a4cd38aa49 Mon Sep 17 00:00:00 2001 From: Jesus Mejia Date: Tue, 21 May 2024 08:53:35 -0600 Subject: [PATCH 12/13] update version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index ffb52431..6c15e06f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Metaheuristics" uuid = "bcdb8e00-2c21-11e9-3065-2b553b22f898" authors = ["Jesus Mejia "] -version = "3.3.5" +version = "3.4.0" [deps] Distances = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7" From a1007955d6a63bdbd9fc16efb7f6daa798562b22 Mon Sep 17 00:00:00 2001 From: Jesus Mejia Date: Tue, 21 May 2024 08:54:26 -0600 Subject: [PATCH 13/13] update julia in documenter workflow --- .github/workflows/documentation-github-actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation-github-actions.yml b/.github/workflows/documentation-github-actions.yml index 378ebba7..56b69f5a 100644 --- a/.github/workflows/documentation-github-actions.yml +++ b/.github/workflows/documentation-github-actions.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@latest with: - version: '1.7' + version: '1.10' - name: Install dependencies run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - name: Build and deploy