diff --git a/src/LinQuadOptInterface.jl b/src/LinQuadOptInterface.jl index 9dd3e4d..5cc6497 100644 --- a/src/LinQuadOptInterface.jl +++ b/src/LinQuadOptInterface.jl @@ -133,13 +133,18 @@ end # Abstract + macro abstract type LinQuadOptimizer <: MOI.AbstractOptimizer end @def LinQuadOptimizerBase begin + inner#::LinQuadOptInterface.LinQuadOptimizer + name::String + obj_is_quad::Bool obj_sense::MOI.OptimizationSense last_variable_reference::UInt64 variable_mapping::Dict{MathOptInterface.VariableIndex, Int} + variable_names::Dict{MathOptInterface.VariableIndex, String} + variable_names_rev::Dict{String, MathOptInterface.VariableIndex} variable_references::Vector{MathOptInterface.VariableIndex} variable_primal_solution::Vector{Float64} @@ -149,13 +154,16 @@ abstract type LinQuadOptimizer <: MOI.AbstractOptimizer end constraint_mapping::LinQuadOptInterface.ConstraintMapping constraint_constant::Vector{Float64} - constraint_primal_solution::Vector{Float64} constraint_dual_solution::Vector{Float64} qconstraint_primal_solution::Vector{Float64} qconstraint_dual_solution::Vector{Float64} + # TODO(odow): temp hack for constraint names + constraint_names::Dict{Any, String} + constraint_names_rev::Dict{String, Any} + objective_constant::Float64 termination_status::MathOptInterface.TerminationStatusCode @@ -170,11 +178,13 @@ end function MOI.isempty(m::LinQuadOptimizer) ret = true - + ret = ret && m.name == "" ret = ret && m.obj_is_quad == false ret = ret && m.obj_sense == MOI.FeasibilitySense ret = ret && m.last_variable_reference == 0 ret = ret && isempty(m.variable_mapping) + ret = ret && isempty(m.variable_names) + ret = ret && isempty(m.variable_names_rev) ret = ret && isempty(m.variable_references) ret = ret && isempty(m.variable_primal_solution) ret = ret && isempty(m.variable_dual_solution) @@ -185,6 +195,8 @@ function MOI.isempty(m::LinQuadOptimizer) ret = ret && isempty(m.constraint_dual_solution) ret = ret && isempty(m.qconstraint_primal_solution) ret = ret && isempty(m.qconstraint_dual_solution) + ret = ret && isempty(m.constraint_names) + ret = ret && isempty(m.constraint_names_rev) ret = ret && m.objective_constant == 0.0 ret = ret && m.termination_status == MOI.OtherError ret = ret && m.primal_status == MOI.UnknownResultStatus @@ -196,7 +208,7 @@ function MOI.isempty(m::LinQuadOptimizer) return ret end function MOI.empty!(m::M, env = nothing) where M<:LinQuadOptimizer - + m.name = "" m.inner = LinQuadModel(M,env) m.obj_is_quad = false @@ -204,6 +216,8 @@ function MOI.empty!(m::M, env = nothing) where M<:LinQuadOptimizer m.last_variable_reference = 0 m.variable_mapping = Dict{MathOptInterface.VariableIndex, Int}() + m.variable_names = Dict{MathOptInterface.VariableIndex, String}() + m.variable_names_rev = Dict{String, MathOptInterface.VariableIndex}() m.variable_references = MathOptInterface.VariableIndex[] m.variable_primal_solution = Float64[] @@ -213,13 +227,15 @@ function MOI.empty!(m::M, env = nothing) where M<:LinQuadOptimizer m.constraint_mapping = LinQuadOptInterface.ConstraintMapping() m.constraint_constant = Float64[] - m.constraint_primal_solution = Float64[] m.constraint_dual_solution = Float64[] m.qconstraint_primal_solution = Float64[] m.qconstraint_dual_solution = Float64[] + m.constraint_names = Dict{Any, String}() + m.constraint_names_rev = Dict{String, Any}() + m.objective_constant = 0.0 m.termination_status = MathOptInterface.OtherError @@ -233,6 +249,14 @@ function MOI.empty!(m::M, env = nothing) where M<:LinQuadOptimizer nothing end +function MOI.get(m::LinQuadOptimizer, ::MOI.Name) + m.name +end +MOI.canget(m::LinQuadOptimizer, ::MOI.Name) = true +function MOI.set!(m::LinQuadOptimizer, ::MOI.Name, name::String) + m.name = name +end +MOI.canset(m::LinQuadOptimizer, ::MOI.Name) = true function MOI.supportsconstraint(m::LinQuadOptimizer, ft::Type{F}, st::Type{S}) where F <: MOI.AbstractFunction where S <: MOI.AbstractSet (ft,st) in lqs_supported_constraints(m) @@ -257,6 +281,6 @@ include("objective.jl") include("solve.jl") include("copy.jl") -include("ref.jl") +include("solver_interface.jl") -end \ No newline at end of file +end diff --git a/src/constraints.jl b/src/constraints.jl index 4db0e56..7954a7e 100644 --- a/src/constraints.jl +++ b/src/constraints.jl @@ -87,7 +87,7 @@ end =# function MOI.get(m::LinQuadOptimizer, ::MOI.ListOfConstraintIndices{F, S}) where F where S - collect(keys(constrdict(m, CI{F,S}(UInt(0))))) + sort(collect(keys(constrdict(m, CI{F,S}(UInt(0))))), by=x->x.value) end function MOI.canget(m::LinQuadOptimizer, ::MOI.ListOfConstraintIndices{F, S}) where F where S return (F,S) in lqs_supported_constraints(m) @@ -131,6 +131,48 @@ function setvariablebound!(m::LinQuadOptimizer, v::SinVar, set::IV) setvariablebound!(m, getcol(m, v), set.lower, _variablelb(m)) end +function MOI.get(m::LinQuadOptimizer, ::MOI.ConstraintName, c::MOI.ConstraintIndex) + if haskey(m.constraint_names, c) + m.constraint_names[c] + else + "" + end +end +MOI.canget(m::LinQuadOptimizer, ::MOI.ConstraintName, ::Type{<:MOI.ConstraintIndex}) = true +function MOI.set!(m::LinQuadOptimizer, ::MOI.ConstraintName, ref::MOI.ConstraintIndex, name::String) + if haskey(m.constraint_names_rev, name) + if m.constraint_names_rev[name] != ref + error("Duplicate constraint name: $(name)") + end + elseif name != "" + if haskey(m.constraint_names, ref) + # we're renaming an existing constraint + old_name = m.constraint_names[ref] + delete!(m.constraint_names_rev, old_name) + end + m.constraint_names[ref] = name + m.constraint_names_rev[name] = ref + end +end +MOI.canset(m::LinQuadOptimizer, ::MOI.ConstraintName, ::Type{<:MOI.ConstraintIndex}) = true + +function deleteconstraintname!(m::LinQuadOptimizer, ref) + if haskey(m.constraint_names, ref) + name = m.constraint_names[ref] + delete!(m.constraint_names_rev, name) + delete!(m.constraint_names, ref) + end +end + +function MOI.get(m::LinQuadOptimizer, ::Type{<:MOI.ConstraintIndex}, name::String) + m.constraint_names_rev[name] +end +MOI.canget(m::LinQuadOptimizer, ::Type{MOI.ConstraintIndex}, name::String) = haskey(m.constraint_names_rev, name) + +function MOI.canget(m::LinQuadOptimizer, ::Type{FS}, name::String) where FS <: MOI.ConstraintIndex + haskey(m.constraint_names_rev, name) && typeof(m.constraint_names_rev[name]) == FS +end + function MOI.addconstraint!(m::LinQuadOptimizer, v::SinVar, set::S) where S <: LinSets setvariablebound!(m, v, set) m.last_constraint_reference += 1 @@ -186,6 +228,7 @@ MOI.canmodifyconstraint(::LinQuadOptimizer, ::SVCI{S}, ::Type{S}) where S <: Lin =# function MOI.delete!(m::LinQuadOptimizer, c::SVCI{S}) where S <: LinSets + deleteconstraintname!(m, c) dict = constrdict(m, c) vref = dict[c] setvariablebound!(m, SinVar(vref), IV(-Inf, Inf)) @@ -216,14 +259,14 @@ function MOI.addconstraint!(m::LinQuadOptimizer, func::VecVar, set::S) where S < n = MOI.dimension(set) lqs_addrows!(m, collect(1:n), getcol.(m, func.variables), ones(n), fill(_getsense(m,set),n), zeros(n)) - + dict = constrdict(m, ref) dict[ref] = collect(rows+1:rows+n) append!(m.constraint_primal_solution, fill(NaN,n)) append!(m.constraint_dual_solution, fill(NaN,n)) - append!(m.constraint_constant, fill(0.0,n)) + append!(m.constraint_constant, fill(0.0,n)) return ref end @@ -278,11 +321,12 @@ function unsafe_addconstraint!(m::LinQuadOptimizer, func::Linear, set::T) where addlinearconstraint!(m, func, set) m.last_constraint_reference += 1 ref = LCI{T}(m.last_constraint_reference) + dict = constrdict(m, ref) dict[ref] = lqs_getnumrows(m) push!(m.constraint_primal_solution, NaN) push!(m.constraint_dual_solution, NaN) - push!(m.constraint_constant, func.constant) + push!(m.constraint_constant, func.constant) return ref end @@ -429,6 +473,7 @@ function deleteref!(m::LinQuadOptimizer, row::Int, ref::LCI{<: LinSets}) deleteref!(cmap(m).interval, row, ref) end function MOI.delete!(m::LinQuadOptimizer, c::LCI{<: LinSets}) + deleteconstraintname!(m, c) dict = constrdict(m, c) row = dict[c] lqs_delrows!(m, row, row) @@ -473,6 +518,7 @@ function MOI.addconstraint!(m::LinQuadOptimizer, v::SinVar, ::MOI.ZeroOne) ref end function MOI.delete!(m::LinQuadOptimizer, c::SVCI{MOI.ZeroOne}) + deleteconstraintname!(m, c) dict = constrdict(m, c) (v, lb, ub) = dict[c] lqs_chgctype!(m, [getcol(m, v)], [lqs_vartype_map(m)[:CONTINUOUS]]) @@ -509,6 +555,7 @@ function MOI.addconstraint!(m::LinQuadOptimizer, v::SinVar, ::MOI.Integer) end function MOI.delete!(m::LinQuadOptimizer, c::SVCI{MOI.Integer}) + deleteconstraintname!(m, c) dict = constrdict(m, c) v = dict[c] lqs_chgctype!(m, [getcol(m, v)], [lqs_vartype_map(m)[:CONTINUOUS]]) @@ -553,6 +600,7 @@ function MOI.addconstraint!(m::LinQuadOptimizer, v::VecVar, sos::SOS2) end function MOI.delete!(m::LinQuadOptimizer, c::VVCI{<:Union{SOS1, SOS2}}) + deleteconstraintname!(m, c) dict = constrdict(m, c) idx = dict[c] lqs_delsos!(m, idx, idx) @@ -597,9 +645,10 @@ function MOI.addconstraint!(m::LinQuadOptimizer, func::Quad, set::S) where S <: m.last_constraint_reference += 1 ref = QCI{S}(m.last_constraint_reference) dict = constrdict(m, ref) - dict[ref] = lqs_getnumqconstrs(m) push!(m.qconstraint_primal_solution, NaN) push!(m.qconstraint_dual_solution, NaN) + # dict[ref] = lqs_getnumqconstrs(m) + dict[ref] = length(m.qconstraint_primal_solution) return ref end @@ -752,6 +801,7 @@ function MOI.transformconstraint!(m::LinQuadOptimizer, ref::LCI{S1}, newset::S2) dict2 = constrdict(m, ref2) dict2[ref2] = row delete!(dict, ref) + deleteconstraintname!(m, ref) return ref2 end function MOI.cantransformconstraint(m::LinQuadOptimizer, ref::LCI{S}, newset::S) where S @@ -759,4 +809,4 @@ function MOI.cantransformconstraint(m::LinQuadOptimizer, ref::LCI{S}, newset::S) end function MOI.cantransformconstraint(m::LinQuadOptimizer, ref::LCI{S1}, newset::S2) where S1 where S2 <: Union{LE, GE, EQ} true -end \ No newline at end of file +end diff --git a/src/copy.jl b/src/copy.jl index 5c09283..bd90c67 100644 --- a/src/copy.jl +++ b/src/copy.jl @@ -1,11 +1,14 @@ -MOI.copy!(dest::LinQuadOptimizer, src::MOI.ModelLike) = MOIU.defaultcopy!(dest, src) -MOI.copy!(dest::MOI.ModelLike, src::LinQuadOptimizer) = MOIU.defaultcopy!(dest, src) -MOI.copy!(dest::LinQuadOptimizer, src::LinQuadOptimizer) = MOIU.defaultcopy!(dest, src) +MOI.copy!(dest::LinQuadOptimizer, src::MOI.ModelLike; copynames=false) = MOIU.defaultcopy!(dest, src, copynames) +MOI.copy!(dest::MOI.ModelLike, src::LinQuadOptimizer; copynames=false) = MOIU.defaultcopy!(dest, src, copynames) +MOI.copy!(dest::LinQuadOptimizer, src::LinQuadOptimizer; copynames=false) = MOIU.defaultcopy!(dest, src, copynames) MOI.canget(::LinQuadOptimizer, ::MOI.ListOfVariableAttributesSet) = true MOI.get(::LinQuadOptimizer, ::MOI.ListOfVariableAttributesSet) = MOI.AbstractVariableAttribute[] MOI.canget(::LinQuadOptimizer, ::MOI.ListOfModelAttributesSet) = true -MOI.get(::LinQuadOptimizer, ::MOI.ListOfModelAttributesSet) = [MOI.ObjectiveSense, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}] +MOI.get(m::LinQuadOptimizer, ::MOI.ListOfModelAttributesSet) = [ + MOI.ObjectiveSense, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}, + (MOI.get(m, MOI.Name()) != "") ? MOI.Name() : nothing +] MOI.canget(::LinQuadOptimizer, ::MOI.ListOfConstraintAttributesSet{F,S}) where F where S = true MOI.get(::LinQuadOptimizer, ::MOI.ListOfConstraintAttributesSet{F,S}) where F where S = MOI.AbstractConstraintAttribute[] - diff --git a/src/objective.jl b/src/objective.jl index a8577a7..bdca433 100644 --- a/src/objective.jl +++ b/src/objective.jl @@ -13,7 +13,7 @@ MOI.canset(m::LinQuadOptimizer, ::Type{MOI.ObjectiveFunction{F}}) where F<:MOI.A function MOI.canset(m::LinQuadOptimizer, ::MOI.ObjectiveFunction{F}) where F<:MOI.AbstractFunction return F in lqs_supported_objectives(m) end -MOI.set!(m::LinQuadOptimizer, ::Type{MOI.ObjectiveFunction{F}}, objf::Linear) where F = MOI.set!(m, MOI.ObjectiveFunction{F}(), objf::Linear) +MOI.set!(m::LinQuadOptimizer, ::Type{MOI.ObjectiveFunction{F}}, objf::Linear) where F = MOI.set!(m, MOI.ObjectiveFunction{F}(), objf::Linear) function MOI.set!(m::LinQuadOptimizer, ::MOI.ObjectiveFunction{F}, objf::Linear) where F cobjf = MOIU.canonical(objf) unsafe_set!(m, MOI.ObjectiveFunction{Linear}(), cobjf) @@ -36,13 +36,13 @@ end function _setsense!(m::LinQuadOptimizer, sense::MOI.OptimizationSense) if sense == MOI.MinSense - lqs_chgobjsen!(m, :Min) + lqs_chgobjsen!(m, :min) m.obj_sense = MOI.MinSense elseif sense == MOI.MaxSense - lqs_chgobjsen!(m, :Max) + lqs_chgobjsen!(m, :max) m.obj_sense = MOI.MaxSense elseif sense == MOI.FeasibilitySense - lqs_chgobjsen!(m, :Min) + lqs_chgobjsen!(m, :min) unsafe_set!(m, MOI.ObjectiveFunction{Linear}(), MOI.ScalarAffineFunction(VarInd[],Float64[],0.0)) m.obj_is_quad = false m.obj_sense = MOI.FeasibilitySense @@ -108,4 +108,4 @@ function MOI.set!(m::LinQuadOptimizer, ::MOI.ObjectiveFunction, objf::Quad) ) m.objective_constant = objf.constant nothing -end \ No newline at end of file +end diff --git a/src/ref.jl b/src/ref.jl deleted file mode 100644 index fb97a85..0000000 --- a/src/ref.jl +++ /dev/null @@ -1,87 +0,0 @@ -# Main - -""" - LinQuadModel(M,env) - -Initializes a model given a model type M and a env, that might be a nothing for some solvers. -""" -function LinQuadModel end - -# LinQuadSolver # Abstract type -function lqs_setparam!(env, name, val) end -function lqs_setlogfile!(env, path) end -function lqs_getprobtype(m::LinQuadOptimizer) end - -lqs_supported_constraints(s) = [] -lqs_supported_objectives(s) = [] - -# Constraints - -function lqs_chgbds!(m::LinQuadOptimizer, colvec, valvec, sensevec) end -function lqs_getlb(m::LinQuadOptimizer, col) end -function lqs_getub(m::LinQuadOptimizer, col) end -function lqs_getnumrows(m::LinQuadOptimizer) end -function lqs_addrows!(m::LinQuadOptimizer, rowvec, colvec, coefvec, sensevec, rhsvec) end -function lqs_getrhs(m::LinQuadOptimizer, row) end -function lqs_getrows(m::LinQuadOptimizer, rowvec) end -function lqs_getcoef(m::LinQuadOptimizer, row, col) end #?? -function lqs_chgcoef!(m::LinQuadOptimizer, row, col, coef) end -function lqs_delrows!(m::LinQuadOptimizer, row, row2) end -function lqs_chgctype!(m::LinQuadOptimizer, colvec, typevec) end -function lqs_chgsense!(m::LinQuadOptimizer, rowvec, sensevec) end - -function lqs_vartype_map(m::LinQuadOptimizer) end -function lqs_make_problem_type_integer(m::LinQuadOptimizer) end -function lqs_make_problem_type_continuous(m::LinQuadOptimizer) end - -function lqs_addsos!(m::LinQuadOptimizer, colvec, valvec, typ) end -function lqs_delsos!(m::LinQuadOptimizer, idx, idx2) end -function lqs_sertype_map(m::LinQuadOptimizer) end -function lqs_getsos(m::LinQuadOptimizer, idx) end - -function lqs_getnumqconstrs(m::LinQuadOptimizer) end -function lqs_addqconstr!(m::LinQuadOptimizer, cols,coefs,rhs,sense, I,J,V) end - -function lqs_chgrngval!(m::LinQuadOptimizer, rows, vals) end# later -function lqs_ctrtype_map(m::LinQuadOptimizer) end - -#Objective - -function lqs_copyquad!(m::LinQuadOptimizer, intvec,intvec2, floatvec) end#? -function lqs_chgobj!(m::LinQuadOptimizer, colvec,coefvec) end -function lqs_chgobjsen!(m::LinQuadOptimizer, symbol) end -function lqs_getobj(m::LinQuadOptimizer) end -function lqs_getobjsen(m::LinQuadOptimizer) end - -#Solve - -function lqs_mipopt!(m::LinQuadOptimizer) end -function lqs_qpopt!(m::LinQuadOptimizer) end -function lqs_lpopt!(m::LinQuadOptimizer) end -function lqs_getstat(m::LinQuadOptimizer) end -function lqs_solninfo(m::LinQuadOptimizer) end # complex -function lqs_getx!(m::LinQuadOptimizer, place) end -function lqs_getax!(m::LinQuadOptimizer, place) end -function lqs_getdj!(m::LinQuadOptimizer, place) end -function lqs_getpi!(m::LinQuadOptimizer, place) end - -function lqs_getobjval(m::LinQuadOptimizer) end -function lqs_getbestobjval(m::LinQuadOptimizer) end -function lqs_getmiprelgap(m::LinQuadOptimizer) end -function lqs_getitcnt(m::LinQuadOptimizer) end -function lqs_getbaritcnt(m::LinQuadOptimizer) end -function lqs_getnodecnt(m::LinQuadOptimizer) end - -function lqs_dualfarkas!(m::LinQuadOptimizer, place) end -function lqs_getray!(m::LinQuadOptimizer, place) end - -function lqs_terminationstatus end -function lqs_primalstatus end -function lqs_dualstatus end - -# Variables - -function lqs_getnumcols(m::LinQuadOptimizer) end -function lqs_newcols!(m::LinQuadOptimizer, int) end -function lqs_delcols!(m::LinQuadOptimizer, col, col2) end -function lqs_addmipstarts!(m::LinQuadOptimizer, colvec, valvec) end \ No newline at end of file diff --git a/src/solve.jl b/src/solve.jl index db0ce74..fccd481 100644 --- a/src/solve.jl +++ b/src/solve.jl @@ -36,6 +36,9 @@ function MOI.optimize!(m::LinQuadOptimizer) # primal solution exists lqs_getx!(m, m.variable_primal_solution) lqs_getax!(m, m.constraint_primal_solution) + if hasquadratic(m) + lqs_getqcax!(m, m.qconstraint_primal_solution) + end m.primal_result_count = 1 # CPLEX can return infeasible points elseif m.primal_status == MOI.InfeasibilityCertificate @@ -46,6 +49,9 @@ function MOI.optimize!(m::LinQuadOptimizer) # dual solution exists lqs_getdj!(m, m.variable_dual_solution) lqs_getpi!(m, m.constraint_dual_solution) + if hasquadratic(m) + lqs_getqcpi!(m, m.qconstraint_dual_solution) + end m.dual_result_count = 1 # dual solution may not be feasible elseif m.dual_status == MOI.InfeasibilityCertificate @@ -146,12 +152,25 @@ MOI.canget(m::LinQuadOptimizer, ::MOI.VariablePrimal, ::Type{<:Vector{VarInd}}) #= Variable Dual solution =# - +isbinding(set::LE, value::Float64) = isapprox(set.upper, value) +isbinding(set::GE, value::Float64) = isapprox(set.lower, value) +isbinding(set::EQ, value::Float64) = isapprox(set.value, value) +isbinding(set::IV, value::Float64) = true function MOI.get(m::LinQuadOptimizer, ::MOI.ConstraintDual, c::SVCI{<: LinSets}) vref = m[c] col = m.variable_mapping[vref] - return m.variable_dual_solution[col] + + # the variable reduced cost is only the constriant dual if the bound is active. + set = MOI.get(m, MOI.ConstraintSet(), c) + solval = m.variable_primal_solution[col] + if isbinding(set, solval) + return m.variable_dual_solution[col] + else + return 0.0 + end end + + MOI.canget(m::LinQuadOptimizer, ::MOI.ConstraintDual, c::SVCI{<: LinSets}) = true MOI.canget(m::LinQuadOptimizer, ::MOI.ConstraintDual, ::Type{<:SVCI{<: LinSets}}) = true @@ -202,6 +221,12 @@ end MOI.canget(m::LinQuadOptimizer, ::MOI.ConstraintPrimal, c::LCI{<: LinSets}) = true MOI.canget(m::LinQuadOptimizer, ::MOI.ConstraintPrimal, ::Type{<:LCI{<: LinSets}}) = true +function MOI.get(m::LinQuadOptimizer, ::MOI.ConstraintPrimal, c::QCI{<: LinSets}) + row = m[c] + return m.qconstraint_primal_solution[row]#+m.qconstraint_constant[row] +end +MOI.canget(m::LinQuadOptimizer, ::MOI.ConstraintPrimal, c::QCI{<: LinSets}) = true +MOI.canget(m::LinQuadOptimizer, ::MOI.ConstraintPrimal, ::Type{<:QCI{<: LinSets}}) = true # vector valued constraint duals function MOI.get(m::LinQuadOptimizer, ::MOI.ConstraintPrimal, c::VLCI{<: Union{MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives}}) @@ -229,6 +254,14 @@ end MOI.canget(m::LinQuadOptimizer, ::MOI.ConstraintDual, c::LCI{<: LinSets}) = true MOI.canget(m::LinQuadOptimizer, ::MOI.ConstraintDual, ::Type{<:LCI{<: LinSets}}) = true +function MOI.get(m::LinQuadOptimizer, ::MOI.ConstraintDual, c::QCI{<: LinSets}) + row = m[c] + return m.qconstraint_dual_solution[row]#+m.qconstraint_constant[row] +end +MOI.canget(m::LinQuadOptimizer, ::MOI.ConstraintDual, c::QCI{<: LinSets}) = true +MOI.canget(m::LinQuadOptimizer, ::MOI.ConstraintDual, ::Type{<:QCI{<: LinSets}}) = true + + # vector valued constraint duals MOI.get(m::LinQuadOptimizer, ::MOI.ConstraintDual, c::VLCI{<: Union{MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives}}) = m.constraint_dual_solution[m[c]] MOI.canget(m::LinQuadOptimizer, ::MOI.ConstraintDual, c::VLCI{<: Union{MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives}}) = true @@ -264,4 +297,4 @@ MOI.canget(m::LinQuadOptimizer, ::MOI.NodeCount) = true # struct RawSolver <: MOI.AbstractOptimizerAttribute end MOI.get(m::LinQuadOptimizer, ::MOI.RawSolver) = m -MOI.canget(m::LinQuadOptimizer, ::MOI.RawSolver) = true \ No newline at end of file +MOI.canget(m::LinQuadOptimizer, ::MOI.RawSolver) = true diff --git a/src/solver_interface.jl b/src/solver_interface.jl new file mode 100644 index 0000000..5043605 --- /dev/null +++ b/src/solver_interface.jl @@ -0,0 +1,482 @@ +#= + This file contains all of the functions that a solver needs to implement + in order to use LQOI. + + min/max: c'x + x'Qx + subject to: + # Variable bounds + abᵢ <= xᵢ <= bbᵢ, i=1,2,...Nx + # Linear Constraints + llᵢ <= alᵢ'x <= ulᵢ, i=1,2,...Nl + # Quadratic Constraints + lqᵢ <= aqᵢ' x + x'Qqᵢ' x <= uqᵢ, i=1,2,...Nq + # SOS1, SOS2 constraints + # Binary Constraints + xᵢ ∈ {0, 1} + # Integer Constraints + xᵢ ∈ Z +=# + +""" + LinQuadModel(M::Type{<:LinQuadOptimizer}, env) + +Initializes a model given a model type `M` and an `env` that might be a `nothing` +for some solvers. +""" +function LinQuadModel end + +""" + lqs_setparam!(m, name, val)::Void + +Set the parameter `name` to `val` for the model `m`. +""" +function lqs_setparam!(m::LinQuadOptimizer, name, val) end + +""" + lqs_setlogfile!(m, path::String)::Void + +Set the log file to `path` for the model `m`. +""" +function lqs_setlogfile!(m::LinQuadOptimizer, path) end + +# TODO(@joaquimg): what is this? +function lqs_getprobtype(m::LinQuadOptimizer) end + +""" + lqs_supported_constraints(m)::Vector{ + Tuple{MOI.AbstractFunction, MOI.AbstractSet} + } + +Get a list of supported constraint types in the model `m`. + +For example, `[(LQOI.Linear, LQOI.EQ)]` +""" +lqs_supported_constraints(m::LinQuadOptimizer) = [] + +""" + lqs_supported_objectives(m)::Vector{MOI.AbstractScalarFunction} + +Get a list of supported objective types in the model `m`. + +For example, `[LQOI.Linear, LQOI.Quad]` +""" +lqs_supported_objectives(m::LinQuadOptimizer) = [] + +# Constraints + +function lqs_chgbds!(m::LinQuadOptimizer, colvec, valvec, sensevec) end + +""" + lqs_getlb(m, col::Int)::Float64 + +Get the lower bound of the variable in 1-indexed column `col` of the model `m`. +""" +function lqs_getlb(m::LinQuadOptimizer, col) end + +""" + lqs_getub(m, col::Int)::Float64 + +Get the upper bound of the variable in 1-indexed column `col` of the model `m`. +""" +function lqs_getub(m::LinQuadOptimizer, col) end + +""" + lqs_getnumrows(m)::Int + +Get the number of linear constraints in the model `m`. +""" +function lqs_getnumrows(m::LinQuadOptimizer) end + +""" + lqs_addrows(m, rows::Vector{Int}, cols::Vector{Int}, coefs::Vector{Float64}, + sense::Vector{Symbol}, rhs::Vector{Float64})::Void + +Adds linear constraints of the form `Ax (sense) rhs` to the model `m`. + +The A matrix is given in triplet form `A[rows[i], cols[i]] = coef[i]` for all +`i`, and `length(rows) == length(cols) == length(coefs)`. + +The `sense` is one of `:RANGE`, `:LOWER`, `:UPPER`, `:EQUALITY`. + +Ranged constraints (sense=`:RANGE`) require a call to `lqs_chgrngval!`. +""" +function lqs_addrows!(m::LinQuadOptimizer, rows, cols, coefs, sense, rhs) end + +""" + lqs_getrhs(m, row::Int)::Float64 + +Get the right-hand side of the linear constraint in the 1-indexed row `row` in +the model `m`. +""" +function lqs_getrhs(m::LinQuadOptimizer, row) end + +# TODO(@joaquim): I think this is really lqs_getrow? (singluar) +""" + lqs_getrows(m, row::Int)::Tuple{Vector{Int}, Vector{Float64}} + +Get the linear component of the constraint in the 1-indexed row `row` in +the model `m`. Returns a tuple of `(cols, vals)`. +""" +function lqs_getrows(m::LinQuadOptimizer, row) end + +""" + lqs_getcoef(m, row::Int, col::Int)::Float64 + +Get the linear coefficient of the variable in column `col`, constraint `row`. +""" +function lqs_getcoef(m::LinQuadOptimizer, row, col) end + +""" + lqs_chgcoef(m, row::Int, col::Int, coef::Float64)::Void + +Set the linear coefficient of the variable in column `col`, constraint `row` to +`coef`. +""" +function lqs_chgcoef!(m::LinQuadOptimizer, row, col, coef) end + +""" + lqs_delrows!(m, start_row::Int, end_row::Int)::Void + +Delete the linear constraints `start_row`, `start_row+1`, ..., `end_row` from +the model `m`. +""" +function lqs_delrows!(m::LinQuadOptimizer, start_row, end_row) end + +""" + lqs_chgctype(m, cols::Vector{Int}, types::Vector{Symbol})::Void + +Change the variable types. Variable type must be `:CONTINUOUS`, `:INTEGER`, or +`:BINARY`. +""" +function lqs_chgctype!(m::LinQuadOptimizer, cols, types) end + +""" + lqs_chgsense(m, rows::Vector{Int}, sense::Vector{Symbol})::Void + +Change the sense of the linear constraints in `rows` to `sense`. + +The `sense` is one of `:RANGE`, `:LOWER`, `:UPPER`, `:EQUALITY`. + +Ranged constraints (sense=`:RANGE`) require a call to `lqs_chgrngval!`. +""" +function lqs_chgsense!(m::LinQuadOptimizer, rows, sense) end + +# TODO(@joaquim): again, why not a function? +""" + lqs_vartype_map(m)::Dict + +Returns a dictionary that maps the variable type to an appropriate backend type. +Variable type must be `:CONTINUOUS`, `:INTEGER`, or `:BINARY`. +""" +function lqs_vartype_map(m::LinQuadOptimizer) end + +""" + lqs_make_problem_type_integer(m)::Void + +If an explicit call is needed to change the problem type integer (e.g., CPLEX). +""" +function lqs_make_problem_type_integer(m::LinQuadOptimizer) end + +""" + lqs_make_problem_type_continuous(m)::Void + +If an explicit call is needed to change the problem type continuous (e.g., CPLEX). +""" +function lqs_make_problem_type_continuous(m::LinQuadOptimizer) end + +""" + lqs_addsos!(m, cols::Vector{Int}, vals::Vector{Float64}, typ::Symbol)::Void + +Add the SOS constraint to the model `m`. `typ` is either `:SOS1` or `:SOS2`. +""" +function lqs_addsos!(m::LinQuadOptimizer, cols, vals, typ) end + +""" + lqs_delrows!(m, start_idx::Int, end_idx::Int)::Void + +Delete the SOS constraints `start_idx`, `start_idx+1`, ..., `end_idx` from +the model `m`. +""" +function lqs_delsos!(m::LinQuadOptimizer, start_idx, end_idx) end + +# TODO(@joaquim): what is sertype. Why not a function? +""" + lqs_sertype_map(m)::Dict + +Returns a dictionary that maps `:SOS1` and `:SOS2` to an appropriate type for +the backend. +""" +function lqs_sertype_map(m::LinQuadOptimizer) end + +""" + lqs_getsos(m, idx::Int)::Tuple{Vector{Int}, Vector{Float64}, Symbol} + +Get the SOS constraint `idx` from the model `m`. Returns the triplet + `(cols, vals, typ)`. +""" +function lqs_getsos(m::LinQuadOptimizer, idx) end + +""" + lqs_getnumqconstrs(m)::Int + +Get the number of quadratic constraints in the model `m`. +""" +function lqs_getnumqconstrs(m::LinQuadOptimizer) end + +""" + lqs_addqconstr!(m, cols::Vector{Int}, coefs::Vector{Float64}, rhs::Float64, + sense::Symbol, I::Vector{Int}, J::Vector{Int}, V::Vector{Float64})::Void + +Add a quadratic constraint `a'x + 0.5 x' Q x`. +See `lqs_addrows!` for information of linear component. +Arguments `(I,J,V)` given in triplet form for the Q matrix in `0.5 x' Q x`. +""" +function lqs_addqconstr!(m::LinQuadOptimizer, cols, coefs, rhs, sense, I, J, V) end + + +""" + lqs_chgrngval!(m, rows::Vector{Int}, vals::Vector{Float64})::Void + +A range constraint `l <= a'x <= u` is added as the linear constraint +`a'x :RANGED l`, then this function is called to set `u - l`, the range value. + +See `lqs_addrows!` for more. +""" +function lqs_chgrngval!(m::LinQuadOptimizer, rows, vals) end# later + +# TODO(@joaquim): Why not a function? +""" + lqs_ctrtype_map(m)::Dict + +Returns a dictionary that maps the constraint type to an appropriate type for +the backend. + +The constraint type is one of `:RANGE`, `:LOWER`, `:UPPER`, `:EQUALITY`. +""" +function lqs_ctrtype_map(m::LinQuadOptimizer) end + +#Objective +""" + lqs_chgobj!(m, I::Vector{Int}, J::Vector{Int}, V::Vector{Float64})::Void + +Set the quadratic component of the objective. Arguments given in triplet form +for the Q matrix in `0.5 x' Q x`. +""" +function lqs_copyquad!(m::LinQuadOptimizer, I, J, V) end + +""" + lqs_chgobj!(m, cols::Vector{Int}, coefs::Vector{Float64})::Void + +Set the linear component of the objective. +""" +function lqs_chgobj!(m::LinQuadOptimizer, cols, coefs) end + +""" + lqs_chgobjsen(m, sense::Symbol)::Void + +Change the optimization sense of the model `m` to `sense`. `sense` must be +`:min` or `:max`. +""" +function lqs_chgobjsen!(m::LinQuadOptimizer, sense) end + +#TODO(@joaquimg): why is this not in-place? +""" + lqs_getobj(m)::Vector{Float64} + +Change the linear coefficients of the objective. +""" +function lqs_getobj(m::LinQuadOptimizer) end + +""" + lqs_getobjsen(m)::MOI.OptimizationSense + +Get the optimization sense of the model `m`. +""" +function lqs_getobjsen(m::LinQuadOptimizer) end + +#Solve + +""" + lqs_mipopt!(m)::Void + +Solve a mixed-integer model `m`. +""" +function lqs_mipopt!(m::LinQuadOptimizer) end + +""" + lqs_qpopt!(m)::Void + +Solve a model `m` with quadratic components. +""" +function lqs_qpopt!(m::LinQuadOptimizer) end + +""" + lqs_lpopt!(m)::Void + +Solve a linear program `m`. +""" +function lqs_lpopt!(m::LinQuadOptimizer) end + +# TODO(@joaquim): what is this? +function lqs_getstat(m::LinQuadOptimizer) end + +# TODO(@joaquim): what is this? +function lqs_solninfo(m::LinQuadOptimizer) end # complex + +""" + lqs_getx!(m, x::Vector{Float64}) + +Get the primal solution for the variables in the model `m`, and +store in `x`. `x`must have one element for each variable. +""" +function lqs_getx!(m::LinQuadOptimizer, x) end + +""" + lqs_getax!(m, x::Vector{Float64}) + +Given a set of linear constraints `l <= a'x <= b` in the model `m`, get the +constraint primal `a'x` for each constraint, and store in `x`. +`x` must have one element for each linear constraint. +""" +function lqs_getax!(m::LinQuadOptimizer, x) end + +""" + lqs_getqcax!(m, x::Vector{Float64}) + +Given a set of quadratic constraints `l <= a'x + x'Qx <= b` in the model `m`, +get the constraint primal `a'x + x'Qx` for each constraint, and store in `x`. +`x` must have one element for each quadratic constraint. +""" +function lqs_getqcax!(m::LinQuadOptimizer, x) end + +""" + lqs_getdj!(m, x::Vector{Float64}) + +Get the dual solution (reduced-costs) for the variables in the model `m`, and +store in `x`. `x`must have one element for each variable. +""" +function lqs_getdj!(m::LinQuadOptimizer, x) end + +""" + lqs_getpi!(m, x::Vector{Float64}) + +Get the dual solution for the linear constraints in the model `m`, and +store in `x`. `x`must have one element for each linear constraint. +""" +function lqs_getpi!(m::LinQuadOptimizer, x) end + +""" + lqs_getqcpi!(m, x::Vector{Float64}) + +Get the dual solution for the quadratic constraints in the model `m`, and +store in `x`. `x`must have one element for each quadratic constraint. +""" +function lqs_getqcpi!(m::LinQuadOptimizer, x) end + +""" + lqs_getobjval!(m) + +Get the objective value of the solved model `m`. +""" +function lqs_getobjval(m::LinQuadOptimizer) end + +# TODO(@joaquimg): what is this? +function lqs_getbestobjval(m::LinQuadOptimizer) end + +""" + lqs_getmiprelgap!(m) + +Get the relative MIP gap of the solved model `m`. +""" +function lqs_getmiprelgap(m::LinQuadOptimizer) end + +""" + lqs_getitcnt!(m) + +Get the number of simplex iterations performed during the most recent +optimization of the model `m`. +""" +function lqs_getitcnt(m::LinQuadOptimizer) end + +""" + lqs_getbaritcnt!(m) + +Get the number of barrier iterations performed during the most recent +optimization of the model `m`. +""" +function lqs_getbaritcnt(m::LinQuadOptimizer) end + +""" + lqs_getnodecnt!(m) + +Get the number of branch-and-cut nodes expolored during the most recent +optimization of the model `m`. +""" +function lqs_getnodecnt(m::LinQuadOptimizer) end + +""" + lqs_dualfarkas!(m, x::Vector{Float64}) + +Get the farkas dual (certificate of primal infeasiblility) for the linear constraints +in the model `m`, and store in `x`. `x`must have one element for each linear +constraint. +""" +function lqs_dualfarkas!(m::LinQuadOptimizer, x) end + +""" + lqs_getray!(m, x::Vector{Float64}) + +Get the unbounded ray (certificate of dual infeasiblility) for the linear constraints +in the model `m`, and store in `x`. `x`must have one element for each variable. +""" +function lqs_getray!(m::LinQuadOptimizer, x) end + +""" + lqs_terminationstatus(m) + +Get the termination status of the model `m`. +""" +function lqs_terminationstatus(m::LinQuadOptimizer) end + +""" + lqs_primalstatus(m) + +Get the primal status of the model `m`. +""" +function lqs_primalstatus(m::LinQuadOptimizer) end + +""" + lqs_dualstatus(m) + +Get the dual status of the model `m`. +""" +function lqs_dualstatus(m::LinQuadOptimizer) end + +# Variables +""" + lqs_getnumcols(m)::Int + +Get the number of variables in the model `m`. +""" +function lqs_getnumcols(m::LinQuadOptimizer) end + +""" + lqs_newcols!(m, n::Int)::Void + +Add `n` new variables to the model `m`. +""" +function lqs_newcols!(m::LinQuadOptimizer, n) end + +""" + lqs_delcols!(m, start_col::Int, end_col::Int)::Void + +Delete the columns `start_col`, `start_col+1`, ..., `end_col` from the model `m`. +""" +function lqs_delcols!(m::LinQuadOptimizer, start_col, end_col) end + +""" + lqs_addmipstarts!(m, cols::Vector{Int}, x::Vector{Float64})::Void + +Add the MIP start `x` for the variables in the columns `cols` of the model `m`. +""" +function lqs_addmipstarts!(m::LinQuadOptimizer, cols, x) end diff --git a/src/variables.jl b/src/variables.jl index 53d912d..302c19e 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -4,6 +4,36 @@ MOI.canaddvariable(::LinQuadOptimizer) = true +function MOI.get(m::LinQuadOptimizer, ::MOI.VariableName, ref::VarInd) + if haskey(m.variable_names, ref) + m.variable_names[ref] + else + "" + end +end +MOI.canget(m::LinQuadOptimizer, ::MOI.VariableName, ::Type{VarInd}) = true +function MOI.set!(m::LinQuadOptimizer, ::MOI.VariableName, ref::VarInd, name::String) + if haskey(m.variable_names_rev, name) + if m.variable_names_rev[name] != ref + error("Duplicate variable name: $(name)") + end + elseif name != "" + if haskey(m.variable_names, ref) + # we're renaming an existing variable + old_name = m.variable_names[ref] + delete!(m.variable_names_rev, old_name) + end + m.variable_names[ref] = name + m.variable_names_rev[name] = ref + end +end +MOI.canset(m::LinQuadOptimizer, ::MOI.VariableName, ::Type{VarInd}) = true + +function MOI.get(m::LinQuadOptimizer, ::Type{MOI.VariableIndex}, name::String) + m.variable_names_rev[name] +end +MOI.canget(m::LinQuadOptimizer, ::Type{MOI.VariableIndex}, name::String) = haskey(m.variable_names_rev, name) + #= Helper functions =# @@ -91,6 +121,11 @@ function MOI.delete!(m::LinQuadOptimizer, ref::VarInd) deleteat!(m.variable_dual_solution, col) deleteref!(m.variable_mapping, col, ref) + if haskey(m.variable_names, ref) + name = m.variable_names[ref] + delete!(m.variable_names_rev, name) + delete!(m.variable_names, ref) + end # deleting from a dict without the key does nothing deletebyval!(cmap(m).upper_bound, ref) deletebyval!(cmap(m).lower_bound, ref) @@ -98,7 +133,7 @@ function MOI.delete!(m::LinQuadOptimizer, ref::VarInd) deletebyval!(cmap(m).interval_bound, ref) end -MOI.candelete(m::LinQuadOptimizer, ref::VarInd) = true +MOI.candelete(m::LinQuadOptimizer, ref::VarInd) = MOI.isvalid(m, ref) # temp fix - change storage for bounds TODO function deletebyval!(dict::Dict{S,T}, in::T) where {S,T} @@ -119,4 +154,4 @@ MOI.canset(m::LinQuadOptimizer, ::MOI.VariablePrimalStart, ::VarInd) = true function MOI.set!(m::LinQuadOptimizer, ::MOI.VariablePrimalStart, refs::Vector{VarInd}, vals::Vector{Float64}) lqs_addmipstarts!(m, getcol.(m, refs), vals) end -MOI.canset(m::LinQuadOptimizer, ::MOI.VariablePrimalStart, ::Vector{VarInd}) = true \ No newline at end of file +MOI.canset(m::LinQuadOptimizer, ::MOI.VariablePrimalStart, ::Vector{VarInd}) = true