# This file contains functions for the Hölder DEA model """ AbstractHolderDEAModel An abstract type representing a Holder DEA model. """ abstract type AbstractHolderDEAModel <: AbstractTechnicalDEAModel end """ HolderL1DEAModel An data structure representing a Höler L1 DEA model. """ struct HolderL1DEAModel <: AbstractHolderDEAModel n::Int64 m::Int64 s::Int64 orient::Symbol rts::Symbol isweighted::Bool dmunames::Union{Vector{String},Nothing} eff::Vector effmin::Vector slackX::Matrix slackY::Matrix lambda::SparseMatrixCSC{Float64, Int64} Xtarget::Matrix Ytarget::Matrix end """ HolderL2DEAModel An data structure representing a Höler L2 DEA model. """ struct HolderL2DEAModel <: AbstractHolderDEAModel n::Int64 m::Int64 s::Int64 orient::Symbol rts::Symbol isweighted::Bool dmunames::Union{Vector{String},Nothing} eff::Vector slackX::Matrix slackY::Matrix lambda::SparseMatrixCSC{Float64, Int64} Xtarget::Matrix Ytarget::Matrix end """ HolderLInfDEAModel An data structure representing a Höler LInf DEA model. """ struct HolderLInfDEAModel <: AbstractHolderDEAModel n::Int64 m::Int64 s::Int64 orient::Symbol rts::Symbol isweighted::Bool dmunames::Union{Vector{String},Nothing} eff::Vector slackX::Matrix slackY::Matrix lambda::SparseMatrixCSC{Float64, Int64} Xtarget::Matrix Ytarget::Matrix end """ deaholder(X, Y; l) Compute the Hölder distance function model using data envelopment analysis for inputs `X` and outputs `Y`, using Hölder norm `l`. # Hölder norm `l` specification - `1`. - `2`. - `Inf`. # Optional Arguments - `weigt=false`: set to `true` for weighted (weakly) Hölder distance function. - `orient=:Input`: chooses the radially oriented input mode. For the radially oriented output model choose `:Output`. - `rts=:CRS`: chooses constant returns to scale. For variable returns to scale choose `:VRS`. - `slack=true`: computes input and output slacks. - `Xref=X`: Identifies the reference set of inputs against which the units are evaluated. - `Yref=Y`: Identifies the reference set of outputs against which the units are evaluated. - `names`: a vector of strings with the names of the decision making units. # Examples ```jldoctest julia> X = [2; 4; 8; 12; 6; 14; 14; 9.412]; julia> Y = [1; 5; 8; 9; 3; 7; 9; 2.353]; julia> deaholder(X, Y, l = 1, rts = :VRS) Hölder L1 DEA Model DMUs = 8; Inputs = 1; Outputs = 1 Orientation = Graph; Returns to Scale = VRS ──────────────────────────────────────────────── efficiency minimum slackX1 slackY1 ──────────────────────────────────────────────── 1 0.0 X1 0.0 0.0 2 0.0 X1 0.0 0.0 3 0.0 X1 0.0 0.0 4 0.0 X1 0.0 0.0 5 3.0 X1 0.0 1.01506e-15 6 2.0 Y1 2.0 0.0 7 0.0 Y1 2.0 0.0 8 6.0 Y1 1.77636e-15 0.0 ──────────────────────────────────────────────── ``` """ function deaholder(X::Union{Matrix,Vector}, Y::Union{Matrix,Vector}; l::Union{Int64,Float64}, weight::Bool = false, orient::Symbol = :Graph, rts::Symbol = :CRS, slack = true, Xref::Union{Matrix,Vector,Nothing} = nothing, Yref::Union{Matrix, Vector,Nothing} = nothing, names::Union{Vector{String},Nothing} = nothing, optimizer::Union{DEAOptimizer,Nothing} = nothing)::AbstractHolderDEAModel # Check parameters nx, m = size(X, 1), size(X, 2) ny, s = size(Y, 1), size(Y, 2) if Xref === nothing Xref = X end if Yref === nothing Yref = Y end nrefx, mref = size(Xref, 1), size(Xref, 2) nrefy, sref = size(Yref, 1), size(Yref, 2) if nx != ny throw(DimensionMismatch("number of rows in X and Y ($nx, $ny) are not equal")); end if nrefx != nrefy throw(DimensionMismatch("number of rows in Xref and Yref ($nrefx, $nrefy) are not equal")); end if m != mref throw(DimensionMismatch("number of columns in X and Xref ($m, $mref) are not equal")); end if s != sref throw(DimensionMismatch("number of columns in Y and Yref ($s, $sref) are not equal")); end if (orient != :Input) && (orient != :Output) && (orient != :Graph) throw(ArgumentError("`orient` must be :Input or :Output")); end # Check l if l == 1 return deaholderl1(X, Y, weight = weight, orient = orient, rts = rts, slack = slack, Xref = Xref, Yref = Yref, names = names, optimizer = optimizer) elseif l == 2 return deaholderl2(X, Y, weight = weight, orient = orient, rts = rts, slack = slack, Xref = Xref, Yref = Yref, names = names, optimizer = optimizer) elseif l == Inf return deaholderlinf(X, Y, weight = weight, orient = orient, rts = rts, slack = slack, Xref = Xref, Yref = Yref, names = names, optimizer = optimizer) else throw(ArgumentError("`l` must be 1, 2 or Inf")); end end function deaholderl1(X::Union{Matrix,Vector}, Y::Union{Matrix,Vector}; weight::Bool = false, orient::Symbol = :Graph, rts::Symbol = :CRS, slack = true, Xref::Union{Matrix,Vector,Nothing} = nothing, Yref::Union{Matrix, Vector,Nothing} = nothing, names::Union{Vector{String},Nothing} = nothing, optimizer::Union{DEAOptimizer,Nothing} = nothing)::HolderL1DEAModel # Get parameters nx, m = size(X, 1), size(X, 2) ny, s = size(Y, 1), size(Y, 2) if Xref === nothing Xref = X end if Yref === nothing Yref = Y end nrefx, mref = size(Xref, 1), size(Xref, 2) nrefy, sref = size(Yref, 1), size(Yref, 2) # Compute DDF efficiency with 1 for each input and output n = nx nref = nrefx effXY = fill(Inf, (n,m+s)) lambdaXY = Array{SparseMatrixCSC{Float64, Int64}}(undef, m + s) if slack == true slackXms = Array{Matrix}(undef, m + s) slackYms = Array{Matrix}(undef, m + s) end if orient == :Graph || orient == :Input for i = 1:m Gxi = zeros(n,m) if weight Gxi[:,i] .= X[:,i] else Gxi[:,i] .= 1 end tempdea = deaddf(X, Y, Gx = Gxi, Gy = :Zeros, rts = rts, Xref = Xref, Yref = Yref, optimizer = optimizer) effXY[:,i] = efficiency(tempdea) lambdaXY[i] = tempdea.lambda if slack == true slackXms[i] = slacks(tempdea, :X) slackYms[i] = slacks(tempdea, :Y) end end end if orient == :Graph || orient == :Output for i = 1:s Gyi = zeros(n,s) if weight Gyi[:,i] .= Y[:,i] else Gyi[:,i] .= 1 end tempdea = deaddf(X, Y, Gx = :Zeros, Gy = Gyi, rts = rts, Xref = Xref, Yref = Yref, optimizer = optimizer) effXY[:,i + m] = efficiency(tempdea) lambdaXY[i + m] = tempdea.lambda if slack == true slackXms[i + m] = slacks(tempdea, :X) slackYms[i + m] = slacks(tempdea, :Y) end end end # Obtain the minimum efficiency scores effi = zeros(n) effmin = fill(0, n) lambda = spzeros(n, nref) if slack == true slackX = zeros(n, m) slackY = zeros(n, s) else slackX = Array{Float64}(undef, 0, 0) slackY = Array{Float64}(undef, 0, 0) end for i = 1:n effi[i], effmin[i] = findmin(effXY[i,:]) lambda[i, :] = lambdaXY[effmin[i]][i,:] if slack == true slackX[i,:] = slackXms[effmin[i]][i,:] slackY[i,:] = slackYms[effmin[i]][i,:] end end # Get X and Y targets Xtarget = convert.(Float64, X[:,:]) Ytarget = convert.(Float64, Y[:,:]) for i = 1:n if effmin[i] <= m if weight Xtarget[i,effmin[i]] = X[i,effmin[i]] - effi[i] * X[i,effmin[i]] else Xtarget[i,effmin[i]] = X[i,effmin[i]] - effi[i] end else if weight Ytarget[i,effmin[i] - m] = Y[i,effmin[i] - m] + effi[i] * Y[i,effmin[i] - m] else Ytarget[i,effmin[i] - m] = Y[i,effmin[i] - m] + effi[i] end end end if slack == true Xtarget = Xtarget - slackX Ytarget = Ytarget + slackY end return HolderL1DEAModel(n, m, s, orient, rts, weight, names, effi, effmin, slackX, slackY, lambda, Xtarget, Ytarget) end function deaholderl2(X::Union{Matrix,Vector}, Y::Union{Matrix,Vector}; weight::Bool = false, orient::Symbol = :Graph, rts::Symbol = :CRS, slack = true, Xref::Union{Matrix,Vector,Nothing} = nothing, Yref::Union{Matrix, Vector,Nothing} = nothing, names::Union{Vector{String},Nothing} = nothing, optimizer::Union{DEAOptimizer,Nothing} = nothing)::HolderL2DEAModel # Check parameters nx, m = size(X, 1), size(X, 2) ny, s = size(Y, 1), size(Y, 2) if Xref === nothing Xref = X end if Yref === nothing Yref = Y end nrefx, mref = size(Xref, 1), size(Xref, 2) nrefy, sref = size(Yref, 1), size(Yref, 2) if rts != :VRS throw(ArgumentError("`rts` must be :VRS if l = 2")); end # Default optimizer if optimizer === nothing throw(ArgumentError("An optimizer that supports SOS constraints must be specified for Hölder l = 2")); end noSOS = false # Compute efficiency for each DMU n = nx nref = n effi = zeros(n) lambdaeff = spzeros(n, nref) Xtarget = convert.(Float64, X[:,:]) Ytarget = convert.(Float64, Y[:,:]) slackX = zeros(n, m) slackY = zeros(n, s) for i=1:n # Value of inputs and outputs to evaluate x0 = X[i,:] y0 = Y[i,:] # Create the optimization model deamodel = newdeamodel(optimizer) if orient == :Input || orient == :Graph @variable(deamodel, Xeff[1:m] >= 0) set_start_value.(Xeff, x0) end if orient == :Output || orient == :Graph @variable(deamodel, Yeff[1:s] >= 0) set_start_value.(Yeff, y0) end if weight if orient == :Graph @objective(deamodel, Min, sum(((x0[j] - Xeff[j]) / x0[j])^2 for j in 1:m) + sum(((Yeff[j] - y0[j]) / y0[j])^2 for j in 1:s) ) elseif orient == :Input @objective(deamodel, Min, sum(((x0[j] - Xeff[j]) / x0[j])^2 for j in 1:m) ) elseif orient == :Output @objective(deamodel, Min, sum(((Yeff[j] - y0[j]) / y0[j])^2 for j in 1:s) ) end else if orient == :Graph @objective(deamodel, Min, sum((x0[j] - Xeff[j])^2 for j in 1:m) + sum((Yeff[j] - y0[j])^2 for j in 1:s) ) elseif orient == :Input @objective(deamodel, Min, sum((x0[j] - Xeff[j])^2 for j in 1:m) ) elseif orient == :Output @objective(deamodel, Min, sum((Yeff[j] - y0[j])^2 for j in 1:s) ) end end @variable(deamodel, gammatau[1:2, 1:nref] >= 0) @variable(deamodel, vsX[1:2,1:m] >= 0) @variable(deamodel, musY[1:2,1:s] >= 0) @variable(deamodel, psi) set_start_value.(gammatau[1,i], 1) if orient == :Input || orient == :Graph @constraint(deamodel, [j in 1:m], sum(Xref[t,j] * gammatau[1,t] for t in 1:nref) == Xeff[j] - vsX[2,j]) elseif orient == :Output @constraint(deamodel, [j in 1:m], sum(Xref[t,j] * gammatau[1,t] for t in 1:nref) == x0[j] - vsX[2,j]) end if orient == :Output || orient == :Graph @constraint(deamodel, [j in 1:s], sum(Yref[t,j] * gammatau[1,t] for t in 1:nref) == Yeff[j] + musY[2,j]) elseif orient == :Input @constraint(deamodel, [j in 1:s], sum(Yref[t,j] * gammatau[1,t] for t in 1:nref) == y0[j] + musY[2,j]) end @constraint(deamodel, sum(gammatau[1,:]) == 1) if orient == :Graph @constraint(deamodel, sum(vsX[1,t] for t in 1:m) + sum(musY[1,t] for t in 1:s) == 1) elseif orient == :Input @constraint(deamodel, sum(vsX[1,t] for t in 1:m) == 1) elseif orient == :Output @constraint(deamodel, sum(musY[1,t] for t in 1:s) == 1) end @constraint(deamodel, [j in 1:nref], sum(vsX[1,t] * Xref[j,t] for t in 1:m) - sum(musY[1,t] * Yref[j,t] for t in 1:s) + psi - gammatau[2,j] == 0 ) # SOS constraints try @constraint(deamodel, [j in 1:nref], gammatau[:,j] in SOS1()) catch @constraint(deamodel, [j in 1:nref], gammatau[1,j] * gammatau[2,j] == 0) noSOS = true end try @constraint(deamodel, [j in 1:m], vsX[:,j] in SOS1()) catch @constraint(deamodel, [j in 1:m], vsX[1,j] * vsX[2,j] == 0) noSOS = true end try @constraint(deamodel, [j in 1:s], musY[:,j] in SOS1()) catch @constraint(deamodel, [j in 1:s], musY[1,j] * musY[2,j] == 0) noSOS = true end # Optimize and return results JuMP.optimize!(deamodel) effi[i] = JuMP.objective_value(deamodel) if effi[i] < 0 # If objective function is negative change to 0, to avoid error when calculatin the square root. @warn ("DMU $i negative value in the objective function: $(effi[i]). Replaced to 0") effi[i] = 0 end effi[i] = sqrt(effi[i] ) lambdaeff[i,:] = JuMP.value.(gammatau[1,:]) if orient == :Input || orient == :Graph Xtarget[i,:] = JuMP.value.(Xeff) end if orient == :Output || orient == :Graph Ytarget[i,:] = JuMP.value.(Yeff) end slackX[i,:] = JuMP.value.(vsX[2,:]) slackY[i,:] = JuMP.value.(musY[2,:]) if (termination_status(deamodel) != MOI.OPTIMAL) && (termination_status(deamodel) != MOI.LOCALLY_SOLVED) @warn ("DMU $i termination status: $(termination_status(deamodel)). Primal status: $(primal_status(deamodel)). Dual status: $(dual_status(deamodel))") end end # Add slacks to X and Y targets Xtarget = Xtarget - slackX Ytarget = Ytarget + slackY # Display warning if model not solved with SOS constraints if noSOS @warn ("Model solved with $(optimizer.optimizer) is innacuate. Use a solver that supports SOS1 constraints.") end return HolderL2DEAModel(n, m, s, orient, rts, weight, names, effi, slackX, slackY, lambdaeff, Xtarget, Ytarget) end function deaholderlinf(X::Union{Matrix,Vector}, Y::Union{Matrix,Vector}; weight::Bool = true, orient::Symbol = :Graph, rts::Symbol = :CRS, slack = true, Xref::Union{Matrix,Vector,Nothing} = nothing, Yref::Union{Matrix, Vector,Nothing} = nothing, names::Union{Vector{String},Nothing} = nothing, optimizer::Union{DEAOptimizer,Nothing} = nothing)::HolderLInfDEAModel # Get parameters nx, m = size(X, 1), size(X, 2) ny, s = size(Y, 1), size(Y, 2) n = nx # Buil direction based on orientation if orient == :Input if weight Gx = X else Gx = :Ones end Gy = :Zeros elseif orient == :Output Gx = :Zeros if weight Gy = Y else Gy = :Ones end elseif orient == :Graph if weight Gx = X Gy = Y else Gx = :Ones Gy = :Ones end end # Get DDF technical efficiency tempdea = deaddf(X, Y, Gx = Gx, Gy = Gy, rts = rts, slack = slack, Xref = Xref, Yref = Yref, optimizer = optimizer) effi = efficiency(tempdea) lambda = peersmatrix(tempdea) slackX = slacks(tempdea, :X) slackY = slacks(tempdea, :Y) Xtarget = targets(tempdea, :X) Ytarget = targets(tempdea, :Y) return HolderLInfDEAModel(n, m, s, orient, rts, weight, names, effi, slackX, slackY, lambda, Xtarget, Ytarget) end function efficiency(model::HolderL1DEAModel, type::Symbol)::Vector if (type == :min) return model.effmin end throw(ArgumentError("$(typeof(model)) with orienation has no efficiency $(type)")); end function Base.show(io::IO, x::HolderL1DEAModel) compact = get(io, :compact, false) n = nobs(x) m = ninputs(x) s = noutputs(x) isweighted = x.isweighted dmunames = names(x) eff = efficiency(x) effmin = x.effmin slackX = slacks(x, :X) slackY = slacks(x, :Y) hasslacks = ! isempty(slackX) effmincol = Array{String}(undef,n) for i = 1:n if effmin[i] <= m effmincol[i] = "X" * string(Int(effmin[i])) else effmincol[i] = "Y" * string(Int(effmin[i] - m)) end end if !compact print(io, "Hölder L1 DEA Model \n") print(io, "DMUs = ", n) print(io, "; Inputs = ", m) print(io, "; Outputs = ", s) print(io, "\n") print(io, "Orientation = ", string(x.orient)) print(io, "; Returns to Scale = ", string(x.rts)) print(io, "\n") if isweighted print(io, "Weighted (weakly) Hölder distance function \n") end if hasslacks == true show(io, CoefTable(hcat(eff, effmincol, slackX, slackY), ["efficiency"; "minimum"; ["slackX$i" for i in 1:m ]; ["slackY$i" for i in 1:s ]], dmunames)) else show(io, CoefTable(hcat(eff, effmincol), ["efficiency"; "minimum"], dmunames)) end end end function Base.show(io::IO, x::HolderL2DEAModel) compact = get(io, :compact, false) n = nobs(x) m = ninputs(x) s = noutputs(x) isweighted = x.isweighted dmunames = names(x) eff = efficiency(x) slackX = slacks(x, :X) slackY = slacks(x, :Y) hasslacks = ! isempty(slackX) if !compact print(io, "Hölder L2 DEA Model \n") print(io, "DMUs = ", n) print(io, "; Inputs = ", m) print(io, "; Outputs = ", s) print(io, "\n") print(io, "Orientation = ", string(x.orient)) print(io, "; Returns to Scale = ", string(x.rts)) print(io, "\n") if isweighted print(io, "Weighted (weakly) Hölder distance function \n") end show(io, CoefTable(hcat(eff, slackX, slackY), ["efficiency"; ["slackX$i" for i in 1:m ]; ["slackY$i" for i in 1:s ]], dmunames)) end end function Base.show(io::IO, x::HolderLInfDEAModel) compact = get(io, :compact, false) n = nobs(x) m = ninputs(x) s = noutputs(x) isweighted = x.isweighted dmunames = names(x) eff = efficiency(x) slackX = slacks(x, :X) slackY = slacks(x, :Y) hasslacks = ! isempty(slackX) if !compact print(io, "Hölder LInf DEA Model \n") print(io, "DMUs = ", n) print(io, "; Inputs = ", m) print(io, "; Outputs = ", s) print(io, "\n") print(io, "Orientation = ", string(x.orient)) print(io, "; Returns to Scale = ", string(x.rts)) print(io, "\n") if isweighted print(io, "Weighted (weakly) Hölder distance function \n") end if hasslacks == true show(io, CoefTable(hcat(eff, slackX, slackY), ["efficiency"; ["slackX$i" for i in 1:m ]; ["slackY$i" for i in 1:s ]], dmunames)) else show(io, CoefTable(hcat(eff), ["efficiency"], dmunames)) end end end