From 732526bdd3548c4bdeb320e28010bf643168bcc8 Mon Sep 17 00:00:00 2001 From: jbytecode Date: Fri, 8 Nov 2024 13:00:44 +0300 Subject: [PATCH] (#63) Initial implementation of the Best-Worst method --- README.md | 2 +- docs/src/mcdms.md | 5 ++ src/JMcDM.jl | 5 ++ src/bestworst.jl | 107 +++++++++++++++++++++++++++++++++++++ src/seca.jl | 1 + test/mcdm/testbestworst.jl | 19 +++++++ test/testmcdm.jl | 1 + 7 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 src/bestworst.jl create mode 100644 test/mcdm/testbestworst.jl diff --git a/README.md b/README.md index 827d3b2..5e2cde8 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ Please check out the reference manual [here](https://jbytecode.github.io/JMcDM/) - TODIM (the Portuguese acronym for multicriteria, interactive decision-making) - CILOS (The Criterion Impact Loss Method) - IDOCRIW (Integrated Determination of Objective Criteria Weights) - +- Best-Worst Method ### SCDM Tools diff --git a/docs/src/mcdms.md b/docs/src/mcdms.md index 0c0b213..df488f4 100644 --- a/docs/src/mcdms.md +++ b/docs/src/mcdms.md @@ -187,4 +187,9 @@ JMcDM.cilos ## IDOCRIW ```@docs JMcDM.idocriw +``` + +## Best-Worst +```@docs +JMcDM.bestworst ``` \ No newline at end of file diff --git a/src/JMcDM.jl b/src/JMcDM.jl index 3659cf2..05701d2 100755 --- a/src/JMcDM.jl +++ b/src/JMcDM.jl @@ -26,6 +26,11 @@ function __init__() include("dataenvelop.jl") import .DataEnvelop: dataenvelop, DataEnvelopResult export DataEnvelopResult + + include("bestworst.jl") + import .BestWorstMethod: bestworst, BestWorstResult + export bestworst + export BestWorstResult end end end diff --git a/src/bestworst.jl b/src/bestworst.jl new file mode 100644 index 0000000..3c1a46c --- /dev/null +++ b/src/bestworst.jl @@ -0,0 +1,107 @@ +module BestWorstMethod + +export bestworst +export BestWorstResult + + +using ..JuMP, ..Ipopt + +struct BestWorstResult + ε::Float64 + weights::Vector +end + + +""" + bestworst(pref_to_best::Vector{Int}, pref_to_worst::Vector{Int})::BestWorstResult + + The Best-Worst Method is a method for deriving weights from preference information. + The method is based on the idea that the best alternative should be as close as possible to the best alternative + and as far as possible from the worst alternative. + + In `pref_to_best`, the best alternative should have the highest preference value of 1. + Similarly, in `pref_to_worst`, the worst alternative should have the highest preference value of 1. + +# Arguments + +- `pref_to_best::Vector{Int}`: Vector of preferences for the best alternative. +- `pref_to_worst::Vector{Int}`: Vector of preferences for the worst alternative. + +# Returns + +- `BestWorstResult`: A struct with the following fields: + - `ε::Float64`: The value of epsilon. + - `weights::Vector`: The weights of the alternatives. + +# Example + +```julia +using JMcDM +pref_to_best = [8, 2, 1] +pref_to_worst = [1, 5, 8] +result = bestworst(pref_to_best, pref_to_worst) +``` + +# References + +- Rezaei, Jafar. "Best-worst multi-criteria decision-making method." Omega 53 (2015): 49-57. + +!!! warning "Dependencies" + This method is enabled when the JuMP and Ipopt packages are installed and loaded. + Please first load the JuMP and Ipopt packages before using this method. + The method is not available in the JMcDM module until the JuMP and Ipopt packages are loaded. +""" +function bestworst(pref_to_best::Vector{Int}, pref_to_worst::Vector{Int})::BestWorstResult + + n = length(pref_to_best) + + if n != length(pref_to_worst) + throw(ArgumentError("The lengths of the preference vectors are not equal.")) + end + + best_index = argmin(pref_to_best) + + if pref_to_best[best_index] != 1 + throw(ArgumentError("The best index must have the highest preference value of 1.")) + end + + worst_index = argmin(pref_to_worst) + + if pref_to_worst[worst_index] != 1 + throw(ArgumentError("The worst index must have the highest preference value of 1.")) + end + + model = Model(Ipopt.Optimizer) + + set_silent(model) + + @variable(model, ε >= 0) + + @variable(model, w[1:n] >= 0) + + @objective(model, Min, ε) + + indices = collect(1:n) + + bestindices = indices[indices.!=best_index] + + worstindices = indices[indices.!=worst_index] + + for i in bestindices + @constraint(model, abs(w[best_index] / w[i] - pref_to_best[i]) <= ε) + end + + for i in worstindices + @constraint(model, abs(w[i] / w[worst_index] - pref_to_worst[i]) <= ε) + end + + @constraint(model, sum(w) == 1) + + optimize!(model) + + result = BestWorstResult(value(ε), value.(w)) + + return result +end + +end # module \ No newline at end of file diff --git a/src/seca.jl b/src/seca.jl index 2669b19..5f46ade 100644 --- a/src/seca.jl +++ b/src/seca.jl @@ -75,6 +75,7 @@ Best indice: ``` # Reference + - [Simultaneous Evaluation of Criteria and Alternatives (SECA) for Multi-Criteria Decision-Making](http://dx.doi.org/10.15388/Informatica.2018.167) !!! warning "Dependencies" diff --git a/test/mcdm/testbestworst.jl b/test/mcdm/testbestworst.jl new file mode 100644 index 0000000..3cd252f --- /dev/null +++ b/test/mcdm/testbestworst.jl @@ -0,0 +1,19 @@ +@testset "Best-Worst" verbose = true begin + + @testset "Basic Example in the Original Paper" begin + + eps = 0.01 + + pref_to_best = [8, 2, 1] + + pref_to_worst = [1, 5, 8] + + result = bestworst(pref_to_best, pref_to_worst) + + @test isapprox(result.ε, 0.26, atol = eps) + + @test isapprox(result.weights, [0.071, 0.338, 0.589], atol = eps) + end + + +end \ No newline at end of file diff --git a/test/testmcdm.jl b/test/testmcdm.jl index dd808e2..cc03472 100644 --- a/test/testmcdm.jl +++ b/test/testmcdm.jl @@ -1,5 +1,6 @@ @testset "MCDM functions" verbose = true begin + include("mcdm/testbestworst.jl") include("mcdm/testidocriw.jl") include("mcdm/testcilos.jl") include("mcdm/testmoosra.jl")