diff --git a/CHANGELOG.md b/CHANGELOG.md index f8b78ec..773b37b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### 0.3.9 +- Implement PSI (Preference Selection Index) method + ### 0.3.8 - Small bug fixes - Remove the critic method from summary() diff --git a/Project.toml b/Project.toml index 4e3a667..40b9e98 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "JMcDM" uuid = "358108f5-d052-4d0a-8344-d5384e00c0e5" authors = ["Mehmet Hakan Satman (jbytecode) ", "Bahadir Fatih Yildirim ", "Ersagun Kuruca"] -version = "0.3.8" +version = "0.3.9" [deps] DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" diff --git a/README.md b/README.md index dc5ff9f..207e4a8 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ Please check out the reference manual [here](https://jbytecode.github.io/JMcDM/d - Copeland (For combining multiple ordering results) - SD Method for determining weights of criteria - ROV (Range of Value) Method +- PSI (Preference Selection Index) Method ### SCDM Tools diff --git a/src/JMcDM.jl b/src/JMcDM.jl index ca477f8..7902125 100755 --- a/src/JMcDM.jl +++ b/src/JMcDM.jl @@ -38,6 +38,7 @@ include("cocoso.jl") include("critic.jl") include("entropy.jl") include("codas.jl") +include("psi.jl") include("summary.jl") @@ -72,6 +73,7 @@ export VikorMethod export WPMMethod export WaspasMethod export MarcosMethod +export PSIMethod export MCDMSetting @@ -101,6 +103,7 @@ export EntropyResult export CODASResult export SDResult export ROVResult +export PSIResult # export game type export GameResult @@ -155,6 +158,7 @@ export entropy export codas export sd export rov +export psi #  export SCDM tools export laplace diff --git a/src/print.jl b/src/print.jl index aa5335c..4630279 100644 --- a/src/print.jl +++ b/src/print.jl @@ -22,7 +22,7 @@ end function Base.show(io::IO, result::CODASResult) println(io, "Scores:") println(io, result.scores) - println(io, "Ranking: ") + println(io, "Ordering: ") println(io, result.ranking) println(io, "Best indices:") println(io, result.bestIndex) @@ -32,14 +32,14 @@ end function Base.show(io::IO, result::COPRASResult) println(io, "Scores:") println(io, result.scores) - println(io, "Ranking: ") + println(io, "Ordering: ") println(io, result.ranking) println(io, "Best indices:") println(io, result.bestIndex) end function Base.show(io::IO, result::CRITICResult) - println(io, "Ranking: ") + println(io, "Ordering: ") println(io, result.ranking) println(io, "Best indices:") println(io, result.bestIndex) @@ -48,7 +48,7 @@ end function Base.show(io::IO, result::CoCoSoResult) println(io, "Scores:") println(io, result.scores) - println(io, "Ranking: ") + println(io, "Ordering: ") println(io, result.ranking) println(io, "Best indices:") println(io, result.bestIndex) @@ -57,7 +57,7 @@ end function Base.show(io::IO, result::EDASResult) println(io, "Scores:") println(io, result.scores) - println(io, "Ranking: ") + println(io, "Ordering: ") println(io, result.ranking) println(io, "Best indices:") println(io, result.bestIndex) @@ -80,7 +80,7 @@ end function Base.show(io::IO, result::MABACResult) println(io, "Scores:") println(io, result.scores) - println(io, "Ranking: ") + println(io, "Ordering: ") println(io, result.ranking) println(io, "Best indices:") println(io, result.bestIndex) @@ -89,7 +89,7 @@ end function Base.show(io::IO, result::MAIRCAResult) println(io, "Scores:") println(io, result.scores) - println(io, "Ranking: ") + println(io, "Ordering: ") println(io, result.ranking) println(io, "Best indices:") println(io, result.bestIndex) @@ -98,7 +98,7 @@ end function Base.show(io::IO, result::MARCOSResult) println(io, "Scores:") println(io, result.scores) - println(io, "Ranking: ") + println(io, "Ordering: ") println(io, result.ranking) println(io, "Best indices:") println(io, result.bestIndex) @@ -114,7 +114,7 @@ end function Base.show(io::IO, result::PrometheeResult) println(io, "Scores:") println(io, result.scores) - println(io, "Ranking: ") + println(io, "Ordering: ") println(io, result.ranking) println(io, "Best indices:") println(io, result.bestIndex) @@ -123,7 +123,7 @@ end function Base.show(io::IO, result::SawResult) println(io, "Scores:") println(io, result.scores) - println(io, "Ranking: ") + println(io, "Ordering: ") println(io, result.ranking) println(io, "Best indices:") println(io, result.bestIndex) @@ -139,7 +139,7 @@ end function Base.show(io::IO, result::WASPASResult) println(io, "Scores:") println(io, result.scores) - println(io, "Ranking: ") + println(io, "Ordering: ") println(io, result.ranking) println(io, "Best indices:") println(io, result.bestIndex) @@ -148,7 +148,7 @@ end function Base.show(io::IO, result::WPMResult) println(io, "Scores:") println(io, result.scores) - println(io, "Ranking: ") + println(io, "Ordering: ") println(io, result.ranking) println(io, "Best indices:") println(io, result.bestIndex) @@ -157,7 +157,7 @@ end function Base.show(io::IO, result::ROVResult) println(io, "Scores:") println(io, result.scores) - println(io, "Ranking: ") + println(io, "Ordering: ") println(io, result.ranks) println(io, "Uminus:") println(io, result.uminus) @@ -165,6 +165,14 @@ function Base.show(io::IO, result::ROVResult) println(io, result.uplus) end +function Base.show(io::IO, result::PSIResult) + println(io, "Scores:") + println(io, result.scores) + println(io, "Ordering: (from worst to best)") + println(io, result.rankings) + println(io, "Best indices:") + println(io, result.bestIndex) +end diff --git a/src/psi.jl b/src/psi.jl new file mode 100644 index 0000000..8c9fd59 --- /dev/null +++ b/src/psi.jl @@ -0,0 +1,131 @@ +""" + psi(decisionMat, fns) + +Apply PSI (Preference Selection Index) method for a given matrix and directions of optimizations. + +# Arguments: + - `decisionMat::DataFrame`: n × m matrix of objective values for n alterntives and m criteria + - `fns::Array{Function, 1}`: m-vector of functions to be applied on the columns. + +# Description +psi() applies the PSI method to rank n alterntives subject to m criteria which are supposed to be +either maximized or minimized. + +# Output +- `::PSIResult`: PSIResult object that holds multiple outputs including scores, rankings, and best index. + +# Examples +```julia-repl +julia> decmat = [3 12.5 2 120 14 3; + 5 15 3 110 38 4; + 3 13 2 120 19 3; + 4 14 2 100 31 4; + 3 15 1.5 125 40 4] +5×6 Array{Float64,2}: + 3.0 12.5 2.0 120.0 14.0 3.0 + 5.0 15.0 3.0 110.0 38.0 4.0 + 3.0 13.0 2.0 120.0 19.0 3.0 + 4.0 14.0 2.0 100.0 31.0 4.0 + 3.0 15.0 1.5 125.0 40.0 4.0 + +julia> df = makeDecisionMatrix(decmat) +5×6 DataFrame + Row │ Crt1 Crt2 Crt3 Crt4 Crt5 Crt6 + │ Float64 Float64 Float64 Float64 Float64 Float64 +─────┼────────────────────────────────────────────────────── + 1 │ 3.0 12.5 2.0 120.0 14.0 3.0 + 2 │ 5.0 15.0 3.0 110.0 38.0 4.0 + 3 │ 3.0 13.0 2.0 120.0 19.0 3.0 + 4 │ 4.0 14.0 2.0 100.0 31.0 4.0 + 5 │ 3.0 15.0 1.5 125.0 40.0 4.0 + + +julia> fns = [maximum, minimum, minimum, maximum, minimum, maximum]; + +julia> result = psi(df, fns) +Scores: +[1.1252480520930113, 0.762593438114615, 1.1060476892230147, 1.0059872302387025, 0.7865885089329105] +Ordering: (from worst to best) +[2, 5, 4, 3, 1] +Best indices: +1 + +julia> result.bestIndex +1 +``` + +# References +Maniya, Kalpesh, and Mangal Guido Bhatt. "A selection of material using a novel type decision-making method: +Preference selection index method." Materials & Design 31.4 (2010): 1785-1789 +""" +function psi(decisionMat::DataFrame, fns::Array{Function,1})::PSIResult + + function PV(v) + mymean = mean(v) + meandiff = v .- mymean + meandiffsq = meandiff .* meandiff + return sum(meandiffsq) + end + + row, col = size(decisionMat) + normalizedDecisionMat = similar(decisionMat) + colminmax = zeros(Float64, col) + @inbounds for i in 1:col + colminmax[i] = decisionMat[:, i] |> fns[i] + if fns[i] == maximum + normalizedDecisionMat[:, i] = decisionMat[:, i] ./ colminmax[i] + elseif fns[i] == minimum + normalizedDecisionMat[:, i] = colminmax[i] ./ decisionMat[:, i] + end + end + + pvs = zeros(Float64, row) + for i in 1:row + pvs[i] = PV(normalizedDecisionMat[i, :] |> collect) + end + + phis = 1.0 .- pvs + sum_phis = sum(phis) + + psis = phis ./ sum_phis + + Is = zeros(Float64, row) + for i in 1:row + Is[i] = psis[i] .* collect(normalizedDecisionMat[i, :]) |> sum + end + + scores = Is + ranks = sortperm(scores) + bestindex = ranks |> last + + result = PSIResult( + scores, + ranks, + bestindex + ) + + return result +end + + +""" + psi(setting) + +Apply PSI (Preference Selection Index) method for a given matrix and weights. + +# Arguments: + - `setting::MCDMSetting`: MCDMSetting object. + +# Description +psi() applies the PSI method to rank n alterntives subject to m criteria which are supposed to be +either maximized or minimized. + +# Output +- `::PSIResult`: PSIResult object that holds multiple outputs including scores, rankings, and best index. +""" +function psi(setting::MCDMSetting)::PSIResult + psi( + setting.df, + setting.fns + ) +end \ No newline at end of file diff --git a/src/types.jl b/src/types.jl index c02d3fe..466c871 100755 --- a/src/types.jl +++ b/src/types.jl @@ -50,6 +50,12 @@ struct MCDMSetting fns::Array{Function, 1} end +struct PSIResult <: MCDMResult + scores::Array{Float64,1} + rankings::Array{Int, 1} + bestIndex::Int +end + struct TopsisResult <: MCDMResult decisionMatrix::DataFrame weights::Array{Float64,1} @@ -377,6 +383,9 @@ end struct MaircaMethod <: MCDMMethod end +struct PSIMethod <: MCDMMethod +end + struct MooraMethod <: MCDMMethod method::Symbol diff --git a/test.jl b/test.jl new file mode 100644 index 0000000..835d2bc --- /dev/null +++ b/test.jl @@ -0,0 +1,18 @@ +using JMcDM +using DataFrames + + + + +df = DataFrame() +df[:, :x] = Float64[9, 8, 7] +df[:, :y] = Float64[7, 7, 8] +df[:, :z] = Float64[6, 9, 6] +df[:, :q] = Float64[7, 6, 6] +w = Float64[4, 2, 6, 8] + +fns = makeminmax([maximum, maximum, maximum, maximum]) + +result = psi(df, fns) + + diff --git a/test/testmcdm.jl b/test/testmcdm.jl index e78c7b5..75537f3 100644 --- a/test/testmcdm.jl +++ b/test/testmcdm.jl @@ -1,5 +1,28 @@ @testset "MCDM functions" begin + @testset "PSI" begin + tol = 0.0001 + + df = DataFrame() + df[:, :x] = Float64[9, 8, 7] + df[:, :y] = Float64[7, 7, 8] + df[:, :z] = Float64[6, 9, 6] + df[:, :q] = Float64[7, 6, 6] + w = Float64[4, 2, 6, 8] + + fns = makeminmax([maximum, maximum, maximum, maximum]) + + result = psi(df, fns) + + @test result isa PSIResult + @test result.bestIndex == 2 + @test isapprox( + [1.1487059780663555, 1.252775986851622, 1.0884916686098811], + result.scores, + atol = tol + ) + end + @testset "ROV" begin tol = 0.01 mat = [