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 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" 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 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..e2405d6a --- /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 + +Greedy randomized adaptive search procedure. + +```@docs +GRASP +``` + +```@docs +Metaheuristics.GreedyRandomizedContructor +``` + +```@docs +Metaheuristics.construct +``` + + +```@docs +Metaheuristics.compute_cost +``` + + +## VND + +Variable Neighborhood Descent. + +```@docs +VND +``` + +## VNS + +General Variable 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/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/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/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..26adf958 --- /dev/null +++ b/src/algorithms/combinatorial/GRASP/GRASP.jl @@ -0,0 +1,165 @@ +abstract type AbstractGRASP <: AbstractParameters end + +struct GRASP{I, T, L} <: AbstractGRASP + initial::I + constructor::T + local_search::L +end + +function construct(constructor) + @error "Define your own randomized greedy constructor" + status.stop = true + nothing +end + +include("constructor.jl") + +""" + GRASP(;initial, constructor, local_search,...) + +Greedy Randomized Adaptive Search Procedure. + +# Allowed parameters + +- `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 + +```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()) + # TODO + if isnothing(constructor) + error("Provide a constructor.") + end + grasp = GRASP(initial, constructor, local_search) + Algorithm(grasp; options, information) +end + +iscompatible(::BitArraySpace, ::AbstractGRASP) = true +iscompatible(::PermutationSpace, ::AbstractGRASP) = true + +function 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 = create_solution(x0, problem) + State(sol, [sol]) +end + +function update_state!( + status, + parameters::AbstractGRASP, + problem, + information, + options, + args...; + kargs... + ) + # 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) + + # since local search can return a solution without evaluation + # it is necessary to evaluate objective function + if x_improved isa AbstractVector + # evaluate solution + 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 is_better(sol, status.best_sol) + status.best_sol = sol + options.debug && @info "A better solution was found." + end +end + +function final_stage!( + status, + parameters::AbstractGRASP, + problem, + information, + options, + args...; + kargs... + ) + # TODO + nothing +end + 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/LocalSearch/LocalSearchUtils.jl b/src/algorithms/combinatorial/LocalSearch/LocalSearchUtils.jl new file mode 100644 index 00000000..d01610e8 --- /dev/null +++ b/src/algorithms/combinatorial/LocalSearch/LocalSearchUtils.jl @@ -0,0 +1,50 @@ +abstract type AbstractLocalSearch end +struct BestImprovingSearch <: AbstractLocalSearch end +struct FirstImprovingSearch <: AbstractLocalSearch end + +include("neighborhood.jl") + +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::InternalNeighborhood, ::BestImprovingSearch, problem) + best = create_solution(copy(x), problem) + for xnew in neighbourhood + sol = create_solution(xnew, problem) + if is_better(sol, best) + best = deepcopy(sol) + end + end + best +end + +function local_search(x, neighbourhood::InternalNeighborhood, ::FirstImprovingSearch, problem) + initial = create_solution(copy(x), problem) + for xnew in neighbourhood + sol = create_solution(xnew, problem) + if is_better(sol, initial) + return deepcopy(sol) + end + end + initial +end + +function local_search(x, ls::AbstractLocalSearch, problem) + if problem.search_space isa PermutationSpace + neighbourhood = TwoOptNeighborhood() + elseif problem.search_space isa BoxConstrained + neighbourhood = GridSampler(problem.search_space) + elseif problem.search_space isa 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/neighborhood.jl b/src/algorithms/combinatorial/LocalSearch/neighborhood.jl new file mode 100644 index 00000000..5a689cf4 --- /dev/null +++ b/src/algorithms/combinatorial/LocalSearch/neighborhood.jl @@ -0,0 +1,53 @@ +abstract type Neighborhood end +abstract type InternalNeighborhood <: Neighborhood end + +struct NeighborhoodIterator{X, N} <: InternalNeighborhood + x::X + neighborhood::N +end + +Base.@kwdef struct TwoOptNeighborhood <: Neighborhood + k::Int = 2 +end + +function neighborhood_structure(x, s, i) + # The i-th neighbour in the k-th neighborhood around x + n = nameof(typeof(s)) + + error(""" + Define your neighborhood as follows: + + function neighborhood_structure(x, s::$n, i) + # ... + end + """ + ) +end + + +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)) + 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 + 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 + diff --git a/src/algorithms/combinatorial/VNS/VND.jl b/src/algorithms/combinatorial/VNS/VND.jl new file mode 100644 index 00000000..726f3363 --- /dev/null +++ b/src/algorithms/combinatorial/VNS/VND.jl @@ -0,0 +1,134 @@ +abstract type AbstractVNS <: AbstractParameters end + + +struct VND{I, N, L} <: AbstractVNS + initial::I + neighborhood::N # neighborhood structures + local_search::L # local search strategy +end + +""" + VND(;initial, neighborhood, local_search,...) + +Variable Neighborhood Descent. + +# 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 + +```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()) + + parameters = VND(initial, neighborhood, local_search) + + Algorithm(parameters; options, information) +end + + +iscompatible(::BitArraySpace, ::AbstractVNS) = true +iscompatible(::PermutationSpace, ::AbstractVNS) = true + +function 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 = create_solution(x0, problem) + # TODO + State(sol, [sol]) +end + +function 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 = 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 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 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..9c8be000 --- /dev/null +++ b/src/algorithms/combinatorial/VNS/VNS.jl @@ -0,0 +1,141 @@ +include("VND.jl") + +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 + +""" + 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 + +```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(), + 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) + + 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 is_better(new, old) + return 1, new + end + k + 1, old +end + +function neighborhood_change(old, new, k, ::CyclicChange) + k += 1 + if is_better(new, old) + return k, new + end + k, old +end + +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 = 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 = parameters.local_search) + _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 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..565c6939 --- /dev/null +++ b/src/algorithms/combinatorial/combinatorial.jl @@ -0,0 +1,8 @@ +include("BRKGA/BRKGA.jl") + +# classical algorithms +include("LocalSearch/LocalSearchUtils.jl") +include("GRASP/GRASP.jl") +include("VNS/VNS.jl") + +export GRASP, VND, VNS 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") 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 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