Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions bench/runbench.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function generate_moi_problem(model, At, b, c;
else
for row in 1:rows
MOI.add_constraint(model, MOI.VectorAffineFunction(
[MOI.VectorAffineTerm(1,
[MOI.VectorAffineTerm(1,
MOI.ScalarAffineTerm(A_vals[i], x[A_cols[i]])
) for i in nzrange(At, row)], [-b[row]]),
MOI.Nonpositives(1))
Expand Down Expand Up @@ -112,7 +112,7 @@ function time_build_and_solve(to_build, to_solve, At, b, c, scalar = true)
end
@time @timeit "opt" MOI.optimize!(to_solve)
MOI.get(to_solve, MOI.ObjectiveValue())
val = MOI.get(to_solve, MOI.SolveTime())
val = MOI.get(to_solve, MOI.SolveTimeSec())
println(val)
end

Expand Down Expand Up @@ -145,4 +145,4 @@ function solve_clp(seed, data; time_limit_sec=Inf)

end

solve_clp(10, RandomLP(10000, 20000, 0.01); time_limit_sec=5)
solve_clp(10, RandomLP(10000, 20000, 0.01); time_limit_sec=5)
185 changes: 61 additions & 124 deletions src/MOI_wrapper/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@ import SparseArrays

const MOI = MathOptInterface

# In comparison to `@mix_of_scalar_sets`, `@product_of_scalar_sets` sorts the
# rows grouping the same sets together. Therefore, `ListOfConstraintIndices`
# is a contiguous range from 1 to the number of rows which makes it more friendly
# with `CleverDict` hence speed up the copy as creating the `IndexMap` is cheaper.
MOI.Utilities.@product_of_scalar_sets(LP, MOI.EqualTo{T}, MOI.LessThan{T}, MOI.GreaterThan{T})
const Model = MOI.Utilities.GenericOptimizer{
Float64,
MOI.Utilities.MatrixOfConstraints{
Float64,
MOI.Utilities.MutableSparseMatrixCSC{Float64,Cint,MOI.Utilities.ZeroBasedIndexing},
MOI.Utilities.Box{Float64},
LP{Float64},
},
}

# Supported scalar sets
const SCALAR_SETS = Union{
MOI.GreaterThan{Float64},
Expand Down Expand Up @@ -241,136 +256,32 @@ end
# `copy_to` function
# =======================

_add_bounds(::Vector{Float64}, ub, i, s::MOI.LessThan{Float64}) = ub[i] = s.upper
_add_bounds(lb, ::Vector{Float64}, i, s::MOI.GreaterThan{Float64}) = lb[i] = s.lower
_add_bounds(lb, ub, i, s::MOI.EqualTo{Float64}) = lb[i], ub[i] = s.value, s.value
_add_bounds(lb, ub, i, s::MOI.Interval{Float64}) = lb[i], ub[i] = s.lower, s.upper

function _extract_bound_data(src, mapping, lb, ub, ::Type{S}) where S
for con_index in MOI.get(
src, MOI.ListOfConstraintIndices{MOI.SingleVariable, S}()
)
f = MOI.get(src, MOI.ConstraintFunction(), con_index)
s = MOI.get(src, MOI.ConstraintSet(), con_index)
column = mapping.varmap[f.variable].value
_add_bounds(lb, ub, column, s)
mapping.conmap[con_index] = MOI.ConstraintIndex{MOI.SingleVariable, S}(column)
end
end

function _copy_to_columns(dest::Optimizer, src, mapping)
x_src = MOI.get(src, MOI.ListOfVariableIndices())
N = Cint(length(x_src))
for i = 1:N
mapping.varmap[x_src[i]] = MOI.VariableIndex(i)
end

fobj = MOI.get(src, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}())
c = fill(0.0, N)
for term in fobj.terms
i = mapping.varmap[term.variable_index].value
c[i] += term.coefficient
end
# Clp seems to negates the objective offset
Clp_setObjectiveOffset(dest, -fobj.constant)
return N, c
end

_bounds(s::MOI.GreaterThan{Float64}) = (s.lower, Inf)
_bounds(s::MOI.LessThan{Float64}) = (-Inf, s.upper)
_bounds(s::MOI.EqualTo{Float64}) = (s.value, s.value)
_bounds(s::MOI.Interval{Float64}) = (s.lower, s.upper)

function add_sizehint!(vec, n)
len = length(vec)
return sizehint!(vec, len + n)
end

function _extract_row_data(src, mapping, lb, ub, I, J, V, ::Type{S}) where S
row = length(I) == 0 ? 1 : I[end] + 1
list = MOI.get(
src, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, S}()
)
add_sizehint!(lb, length(list))
add_sizehint!(ub, length(list))
n_terms = 0
fs = Array{MOI.ScalarAffineFunction{Float64}}(undef, length(list))
for (i,c_index) in enumerate(list)
f = MOI.get(src, MOI.ConstraintFunction(), c_index)
fs[i] = f
l, u = _bounds(MOI.get(src, MOI.ConstraintSet(), c_index))
push!(lb, l - f.constant)
push!(ub, u - f.constant)
n_terms += length(f.terms)
end
add_sizehint!(I, n_terms)
add_sizehint!(J, n_terms)
add_sizehint!(V, n_terms)
for (i,c_index) in enumerate(list)
f = fs[i]#MOI.get(src, MOI.ConstraintFunction(), c_index)
for term in f.terms
push!(I, row)
push!(J, Cint(mapping.varmap[term.variable_index].value))
push!(V, term.coefficient)
end
mapping.conmap[c_index] = MOI.ConstraintIndex{
MOI.ScalarAffineFunction{Float64}, S
}(row)
row += 1
end
return
end

function test_data(src, dest)
for (F, S) in MOI.get(src, MOI.ListOfConstraints())
if !MOI.supports_constraint(dest, F, S)
throw(MOI.UnsupportedConstraint{F, S}("Clp.Optimizer does not support constraints of type $F-in-$S."))
end
end
fobj_type = MOI.get(src, MOI.ObjectiveFunctionType())
if !MOI.supports(dest, MOI.ObjectiveFunction{fobj_type}())
throw(MOI.UnsupportedAttribute(MOI.ObjectiveFunction(fobj_type)))
end
end

function MOI.copy_to(
function _copy_to(
dest::Optimizer,
src::MOI.ModelLike;
copy_names::Bool = false
src::Model
)
@assert MOI.is_empty(dest)
test_data(src, dest)

mapping = MOI.Utilities.IndexMap()
N, c = _copy_to_columns(dest, src, mapping)
cl, cu = fill(-Inf, N), fill(Inf, N)
rl, ru, I, J, V = Float64[], Float64[], Cint[], Cint[], Float64[]

_extract_bound_data(src, mapping, cl, cu, MOI.GreaterThan{Float64})
_extract_row_data(src, mapping, rl, ru, I, J, V, MOI.GreaterThan{Float64})
_extract_bound_data(src, mapping, cl, cu, MOI.LessThan{Float64})
_extract_row_data(src, mapping, rl, ru, I, J, V, MOI.LessThan{Float64})
_extract_bound_data(src, mapping, cl, cu, MOI.EqualTo{Float64})
_extract_row_data(src, mapping, rl, ru, I, J, V, MOI.EqualTo{Float64})
_extract_bound_data(src, mapping, cl, cu, MOI.Interval{Float64})
_extract_row_data(src, mapping, rl, ru, I, J, V, MOI.Interval{Float64})

M = Cint(length(rl))
A = SparseArrays.sparse(I, J, V, M, N)
A = src.constraints.coefficients
row_bounds = src.constraints.constants
obj = MOI.get(src, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}())
c = zeros(A.n)
for term in obj.terms
c[term.variable.value] += term.coefficient
end
Clp_setObjectiveOffset(dest, -obj.constant)
Clp_loadProblem(
dest,
A.n,
A.m,
A.colptr .- Cint(1),
A.rowval .- Cint(1),
A.colptr,
A.rowval,
A.nzval,
cl,
cu,
src.lower_bound,
src.upper_bound,
c,
rl,
ru
row_bounds.lower,
row_bounds.upper,
)

sense = MOI.get(src, MOI.ObjectiveSense())
if sense == MOI.MIN_SENSE
Clp_setObjSense(dest, 1)
Expand All @@ -380,7 +291,33 @@ function MOI.copy_to(
@assert sense == MOI.FEASIBILITY_SENSE
Clp_setObjSense(dest, 0)
end
return mapping
return
end
function MOI.copy_to(
dest::Optimizer,
src::Model;
copy_names::Bool = false
)
_copy_to(dest, src)
return MOI.Utilities.identity_index_map(src)
end
function MOI.copy_to(
dest::Optimizer,
src::MOI.Utilities.UniversalFallback{Model};
copy_names::Bool = false
)
return MOI.copy_to(dest, src.model)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this just ignore any extra attributes in src?

end

function MOI.copy_to(
dest::Optimizer,
src::MOI.ModelLike;
copy_names::Bool = false
)
model = Model()
index_map = MOI.copy_to(model, src)
_copy_to(dest, model)
return index_map
end

# ===============================
Expand All @@ -395,7 +332,7 @@ function MOI.optimize!(model::Optimizer)
return
end

function MOI.get(model::Optimizer, ::MOI.SolveTime)
function MOI.get(model::Optimizer, ::MOI.SolveTimeSec)
return model.solve_time
end

Expand Down Expand Up @@ -461,7 +398,7 @@ function MOI.get(model::Optimizer, ::MOI.ResultCount)
end

function MOI.get(model::Optimizer, attr::MOI.PrimalStatus)
if attr.N != 1
if attr.result_index != 1
return MOI.NO_SOLUTION
elseif Clp_isProvenDualInfeasible(model) != 0
return MOI.INFEASIBILITY_CERTIFICATE
Expand All @@ -473,7 +410,7 @@ function MOI.get(model::Optimizer, attr::MOI.PrimalStatus)
end

function MOI.get(model::Optimizer, attr::MOI.DualStatus)
if attr.N != 1
if attr.result_index != 1
return MOI.NO_SOLUTION
elseif Clp_isProvenPrimalInfeasible(model) != 0
return MOI.INFEASIBILITY_CERTIFICATE
Expand Down