From b9c81fee159b1479f42595fa12716898bd2527cd Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Tue, 29 May 2018 12:48:58 +0200 Subject: [PATCH 01/22] MOI interface through LQOI (Partially implemented) --- REQUIRE | 1 + src/Clp.jl | 1 + src/ClpCInterface.jl | 26 ++ src/MOIWrapper.jl | 604 +++++++++++++++++++++++++++++++++++++++++++ test/MOIWrapper.jl | 12 + test/runtests.jl | 1 + 6 files changed, 645 insertions(+) create mode 100644 src/MOIWrapper.jl create mode 100644 test/MOIWrapper.jl diff --git a/REQUIRE b/REQUIRE index f20e681..d567223 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,3 +1,4 @@ julia 0.6 Cbc MathProgBase 0.5 0.8 +LinQuadOptInterface 0.0 0.1 diff --git a/src/Clp.jl b/src/Clp.jl index e8e5882..4997732 100644 --- a/src/Clp.jl +++ b/src/Clp.jl @@ -9,6 +9,7 @@ module Clp include("ClpCInterface.jl") include("ClpSolverInterface.jl") +include("MOIWrapper.jl") using Clp.ClpMathProgSolverInterface export ClpSolver diff --git a/src/ClpCInterface.jl b/src/ClpCInterface.jl index c8af204..ac61362 100644 --- a/src/ClpCInterface.jl +++ b/src/ClpCInterface.jl @@ -19,7 +19,9 @@ export resize, delete_rows, add_rows, + add_row, delete_columns, + add_column, add_columns, chg_row_lower, chg_row_upper, @@ -418,12 +420,36 @@ function add_rows(model::ClpModel, number::Integer, row_lower::Vector{Float64}, @clp_ccall addRows Void (Ptr{Void}, Int32, Ptr{Float64}, Ptr{Float64}, Ptr{Int32}, Ptr{Int32}, Ptr{Float64}) model.p number row_lower row_upper row_starts columns elements end +#This function exists in cpp but not c interface +function add_row(model::ClpModel, number_in_row::Integer, columns::Vector{Int32}, elements::Vector{Float64}, + row_lower::Float64, row_upper::Float64) + _row_starts = Vector{Int32}(2) + _row_starts[1] = 0 + _row_starts[2] = number_in_row + _row_upper = [row_upper] + _row_lower = [row_lower] + add_rows(model, 1, _row_lower, _row_upper, _row_starts, columns, elements) +end + # Delete columns. function delete_columns(model::ClpModel, which::Vector{Int32}) _jl__check_model(model) @clp_ccall deleteColumns Void (Ptr{Void},Int32,Ptr{Int32}) model.p length(which) which end +#This function exists in cpp but not c interface +function add_column(model::ClpModel, number_in_column::Integer, rows::Vector{Int32}, + elements::Vector{Float64}, column_lower::Float64, + column_upper::Float64, objective::Float64) + _column_starts = Vector{Int32}(2) + _column_starts[1] = 0 + _column_starts[2] = number_in_column + _column_upper = [column_upper] + _column_lower = [column_lower] + _objective = [objective] + add_columns(model, 1, _column_lower, _column_upper, _objective, _column_starts, rows, elements) +end + # Add columns. function add_columns(model::ClpModel, number::Integer, column_lower::Vector{Float64}, column_upper::Vector{Float64}, diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl new file mode 100644 index 0000000..9a4cf85 --- /dev/null +++ b/src/MOIWrapper.jl @@ -0,0 +1,604 @@ +export ClpOptimizer + +using LinQuadOptInterface +using Clp.ClpCInterface + +function replaceInf(x) + for i in 1:length(x) + if x[i] > 1e20 + x[i] = Inf + elseif x[i] < -1e20 + x[i] = -Inf + end + end + return x +end + +const LQOI = LinQuadOptInterface +const MOI = LQOI.MOI + +const SUPPORTED_OBJECTIVES = [ + LQOI.Linear +] + +const SUPPORTED_CONSTRAINTS = [ + (LQOI.Linear, LQOI.EQ), + (LQOI.Linear, LQOI.LE), + (LQOI.Linear, LQOI.GE), + (LQOI.SinVar, LQOI.EQ), + (LQOI.SinVar, LQOI.LE), + (LQOI.SinVar, LQOI.GE), + (LQOI.SinVar, LQOI.IV), + (LQOI.SinVar, MOI.ZeroOne), + (LQOI.SinVar, MOI.Integer), + (LQOI.VecVar, LQOI.SOS1), + (LQOI.VecVar, LQOI.SOS2), + (LQOI.SinVar, MOI.Semicontinuous{Float64}), + (LQOI.SinVar, MOI.Semiinteger{Float64}), + (LQOI.VecVar, MOI.Nonnegatives), + (LQOI.VecVar, MOI.Nonpositives), + (LQOI.VecVar, MOI.Zeros), + (LQOI.VecLin, MOI.Nonnegatives), + (LQOI.VecLin, MOI.Nonpositives), + (LQOI.VecLin, MOI.Zeros) +] + +mutable struct ClpOptimizer <: LQOI.LinQuadOptimizer + LQOI.@LinQuadOptimizerBase + # solveroptions::ClpSolve + # env + params::Dict{String,Any} + ClpOptimizer(::Void) = new() +end + +### Options + +# map option name to C function +# const optionmap = Dict( +# :PrimalTolerance => set_primal_tolerance, +# :DualTolerance => set_dual_tolerance, +# :DualObjectiveLimit => set_dual_objective_limit, +# :MaximumIterations => set_maximum_iterations, +# :MaximumSeconds => set_maximum_seconds, +# :LogLevel => set_log_level, +# :Scaling => scaling, +# :Perturbation => set_perturbation, +# #:Algorithm => set_algorithm +# ) +# # These options are set by using the ClpSolve object +# const solveoptionmap = Dict( +# :PresolveType => set_presolve_type, +# :SolveType => set_solve_type, +# :InfeasibleReturn => set_infeasible_return, +# ) +# +# function setoption(m::ClpOptimizer, name::Symbol, value) +# if haskey(optionmap, name) +# optionmap[name](m.inner,value) +# elseif haskey(solveoptionmap, name) +# solveoptionmap[name](m.solveroptions,value) +# else +# error("Unrecognized option: $name") +# end +# end + +function ClpOptimizer(;kwargs...) + # env = Env() + m = ClpOptimizer(nothing) + # m.env = env + m.params = Dict{String,Any}() + MOI.empty!(m) + for (name,value) in kwargs + m.params[string(name)] = value + # setoption(m.inner, string(name), value) + end + return m +end + +LQOI.LinearQuadraticModel(::Type{ClpOptimizer},env) = ClpModel() + +LQOI.supported_constraints(s::ClpOptimizer) = SUPPORTED_CONSTRAINTS +LQOI.supported_objectives(s::ClpOptimizer) = SUPPORTED_OBJECTIVES + +""" +Change the bounds of the variable. The sense of the upperbound +is given by `backend_type(m, Val{:Upperbound}())`. The sense +of the lowerbound is given by `backend_type(m, Val{:Lowerbound}())` +""" +function LQOI.change_variable_bounds!(instance::ClpOptimizer, cols::Vector{Int}, values::Vector{Float64}, + senses::Vector) + + upperbounds = replaceInf(get_col_upper(instance.inner)) + lowerbounds = replaceInf(get_col_lower(instance.inner)) + + for i in 1:length(senses) + if senses[i] == Cchar('U') + upperbounds[cols[i]] = values[i] + elseif senses[i] == Cchar('L') + lowerbounds[cols[i]] = values[i] + else + error("sense is " * string(senses[i]) * ", but only " * Cchar('U') * + " and " * Cchar('L') * " senses are supported") + end + end + + chg_column_upper(instance.inner, upperbounds) + chg_column_lower(instance.inner, lowerbounds) +end + +""" +get_variable_lowerbound(m, col::Int)::Float64 + +Get the lower bound of the variable in 1-indexed column `col` of the model `m`. +""" +function LQOI.get_variable_lowerbound(instance::ClpOptimizer, col::Int)::Float64 + lower = replaceInf(get_col_lower(instance.inner)) + return lower[col]; +end + +""" +get_variable_upperbound(m, col::Int)::Float64 + +Get the upper bound of the variable in 1-indexed column `col` of the model `m`. +""" +function LQOI.get_variable_upperbound(instance::ClpOptimizer, col::Int)::Float64 + upper = replaceInf(get_col_upper(instance.inner)) + return upper[col]; +end + +""" +get_number_linear_constraints(m)::Int + +Get the number of linear constraints in the model `m`. +""" +function LQOI.get_number_linear_constraints(instance::ClpOptimizer) + return get_num_rows(instance.inner) +end + +""" +add_linear_constraints!(m, rows::Vector{Int}, cols::Vector{Int}, + coefs::Vector{Float64}, + sense::Vector{Cchar}, 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[row(i), cols[i]] = coef[i]` for all +`i` where `row(i)` is the largest element in `rows` that is smaller or equal +than `i`. `rows` is a list of integers sorted in increasing order. It contains +the starting index (of `cols`) for each row. `length(cols) == length(coefs)` +and `length(rows) == length(sense) == length(rhs)`. + +The `sense` is given by `backend_type(m, set)`. + +Ranged constraints (`set=MOI.Interval`) should be added via `add_ranged_constraint!` +instead. +""" +function LQOI.add_linear_constraints!(instance::ClpOptimizer, rows::Vector{Int}, cols::Vector{Int}, + coefs::Vector{Float64}, sense::Vector{Cchar}, rhs::Vector{Float64})::Void + + nbrows = length(rhs) + for row in 1:nbrows + elements = Vector{Float64}() + columns = Vector{Int32}() + lower = -Inf + upper = Inf + + if sense[row] == Cchar('L') + upper = rhs[row] + elseif sense[row] == Cchar('G') + lower = rhs[row] + elseif sense[row] == Cchar('E') + upper = lower = rhs[row] + else + error("sense must be Cchar(x) where x is in ['L','G',E']") + end + + number_in_row = 0 + for i in 1:length(cols) + if nbrows != 1 && rows[i] != row + continue + end + number_in_row += 1 + push!(elements, coefs[i]) + push!(columns, cols[i] - 1) + end + + add_row(instance.inner, Cint(number_in_row), columns, elements, lower, upper) + end +end +# +# """ +# add_ranged_constraint!(m, rows::Vector{Int}, cols::Vector{Int}, +# coefs::Vector{Float64}, lowerbound::Vector{Float64}, upperbound::Vector{Float64}) +# +# Adds linear constraints of the form `lowerbound <= Ax <= upperbound` to the +# model `m`. +# +# The A matrix is given in triplet form `A[rows[i], cols[i]] = coef[i]` for all +# `i`, +# +# This is a special case compared to standard `add_linear_constraints!` since it +# is often implemented via multiple API calls. +# """ +# # function add_ranged_constraints! end +# +# """ +# modify_ranged_constraint!(m, rows::Vector{Int}, lowerbound::Vector{Float64}, upperbound::Vector{Float64}) +# +# Modify the lower and upperbounds of a ranged constraint in the model `m`. +# +# This is a special case compared to standard the `change_coefficient!` since it +# is often implemented via multiple API calls. +# """ +# # function modify_ranged_constraints! end +# +# + +""" +get_rhs(m, row::Int)::Float64 + +Get the right-hand side of the linear constraint in the 1-indexed row `row` in +the model `m`. +""" +function LQOI.get_rhs(instance::ClpOptimizer, row::Int)::Float64 + lower = replaceInf(get_row_lower(instance.inner)) + upper = replaceInf(get_row_upper(instance.inner)) + lb = lower[row] + ub = upper[row] + + if lb > -Inf + return lb + elseif ub < Inf + return ub + else + error("Either row_lower or row_upper must be of abs less than 1e20") + end +end + +""" +get_linear_constraint(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 LQOI.get_linear_constraint(instance::ClpOptimizer, row::Int)::Tuple{Vector{Int}, Vector{Float64}} + A = get_constraint_matrix(instance.inner) + A_row = A[row,:] + return (Array{Int}(A_row.nzind) .- 1, A_row.nzval) +end + +# """ +# change_coefficient!(m, row, col, coef) +# +# Set the linear coefficient of the variable in column `col`, constraint `row` to +# `coef`. +# """ +# function LQOI.change_coefficient!(instance::ClpOptimizer, row, col, coef) +# +# end + +# """ +# delete_linear_constraints!(m, start_row::Int, end_row::Int)::Void +# +# Delete the linear constraints `start_row`, `start_row+1`, ..., `end_row` from +# the model `m`. +# """ +# # function delete_linear_constraints! end +# +# +# """ +# lqs_chgctype(m, cols::Vector{Int}, types):Void +# +# Change the variable types. Type is the output of one of: +# - `backend_type(m, ::ZeroOne)`, for binary variables; +# - `backend_type(m, ::Integer)`, for integer variables; and +# - `backend_type(m, Val{:Continuous}())`, for continuous variables. +# """ +# # function change_variable_types! end +# +# """ +# change_linear_constraint_sense!(m, rows::Vector{Int}, sense::Vector{Symbol})::Void +# +# Change the sense of the linear constraints in `rows` to `sense`. +# +# `sense` is the output of `backend_type(m, set)`, where `set` +# is the corresponding set for the row `rows[i]`. +# +# `Interval` constraints require a call to `change_range_value!`. +# """ +# # function change_linear_constraint_sense! end +# +# """ +# make_problem_type_integer(m)::Void +# +# If an explicit call is needed to change the problem type integer (e.g., CPLEX). +# """ +# function make_problem_type_integer(m::LinQuadOptimizer) +# nothing # default +# end +# +# """ +# make_problem_type_continuous(m)::Void +# +# If an explicit call is needed to change the problem type continuous (e.g., CPLEX). +# """ +# function make_problem_type_continuous(m::LinQuadOptimizer) +# nothing # default +# end +# + +""" +set_linear_objective!(m, cols::Vector{Int}, coefs::Vector{Float64})::Void + +Set the linear component of the objective. +""" +function LQOI.set_linear_objective!(instance::ClpOptimizer, cols::Vector{Int}, coefs::Vector{Float64})::Void + obj_in = zeros(Float64, get_num_cols(instance.inner)) + for i in 1:length(cols) + obj_in[cols[i]] = coefs[i] + end + chg_obj_coefficients(instance.inner, obj_in) +end + +""" +change_objective_sense!(m, sense::Symbol)::Void + +Change the optimization sense of the model `m` to `sense`. `sense` must be +`:min` or `:max`. +""" +function LQOI.change_objective_sense!(instance::ClpOptimizer, sense::Symbol) + if sense == :min + set_obj_sense(instance.inner, 1.0) + elseif sense == :max + set_obj_sense(instance.inner, -1.0) + else + error("sense must be either :min or :max") + end +end + + +""" +get_linear_objective!(m, x::Vector{Float64}) + +Change the linear coefficients of the objective and store +in `x`. +""" +function LQOI.get_linear_objective!(instance::ClpOptimizer, x::Vector{Float64}) + obj = get_obj_coefficients(instance.inner) + for i in 1:length(obj) + x[i] = obj[i] + end +end + +# """ +# get_objectivesense(m)::MOI.OptimizationSense +# +# Get the optimization sense of the model `m`. +# """ +# # function get_objectivesense end +# +# """ +# solve_mip_problem!(m)::Void +# +# Solve a mixed-integer model `m`. +# """ +# # function solve_mip_problem! end +# + +""" +solve_linear_problem!(m)::Void + +Solve a linear program `m`. +""" +function LQOI.solve_linear_problem!(instance::ClpOptimizer)::Void + initial_solve_with_options(instance.inner, ClpSolve()) + return +end + +""" +get_variable_primal_solution!(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 LQOI.get_variable_primal_solution!(instance::ClpOptimizer, x::Vector{Float64}) + solution = primal_column_solution(instance.inner) + for i in 1:length(solution) + x[i] = solution[i] + end +end + +""" +get_linear_primal_solution!(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 LQOI.get_linear_primal_solution!(instance::ClpOptimizer, x::Vector{Float64}) + solution = primal_row_solution(instance.inner) + for i in 1:length(solution) + x[i] = solution[i] + end +end + +""" +get_variable_dual_solution!(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 LQOI.get_variable_dual_solution!(instance::ClpOptimizer, x::Vector{Float64}) + solution = dual_column_solution(instance.inner) + for i in 1:length(solution) + x[i] = solution[i] + end +end + +""" +get_linear_dual_solution!(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 LQOI.get_linear_dual_solution!(instance::ClpOptimizer, x::Vector{Float64}) + solution = dual_row_solution(instance.inner) + for i in 1:length(solution) + x[i] = solution[i] + end +end + +""" +get_objective_value(m) + +Get the objective value of the solved model `m`. +""" +function LQOI.get_objective_value(instance::ClpOptimizer) + return objective_value(instance.inner) +end + +# +# """ +# get_objective_bound(m) +# +# Get the objective bound of the model `m`. +# """ +# # function get_objective_bound end +# +# """ +# get_relative_mip_gap(m) +# +# Get the relative MIP gap of the solved model `m`. +# """ +# # function get_relative_mip_gap end +# +# """ +# get_iteration_count(m) +# +# Get the number of simplex iterations performed during the most recent +# optimization of the model `m`. +# """ +# # function get_iteration_count end +# +# """ +# get_barrier_iterations(m) +# +# Get the number of barrier iterations performed during the most recent +# optimization of the model `m`. +# """ +# # function get_barrier_iterations end +# +# """ +# get_node_count(m) +# +# Get the number of branch-and-cut nodes expolored during the most recent +# optimization of the model `m`. +# """ +# # function get_node_count end +# +# """ +# get_farkas_dual!(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 get_farkas_dual! end +# +# """ +# get_unbounded_ray!(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 get_unbounded_ray! end +# + +""" +get_termination_status(m) + +Get the termination status of the model `m`. +""" +function LQOI.get_termination_status(instance::ClpOptimizer) + s = ClpCInterface.status(instance.inner) + if s == 0 + return MOI.Success + elseif s == 1 + return MOI.InfeasibleNoResult + elseif s == 2 + return MOI.UnboundedNoResult + # if s in [0, 1, 2] + # return MOI.Success + elseif s == 3 + return MOI.OtherLimit + elseif s == 4 + return MOI.OtherError + else + error("status returned by Clp must be in [0,1,2,3,4]") + end +end + +""" +get_primal_status(m) + +Get the primal status of the model `m`. +""" +function LQOI.get_primal_status(instance::ClpOptimizer) + if primal_feasible(instance.inner) + return MOI.FeasiblePoint + else + return MOI.UnknownResultStatus + end +end + +""" +get_dual_status(m) + +Get the dual status of the model `m`. +""" +function LQOI.get_dual_status(instance::ClpOptimizer) + if dual_feasible(instance.inner) + return MOI.FeasiblePoint + else + return MOI.UnknownResultStatus + end +end + +""" +get_number_variables(m)::Int + +Get the number of variables in the model `m`. +""" +function LQOI.get_number_variables(instance::ClpOptimizer) + return get_num_cols(instance.inner) +end + +""" +add_variables!(m, n::Int)::Void + +Add `n` new variables to the model `m`. +""" +function LQOI.add_variables!(instance::ClpOptimizer, n::Int)::Void + for i in 1:n + numberInColumn = 0 + rows = Vector{Int32}() + elements = Vector{Float64}() + add_column(instance.inner, numberInColumn, rows, elements, -Inf, +Inf, 0.0) + end +end + +# +# """ +# delete_variables!(m, start_col::Int, end_col::Int)::Void +# +# Delete the columns `start_col`, `start_col+1`, ..., `end_col` from the model `m`. +# """ +# # function delete_variables! end +# +# """ +# add_mip_starts!(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 add_mip_starts! end diff --git a/test/MOIWrapper.jl b/test/MOIWrapper.jl new file mode 100644 index 0000000..973a35b --- /dev/null +++ b/test/MOIWrapper.jl @@ -0,0 +1,12 @@ +using Base.Test, MathOptInterface, MathOptInterface.Test + +const MOI = MathOptInterface +const MOIT = MathOptInterface.Test + +@testset "Linear tests" begin + linconfig = MOIT.TestConfig() + @testset "Default Solver" begin + solver = ClpOptimizer()#(OutputFlag=0) + MOIT.contlineartest(solver, linconfig, ["linear10","linear12","linear8a","linear8b","linear8c"]) + end +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 1aeb744..8591ec7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,3 +1,4 @@ using Clp include("mathprog.jl") +# include("MOIWrapper.jl") From 0a07fdbc8ad389520ab94c7046ce7099f1ef54f5 Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Thu, 31 May 2018 21:54:13 +0200 Subject: [PATCH 02/22] more impl. --- REQUIRE | 2 +- src/MOIWrapper.jl | 66 ++++++++++++++++++++++++++--------------------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/REQUIRE b/REQUIRE index d567223..ac3be85 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,4 @@ julia 0.6 Cbc MathProgBase 0.5 0.8 -LinQuadOptInterface 0.0 0.1 +# LinQuadOptInterface 0.0 0.1 diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index 9a4cf85..aad7e24 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -31,10 +31,6 @@ const SUPPORTED_CONSTRAINTS = [ (LQOI.SinVar, LQOI.IV), (LQOI.SinVar, MOI.ZeroOne), (LQOI.SinVar, MOI.Integer), - (LQOI.VecVar, LQOI.SOS1), - (LQOI.VecVar, LQOI.SOS2), - (LQOI.SinVar, MOI.Semicontinuous{Float64}), - (LQOI.SinVar, MOI.Semiinteger{Float64}), (LQOI.VecVar, MOI.Nonnegatives), (LQOI.VecVar, MOI.Nonpositives), (LQOI.VecVar, MOI.Zeros), @@ -267,25 +263,34 @@ function LQOI.get_linear_constraint(instance::ClpOptimizer, row::Int)::Tuple{Vec return (Array{Int}(A_row.nzind) .- 1, A_row.nzval) end -# """ -# change_coefficient!(m, row, col, coef) -# -# Set the linear coefficient of the variable in column `col`, constraint `row` to -# `coef`. -# """ -# function LQOI.change_coefficient!(instance::ClpOptimizer, row, col, coef) -# -# end +""" +change_coefficient!(m, row, col, coef) + +Set the linear coefficient of the variable in column `col`, constraint `row` to +`coef`. +""" +function LQOI.change_coefficient!(instance::ClpOptimizer, row, col, coef) + if row == 0 + objcoefs = get_obj_coefficients(instance.inner) + objcoefs[col] = coef + chg_obj_coefficients(instance.inner, objcoefs) + else + error("Constraint LHS modification is not supported by CLP.jl") + end +end -# """ -# delete_linear_constraints!(m, start_row::Int, end_row::Int)::Void -# -# Delete the linear constraints `start_row`, `start_row+1`, ..., `end_row` from -# the model `m`. -# """ -# # function delete_linear_constraints! end -# -# +""" +delete_linear_constraints!(m, start_row::Int, end_row::Int)::Void + +Delete the linear constraints `start_row`, `start_row+1`, ..., `end_row` from +the model `m`. +""" +function LQOI.delete_linear_constraints!(instance::ClpOptimizer, start_row::Int, end_row::Int)::Void + which = [Int32(i-1) for i in start_row:end_row] + delete_rows(instance.inner, which) +end + + # """ # lqs_chgctype(m, cols::Vector{Int}, types):Void # @@ -588,13 +593,16 @@ function LQOI.add_variables!(instance::ClpOptimizer, n::Int)::Void end end -# -# """ -# delete_variables!(m, start_col::Int, end_col::Int)::Void -# -# Delete the columns `start_col`, `start_col+1`, ..., `end_col` from the model `m`. -# """ -# # function delete_variables! end + +""" +delete_variables!(m, start_col::Int, end_col::Int)::Void + +Delete the columns `start_col`, `start_col+1`, ..., `end_col` from the model `m`. +""" +function LQOI.delete_variables!(instance::ClpOptimizer, start_col::Int, end_col::Int)::Void + which = [Int32(i-1) for i in start_col:end_col] + delete_columns(instance.inner, which) +end # # """ # add_mip_starts!(m, cols::Vector{Int}, x::Vector{Float64})::Void From 553b9ea3be3e594b70fe4dd5b6243297a65f2dbc Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Thu, 7 Jun 2018 11:20:13 +0200 Subject: [PATCH 03/22] Finished first impl and fixed tests. --- REQUIRE | 2 +- src/MOIWrapper.jl | 226 ++++++++++++++++++++++++--------------------- test/MOIWrapper.jl | 4 +- test/runtests.jl | 2 +- 4 files changed, 126 insertions(+), 108 deletions(-) diff --git a/REQUIRE b/REQUIRE index ac3be85..e3f6c8c 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,4 @@ julia 0.6 Cbc MathProgBase 0.5 0.8 -# LinQuadOptInterface 0.0 0.1 +LinQuadOptInterface diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index aad7e24..1fa835f 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -41,42 +41,41 @@ const SUPPORTED_CONSTRAINTS = [ mutable struct ClpOptimizer <: LQOI.LinQuadOptimizer LQOI.@LinQuadOptimizerBase - # solveroptions::ClpSolve # env - params::Dict{String,Any} + params::Dict{Symbol,Any} ClpOptimizer(::Void) = new() end ### Options # map option name to C function -# const optionmap = Dict( -# :PrimalTolerance => set_primal_tolerance, -# :DualTolerance => set_dual_tolerance, -# :DualObjectiveLimit => set_dual_objective_limit, -# :MaximumIterations => set_maximum_iterations, -# :MaximumSeconds => set_maximum_seconds, -# :LogLevel => set_log_level, -# :Scaling => scaling, -# :Perturbation => set_perturbation, -# #:Algorithm => set_algorithm -# ) -# # These options are set by using the ClpSolve object -# const solveoptionmap = Dict( -# :PresolveType => set_presolve_type, -# :SolveType => set_solve_type, -# :InfeasibleReturn => set_infeasible_return, -# ) -# -# function setoption(m::ClpOptimizer, name::Symbol, value) -# if haskey(optionmap, name) -# optionmap[name](m.inner,value) -# elseif haskey(solveoptionmap, name) -# solveoptionmap[name](m.solveroptions,value) -# else -# error("Unrecognized option: $name") -# end -# end +const optionmap = Dict( + :PrimalTolerance => set_primal_tolerance, + :DualTolerance => set_dual_tolerance, + :DualObjectiveLimit => set_dual_objective_limit, + :MaximumIterations => set_maximum_iterations, + :MaximumSeconds => set_maximum_seconds, + :LogLevel => set_log_level, + :Scaling => scaling, + :Perturbation => set_perturbation, + #:Algorithm => set_algorithm + ) +# These options are set by using the ClpSolve object +const solveoptionmap = Dict( + :PresolveType => set_presolve_type, + :SolveType => set_solve_type, + :InfeasibleReturn => set_infeasible_return, + ) + +function setoption(m::ClpOptimizer, name::Symbol, value) + if haskey(optionmap, name) + optionmap[name](m.inner,value) + elseif haskey(solveoptionmap, name) + solveoptionmap[name](m.solveroptions,value) + else + error("Unrecognized option: $name") + end +end function ClpOptimizer(;kwargs...) # env = Env() @@ -85,8 +84,7 @@ function ClpOptimizer(;kwargs...) m.params = Dict{String,Any}() MOI.empty!(m) for (name,value) in kwargs - m.params[string(name)] = value - # setoption(m.inner, string(name), value) + m.params[Symbol(name)] = value end return m end @@ -170,7 +168,7 @@ Ranged constraints (`set=MOI.Interval`) should be added via `add_ranged_constrai instead. """ function LQOI.add_linear_constraints!(instance::ClpOptimizer, rows::Vector{Int}, cols::Vector{Int}, - coefs::Vector{Float64}, sense::Vector{Cchar}, rhs::Vector{Float64})::Void + coefs::Vector{Float64}, sense::Vector{Cchar}, rhs::Vector{Float64})::Void nbrows = length(rhs) for row in 1:nbrows @@ -190,10 +188,9 @@ function LQOI.add_linear_constraints!(instance::ClpOptimizer, rows::Vector{Int}, end number_in_row = 0 - for i in 1:length(cols) - if nbrows != 1 && rows[i] != row - continue - end + first = rows[row] + last = (row==length(sense)) ? length(cols) : rows[row+1]-1 + for i in first:last number_in_row += 1 push!(elements, coefs[i]) push!(columns, cols[i] - 1) @@ -202,6 +199,7 @@ function LQOI.add_linear_constraints!(instance::ClpOptimizer, rows::Vector{Int}, add_row(instance.inner, Cint(number_in_row), columns, elements, lower, upper) end end + # # """ # add_ranged_constraint!(m, rows::Vector{Int}, cols::Vector{Int}, @@ -262,22 +260,40 @@ function LQOI.get_linear_constraint(instance::ClpOptimizer, row::Int)::Tuple{Vec A_row = A[row,:] return (Array{Int}(A_row.nzind) .- 1, A_row.nzval) end - + +""" +change_objective_coefficient!(m, col, coef) + +Set the linear coefficient of the variable in column `col` to `coef` in the objective function. """ -change_coefficient!(m, row, col, coef) +function LQOI.change_objective_coefficient!(instance::ClpOptimizer, col, coef) + objcoefs = get_obj_coefficients(instance.inner) + objcoefs[col] = coef + chg_obj_coefficients(instance.inner, objcoefs) +end + +""" +change_rhs_coefficient!(m, row, coef) -Set the linear coefficient of the variable in column `col`, constraint `row` to -`coef`. +Set the rhs of the constraint in row `row` to `coef`. """ -function LQOI.change_coefficient!(instance::ClpOptimizer, row, col, coef) - if row == 0 - objcoefs = get_obj_coefficients(instance.inner) - objcoefs[col] = coef - chg_obj_coefficients(instance.inner, objcoefs) +function LQOI.change_rhs_coefficient!(instance::ClpOptimizer, row, coef) + lower = replaceInf(get_row_lower(instance.inner)) + upper = replaceInf(get_row_upper(instance.inner)) + lb = lower[row] + ub = upper[row] + + if lb > -Inf + lower[row] = coef + chg_row_lower(instance.inner, lower) + elseif ub < Inf + upper[row] = coef + chg_row_upper(instance.inner, upper) else - error("Constraint LHS modification is not supported by CLP.jl") + error("Either row_lower or row_upper must be of abs less than 1e20") end end + """ delete_linear_constraints!(m, start_row::Int, end_row::Int)::Void @@ -291,46 +307,51 @@ function LQOI.delete_linear_constraints!(instance::ClpOptimizer, start_row::Int, end -# """ -# lqs_chgctype(m, cols::Vector{Int}, types):Void -# -# Change the variable types. Type is the output of one of: -# - `backend_type(m, ::ZeroOne)`, for binary variables; -# - `backend_type(m, ::Integer)`, for integer variables; and -# - `backend_type(m, Val{:Continuous}())`, for continuous variables. -# """ -# # function change_variable_types! end -# -# """ -# change_linear_constraint_sense!(m, rows::Vector{Int}, sense::Vector{Symbol})::Void -# -# Change the sense of the linear constraints in `rows` to `sense`. -# -# `sense` is the output of `backend_type(m, set)`, where `set` -# is the corresponding set for the row `rows[i]`. -# -# `Interval` constraints require a call to `change_range_value!`. -# """ -# # function change_linear_constraint_sense! end -# -# """ -# make_problem_type_integer(m)::Void -# -# If an explicit call is needed to change the problem type integer (e.g., CPLEX). -# """ -# function make_problem_type_integer(m::LinQuadOptimizer) -# nothing # default -# end -# -# """ -# make_problem_type_continuous(m)::Void -# -# If an explicit call is needed to change the problem type continuous (e.g., CPLEX). -# """ -# function make_problem_type_continuous(m::LinQuadOptimizer) -# nothing # default -# end -# +""" +change_linear_constraint_sense!(m, rows::Vector{Int}, sense::Vector{Cchar})::Void + +Change the sense of the linear constraints in `rows` to `sense`. + +`sense` is the output of `backend_type(m, set)`, where `set` +is the corresponding set for the row `rows[i]`. + +""" +function LQOI.change_linear_constraint_sense!(instance::ClpOptimizer, rows::Vector{Int}, sense::Vector{Cchar} )::Void + lower = replaceInf(get_row_lower(instance.inner)) + upper = replaceInf(get_row_upper(instance.inner)) + + for (i,row) in enumerate(rows) + lb = lower[row] + ub = upper[row] + if lb > -Inf + rhs = lb + elseif ub < Inf + rhs = ub + else + error("Either row_lower or row_upper must be of abs less than 1e20") + end + + sense = sense[i] + if sense == Cchar('G') + lb = rhs + ub = Inf + elseif sense == Cchar('L') + lb = -Inf + ub = rhs + elseif sense == Cchar('E') + lb = rhs + ub = rhs + end + + lower[row] = lb + upper[row] = ub + end + + chg_row_upper(instance.inner, upper) + chg_row_lower(instance.inner, lower) +end + + """ set_linear_objective!(m, cols::Vector{Int}, coefs::Vector{Float64})::Void @@ -381,14 +402,7 @@ end # Get the optimization sense of the model `m`. # """ # # function get_objectivesense end -# -# """ -# solve_mip_problem!(m)::Void -# -# Solve a mixed-integer model `m`. -# """ -# # function solve_mip_problem! end -# +# """ solve_linear_problem!(m)::Void @@ -396,7 +410,19 @@ solve_linear_problem!(m)::Void Solve a linear program `m`. """ function LQOI.solve_linear_problem!(instance::ClpOptimizer)::Void - initial_solve_with_options(instance.inner, ClpSolve()) + solveroptions = ClpSolve() + model = instance.inner + for (name, value) in instance.params + if haskey(optionmap, name) + optionmap[name](model,value) + elseif haskey(solveoptionmap, name) + solveoptionmap[name](solveroptions,value) + else + error("Unrecognized option: $name") + end + end + + initial_solve_with_options(instance.inner, solveroptions) return end @@ -593,7 +619,6 @@ function LQOI.add_variables!(instance::ClpOptimizer, n::Int)::Void end end - """ delete_variables!(m, start_col::Int, end_col::Int)::Void @@ -602,11 +627,4 @@ Delete the columns `start_col`, `start_col+1`, ..., `end_col` from the model `m` function LQOI.delete_variables!(instance::ClpOptimizer, start_col::Int, end_col::Int)::Void which = [Int32(i-1) for i in start_col:end_col] delete_columns(instance.inner, which) -end -# -# """ -# add_mip_starts!(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 add_mip_starts! end +end \ No newline at end of file diff --git a/test/MOIWrapper.jl b/test/MOIWrapper.jl index 973a35b..4bffc79 100644 --- a/test/MOIWrapper.jl +++ b/test/MOIWrapper.jl @@ -4,9 +4,9 @@ const MOI = MathOptInterface const MOIT = MathOptInterface.Test @testset "Linear tests" begin - linconfig = MOIT.TestConfig() + linconfig = MOIT.TestConfig(modify_lhs = false) @testset "Default Solver" begin - solver = ClpOptimizer()#(OutputFlag=0) + solver = ClpOptimizer(LogLevel = 0) MOIT.contlineartest(solver, linconfig, ["linear10","linear12","linear8a","linear8b","linear8c"]) end end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 8591ec7..16aedc1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ using Clp include("mathprog.jl") -# include("MOIWrapper.jl") +include("MOIWrapper.jl") From adf78102628a70393c49efe7116ca25c8c6dd787 Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Wed, 13 Jun 2018 10:52:44 +0200 Subject: [PATCH 04/22] updated to use CSR struct --- src/MOIWrapper.jl | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index 1fa835f..9ef178b 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -150,26 +150,23 @@ function LQOI.get_number_linear_constraints(instance::ClpOptimizer) end """ -add_linear_constraints!(m, rows::Vector{Int}, cols::Vector{Int}, - coefs::Vector{Float64}, - sense::Vector{Cchar}, rhs::Vector{Float64})::Void + add_linear_constraints!(m, A::CSRMatrix{Float64}, sense::Vector{Cchar}, 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[row(i), cols[i]] = coef[i]` for all -`i` where `row(i)` is the largest element in `rows` that is smaller or equal -than `i`. `rows` is a list of integers sorted in increasing order. It contains -the starting index (of `cols`) for each row. `length(cols) == length(coefs)` -and `length(rows) == length(sense) == length(rhs)`. - +`sense` and `rhs` contain one element for each row in `A`. The `sense` is given by `backend_type(m, set)`. - -Ranged constraints (`set=MOI.Interval`) should be added via `add_ranged_constraint!` -instead. +Ranged constraints (`set=MOI.Interval`) should be added via `add_ranged_constraint!` instead. +See also: `LinQuadOptInterface.CSRMatrix`. """ -function LQOI.add_linear_constraints!(instance::ClpOptimizer, rows::Vector{Int}, cols::Vector{Int}, - coefs::Vector{Float64}, sense::Vector{Cchar}, rhs::Vector{Float64})::Void - +# function LQOI.add_linear_constraints!(instance::ClpOptimizer, rows::Vector{Int}, cols::Vector{Int}, +# coefs::Vector{Float64}, sense::Vector{Cchar}, rhs::Vector{Float64})::Void +function LQOI.add_linear_constraints!(instance::ClpOptimizer, A::LQOI.CSRMatrix{Float64}, sense::Vector{Cchar}, + rhs::Vector{Float64})::Void + + rows = A.row_pointers + cols = A.columns + coefs = A.coefficients + nbrows = length(rhs) for row in 1:nbrows elements = Vector{Float64}() From 35b982c311ac9e1a06a85ca59c2e00539d270354 Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Tue, 19 Jun 2018 14:39:08 +0200 Subject: [PATCH 05/22] minor updates. more tests --- src/MOIWrapper.jl | 6 ++---- test/MOIWrapper.jl | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index 9ef178b..7d5da07 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -255,7 +255,7 @@ the model `m`. Returns a tuple of `(cols, vals)`. function LQOI.get_linear_constraint(instance::ClpOptimizer, row::Int)::Tuple{Vector{Int}, Vector{Float64}} A = get_constraint_matrix(instance.inner) A_row = A[row,:] - return (Array{Int}(A_row.nzind) .- 1, A_row.nzval) + return (Array{Int}(A_row.nzind), A_row.nzval) end """ @@ -388,9 +388,7 @@ in `x`. """ function LQOI.get_linear_objective!(instance::ClpOptimizer, x::Vector{Float64}) obj = get_obj_coefficients(instance.inner) - for i in 1:length(obj) - x[i] = obj[i] - end + copy!(x, obj) end # """ diff --git a/test/MOIWrapper.jl b/test/MOIWrapper.jl index 4bffc79..72a97e2 100644 --- a/test/MOIWrapper.jl +++ b/test/MOIWrapper.jl @@ -9,4 +9,41 @@ const MOIT = MathOptInterface.Test solver = ClpOptimizer(LogLevel = 0) MOIT.contlineartest(solver, linconfig, ["linear10","linear12","linear8a","linear8b","linear8c"]) end +end + +@testset "ModelLike tests" begin + solver = ClpOptimizer(LogLevel = 0) + MOIT.nametest(solver) + @testset "validtest" begin + MOIT.validtest(solver) + end + @testset "emptytest" begin + MOIT.emptytest(solver) + end + @testset "orderedindicestest" begin + MOIT.orderedindicestest(solver) + end + @testset "canaddconstrainttest" begin + MOIT.canaddconstrainttest(solver, Float64, Complex{Float64}) + end + @testset "copytest" begin + solver2 = ClpOptimizer(LogLevel = 0) + MOIT.copytest(solver,solver2) + end +end + +@testset "Unit Tests" begin + config = MOIT.TestConfig() + solver = ClpOptimizer(LogLevel = 0) + + MOIT.basic_constraint_tests(solver, config, exclude = [ + (MOI.SingleVariable, MOI.Integer), + (MOI.SingleVariable, MOI.ZeroOne) + ]) + + MOIT.unittest(solver, config, [ + "solve_affine_interval", + "solve_qcp_edge_cases", + "solve_qp_edge_cases" + ]) end \ No newline at end of file From da2ad75faca45e777536eaeaa5a8ebefba901ae4 Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Wed, 20 Jun 2018 11:56:03 +0200 Subject: [PATCH 06/22] a few fixes --- src/MOIWrapper.jl | 64 ++++++++++++++++++++++++++++++++-------------- test/MOIWrapper.jl | 6 +---- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index 7d5da07..aaae504 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -29,8 +29,6 @@ const SUPPORTED_CONSTRAINTS = [ (LQOI.SinVar, LQOI.LE), (LQOI.SinVar, LQOI.GE), (LQOI.SinVar, LQOI.IV), - (LQOI.SinVar, MOI.ZeroOne), - (LQOI.SinVar, MOI.Integer), (LQOI.VecVar, MOI.Nonnegatives), (LQOI.VecVar, MOI.Nonpositives), (LQOI.VecVar, MOI.Zeros), @@ -149,6 +147,22 @@ function LQOI.get_number_linear_constraints(instance::ClpOptimizer) return get_num_rows(instance.inner) end +function addrow(row, lower, upper, instance, rows, cols, coefs) + elements = Float64[] + columns = Int32[] + + number_in_row = 0 + first = rows[row] + last = (row==length(rows)) ? length(cols) : rows[row+1]-1 + for i in first:last + number_in_row += 1 + push!(elements, coefs[i]) + push!(columns, cols[i] - 1) + end + + add_row(instance.inner, Cint(number_in_row), columns, elements, lower, upper) +end + """ add_linear_constraints!(m, A::CSRMatrix{Float64}, sense::Vector{Cchar}, rhs::Vector{Float64})::Void @@ -158,8 +172,6 @@ The `sense` is given by `backend_type(m, set)`. Ranged constraints (`set=MOI.Interval`) should be added via `add_ranged_constraint!` instead. See also: `LinQuadOptInterface.CSRMatrix`. """ -# function LQOI.add_linear_constraints!(instance::ClpOptimizer, rows::Vector{Int}, cols::Vector{Int}, -# coefs::Vector{Float64}, sense::Vector{Cchar}, rhs::Vector{Float64})::Void function LQOI.add_linear_constraints!(instance::ClpOptimizer, A::LQOI.CSRMatrix{Float64}, sense::Vector{Cchar}, rhs::Vector{Float64})::Void @@ -168,9 +180,7 @@ function LQOI.add_linear_constraints!(instance::ClpOptimizer, A::LQOI.CSRMatrix{ coefs = A.coefficients nbrows = length(rhs) - for row in 1:nbrows - elements = Vector{Float64}() - columns = Vector{Int32}() + for row in 1:nbrows lower = -Inf upper = Inf @@ -184,16 +194,32 @@ function LQOI.add_linear_constraints!(instance::ClpOptimizer, A::LQOI.CSRMatrix{ error("sense must be Cchar(x) where x is in ['L','G',E']") end - number_in_row = 0 - first = rows[row] - last = (row==length(sense)) ? length(cols) : rows[row+1]-1 - for i in first:last - number_in_row += 1 - push!(elements, coefs[i]) - push!(columns, cols[i] - 1) - end - - add_row(instance.inner, Cint(number_in_row), columns, elements, lower, upper) + addrow(row, lower, upper, instance, rows, cols, coefs) + end +end + +""" + add_ranged_constraints!(m, A::CSRMatrix{Float64}, lb::Vector{Float64}, ub::Vector{Float64})::Void + +Adds linear constraints of the form `Ax (sense) rhs` to the model `m`. +`sense` and `rhs` contain one element for each row in `A`. +The `sense` is given by `backend_type(m, set)`. +Ranged constraints (`set=MOI.Interval`) should be added via `add_ranged_constraint!` instead. +See also: `LinQuadOptInterface.CSRMatrix`. +""" +function LQOI.add_ranged_constraints!(instance::Clp.ClpOptimizer, A::LinQuadOptInterface.CSRMatrix{Float64}, + lb::Vector{Float64}, ub::Vector{Float64}) + + rows = A.row_pointers + cols = A.columns + coefs = A.coefficients + + nbrows = length(lb) + for row in 1:nbrows + lower = lb[row] + upper = ub[row] + + addrow(row, lower, upper, instance, rows, cols, coefs) end end @@ -608,8 +634,8 @@ Add `n` new variables to the model `m`. function LQOI.add_variables!(instance::ClpOptimizer, n::Int)::Void for i in 1:n numberInColumn = 0 - rows = Vector{Int32}() - elements = Vector{Float64}() + rows = Int32[] + elements = Float64[] add_column(instance.inner, numberInColumn, rows, elements, -Inf, +Inf, 0.0) end end diff --git a/test/MOIWrapper.jl b/test/MOIWrapper.jl index 72a97e2..d78c061 100644 --- a/test/MOIWrapper.jl +++ b/test/MOIWrapper.jl @@ -36,13 +36,9 @@ end config = MOIT.TestConfig() solver = ClpOptimizer(LogLevel = 0) - MOIT.basic_constraint_tests(solver, config, exclude = [ - (MOI.SingleVariable, MOI.Integer), - (MOI.SingleVariable, MOI.ZeroOne) - ]) + MOIT.basic_constraint_tests(solver, config) MOIT.unittest(solver, config, [ - "solve_affine_interval", "solve_qcp_edge_cases", "solve_qp_edge_cases" ]) From 9d84bcc219a6c98e9d55ca621d5b30c3b6639fd5 Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Wed, 20 Jun 2018 12:19:06 +0200 Subject: [PATCH 07/22] using copy! --- src/MOIWrapper.jl | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index aaae504..81416c7 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -455,9 +455,7 @@ store in `x`. `x`must have one element for each variable. """ function LQOI.get_variable_primal_solution!(instance::ClpOptimizer, x::Vector{Float64}) solution = primal_column_solution(instance.inner) - for i in 1:length(solution) - x[i] = solution[i] - end + copy!(x,solution) end """ @@ -469,9 +467,7 @@ constraint primal `a'x` for each constraint, and store in `x`. """ function LQOI.get_linear_primal_solution!(instance::ClpOptimizer, x::Vector{Float64}) solution = primal_row_solution(instance.inner) - for i in 1:length(solution) - x[i] = solution[i] - end + copy!(x,solution) end """ @@ -482,9 +478,7 @@ store in `x`. `x`must have one element for each variable. """ function LQOI.get_variable_dual_solution!(instance::ClpOptimizer, x::Vector{Float64}) solution = dual_column_solution(instance.inner) - for i in 1:length(solution) - x[i] = solution[i] - end + copy!(x,solution) end """ From 1c60f520d708107c2994e625910a09a89c393c08 Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Sat, 30 Jun 2018 14:21:39 +0200 Subject: [PATCH 08/22] removed some commented out code --- src/MOIWrapper.jl | 89 +---------------------------------------------- 1 file changed, 1 insertion(+), 88 deletions(-) diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index 81416c7..8c55998 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -223,33 +223,6 @@ function LQOI.add_ranged_constraints!(instance::Clp.ClpOptimizer, A::LinQuadOptI end end -# -# """ -# add_ranged_constraint!(m, rows::Vector{Int}, cols::Vector{Int}, -# coefs::Vector{Float64}, lowerbound::Vector{Float64}, upperbound::Vector{Float64}) -# -# Adds linear constraints of the form `lowerbound <= Ax <= upperbound` to the -# model `m`. -# -# The A matrix is given in triplet form `A[rows[i], cols[i]] = coef[i]` for all -# `i`, -# -# This is a special case compared to standard `add_linear_constraints!` since it -# is often implemented via multiple API calls. -# """ -# # function add_ranged_constraints! end -# -# """ -# modify_ranged_constraint!(m, rows::Vector{Int}, lowerbound::Vector{Float64}, upperbound::Vector{Float64}) -# -# Modify the lower and upperbounds of a ranged constraint in the model `m`. -# -# This is a special case compared to standard the `change_coefficient!` since it -# is often implemented via multiple API calls. -# """ -# # function modify_ranged_constraints! end -# -# """ get_rhs(m, row::Int)::Float64 @@ -489,9 +462,7 @@ store in `x`. `x`must have one element for each linear constraint. """ function LQOI.get_linear_dual_solution!(instance::ClpOptimizer, x::Vector{Float64}) solution = dual_row_solution(instance.inner) - for i in 1:length(solution) - x[i] = solution[i] - end + copy!(x,solution) end """ @@ -503,64 +474,6 @@ function LQOI.get_objective_value(instance::ClpOptimizer) return objective_value(instance.inner) end -# -# """ -# get_objective_bound(m) -# -# Get the objective bound of the model `m`. -# """ -# # function get_objective_bound end -# -# """ -# get_relative_mip_gap(m) -# -# Get the relative MIP gap of the solved model `m`. -# """ -# # function get_relative_mip_gap end -# -# """ -# get_iteration_count(m) -# -# Get the number of simplex iterations performed during the most recent -# optimization of the model `m`. -# """ -# # function get_iteration_count end -# -# """ -# get_barrier_iterations(m) -# -# Get the number of barrier iterations performed during the most recent -# optimization of the model `m`. -# """ -# # function get_barrier_iterations end -# -# """ -# get_node_count(m) -# -# Get the number of branch-and-cut nodes expolored during the most recent -# optimization of the model `m`. -# """ -# # function get_node_count end -# -# """ -# get_farkas_dual!(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 get_farkas_dual! end -# -# """ -# get_unbounded_ray!(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 get_unbounded_ray! end -# - """ get_termination_status(m) From d4042f0facb33ef946f0f372812911200997b85d Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Sat, 30 Jun 2018 20:22:12 +0200 Subject: [PATCH 09/22] minor modifs --- REQUIRE | 2 +- src/MOIWrapper.jl | 62 +++++++++++++++++++++++----------------------- test/MOIWrapper.jl | 12 ++++----- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/REQUIRE b/REQUIRE index e3f6c8c..b38263e 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,4 @@ julia 0.6 Cbc MathProgBase 0.5 0.8 -LinQuadOptInterface +LinQuadOptInterface 0.1 0.2 diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index 8c55998..5fc9e0c 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -14,6 +14,14 @@ function replaceInf(x) return x end +function replaceInf(x, index) + if x[index] > 1e20 + x[index] = Inf + elseif x[index] < -1e20 + x[index] = -Inf + end +end + const LQOI = LinQuadOptInterface const MOI = LQOI.MOI @@ -93,9 +101,7 @@ LQOI.supported_constraints(s::ClpOptimizer) = SUPPORTED_CONSTRAINTS LQOI.supported_objectives(s::ClpOptimizer) = SUPPORTED_OBJECTIVES """ -Change the bounds of the variable. The sense of the upperbound -is given by `backend_type(m, Val{:Upperbound}())`. The sense -of the lowerbound is given by `backend_type(m, Val{:Lowerbound}())` +Change the bounds of the variable. """ function LQOI.change_variable_bounds!(instance::ClpOptimizer, cols::Vector{Int}, values::Vector{Float64}, senses::Vector) @@ -124,8 +130,9 @@ get_variable_lowerbound(m, col::Int)::Float64 Get the lower bound of the variable in 1-indexed column `col` of the model `m`. """ function LQOI.get_variable_lowerbound(instance::ClpOptimizer, col::Int)::Float64 - lower = replaceInf(get_col_lower(instance.inner)) - return lower[col]; + lower = get_col_lower(instance.inner) + replaceInf(lower, col) + return lower[col] end """ @@ -134,8 +141,9 @@ get_variable_upperbound(m, col::Int)::Float64 Get the upper bound of the variable in 1-indexed column `col` of the model `m`. """ function LQOI.get_variable_upperbound(instance::ClpOptimizer, col::Int)::Float64 - upper = replaceInf(get_col_upper(instance.inner)) - return upper[col]; + upper = get_col_upper(instance.inner) + replaceInf(upper, col) + return upper[col] end """ @@ -147,7 +155,13 @@ function LQOI.get_number_linear_constraints(instance::ClpOptimizer) return get_num_rows(instance.inner) end -function addrow(row, lower, upper, instance, rows, cols, coefs) +""" +Helper function for adding a row. it adds the row in the components +that build the LQOI sparse matrix, and it adds the row in the solver. +""" +function addrow(row::Int, lower::Float64, upper::Float64, instance::ClpOptimizer, + rows::Vector{Int}, cols::Vector{Int}, coefs::Vector{Float64}) + elements = Float64[] columns = Int32[] @@ -168,22 +182,24 @@ end Adds linear constraints of the form `Ax (sense) rhs` to the model `m`. `sense` and `rhs` contain one element for each row in `A`. -The `sense` is given by `backend_type(m, set)`. -Ranged constraints (`set=MOI.Interval`) should be added via `add_ranged_constraint!` instead. -See also: `LinQuadOptInterface.CSRMatrix`. """ function LQOI.add_linear_constraints!(instance::ClpOptimizer, A::LQOI.CSRMatrix{Float64}, sense::Vector{Cchar}, rhs::Vector{Float64})::Void - + rows = A.row_pointers cols = A.columns coefs = A.coefficients nbrows = length(rhs) for row in 1:nbrows - lower = -Inf - upper = Inf + if (rhs[row] > 1e20) + error("rhs must always be less than 1e20") + elseif (rhs[row] < -1e20) + error("rhs must always be greater than -1e20") + end + lower = -Inf + upper = Inf if sense[row] == Cchar('L') upper = rhs[row] elseif sense[row] == Cchar('G') @@ -203,9 +219,6 @@ end Adds linear constraints of the form `Ax (sense) rhs` to the model `m`. `sense` and `rhs` contain one element for each row in `A`. -The `sense` is given by `backend_type(m, set)`. -Ranged constraints (`set=MOI.Interval`) should be added via `add_ranged_constraint!` instead. -See also: `LinQuadOptInterface.CSRMatrix`. """ function LQOI.add_ranged_constraints!(instance::Clp.ClpOptimizer, A::LinQuadOptInterface.CSRMatrix{Float64}, lb::Vector{Float64}, ub::Vector{Float64}) @@ -307,10 +320,6 @@ end change_linear_constraint_sense!(m, rows::Vector{Int}, sense::Vector{Cchar})::Void Change the sense of the linear constraints in `rows` to `sense`. - -`sense` is the output of `backend_type(m, set)`, where `set` -is the corresponding set for the row `rows[i]`. - """ function LQOI.change_linear_constraint_sense!(instance::ClpOptimizer, rows::Vector{Int}, sense::Vector{Cchar} )::Void lower = replaceInf(get_row_lower(instance.inner)) @@ -356,9 +365,7 @@ Set the linear component of the objective. """ function LQOI.set_linear_objective!(instance::ClpOptimizer, cols::Vector{Int}, coefs::Vector{Float64})::Void obj_in = zeros(Float64, get_num_cols(instance.inner)) - for i in 1:length(cols) - obj_in[cols[i]] = coefs[i] - end + obj_in[cols] .= coefs chg_obj_coefficients(instance.inner, obj_in) end @@ -390,13 +397,6 @@ function LQOI.get_linear_objective!(instance::ClpOptimizer, x::Vector{Float64}) copy!(x, obj) end -# """ -# get_objectivesense(m)::MOI.OptimizationSense -# -# Get the optimization sense of the model `m`. -# """ -# # function get_objectivesense end -# """ solve_linear_problem!(m)::Void diff --git a/test/MOIWrapper.jl b/test/MOIWrapper.jl index d78c061..f5fe3a0 100644 --- a/test/MOIWrapper.jl +++ b/test/MOIWrapper.jl @@ -20,9 +20,10 @@ end @testset "emptytest" begin MOIT.emptytest(solver) end - @testset "orderedindicestest" begin - MOIT.orderedindicestest(solver) - end + # TODO uncomment after adding MOIT.TestConfig.multiple_bounds + # @testset "orderedindicestest" begin + # MOIT.orderedindicestest(solver) + # end @testset "canaddconstrainttest" begin MOIT.canaddconstrainttest(solver, Float64, Complex{Float64}) end @@ -35,9 +36,8 @@ end @testset "Unit Tests" begin config = MOIT.TestConfig() solver = ClpOptimizer(LogLevel = 0) - - MOIT.basic_constraint_tests(solver, config) - + # TODO uncomment after adding MOIT.TestConfig.multiple_bounds + # MOIT.basic_constraint_tests(solver, config) MOIT.unittest(solver, config, [ "solve_qcp_edge_cases", "solve_qp_edge_cases" From 5dfb6179087421eb76289397fc15219d7f3da889 Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Wed, 4 Jul 2018 11:17:05 +0200 Subject: [PATCH 10/22] some fixes --- src/ClpCInterface.jl | 12 ++--- src/MOIWrapper.jl | 117 ++++++++++++++++++++++--------------------- test/MOIWrapper.jl | 4 +- 3 files changed, 69 insertions(+), 64 deletions(-) diff --git a/src/ClpCInterface.jl b/src/ClpCInterface.jl index ac61362..0983d86 100644 --- a/src/ClpCInterface.jl +++ b/src/ClpCInterface.jl @@ -423,12 +423,12 @@ end #This function exists in cpp but not c interface function add_row(model::ClpModel, number_in_row::Integer, columns::Vector{Int32}, elements::Vector{Float64}, row_lower::Float64, row_upper::Float64) - _row_starts = Vector{Int32}(2) - _row_starts[1] = 0 - _row_starts[2] = number_in_row - _row_upper = [row_upper] - _row_lower = [row_lower] - add_rows(model, 1, _row_lower, _row_upper, _row_starts, columns, elements) + row_starts_vector = Vector{Int32}(2) + row_starts_vector[1] = 0 + row_starts_vector[2] = number_in_row + row_upper_vector = [row_upper] + row_lower_vector = [row_lower] + add_rows(model, 1, row_lower_vector, row_upper_vector, row_starts_vector, columns, elements) end # Delete columns. diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index 5fc9e0c..ca50694 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -3,7 +3,7 @@ export ClpOptimizer using LinQuadOptInterface using Clp.ClpCInterface -function replaceInf(x) +function replace_inf(x) for i in 1:length(x) if x[i] > 1e20 x[i] = Inf @@ -14,7 +14,7 @@ function replaceInf(x) return x end -function replaceInf(x, index) +function replace_inf(x, index) if x[index] > 1e20 x[index] = Inf elseif x[index] < -1e20 @@ -47,7 +47,6 @@ const SUPPORTED_CONSTRAINTS = [ mutable struct ClpOptimizer <: LQOI.LinQuadOptimizer LQOI.@LinQuadOptimizerBase - # env params::Dict{Symbol,Any} ClpOptimizer(::Void) = new() end @@ -64,7 +63,6 @@ const optionmap = Dict( :LogLevel => set_log_level, :Scaling => scaling, :Perturbation => set_perturbation, - #:Algorithm => set_algorithm ) # These options are set by using the ClpSolve object const solveoptionmap = Dict( @@ -84,9 +82,7 @@ function setoption(m::ClpOptimizer, name::Symbol, value) end function ClpOptimizer(;kwargs...) - # env = Env() m = ClpOptimizer(nothing) - # m.env = env m.params = Dict{String,Any}() MOI.empty!(m) for (name,value) in kwargs @@ -101,13 +97,16 @@ LQOI.supported_constraints(s::ClpOptimizer) = SUPPORTED_CONSTRAINTS LQOI.supported_objectives(s::ClpOptimizer) = SUPPORTED_OBJECTIVES """ + change_variable_bounds!(instance, cols::Vector{Int}, + values::Vector{Float64}, senses::Vector) + Change the bounds of the variable. """ -function LQOI.change_variable_bounds!(instance::ClpOptimizer, cols::Vector{Int}, values::Vector{Float64}, - senses::Vector) +function LQOI.change_variable_bounds!(instance::ClpOptimizer, cols::Vector{Int}, + values::Vector{Float64}, senses::Vector) - upperbounds = replaceInf(get_col_upper(instance.inner)) - lowerbounds = replaceInf(get_col_lower(instance.inner)) + upperbounds = replace_inf(get_col_upper(instance.inner)) + lowerbounds = replace_inf(get_col_lower(instance.inner)) for i in 1:length(senses) if senses[i] == Cchar('U') @@ -125,29 +124,29 @@ function LQOI.change_variable_bounds!(instance::ClpOptimizer, cols::Vector{Int}, end """ -get_variable_lowerbound(m, col::Int)::Float64 + get_variable_lowerbound(instance, col::Int)::Float64 Get the lower bound of the variable in 1-indexed column `col` of the model `m`. """ function LQOI.get_variable_lowerbound(instance::ClpOptimizer, col::Int)::Float64 lower = get_col_lower(instance.inner) - replaceInf(lower, col) + replace_inf(lower, col) return lower[col] end """ -get_variable_upperbound(m, col::Int)::Float64 + get_variable_upperbound(instance, col::Int)::Float64 Get the upper bound of the variable in 1-indexed column `col` of the model `m`. """ function LQOI.get_variable_upperbound(instance::ClpOptimizer, col::Int)::Float64 upper = get_col_upper(instance.inner) - replaceInf(upper, col) + replace_inf(upper, col) return upper[col] end """ -get_number_linear_constraints(m)::Int + get_number_linear_constraints(instance)::Int Get the number of linear constraints in the model `m`. """ @@ -156,10 +155,13 @@ function LQOI.get_number_linear_constraints(instance::ClpOptimizer) end """ + append_row(row::Int, lower::Float64, upper::Float64, instance::ClpOptimizer, + rows::Vector{Int}, cols::Vector{Int}, coefs::Vector{Float64}) + Helper function for adding a row. it adds the row in the components that build the LQOI sparse matrix, and it adds the row in the solver. """ -function addrow(row::Int, lower::Float64, upper::Float64, instance::ClpOptimizer, +function append_row(row::Int, lower::Float64, upper::Float64, instance::ClpOptimizer, rows::Vector{Int}, cols::Vector{Int}, coefs::Vector{Float64}) elements = Float64[] @@ -178,20 +180,21 @@ function addrow(row::Int, lower::Float64, upper::Float64, instance::ClpOptimizer end """ - add_linear_constraints!(m, A::CSRMatrix{Float64}, sense::Vector{Cchar}, rhs::Vector{Float64})::Void + add_linear_constraints!(instance, A::CSRMatrix{Float64}, sense::Vector{Cchar}, + rhs::Vector{Float64})::Void Adds linear constraints of the form `Ax (sense) rhs` to the model `m`. `sense` and `rhs` contain one element for each row in `A`. """ -function LQOI.add_linear_constraints!(instance::ClpOptimizer, A::LQOI.CSRMatrix{Float64}, sense::Vector{Cchar}, - rhs::Vector{Float64})::Void +function LQOI.add_linear_constraints!(instance::ClpOptimizer, A::LQOI.CSRMatrix{Float64}, + sense::Vector{Cchar}, rhs::Vector{Float64})::Void rows = A.row_pointers cols = A.columns coefs = A.coefficients - nbrows = length(rhs) - for row in 1:nbrows + num_rows = length(rhs) + for row in 1:num_rows if (rhs[row] > 1e20) error("rhs must always be less than 1e20") elseif (rhs[row] < -1e20) @@ -210,42 +213,44 @@ function LQOI.add_linear_constraints!(instance::ClpOptimizer, A::LQOI.CSRMatrix{ error("sense must be Cchar(x) where x is in ['L','G',E']") end - addrow(row, lower, upper, instance, rows, cols, coefs) + append_row(row, lower, upper, instance, rows, cols, coefs) end end """ - add_ranged_constraints!(m, A::CSRMatrix{Float64}, lb::Vector{Float64}, ub::Vector{Float64})::Void + add_ranged_constraints!(instance, A::CSRMatrix{Float64}, lb::Vector{Float64}, + ub::Vector{Float64})::Void Adds linear constraints of the form `Ax (sense) rhs` to the model `m`. `sense` and `rhs` contain one element for each row in `A`. """ -function LQOI.add_ranged_constraints!(instance::Clp.ClpOptimizer, A::LinQuadOptInterface.CSRMatrix{Float64}, +function LQOI.add_ranged_constraints!(instance::Clp.ClpOptimizer, + A::LinQuadOptInterface.CSRMatrix{Float64}, lb::Vector{Float64}, ub::Vector{Float64}) rows = A.row_pointers cols = A.columns coefs = A.coefficients - nbrows = length(lb) - for row in 1:nbrows + num_rows = length(lb) + for row in 1:num_rows lower = lb[row] upper = ub[row] - addrow(row, lower, upper, instance, rows, cols, coefs) + append_row(row, lower, upper, instance, rows, cols, coefs) end end """ -get_rhs(m, row::Int)::Float64 + get_rhs(instance, row::Int)::Float64 Get the right-hand side of the linear constraint in the 1-indexed row `row` in the model `m`. """ function LQOI.get_rhs(instance::ClpOptimizer, row::Int)::Float64 - lower = replaceInf(get_row_lower(instance.inner)) - upper = replaceInf(get_row_upper(instance.inner)) + lower = replace_inf(get_row_lower(instance.inner)) + upper = replace_inf(get_row_upper(instance.inner)) lb = lower[row] ub = upper[row] @@ -259,7 +264,7 @@ function LQOI.get_rhs(instance::ClpOptimizer, row::Int)::Float64 end """ -get_linear_constraint(m, row::Int)::Tuple{Vector{Int}, Vector{Float64}} + get_linear_constraint(instance, 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)`. @@ -271,7 +276,7 @@ function LQOI.get_linear_constraint(instance::ClpOptimizer, row::Int)::Tuple{Vec end """ -change_objective_coefficient!(m, col, coef) + change_objective_coefficient!(instance, col, coef) Set the linear coefficient of the variable in column `col` to `coef` in the objective function. """ @@ -282,13 +287,13 @@ function LQOI.change_objective_coefficient!(instance::ClpOptimizer, col, coef) end """ -change_rhs_coefficient!(m, row, coef) + change_rhs_coefficient!(instance, row, coef) Set the rhs of the constraint in row `row` to `coef`. """ function LQOI.change_rhs_coefficient!(instance::ClpOptimizer, row, coef) - lower = replaceInf(get_row_lower(instance.inner)) - upper = replaceInf(get_row_upper(instance.inner)) + lower = replace_inf(get_row_lower(instance.inner)) + upper = replace_inf(get_row_upper(instance.inner)) lb = lower[row] ub = upper[row] @@ -305,7 +310,7 @@ end """ -delete_linear_constraints!(m, start_row::Int, end_row::Int)::Void + delete_linear_constraints!(instance, start_row::Int, end_row::Int)::Void Delete the linear constraints `start_row`, `start_row+1`, ..., `end_row` from the model `m`. @@ -317,13 +322,13 @@ end """ -change_linear_constraint_sense!(m, rows::Vector{Int}, sense::Vector{Cchar})::Void + change_linear_constraint_sense!(instance, rows::Vector{Int}, sense::Vector{Cchar})::Void Change the sense of the linear constraints in `rows` to `sense`. """ function LQOI.change_linear_constraint_sense!(instance::ClpOptimizer, rows::Vector{Int}, sense::Vector{Cchar} )::Void - lower = replaceInf(get_row_lower(instance.inner)) - upper = replaceInf(get_row_upper(instance.inner)) + lower = replace_inf(get_row_lower(instance.inner)) + upper = replace_inf(get_row_upper(instance.inner)) for (i,row) in enumerate(rows) lb = lower[row] @@ -359,7 +364,7 @@ end """ -set_linear_objective!(m, cols::Vector{Int}, coefs::Vector{Float64})::Void + set_linear_objective!(instance, cols::Vector{Int}, coefs::Vector{Float64})::Void Set the linear component of the objective. """ @@ -370,7 +375,7 @@ function LQOI.set_linear_objective!(instance::ClpOptimizer, cols::Vector{Int}, c end """ -change_objective_sense!(m, sense::Symbol)::Void + change_objective_sense!(instance, sense::Symbol)::Void Change the optimization sense of the model `m` to `sense`. `sense` must be `:min` or `:max`. @@ -387,7 +392,7 @@ end """ -get_linear_objective!(m, x::Vector{Float64}) + get_linear_objective!(instance, x::Vector{Float64}) Change the linear coefficients of the objective and store in `x`. @@ -399,7 +404,7 @@ end """ -solve_linear_problem!(m)::Void + solve_linear_problem!(instance)::Void Solve a linear program `m`. """ @@ -408,7 +413,7 @@ function LQOI.solve_linear_problem!(instance::ClpOptimizer)::Void model = instance.inner for (name, value) in instance.params if haskey(optionmap, name) - optionmap[name](model,value) + optionmap[name](model, value) elseif haskey(solveoptionmap, name) solveoptionmap[name](solveroptions,value) else @@ -421,7 +426,7 @@ function LQOI.solve_linear_problem!(instance::ClpOptimizer)::Void end """ -get_variable_primal_solution!(m, x::Vector{Float64}) + get_variable_primal_solution!(instance, 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. @@ -432,7 +437,7 @@ function LQOI.get_variable_primal_solution!(instance::ClpOptimizer, x::Vector{Fl end """ -get_linear_primal_solution!(m, x::Vector{Float64}) + get_linear_primal_solution!(instance, 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`. @@ -444,7 +449,7 @@ function LQOI.get_linear_primal_solution!(instance::ClpOptimizer, x::Vector{Floa end """ -get_variable_dual_solution!(m, x::Vector{Float64}) + get_variable_dual_solution!(instance, 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. @@ -455,7 +460,7 @@ function LQOI.get_variable_dual_solution!(instance::ClpOptimizer, x::Vector{Floa end """ -get_linear_dual_solution!(m, x::Vector{Float64}) + get_linear_dual_solution!(instance, 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. @@ -466,7 +471,7 @@ function LQOI.get_linear_dual_solution!(instance::ClpOptimizer, x::Vector{Float6 end """ -get_objective_value(m) + get_objective_value(instance) Get the objective value of the solved model `m`. """ @@ -475,7 +480,7 @@ function LQOI.get_objective_value(instance::ClpOptimizer) end """ -get_termination_status(m) + get_termination_status(instance) Get the termination status of the model `m`. """ @@ -487,8 +492,6 @@ function LQOI.get_termination_status(instance::ClpOptimizer) return MOI.InfeasibleNoResult elseif s == 2 return MOI.UnboundedNoResult - # if s in [0, 1, 2] - # return MOI.Success elseif s == 3 return MOI.OtherLimit elseif s == 4 @@ -499,7 +502,7 @@ function LQOI.get_termination_status(instance::ClpOptimizer) end """ -get_primal_status(m) + get_primal_status(instance) Get the primal status of the model `m`. """ @@ -512,7 +515,7 @@ function LQOI.get_primal_status(instance::ClpOptimizer) end """ -get_dual_status(m) + get_dual_status(instance) Get the dual status of the model `m`. """ @@ -525,7 +528,7 @@ function LQOI.get_dual_status(instance::ClpOptimizer) end """ -get_number_variables(m)::Int + get_number_variables(instance)::Int Get the number of variables in the model `m`. """ @@ -534,7 +537,7 @@ function LQOI.get_number_variables(instance::ClpOptimizer) end """ -add_variables!(m, n::Int)::Void + add_variables!(instance, n::Int)::Void Add `n` new variables to the model `m`. """ @@ -548,7 +551,7 @@ function LQOI.add_variables!(instance::ClpOptimizer, n::Int)::Void end """ -delete_variables!(m, start_col::Int, end_col::Int)::Void + delete_variables!(instance, start_col::Int, end_col::Int)::Void Delete the columns `start_col`, `start_col+1`, ..., `end_col` from the model `m`. """ diff --git a/test/MOIWrapper.jl b/test/MOIWrapper.jl index f5fe3a0..092213b 100644 --- a/test/MOIWrapper.jl +++ b/test/MOIWrapper.jl @@ -7,7 +7,9 @@ const MOIT = MathOptInterface.Test linconfig = MOIT.TestConfig(modify_lhs = false) @testset "Default Solver" begin solver = ClpOptimizer(LogLevel = 0) - MOIT.contlineartest(solver, linconfig, ["linear10","linear12","linear8a","linear8b","linear8c"]) + # linear1 test is disabled due to the following bug. + # https://projects.coin-or.org/Clp/ticket/84 + MOIT.contlineartest(solver, linconfig, ["linear1", "linear10","linear12","linear8a","linear8b","linear8c"]) end end From a79e3fda6b2b11cdfc64db0310403d2c67459513 Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Tue, 10 Jul 2018 15:08:12 +0200 Subject: [PATCH 11/22] exculded test linear11 --- test/MOIWrapper.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/MOIWrapper.jl b/test/MOIWrapper.jl index 092213b..6e61b46 100644 --- a/test/MOIWrapper.jl +++ b/test/MOIWrapper.jl @@ -9,7 +9,8 @@ const MOIT = MathOptInterface.Test solver = ClpOptimizer(LogLevel = 0) # linear1 test is disabled due to the following bug. # https://projects.coin-or.org/Clp/ticket/84 - MOIT.contlineartest(solver, linconfig, ["linear1", "linear10","linear12","linear8a","linear8b","linear8c"]) + MOIT.contlineartest(solver, linconfig, ["linear1", "linear10","linear11", + "linear12","linear8a","linear8b","linear8c"]) end end From b86374b19f0c9d0f0b15d20a82cda350cd87b248 Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Sat, 28 Jul 2018 15:23:06 +0200 Subject: [PATCH 12/22] removed the LQOI version upperbound --- REQUIRE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REQUIRE b/REQUIRE index b38263e..c9bad9a 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,4 @@ julia 0.6 Cbc MathProgBase 0.5 0.8 -LinQuadOptInterface 0.1 0.2 +LinQuadOptInterface 0.1 From a89e1c37200db8dd0a35f0929b04091bedf27f7e Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Sat, 28 Jul 2018 17:13:50 +0200 Subject: [PATCH 13/22] limited LQOI to 2.0 inclusive --- REQUIRE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REQUIRE b/REQUIRE index c9bad9a..6f4c74f 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,4 @@ julia 0.6 Cbc MathProgBase 0.5 0.8 -LinQuadOptInterface 0.1 +LinQuadOptInterface 0.1 0.3 From ea529ee9cbcdab09fe4099dd16d9c2e067de8883 Mon Sep 17 00:00:00 2001 From: odow Date: Sun, 29 Jul 2018 15:33:09 +1200 Subject: [PATCH 14/22] Tidy up. Remove unneeded docstrings, fix a few functions, enable more tests. --- src/ClpCInterface.jl | 25 +-- src/MOIWrapper.jl | 416 +++++++++++++------------------------------ test/MOIWrapper.jl | 38 ++-- 3 files changed, 156 insertions(+), 323 deletions(-) diff --git a/src/ClpCInterface.jl b/src/ClpCInterface.jl index 0983d86..b57aae8 100644 --- a/src/ClpCInterface.jl +++ b/src/ClpCInterface.jl @@ -416,19 +416,14 @@ function add_rows(model::ClpModel, number::Integer, row_lower::Vector{Float64}, row_starts::Vector{Int32}, columns::Vector{Int32}, elements::Vector{Float64}) _jl__check_model(model) - @clp_ccall addRows Void (Ptr{Void}, Int32, Ptr{Float64}, Ptr{Float64}, Ptr{Int32}, Ptr{Int32}, Ptr{Float64}) model.p number row_lower row_upper row_starts columns elements end #This function exists in cpp but not c interface -function add_row(model::ClpModel, number_in_row::Integer, columns::Vector{Int32}, elements::Vector{Float64}, - row_lower::Float64, row_upper::Float64) - row_starts_vector = Vector{Int32}(2) - row_starts_vector[1] = 0 - row_starts_vector[2] = number_in_row - row_upper_vector = [row_upper] - row_lower_vector = [row_lower] - add_rows(model, 1, row_lower_vector, row_upper_vector, row_starts_vector, columns, elements) +function add_row(model::ClpModel, nnz::Cint, columns::Vector{Int32}, + elements::Vector{Float64}, row_lower::Float64, row_upper::Float64) + add_rows(model, 1, [row_lower], [row_upper], [Cint(0), nnz], columns, + elements) end # Delete columns. @@ -438,16 +433,11 @@ function delete_columns(model::ClpModel, which::Vector{Int32}) end #This function exists in cpp but not c interface -function add_column(model::ClpModel, number_in_column::Integer, rows::Vector{Int32}, +function add_column(model::ClpModel, nnz::Int32, rows::Vector{Int32}, elements::Vector{Float64}, column_lower::Float64, column_upper::Float64, objective::Float64) - _column_starts = Vector{Int32}(2) - _column_starts[1] = 0 - _column_starts[2] = number_in_column - _column_upper = [column_upper] - _column_lower = [column_lower] - _objective = [objective] - add_columns(model, 1, _column_lower, _column_upper, _objective, _column_starts, rows, elements) + add_columns(model, 1, [column_lower], [column_upper], [objective], + [Int32(0), nnz], rows, elements) end # Add columns. @@ -466,7 +456,6 @@ function add_columns(model::ClpModel, column_lower::Vector{Float64}, column_upper::Vector{Float64}, objective::Vector{Float64}, new_columns::SparseMatrixCSC{Float64,Int32}) - add_columns(model, new_columns.n, column_lower, column_upper, objective, new_columns.colptr-convert(Int32,1), new_columns.rowval-convert(Int32,1), new_columns.nzval) end diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index ca50694..f289853 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -1,26 +1,7 @@ export ClpOptimizer using LinQuadOptInterface -using Clp.ClpCInterface - -function replace_inf(x) - for i in 1:length(x) - if x[i] > 1e20 - x[i] = Inf - elseif x[i] < -1e20 - x[i] = -Inf - end - end - return x -end - -function replace_inf(x, index) - if x[index] > 1e20 - x[index] = Inf - elseif x[index] < -1e20 - x[index] = -Inf - end -end +using .ClpCInterface const LQOI = LinQuadOptInterface const MOI = LQOI.MOI @@ -91,246 +72,184 @@ function ClpOptimizer(;kwargs...) return m end -LQOI.LinearQuadraticModel(::Type{ClpOptimizer},env) = ClpModel() +LQOI.LinearQuadraticModel(::Type{ClpOptimizer},env) = ClpModel() LQOI.supported_constraints(s::ClpOptimizer) = SUPPORTED_CONSTRAINTS LQOI.supported_objectives(s::ClpOptimizer) = SUPPORTED_OBJECTIVES """ - change_variable_bounds!(instance, cols::Vector{Int}, - values::Vector{Float64}, senses::Vector) - -Change the bounds of the variable. + replace_inf(x::Vector{<:Real}) + +Replaces any occurances of an element `x[i]>1e20` with `Inf` and `x[i]<-1e20` +with `-Inf`. +""" +function replace_inf(x::Vector{<:Real}) + for i in 1:length(x) + if x[i] > 1e20 + x[i] = Inf + elseif x[i] < -1e20 + x[i] = -Inf + end + end + return x +end + +""" + replace_inf(x::Real) + +Return `Inf` if `x>1e20`, `-Inf` if `x<-1e20`, and `x` otherwise. """ -function LQOI.change_variable_bounds!(instance::ClpOptimizer, cols::Vector{Int}, +function replace_inf(x::Real) + if x > 1e20 + return Inf + elseif x < -1e20 + return -Inf + else + return x + end +end + +function LQOI.change_variable_bounds!(instance::ClpOptimizer, cols::Vector{Int}, values::Vector{Float64}, senses::Vector) - - upperbounds = replace_inf(get_col_upper(instance.inner)) - lowerbounds = replace_inf(get_col_lower(instance.inner)) - + upperbounds = replace_inf(get_col_upper(instance.inner)) + lowerbounds = replace_inf(get_col_lower(instance.inner)) for i in 1:length(senses) if senses[i] == Cchar('U') upperbounds[cols[i]] = values[i] elseif senses[i] == Cchar('L') lowerbounds[cols[i]] = values[i] - else - error("sense is " * string(senses[i]) * ", but only " * Cchar('U') * - " and " * Cchar('L') * " senses are supported") + else + error("sense is $(senses[i]), but only 'U' and 'L' are supported") end end - chg_column_upper(instance.inner, upperbounds) chg_column_lower(instance.inner, lowerbounds) end -""" - get_variable_lowerbound(instance, col::Int)::Float64 - -Get the lower bound of the variable in 1-indexed column `col` of the model `m`. -""" -function LQOI.get_variable_lowerbound(instance::ClpOptimizer, col::Int)::Float64 +function LQOI.get_variable_lowerbound(instance::ClpOptimizer, col::Int) lower = get_col_lower(instance.inner) - replace_inf(lower, col) - return lower[col] + return replace_inf(lower[col]) end -""" - get_variable_upperbound(instance, col::Int)::Float64 - -Get the upper bound of the variable in 1-indexed column `col` of the model `m`. -""" -function LQOI.get_variable_upperbound(instance::ClpOptimizer, col::Int)::Float64 +function LQOI.get_variable_upperbound(instance::ClpOptimizer, col::Int) upper = get_col_upper(instance.inner) - replace_inf(upper, col) - return upper[col] + return replace_inf(upper[col]) end -""" - get_number_linear_constraints(instance)::Int - -Get the number of linear constraints in the model `m`. -""" function LQOI.get_number_linear_constraints(instance::ClpOptimizer) return get_num_rows(instance.inner) end """ - append_row(row::Int, lower::Float64, upper::Float64, instance::ClpOptimizer, - rows::Vector{Int}, cols::Vector{Int}, coefs::Vector{Float64}) + append_row(instance::ClpOptimizer, row::Int, lower::Float64, upper::Float64, + rows::Vector{Int}, cols::Vector{Int}, coefs::Vector{Float64}) -Helper function for adding a row. it adds the row in the components -that build the LQOI sparse matrix, and it adds the row in the solver. +Given a sparse matrix in the triplet-form `(rows, cols, coefs)`, add row `row` +with upper bound `upper` and lower bound `lower` to the instance `instance`. """ -function append_row(row::Int, lower::Float64, upper::Float64, instance::ClpOptimizer, - rows::Vector{Int}, cols::Vector{Int}, coefs::Vector{Float64}) - - elements = Float64[] - columns = Int32[] - - number_in_row = 0 +function append_row(instance::ClpOptimizer, row::Int, lower::Float64, + upper::Float64, rows::Vector{Int}, cols::Vector{Int}, + coefs::Vector{Float64}) + row_variables = Int32[] + row_coefficients = Float64[] first = rows[row] last = (row==length(rows)) ? length(cols) : rows[row+1]-1 for i in first:last - number_in_row += 1 - push!(elements, coefs[i]) - push!(columns, cols[i] - 1) - end - - add_row(instance.inner, Cint(number_in_row), columns, elements, lower, upper) + push!(row_coefficients, coefs[i]) + push!(row_variables, cols[i] - 1) + end + add_row(instance.inner, Cint(length(row_variables)), row_variables, + row_coefficients, lower, upper) end -""" - add_linear_constraints!(instance, A::CSRMatrix{Float64}, sense::Vector{Cchar}, - rhs::Vector{Float64})::Void - -Adds linear constraints of the form `Ax (sense) rhs` to the model `m`. -`sense` and `rhs` contain one element for each row in `A`. -""" -function LQOI.add_linear_constraints!(instance::ClpOptimizer, A::LQOI.CSRMatrix{Float64}, - sense::Vector{Cchar}, rhs::Vector{Float64})::Void - +function LQOI.add_linear_constraints!(instance::ClpOptimizer, A::LQOI.CSRMatrix{Float64}, + senses::Vector{Cchar}, right_hand_sides::Vector{Float64}) rows = A.row_pointers cols = A.columns coefs = A.coefficients - - num_rows = length(rhs) - for row in 1:num_rows - if (rhs[row] > 1e20) + for (row, (rhs, sense)) in enumerate(zip(right_hand_sides, senses)) + if (rhs > 1e20) error("rhs must always be less than 1e20") - elseif (rhs[row] < -1e20) + elseif (rhs < -1e20) error("rhs must always be greater than -1e20") end - lower = -Inf - upper = Inf - if sense[row] == Cchar('L') - upper = rhs[row] - elseif sense[row] == Cchar('G') - lower = rhs[row] - elseif sense[row] == Cchar('E') - upper = lower = rhs[row] + upper = Inf + if sense == Cchar('L') + upper = rhs + elseif sense == Cchar('G') + lower = rhs + elseif sense == Cchar('E') + upper = lower = rhs else error("sense must be Cchar(x) where x is in ['L','G',E']") end - - append_row(row, lower, upper, instance, rows, cols, coefs) + append_row(instance, row, lower, upper, rows, cols, coefs) end end -""" - add_ranged_constraints!(instance, A::CSRMatrix{Float64}, lb::Vector{Float64}, - ub::Vector{Float64})::Void - -Adds linear constraints of the form `Ax (sense) rhs` to the model `m`. -`sense` and `rhs` contain one element for each row in `A`. -""" -function LQOI.add_ranged_constraints!(instance::Clp.ClpOptimizer, - A::LinQuadOptInterface.CSRMatrix{Float64}, +function LQOI.add_ranged_constraints!(instance::Clp.ClpOptimizer, + A::LinQuadOptInterface.CSRMatrix{Float64}, lb::Vector{Float64}, ub::Vector{Float64}) - rows = A.row_pointers cols = A.columns coefs = A.coefficients - - num_rows = length(lb) - for row in 1:num_rows - lower = lb[row] - upper = ub[row] - - append_row(row, lower, upper, instance, rows, cols, coefs) + for row in 1:length(lb) + append_row(instance, row, lb[row], ub[row], rows, cols, coefs) end end - -""" - get_rhs(instance, row::Int)::Float64 - -Get the right-hand side of the linear constraint in the 1-indexed row `row` in -the model `m`. -""" -function LQOI.get_rhs(instance::ClpOptimizer, row::Int)::Float64 - lower = replace_inf(get_row_lower(instance.inner)) - upper = replace_inf(get_row_upper(instance.inner)) - lb = lower[row] - ub = upper[row] - - if lb > -Inf - return lb - elseif ub < Inf - return ub +function LQOI.get_rhs(instance::ClpOptimizer, row::Int) + lower_bounds = get_row_lower(instance.inner) + upper_bounds = get_row_upper(instance.inner) + lower_bound = replace_inf(lower_bounds[row]) + upper_bound = replace_inf(upper_bounds[row]) + if lower_bound > -Inf + return lower_bound + elseif upper_bound < Inf + return upper_bound else error("Either row_lower or row_upper must be of abs less than 1e20") end end -""" - get_linear_constraint(instance, 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 LQOI.get_linear_constraint(instance::ClpOptimizer, row::Int)::Tuple{Vector{Int}, Vector{Float64}} A = get_constraint_matrix(instance.inner) A_row = A[row,:] return (Array{Int}(A_row.nzind), A_row.nzval) end -""" - change_objective_coefficient!(instance, col, coef) - -Set the linear coefficient of the variable in column `col` to `coef` in the objective function. -""" function LQOI.change_objective_coefficient!(instance::ClpOptimizer, col, coef) objcoefs = get_obj_coefficients(instance.inner) objcoefs[col] = coef chg_obj_coefficients(instance.inner, objcoefs) end -""" - change_rhs_coefficient!(instance, row, coef) - -Set the rhs of the constraint in row `row` to `coef`. -""" function LQOI.change_rhs_coefficient!(instance::ClpOptimizer, row, coef) - lower = replace_inf(get_row_lower(instance.inner)) - upper = replace_inf(get_row_upper(instance.inner)) - lb = lower[row] - ub = upper[row] - - if lb > -Inf - lower[row] = coef - chg_row_lower(instance.inner, lower) - elseif ub < Inf - upper[row] = coef - chg_row_upper(instance.inner, upper) + lower_bounds = get_row_lower(instance.inner) + upper_bounds = get_row_upper(instance.inner) + lower_bound = replace_inf(lower_bounds[row]) + upper_bound = replace_inf(upper_bounds[row]) + if lower_bound > -Inf + lower_bounds[row] = coef + chg_row_lower(instance.inner, lower_bounds) + elseif upper_bound < Inf + upper_bounds[row] = coef + chg_row_upper(instance.inner, upper_bounds) else error("Either row_lower or row_upper must be of abs less than 1e20") end end - -""" - delete_linear_constraints!(instance, start_row::Int, end_row::Int)::Void - -Delete the linear constraints `start_row`, `start_row+1`, ..., `end_row` from -the model `m`. -""" -function LQOI.delete_linear_constraints!(instance::ClpOptimizer, start_row::Int, end_row::Int)::Void - which = [Int32(i-1) for i in start_row:end_row] - delete_rows(instance.inner, which) +function LQOI.delete_linear_constraints!(instance::ClpOptimizer, start_row::Int, end_row::Int) + rows = [Cint(i-1) for i in start_row:end_row] + delete_rows(instance.inner, rows) end - -""" - change_linear_constraint_sense!(instance, rows::Vector{Int}, sense::Vector{Cchar})::Void - -Change the sense of the linear constraints in `rows` to `sense`. -""" -function LQOI.change_linear_constraint_sense!(instance::ClpOptimizer, rows::Vector{Int}, sense::Vector{Cchar} )::Void +function LQOI.change_linear_constraint_sense!(instance::ClpOptimizer, rows::Vector{Int}, senses::Vector{Cchar}) lower = replace_inf(get_row_lower(instance.inner)) upper = replace_inf(get_row_upper(instance.inner)) - - for (i,row) in enumerate(rows) + for (sense, row) in zip(senses, rows) lb = lower[row] ub = upper[row] if lb > -Inf @@ -340,46 +259,31 @@ function LQOI.change_linear_constraint_sense!(instance::ClpOptimizer, rows::Vect else error("Either row_lower or row_upper must be of abs less than 1e20") end - - sense = sense[i] if sense == Cchar('G') lb = rhs ub = Inf elseif sense == Cchar('L') lb = -Inf ub = rhs - elseif sense == Cchar('E') + elseif sense == Cchar('E') lb = rhs ub = rhs end - lower[row] = lb - upper[row] = ub + upper[row] = ub end - chg_row_upper(instance.inner, upper) chg_row_lower(instance.inner, lower) end - - -""" - set_linear_objective!(instance, cols::Vector{Int}, coefs::Vector{Float64})::Void - -Set the linear component of the objective. -""" -function LQOI.set_linear_objective!(instance::ClpOptimizer, cols::Vector{Int}, coefs::Vector{Float64})::Void - obj_in = zeros(Float64, get_num_cols(instance.inner)) - obj_in[cols] .= coefs - chg_obj_coefficients(instance.inner, obj_in) +function LQOI.set_linear_objective!(instance::ClpOptimizer, cols::Vector{Int}, coefs::Vector{Float64}) + objective_coefficients = zeros(Float64, get_num_cols(instance.inner)) + for (col, coef) in zip(cols, coefs) + objective_coefficients[col] += coef + end + chg_obj_coefficients(instance.inner, objective_coefficients) end -""" - change_objective_sense!(instance, sense::Symbol)::Void - -Change the optimization sense of the model `m` to `sense`. `sense` must be -`:min` or `:max`. -""" function LQOI.change_objective_sense!(instance::ClpOptimizer, sense::Symbol) if sense == :min set_obj_sense(instance.inner, 1.0) @@ -390,25 +294,12 @@ function LQOI.change_objective_sense!(instance::ClpOptimizer, sense::Symbol) end end - -""" - get_linear_objective!(instance, x::Vector{Float64}) - -Change the linear coefficients of the objective and store -in `x`. -""" function LQOI.get_linear_objective!(instance::ClpOptimizer, x::Vector{Float64}) obj = get_obj_coefficients(instance.inner) copy!(x, obj) end - -""" - solve_linear_problem!(instance)::Void - -Solve a linear program `m`. -""" -function LQOI.solve_linear_problem!(instance::ClpOptimizer)::Void +function LQOI.solve_linear_problem!(instance::ClpOptimizer) solveroptions = ClpSolve() model = instance.inner for (name, value) in instance.params @@ -420,142 +311,77 @@ function LQOI.solve_linear_problem!(instance::ClpOptimizer)::Void error("Unrecognized option: $name") end end - initial_solve_with_options(instance.inner, solveroptions) - return end -""" - get_variable_primal_solution!(instance, 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 LQOI.get_variable_primal_solution!(instance::ClpOptimizer, x::Vector{Float64}) solution = primal_column_solution(instance.inner) copy!(x,solution) end -""" - get_linear_primal_solution!(instance, 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 LQOI.get_linear_primal_solution!(instance::ClpOptimizer, x::Vector{Float64}) solution = primal_row_solution(instance.inner) copy!(x,solution) end -""" - get_variable_dual_solution!(instance, 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 LQOI.get_variable_dual_solution!(instance::ClpOptimizer, x::Vector{Float64}) solution = dual_column_solution(instance.inner) copy!(x,solution) end -""" - get_linear_dual_solution!(instance, 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 LQOI.get_linear_dual_solution!(instance::ClpOptimizer, x::Vector{Float64}) solution = dual_row_solution(instance.inner) copy!(x,solution) end -""" - get_objective_value(instance) - -Get the objective value of the solved model `m`. -""" function LQOI.get_objective_value(instance::ClpOptimizer) return objective_value(instance.inner) end -""" - get_termination_status(instance) - -Get the termination status of the model `m`. -""" function LQOI.get_termination_status(instance::ClpOptimizer) - s = ClpCInterface.status(instance.inner) - if s == 0 + status = ClpCInterface.status(instance.inner) + if status == 0 return MOI.Success - elseif s == 1 + elseif status == 1 return MOI.InfeasibleNoResult - elseif s == 2 + elseif status == 2 return MOI.UnboundedNoResult - elseif s == 3 + elseif status == 3 return MOI.OtherLimit - elseif s == 4 + elseif status == 4 return MOI.OtherError else - error("status returned by Clp must be in [0,1,2,3,4]") + error("status returned was $(status), but it must be in [0,1,2,3,4]") end end -""" - get_primal_status(instance) - -Get the primal status of the model `m`. -""" function LQOI.get_primal_status(instance::ClpOptimizer) if primal_feasible(instance.inner) return MOI.FeasiblePoint - else + else return MOI.UnknownResultStatus end end -""" - get_dual_status(instance) - -Get the dual status of the model `m`. -""" function LQOI.get_dual_status(instance::ClpOptimizer) if dual_feasible(instance.inner) - return MOI.FeasiblePoint + return MOI.FeasiblePoint else return MOI.UnknownResultStatus - end + end end -""" - get_number_variables(instance)::Int - -Get the number of variables in the model `m`. -""" function LQOI.get_number_variables(instance::ClpOptimizer) return get_num_cols(instance.inner) end -""" - add_variables!(instance, n::Int)::Void - -Add `n` new variables to the model `m`. -""" -function LQOI.add_variables!(instance::ClpOptimizer, n::Int)::Void +function LQOI.add_variables!(instance::ClpOptimizer, n::Int) for i in 1:n - numberInColumn = 0 - rows = Int32[] - elements = Float64[] - add_column(instance.inner, numberInColumn, rows, elements, -Inf, +Inf, 0.0) + add_column(instance.inner, Cint(0), Int32[], Float64[], -Inf, Inf, 0.0) end end -""" - delete_variables!(instance, start_col::Int, end_col::Int)::Void - -Delete the columns `start_col`, `start_col+1`, ..., `end_col` from the model `m`. -""" -function LQOI.delete_variables!(instance::ClpOptimizer, start_col::Int, end_col::Int)::Void - which = [Int32(i-1) for i in start_col:end_col] - delete_columns(instance.inner, which) -end \ No newline at end of file +function LQOI.delete_variables!(instance::ClpOptimizer, start_col::Int, end_col::Int) + columns = [Cint(i-1) for i in start_col:end_col] + delete_columns(instance.inner, columns) +end diff --git a/test/MOIWrapper.jl b/test/MOIWrapper.jl index 6e61b46..4a5e0a2 100644 --- a/test/MOIWrapper.jl +++ b/test/MOIWrapper.jl @@ -1,31 +1,49 @@ using Base.Test, MathOptInterface, MathOptInterface.Test const MOI = MathOptInterface +const MOIB = MathOptInterface.Bridges const MOIT = MathOptInterface.Test @testset "Linear tests" begin linconfig = MOIT.TestConfig(modify_lhs = false) - @testset "Default Solver" begin - solver = ClpOptimizer(LogLevel = 0) + solver = ClpOptimizer(LogLevel = 0) + MOIT.contlineartest(solver, linconfig, [ # linear1 test is disabled due to the following bug. # https://projects.coin-or.org/Clp/ticket/84 - MOIT.contlineartest(solver, linconfig, ["linear1", "linear10","linear11", - "linear12","linear8a","linear8b","linear8c"]) + "linear1", + # linear10 test is disabled because it has interval sets + "linear10", + # these tests are diabled because they need infeasibility certificates + "linear8a","linear8b","linear8c", "linear12" + ]) + + @testset "Interval Bridge" begin + MOIT.linear10test(MOIB.SplitInterval{Float64}(solver), linconfig) + end + + @testset "Infeasiblity" begin + linconfig_nocertificate = MOIT.TestConfig(modify_lhs = false, infeas_certificates=false) + # TODO(odow): we return a ResultCount of 1 without a result somewhere + # MOIT.linear8atest(solver, linconfig_nocertificate) + # MOIT.linear8btest(solver, linconfig_nocertificate) + # MOIT.linear8ctest(solver, linconfig_nocertificate) + # MOIT.linear12test(solver, linconfig_nocertificate) end end @testset "ModelLike tests" begin solver = ClpOptimizer(LogLevel = 0) - MOIT.nametest(solver) + @testset "nametest" begin + MOIT.nametest(solver) + end @testset "validtest" begin MOIT.validtest(solver) end @testset "emptytest" begin MOIT.emptytest(solver) end - # TODO uncomment after adding MOIT.TestConfig.multiple_bounds # @testset "orderedindicestest" begin - # MOIT.orderedindicestest(solver) + # MOIT.orderedindicestest(solver) # end @testset "canaddconstrainttest" begin MOIT.canaddconstrainttest(solver, Float64, Complex{Float64}) @@ -42,7 +60,7 @@ end # TODO uncomment after adding MOIT.TestConfig.multiple_bounds # MOIT.basic_constraint_tests(solver, config) MOIT.unittest(solver, config, [ - "solve_qcp_edge_cases", + "solve_qcp_edge_cases", "solve_qp_edge_cases" - ]) -end \ No newline at end of file + ]) +end From 9aca0f62874a31c38554f3a17342297431782f73 Mon Sep 17 00:00:00 2001 From: odow Date: Sun, 29 Jul 2018 20:33:09 +1200 Subject: [PATCH 15/22] Handle infeasiblity certificates and other matters: --- .travis.yml | 4 +-- src/MOIWrapper.jl | 64 +++++++++++++++++++++++++++++----------------- test/MOIWrapper.jl | 18 +++---------- test/runtests.jl | 11 +++++--- 4 files changed, 53 insertions(+), 44 deletions(-) diff --git a/.travis.yml b/.travis.yml index 02b87d2..5670834 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,5 +10,5 @@ notifications: script: - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - julia -e 'Pkg.clone(pwd()); Pkg.build("Clp"); Pkg.test("Clp"; coverage=true)' -#after_success: - #- julia -e 'cd(Pkg.dir("Clp")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder())' +after_success: + - julia -e 'cd(Pkg.dir("Clp")); Pkg.add("Coverage"); using Coverage; Codecov.submit(Codecov.process_folder())' diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index f289853..e3b78d7 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -52,30 +52,30 @@ const solveoptionmap = Dict( :InfeasibleReturn => set_infeasible_return, ) -function setoption(m::ClpOptimizer, name::Symbol, value) +function setoption(optimizer::ClpOptimizer, name::Symbol, value) if haskey(optionmap, name) - optionmap[name](m.inner,value) + optionmap[name](optimizer.inner,value) elseif haskey(solveoptionmap, name) - solveoptionmap[name](m.solveroptions,value) + solveoptionmap[name](optimizer.solveroptions,value) else error("Unrecognized option: $name") end end function ClpOptimizer(;kwargs...) - m = ClpOptimizer(nothing) - m.params = Dict{String,Any}() - MOI.empty!(m) + optimizer = ClpOptimizer(nothing) + optimizer.params = Dict{String,Any}() + MOI.empty!(optimizer) for (name,value) in kwargs - m.params[Symbol(name)] = value + optimizer.params[Symbol(name)] = value end - return m + return optimizer end LQOI.LinearQuadraticModel(::Type{ClpOptimizer},env) = ClpModel() -LQOI.supported_constraints(s::ClpOptimizer) = SUPPORTED_CONSTRAINTS -LQOI.supported_objectives(s::ClpOptimizer) = SUPPORTED_OBJECTIVES +LQOI.supported_constraints(optimizer::ClpOptimizer) = SUPPORTED_CONSTRAINTS +LQOI.supported_objectives(optimizer::ClpOptimizer) = SUPPORTED_OBJECTIVES """ replace_inf(x::Vector{<:Real}) @@ -295,8 +295,7 @@ function LQOI.change_objective_sense!(instance::ClpOptimizer, sense::Symbol) end function LQOI.get_linear_objective!(instance::ClpOptimizer, x::Vector{Float64}) - obj = get_obj_coefficients(instance.inner) - copy!(x, obj) + copy!(x, get_obj_coefficients(instance.inner)) end function LQOI.solve_linear_problem!(instance::ClpOptimizer) @@ -315,37 +314,50 @@ function LQOI.solve_linear_problem!(instance::ClpOptimizer) end function LQOI.get_variable_primal_solution!(instance::ClpOptimizer, x::Vector{Float64}) - solution = primal_column_solution(instance.inner) - copy!(x,solution) + copy!(x, primal_column_solution(instance.inner)) end function LQOI.get_linear_primal_solution!(instance::ClpOptimizer, x::Vector{Float64}) - solution = primal_row_solution(instance.inner) - copy!(x,solution) + copy!(x, primal_row_solution(instance.inner)) end function LQOI.get_variable_dual_solution!(instance::ClpOptimizer, x::Vector{Float64}) - solution = dual_column_solution(instance.inner) - copy!(x,solution) + copy!(x, dual_column_solution(instance.inner)) end function LQOI.get_linear_dual_solution!(instance::ClpOptimizer, x::Vector{Float64}) - solution = dual_row_solution(instance.inner) - copy!(x,solution) + copy!(x, dual_row_solution(instance.inner)) end function LQOI.get_objective_value(instance::ClpOptimizer) return objective_value(instance.inner) end +function LQOI.get_farkas_dual!(instance::ClpOptimizer, result::Vector{Float64}) + copy!(result, infeasibility_ray(instance.inner)) + scale!(result, -1.0) +end + +function LQOI.get_unbounded_ray!(instance::ClpOptimizer, result::Vector{Float64}) + copy!(result, unbounded_ray(instance.inner)) +end + function LQOI.get_termination_status(instance::ClpOptimizer) status = ClpCInterface.status(instance.inner) if status == 0 return MOI.Success elseif status == 1 - return MOI.InfeasibleNoResult + if is_proven_primal_infeasible(instance.inner) + return MOI.Success + else + return MOI.InfeasibleNoResult + end elseif status == 2 - return MOI.UnboundedNoResult + if is_proven_dual_infeasible(instance.inner) + return MOI.Success + else + return MOI.UnboundedNoResult + end elseif status == 3 return MOI.OtherLimit elseif status == 4 @@ -356,7 +368,9 @@ function LQOI.get_termination_status(instance::ClpOptimizer) end function LQOI.get_primal_status(instance::ClpOptimizer) - if primal_feasible(instance.inner) + if is_proven_dual_infeasible(instance.inner) + return MOI.InfeasibilityCertificate + elseif primal_feasible(instance.inner) return MOI.FeasiblePoint else return MOI.UnknownResultStatus @@ -364,7 +378,9 @@ function LQOI.get_primal_status(instance::ClpOptimizer) end function LQOI.get_dual_status(instance::ClpOptimizer) - if dual_feasible(instance.inner) + if is_proven_primal_infeasible(instance.inner) + return MOI.InfeasibilityCertificate + elseif dual_feasible(instance.inner) return MOI.FeasiblePoint else return MOI.UnknownResultStatus diff --git a/test/MOIWrapper.jl b/test/MOIWrapper.jl index 4a5e0a2..56c25fe 100644 --- a/test/MOIWrapper.jl +++ b/test/MOIWrapper.jl @@ -1,4 +1,4 @@ -using Base.Test, MathOptInterface, MathOptInterface.Test +using MathOptInterface, MathOptInterface.Test const MOI = MathOptInterface const MOIB = MathOptInterface.Bridges @@ -11,24 +11,13 @@ const MOIT = MathOptInterface.Test # linear1 test is disabled due to the following bug. # https://projects.coin-or.org/Clp/ticket/84 "linear1", - # linear10 test is disabled because it has interval sets - "linear10", - # these tests are diabled because they need infeasibility certificates - "linear8a","linear8b","linear8c", "linear12" + "linear10", # linear10 test is tested below because it has interval sets + "linear12" # incorrect certificate? Test 2 * cd1 ≈ -bndxd fails ]) @testset "Interval Bridge" begin MOIT.linear10test(MOIB.SplitInterval{Float64}(solver), linconfig) end - - @testset "Infeasiblity" begin - linconfig_nocertificate = MOIT.TestConfig(modify_lhs = false, infeas_certificates=false) - # TODO(odow): we return a ResultCount of 1 without a result somewhere - # MOIT.linear8atest(solver, linconfig_nocertificate) - # MOIT.linear8btest(solver, linconfig_nocertificate) - # MOIT.linear8ctest(solver, linconfig_nocertificate) - # MOIT.linear12test(solver, linconfig_nocertificate) - end end @testset "ModelLike tests" begin @@ -57,7 +46,6 @@ end @testset "Unit Tests" begin config = MOIT.TestConfig() solver = ClpOptimizer(LogLevel = 0) - # TODO uncomment after adding MOIT.TestConfig.multiple_bounds # MOIT.basic_constraint_tests(solver, config) MOIT.unittest(solver, config, [ "solve_qcp_edge_cases", diff --git a/test/runtests.jl b/test/runtests.jl index 16aedc1..8febfc6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,9 @@ -using Clp +using Clp, Base.Test -include("mathprog.jl") -include("MOIWrapper.jl") +@testset "MathProgBase" begin + include("mathprog.jl") +end + +@testset "MathOptInterface" begin + include("MOIWrapper.jl") +end From 07d4e91e1dc169055cd818407e41e3c6e23b2271 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 30 Jul 2018 10:04:41 +1200 Subject: [PATCH 16/22] Minor re-org --- src/MOIWrapper.jl | 3 ++- test/MOIWrapper.jl | 29 ++++++++++++++++------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index e3b78d7..0d97b9b 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -119,7 +119,8 @@ function LQOI.change_variable_bounds!(instance::ClpOptimizer, cols::Vector{Int}, elseif senses[i] == Cchar('L') lowerbounds[cols[i]] = values[i] else - error("sense is $(senses[i]), but only 'U' and 'L' are supported") + error("sense is Cchar('$(Char(senses[i]))'), but only Cchar('U') " * + "Cchar('L') are supported.") end end chg_column_upper(instance.inner, upperbounds) diff --git a/test/MOIWrapper.jl b/test/MOIWrapper.jl index 56c25fe..e0e2a4e 100644 --- a/test/MOIWrapper.jl +++ b/test/MOIWrapper.jl @@ -1,9 +1,19 @@ -using MathOptInterface, MathOptInterface.Test +using MathOptInterface const MOI = MathOptInterface const MOIB = MathOptInterface.Bridges const MOIT = MathOptInterface.Test +@testset "Unit Tests" begin + config = MOIT.TestConfig() + solver = ClpOptimizer(LogLevel = 0) + # MOIT.basic_constraint_tests(solver, config) + MOIT.unittest(solver, config, [ + "solve_qcp_edge_cases", + "solve_qp_edge_cases" + ]) +end + @testset "Linear tests" begin linconfig = MOIT.TestConfig(modify_lhs = false) solver = ClpOptimizer(LogLevel = 0) @@ -11,8 +21,11 @@ const MOIT = MathOptInterface.Test # linear1 test is disabled due to the following bug. # https://projects.coin-or.org/Clp/ticket/84 "linear1", - "linear10", # linear10 test is tested below because it has interval sets - "linear12" # incorrect certificate? Test 2 * cd1 ≈ -bndxd fails + # linear10 test is tested below because it has interval sets + "linear10", + # linear12 test requires the InfeasibilityCertificate for variable + # bounds. These are available through C++, but not via the C interface. + "linear12" ]) @testset "Interval Bridge" begin @@ -42,13 +55,3 @@ end MOIT.copytest(solver,solver2) end end - -@testset "Unit Tests" begin - config = MOIT.TestConfig() - solver = ClpOptimizer(LogLevel = 0) - # MOIT.basic_constraint_tests(solver, config) - MOIT.unittest(solver, config, [ - "solve_qcp_edge_cases", - "solve_qp_edge_cases" - ]) -end From 3728aafa5bb56a01c45052a858ca2f70af15c323 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 30 Jul 2018 12:50:46 +1200 Subject: [PATCH 17/22] Remove unneeded code --- src/MOIWrapper.jl | 92 +++++++++++++++-------------------------------- 1 file changed, 29 insertions(+), 63 deletions(-) diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index 0d97b9b..dad2843 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -52,16 +52,6 @@ const solveoptionmap = Dict( :InfeasibleReturn => set_infeasible_return, ) -function setoption(optimizer::ClpOptimizer, name::Symbol, value) - if haskey(optionmap, name) - optionmap[name](optimizer.inner,value) - elseif haskey(solveoptionmap, name) - solveoptionmap[name](optimizer.solveroptions,value) - else - error("Unrecognized option: $name") - end -end - function ClpOptimizer(;kwargs...) optimizer = ClpOptimizer(nothing) optimizer.params = Dict{String,Any}() @@ -77,23 +67,6 @@ LQOI.LinearQuadraticModel(::Type{ClpOptimizer},env) = ClpModel() LQOI.supported_constraints(optimizer::ClpOptimizer) = SUPPORTED_CONSTRAINTS LQOI.supported_objectives(optimizer::ClpOptimizer) = SUPPORTED_OBJECTIVES -""" - replace_inf(x::Vector{<:Real}) - -Replaces any occurances of an element `x[i]>1e20` with `Inf` and `x[i]<-1e20` -with `-Inf`. -""" -function replace_inf(x::Vector{<:Real}) - for i in 1:length(x) - if x[i] > 1e20 - x[i] = Inf - elseif x[i] < -1e20 - x[i] = -Inf - end - end - return x -end - """ replace_inf(x::Real) @@ -111,15 +84,15 @@ end function LQOI.change_variable_bounds!(instance::ClpOptimizer, cols::Vector{Int}, values::Vector{Float64}, senses::Vector) - upperbounds = replace_inf(get_col_upper(instance.inner)) - lowerbounds = replace_inf(get_col_lower(instance.inner)) - for i in 1:length(senses) - if senses[i] == Cchar('U') - upperbounds[cols[i]] = values[i] - elseif senses[i] == Cchar('L') - lowerbounds[cols[i]] = values[i] + upperbounds = get_col_upper(instance.inner) + lowerbounds = get_col_lower(instance.inner) + for (col, value, sense) in zip(cols, values, senses) + if sense == Cchar('U') + upperbounds[col] = value + elseif sense == Cchar('L') + lowerbounds[col] = value else - error("sense is Cchar('$(Char(senses[i]))'), but only Cchar('U') " * + error("sense is Cchar('$(Char(sense))'), but only Cchar('U') " * "Cchar('L') are supported.") end end @@ -151,16 +124,13 @@ with upper bound `upper` and lower bound `lower` to the instance `instance`. function append_row(instance::ClpOptimizer, row::Int, lower::Float64, upper::Float64, rows::Vector{Int}, cols::Vector{Int}, coefs::Vector{Float64}) - row_variables = Int32[] - row_coefficients = Float64[] - first = rows[row] - last = (row==length(rows)) ? length(cols) : rows[row+1]-1 - for i in first:last - push!(row_coefficients, coefs[i]) - push!(row_variables, cols[i] - 1) + indices = if row == length(rows) + rows[row]:length(cols) + else + rows[row]:(rows[row+1]-1) end - add_row(instance.inner, Cint(length(row_variables)), row_variables, - row_coefficients, lower, upper) + add_row(instance.inner, Cint(length(indices)), Cint.(cols[indices]-1), + coefs[indices], lower, upper) end function LQOI.add_linear_constraints!(instance::ClpOptimizer, A::LQOI.CSRMatrix{Float64}, @@ -169,9 +139,9 @@ function LQOI.add_linear_constraints!(instance::ClpOptimizer, A::LQOI.CSRMatrix{ cols = A.columns coefs = A.coefficients for (row, (rhs, sense)) in enumerate(zip(right_hand_sides, senses)) - if (rhs > 1e20) + if rhs > 1e20 error("rhs must always be less than 1e20") - elseif (rhs < -1e20) + elseif rhs < -1e20 error("rhs must always be greater than -1e20") end lower = -Inf @@ -217,7 +187,7 @@ end function LQOI.get_linear_constraint(instance::ClpOptimizer, row::Int)::Tuple{Vector{Int}, Vector{Float64}} A = get_constraint_matrix(instance.inner) A_row = A[row,:] - return (Array{Int}(A_row.nzind), A_row.nzval) + return Array{Int}(A_row.nzind), A_row.nzval end function LQOI.change_objective_coefficient!(instance::ClpOptimizer, col, coef) @@ -243,13 +213,12 @@ function LQOI.change_rhs_coefficient!(instance::ClpOptimizer, row, coef) end function LQOI.delete_linear_constraints!(instance::ClpOptimizer, start_row::Int, end_row::Int) - rows = [Cint(i-1) for i in start_row:end_row] - delete_rows(instance.inner, rows) + delete_rows(instance.inner, [Cint(i-1) for i in start_row:end_row]) end function LQOI.change_linear_constraint_sense!(instance::ClpOptimizer, rows::Vector{Int}, senses::Vector{Cchar}) - lower = replace_inf(get_row_lower(instance.inner)) - upper = replace_inf(get_row_upper(instance.inner)) + lower = replace_inf.(get_row_lower(instance.inner)) + upper = replace_inf.(get_row_upper(instance.inner)) for (sense, row) in zip(senses, rows) lb = lower[row] ub = upper[row] @@ -261,17 +230,15 @@ function LQOI.change_linear_constraint_sense!(instance::ClpOptimizer, rows::Vect error("Either row_lower or row_upper must be of abs less than 1e20") end if sense == Cchar('G') - lb = rhs - ub = Inf + lower[row] = rhs + upper[row] = Inf elseif sense == Cchar('L') - lb = -Inf - ub = rhs + lower[row] = -Inf + upper[row] = rhs elseif sense == Cchar('E') - lb = rhs - ub = rhs + lower[row] = rhs + upper[row] = rhs end - lower[row] = lb - upper[row] = ub end chg_row_upper(instance.inner, upper) chg_row_lower(instance.inner, lower) @@ -392,13 +359,12 @@ function LQOI.get_number_variables(instance::ClpOptimizer) return get_num_cols(instance.inner) end -function LQOI.add_variables!(instance::ClpOptimizer, n::Int) - for i in 1:n +function LQOI.add_variables!(instance::ClpOptimizer, number_of_variables::Int) + for i in 1:number_of_variables add_column(instance.inner, Cint(0), Int32[], Float64[], -Inf, Inf, 0.0) end end function LQOI.delete_variables!(instance::ClpOptimizer, start_col::Int, end_col::Int) - columns = [Cint(i-1) for i in start_col:end_col] - delete_columns(instance.inner, columns) + delete_columns(instance.inner, [Cint(i-1) for i in start_col:end_col]) end From 0c8fc6dfcbe2aad5334d226683ed816fa7d851f8 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 30 Jul 2018 13:02:56 +1200 Subject: [PATCH 18/22] Exclude linear11 test --- test/MOIWrapper.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/MOIWrapper.jl b/test/MOIWrapper.jl index e0e2a4e..8382ba4 100644 --- a/test/MOIWrapper.jl +++ b/test/MOIWrapper.jl @@ -18,11 +18,14 @@ end linconfig = MOIT.TestConfig(modify_lhs = false) solver = ClpOptimizer(LogLevel = 0) MOIT.contlineartest(solver, linconfig, [ - # linear1 test is disabled due to the following bug. + # linear1 test is disabled due to the following bug: # https://projects.coin-or.org/Clp/ticket/84 "linear1", - # linear10 test is tested below because it has interval sets + # linear10 test is tested below because it has interval sets. "linear10", + # linear11 test is excluded as it fails on Linux for some reason. + # It passes on Mac and Windows. + "linear11", # linear12 test requires the InfeasibilityCertificate for variable # bounds. These are available through C++, but not via the C interface. "linear12" From af551f7439d7aa7f9194d783bb826f73e7ab8d68 Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Thu, 9 Aug 2018 23:45:38 +0200 Subject: [PATCH 19/22] minor updates --- src/Clp.jl | 1 + src/MOIWrapper.jl | 3 ++- test/MOIWrapper.jl | 15 ++++++--------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Clp.jl b/src/Clp.jl index 4997732..85d6ef7 100644 --- a/src/Clp.jl +++ b/src/Clp.jl @@ -6,6 +6,7 @@ __precompile__() module Clp +using Compat include("ClpCInterface.jl") include("ClpSolverInterface.jl") diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index dad2843..638f5ba 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -7,7 +7,8 @@ const LQOI = LinQuadOptInterface const MOI = LQOI.MOI const SUPPORTED_OBJECTIVES = [ - LQOI.Linear + LQOI.Linear, + LQOI.SinVar ] const SUPPORTED_CONSTRAINTS = [ diff --git a/test/MOIWrapper.jl b/test/MOIWrapper.jl index 8382ba4..2dea33d 100644 --- a/test/MOIWrapper.jl +++ b/test/MOIWrapper.jl @@ -9,8 +9,11 @@ const MOIT = MathOptInterface.Test solver = ClpOptimizer(LogLevel = 0) # MOIT.basic_constraint_tests(solver, config) MOIT.unittest(solver, config, [ - "solve_qcp_edge_cases", - "solve_qp_edge_cases" + "solve_qp_edge_cases", # unsupported + "solve_qcp_edge_cases", # unsupported + "solve_affine_interval", # unsupported + "solve_objbound_edge_cases", # unsupported integer variables + "solve_integer_edge_cases", # unsupported integer variables ]) end @@ -46,13 +49,7 @@ end end @testset "emptytest" begin MOIT.emptytest(solver) - end - # @testset "orderedindicestest" begin - # MOIT.orderedindicestest(solver) - # end - @testset "canaddconstrainttest" begin - MOIT.canaddconstrainttest(solver, Float64, Complex{Float64}) - end + end @testset "copytest" begin solver2 = ClpOptimizer(LogLevel = 0) MOIT.copytest(solver,solver2) From 575aff7be3157da67f46721cb880502bf589d85b Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Thu, 9 Aug 2018 23:48:26 +0200 Subject: [PATCH 20/22] renaming ClpOptimizer --> Clp.Optimizer --- src/MOIWrapper.jl | 77 +++++++++++++++++++++++----------------------- test/MOIWrapper.jl | 8 ++--- 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/src/MOIWrapper.jl b/src/MOIWrapper.jl index 638f5ba..408813d 100644 --- a/src/MOIWrapper.jl +++ b/src/MOIWrapper.jl @@ -1,4 +1,3 @@ -export ClpOptimizer using LinQuadOptInterface using .ClpCInterface @@ -27,10 +26,10 @@ const SUPPORTED_CONSTRAINTS = [ (LQOI.VecLin, MOI.Zeros) ] -mutable struct ClpOptimizer <: LQOI.LinQuadOptimizer +mutable struct Optimizer <: LQOI.LinQuadOptimizer LQOI.@LinQuadOptimizerBase params::Dict{Symbol,Any} - ClpOptimizer(::Void) = new() + Optimizer(::Void) = new() end ### Options @@ -53,8 +52,8 @@ const solveoptionmap = Dict( :InfeasibleReturn => set_infeasible_return, ) -function ClpOptimizer(;kwargs...) - optimizer = ClpOptimizer(nothing) +function Optimizer(;kwargs...) + optimizer = Optimizer(nothing) optimizer.params = Dict{String,Any}() MOI.empty!(optimizer) for (name,value) in kwargs @@ -63,10 +62,10 @@ function ClpOptimizer(;kwargs...) return optimizer end -LQOI.LinearQuadraticModel(::Type{ClpOptimizer},env) = ClpModel() +LQOI.LinearQuadraticModel(::Type{Optimizer},env) = ClpModel() -LQOI.supported_constraints(optimizer::ClpOptimizer) = SUPPORTED_CONSTRAINTS -LQOI.supported_objectives(optimizer::ClpOptimizer) = SUPPORTED_OBJECTIVES +LQOI.supported_constraints(optimizer::Optimizer) = SUPPORTED_CONSTRAINTS +LQOI.supported_objectives(optimizer::Optimizer) = SUPPORTED_OBJECTIVES """ replace_inf(x::Real) @@ -83,7 +82,7 @@ function replace_inf(x::Real) end end -function LQOI.change_variable_bounds!(instance::ClpOptimizer, cols::Vector{Int}, +function LQOI.change_variable_bounds!(instance::Optimizer, cols::Vector{Int}, values::Vector{Float64}, senses::Vector) upperbounds = get_col_upper(instance.inner) lowerbounds = get_col_lower(instance.inner) @@ -101,28 +100,28 @@ function LQOI.change_variable_bounds!(instance::ClpOptimizer, cols::Vector{Int}, chg_column_lower(instance.inner, lowerbounds) end -function LQOI.get_variable_lowerbound(instance::ClpOptimizer, col::Int) +function LQOI.get_variable_lowerbound(instance::Optimizer, col::Int) lower = get_col_lower(instance.inner) return replace_inf(lower[col]) end -function LQOI.get_variable_upperbound(instance::ClpOptimizer, col::Int) +function LQOI.get_variable_upperbound(instance::Optimizer, col::Int) upper = get_col_upper(instance.inner) return replace_inf(upper[col]) end -function LQOI.get_number_linear_constraints(instance::ClpOptimizer) +function LQOI.get_number_linear_constraints(instance::Optimizer) return get_num_rows(instance.inner) end """ - append_row(instance::ClpOptimizer, row::Int, lower::Float64, upper::Float64, + append_row(instance::Optimizer, row::Int, lower::Float64, upper::Float64, rows::Vector{Int}, cols::Vector{Int}, coefs::Vector{Float64}) Given a sparse matrix in the triplet-form `(rows, cols, coefs)`, add row `row` with upper bound `upper` and lower bound `lower` to the instance `instance`. """ -function append_row(instance::ClpOptimizer, row::Int, lower::Float64, +function append_row(instance::Optimizer, row::Int, lower::Float64, upper::Float64, rows::Vector{Int}, cols::Vector{Int}, coefs::Vector{Float64}) indices = if row == length(rows) @@ -134,7 +133,7 @@ function append_row(instance::ClpOptimizer, row::Int, lower::Float64, coefs[indices], lower, upper) end -function LQOI.add_linear_constraints!(instance::ClpOptimizer, A::LQOI.CSRMatrix{Float64}, +function LQOI.add_linear_constraints!(instance::Optimizer, A::LQOI.CSRMatrix{Float64}, senses::Vector{Cchar}, right_hand_sides::Vector{Float64}) rows = A.row_pointers cols = A.columns @@ -160,7 +159,7 @@ function LQOI.add_linear_constraints!(instance::ClpOptimizer, A::LQOI.CSRMatrix{ end end -function LQOI.add_ranged_constraints!(instance::Clp.ClpOptimizer, +function LQOI.add_ranged_constraints!(instance::Clp.Optimizer, A::LinQuadOptInterface.CSRMatrix{Float64}, lb::Vector{Float64}, ub::Vector{Float64}) rows = A.row_pointers @@ -171,7 +170,7 @@ function LQOI.add_ranged_constraints!(instance::Clp.ClpOptimizer, end end -function LQOI.get_rhs(instance::ClpOptimizer, row::Int) +function LQOI.get_rhs(instance::Optimizer, row::Int) lower_bounds = get_row_lower(instance.inner) upper_bounds = get_row_upper(instance.inner) lower_bound = replace_inf(lower_bounds[row]) @@ -185,19 +184,19 @@ function LQOI.get_rhs(instance::ClpOptimizer, row::Int) end end -function LQOI.get_linear_constraint(instance::ClpOptimizer, row::Int)::Tuple{Vector{Int}, Vector{Float64}} +function LQOI.get_linear_constraint(instance::Optimizer, row::Int)::Tuple{Vector{Int}, Vector{Float64}} A = get_constraint_matrix(instance.inner) A_row = A[row,:] return Array{Int}(A_row.nzind), A_row.nzval end -function LQOI.change_objective_coefficient!(instance::ClpOptimizer, col, coef) +function LQOI.change_objective_coefficient!(instance::Optimizer, col, coef) objcoefs = get_obj_coefficients(instance.inner) objcoefs[col] = coef chg_obj_coefficients(instance.inner, objcoefs) end -function LQOI.change_rhs_coefficient!(instance::ClpOptimizer, row, coef) +function LQOI.change_rhs_coefficient!(instance::Optimizer, row, coef) lower_bounds = get_row_lower(instance.inner) upper_bounds = get_row_upper(instance.inner) lower_bound = replace_inf(lower_bounds[row]) @@ -213,11 +212,11 @@ function LQOI.change_rhs_coefficient!(instance::ClpOptimizer, row, coef) end end -function LQOI.delete_linear_constraints!(instance::ClpOptimizer, start_row::Int, end_row::Int) +function LQOI.delete_linear_constraints!(instance::Optimizer, start_row::Int, end_row::Int) delete_rows(instance.inner, [Cint(i-1) for i in start_row:end_row]) end -function LQOI.change_linear_constraint_sense!(instance::ClpOptimizer, rows::Vector{Int}, senses::Vector{Cchar}) +function LQOI.change_linear_constraint_sense!(instance::Optimizer, rows::Vector{Int}, senses::Vector{Cchar}) lower = replace_inf.(get_row_lower(instance.inner)) upper = replace_inf.(get_row_upper(instance.inner)) for (sense, row) in zip(senses, rows) @@ -245,7 +244,7 @@ function LQOI.change_linear_constraint_sense!(instance::ClpOptimizer, rows::Vect chg_row_lower(instance.inner, lower) end -function LQOI.set_linear_objective!(instance::ClpOptimizer, cols::Vector{Int}, coefs::Vector{Float64}) +function LQOI.set_linear_objective!(instance::Optimizer, cols::Vector{Int}, coefs::Vector{Float64}) objective_coefficients = zeros(Float64, get_num_cols(instance.inner)) for (col, coef) in zip(cols, coefs) objective_coefficients[col] += coef @@ -253,7 +252,7 @@ function LQOI.set_linear_objective!(instance::ClpOptimizer, cols::Vector{Int}, c chg_obj_coefficients(instance.inner, objective_coefficients) end -function LQOI.change_objective_sense!(instance::ClpOptimizer, sense::Symbol) +function LQOI.change_objective_sense!(instance::Optimizer, sense::Symbol) if sense == :min set_obj_sense(instance.inner, 1.0) elseif sense == :max @@ -263,11 +262,11 @@ function LQOI.change_objective_sense!(instance::ClpOptimizer, sense::Symbol) end end -function LQOI.get_linear_objective!(instance::ClpOptimizer, x::Vector{Float64}) +function LQOI.get_linear_objective!(instance::Optimizer, x::Vector{Float64}) copy!(x, get_obj_coefficients(instance.inner)) end -function LQOI.solve_linear_problem!(instance::ClpOptimizer) +function LQOI.solve_linear_problem!(instance::Optimizer) solveroptions = ClpSolve() model = instance.inner for (name, value) in instance.params @@ -282,36 +281,36 @@ function LQOI.solve_linear_problem!(instance::ClpOptimizer) initial_solve_with_options(instance.inner, solveroptions) end -function LQOI.get_variable_primal_solution!(instance::ClpOptimizer, x::Vector{Float64}) +function LQOI.get_variable_primal_solution!(instance::Optimizer, x::Vector{Float64}) copy!(x, primal_column_solution(instance.inner)) end -function LQOI.get_linear_primal_solution!(instance::ClpOptimizer, x::Vector{Float64}) +function LQOI.get_linear_primal_solution!(instance::Optimizer, x::Vector{Float64}) copy!(x, primal_row_solution(instance.inner)) end -function LQOI.get_variable_dual_solution!(instance::ClpOptimizer, x::Vector{Float64}) +function LQOI.get_variable_dual_solution!(instance::Optimizer, x::Vector{Float64}) copy!(x, dual_column_solution(instance.inner)) end -function LQOI.get_linear_dual_solution!(instance::ClpOptimizer, x::Vector{Float64}) +function LQOI.get_linear_dual_solution!(instance::Optimizer, x::Vector{Float64}) copy!(x, dual_row_solution(instance.inner)) end -function LQOI.get_objective_value(instance::ClpOptimizer) +function LQOI.get_objective_value(instance::Optimizer) return objective_value(instance.inner) end -function LQOI.get_farkas_dual!(instance::ClpOptimizer, result::Vector{Float64}) +function LQOI.get_farkas_dual!(instance::Optimizer, result::Vector{Float64}) copy!(result, infeasibility_ray(instance.inner)) scale!(result, -1.0) end -function LQOI.get_unbounded_ray!(instance::ClpOptimizer, result::Vector{Float64}) +function LQOI.get_unbounded_ray!(instance::Optimizer, result::Vector{Float64}) copy!(result, unbounded_ray(instance.inner)) end -function LQOI.get_termination_status(instance::ClpOptimizer) +function LQOI.get_termination_status(instance::Optimizer) status = ClpCInterface.status(instance.inner) if status == 0 return MOI.Success @@ -336,7 +335,7 @@ function LQOI.get_termination_status(instance::ClpOptimizer) end end -function LQOI.get_primal_status(instance::ClpOptimizer) +function LQOI.get_primal_status(instance::Optimizer) if is_proven_dual_infeasible(instance.inner) return MOI.InfeasibilityCertificate elseif primal_feasible(instance.inner) @@ -346,7 +345,7 @@ function LQOI.get_primal_status(instance::ClpOptimizer) end end -function LQOI.get_dual_status(instance::ClpOptimizer) +function LQOI.get_dual_status(instance::Optimizer) if is_proven_primal_infeasible(instance.inner) return MOI.InfeasibilityCertificate elseif dual_feasible(instance.inner) @@ -356,16 +355,16 @@ function LQOI.get_dual_status(instance::ClpOptimizer) end end -function LQOI.get_number_variables(instance::ClpOptimizer) +function LQOI.get_number_variables(instance::Optimizer) return get_num_cols(instance.inner) end -function LQOI.add_variables!(instance::ClpOptimizer, number_of_variables::Int) +function LQOI.add_variables!(instance::Optimizer, number_of_variables::Int) for i in 1:number_of_variables add_column(instance.inner, Cint(0), Int32[], Float64[], -Inf, Inf, 0.0) end end -function LQOI.delete_variables!(instance::ClpOptimizer, start_col::Int, end_col::Int) +function LQOI.delete_variables!(instance::Optimizer, start_col::Int, end_col::Int) delete_columns(instance.inner, [Cint(i-1) for i in start_col:end_col]) end diff --git a/test/MOIWrapper.jl b/test/MOIWrapper.jl index 2dea33d..93c6ce8 100644 --- a/test/MOIWrapper.jl +++ b/test/MOIWrapper.jl @@ -6,7 +6,7 @@ const MOIT = MathOptInterface.Test @testset "Unit Tests" begin config = MOIT.TestConfig() - solver = ClpOptimizer(LogLevel = 0) + solver = Clp.Optimizer(LogLevel = 0) # MOIT.basic_constraint_tests(solver, config) MOIT.unittest(solver, config, [ "solve_qp_edge_cases", # unsupported @@ -19,7 +19,7 @@ end @testset "Linear tests" begin linconfig = MOIT.TestConfig(modify_lhs = false) - solver = ClpOptimizer(LogLevel = 0) + solver = Clp.Optimizer(LogLevel = 0) MOIT.contlineartest(solver, linconfig, [ # linear1 test is disabled due to the following bug: # https://projects.coin-or.org/Clp/ticket/84 @@ -40,7 +40,7 @@ end end @testset "ModelLike tests" begin - solver = ClpOptimizer(LogLevel = 0) + solver = Clp.Optimizer(LogLevel = 0) @testset "nametest" begin MOIT.nametest(solver) end @@ -51,7 +51,7 @@ end MOIT.emptytest(solver) end @testset "copytest" begin - solver2 = ClpOptimizer(LogLevel = 0) + solver2 = Clp.Optimizer(LogLevel = 0) MOIT.copytest(solver,solver2) end end From 4848bbed37566b8589eb60f80270e92f5433d008 Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Thu, 9 Aug 2018 23:49:07 +0200 Subject: [PATCH 21/22] update requires --- REQUIRE | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/REQUIRE b/REQUIRE index 6f4c74f..c3899b1 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,5 @@ julia 0.6 +Compat 0.59 Cbc MathProgBase 0.5 0.8 -LinQuadOptInterface 0.1 0.3 +LinQuadOptInterface 0.3 0.4 From 8806d61f0fb45f3967bb287f3303e9c03e16fb50 Mon Sep 17 00:00:00 2001 From: Issam Tahiri Date: Fri, 10 Aug 2018 11:20:01 +0200 Subject: [PATCH 22/22] enabled a test, added 0.7 to travis --- .travis.yml | 2 +- test/MOIWrapper.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5670834..24ce1af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ os: - osx julia: - 0.6 - - nightly + - 0.7 notifications: email: false script: diff --git a/test/MOIWrapper.jl b/test/MOIWrapper.jl index 93c6ce8..20e2a66 100644 --- a/test/MOIWrapper.jl +++ b/test/MOIWrapper.jl @@ -7,7 +7,7 @@ const MOIT = MathOptInterface.Test @testset "Unit Tests" begin config = MOIT.TestConfig() solver = Clp.Optimizer(LogLevel = 0) - # MOIT.basic_constraint_tests(solver, config) + MOIT.basic_constraint_tests(solver, config) MOIT.unittest(solver, config, [ "solve_qp_edge_cases", # unsupported "solve_qcp_edge_cases", # unsupported