Skip to content

Commit

Permalink
(#63) Initial implementation of the Best-Worst method
Browse files Browse the repository at this point in the history
  • Loading branch information
jbytecode committed Nov 8, 2024
1 parent 01fe93b commit 732526b
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions docs/src/mcdms.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,9 @@ JMcDM.cilos
## IDOCRIW
```@docs
JMcDM.idocriw
```

## Best-Worst
```@docs
JMcDM.bestworst
```
5 changes: 5 additions & 0 deletions src/JMcDM.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
107 changes: 107 additions & 0 deletions src/bestworst.jl
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions src/seca.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
19 changes: 19 additions & 0 deletions test/mcdm/testbestworst.jl
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions test/testmcdm.jl
Original file line number Diff line number Diff line change
@@ -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")
Expand Down

0 comments on commit 732526b

Please sign in to comment.