Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP : MOI wrapper (based on LQOI) #27

Merged
merged 23 commits into from
Aug 14, 2018
Merged

WIP : MOI wrapper (based on LQOI) #27

merged 23 commits into from
Aug 14, 2018

Conversation

IssamT
Copy link
Contributor

@IssamT IssamT commented May 29, 2018

No description provided.

solver = ClpOptimizer(LogLevel = 0)
MOIT.contlineartest(solver, linconfig, ["linear10","linear12","linear8a","linear8b","linear8c"])
end
end
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added. Thanks for the review !

"""
function LQOI.get_linear_objective!(instance::ClpOptimizer, x::Vector{Float64})
obj = get_obj_coefficients(instance.inner)
for i in 1:length(obj)
Copy link
Member

Choose a reason for hiding this comment

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

copy!(x, obj)

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)
Copy link
Member

Choose a reason for hiding this comment

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

With latest LQOI, we return 1-indexed columns

Copy link
Member

@odow odow left a comment

Choose a reason for hiding this comment

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

Good stuff. We should strip out all the commented out functions that relate to MIPs at some point before merging

solution = primal_column_solution(instance.inner)
for i in 1:length(solution)
x[i] = solution[i]
end
Copy link
Member

Choose a reason for hiding this comment

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

For all of these, there is copy!(x, solution)

for i in 1:n
numberInColumn = 0
rows = Vector{Int32}()
elements = Vector{Float64}()
Copy link
Member

Choose a reason for hiding this comment

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

I think Int[] and Float64[] are the preferred style over the above.


MOIT.basic_constraint_tests(solver, config, exclude = [
(MOI.SingleVariable, MOI.Integer),
(MOI.SingleVariable, MOI.ZeroOne)
Copy link
Member

Choose a reason for hiding this comment

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

Hmm you shouldn't have to exclude these. This should only test constraints that you support. (See my comment in supported constraints.)

(LQOI.SinVar, LQOI.GE),
(LQOI.SinVar, LQOI.IV),
(LQOI.SinVar, MOI.ZeroOne),
(LQOI.SinVar, MOI.Integer),
Copy link
Member

Choose a reason for hiding this comment

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

Clp doesn't support binary and integer constraints

])

MOIT.unittest(solver, config, [
"solve_affine_interval",
Copy link
Member

Choose a reason for hiding this comment

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

Why is this excluded?

REQUIRE Outdated
@@ -1,3 +1,4 @@
julia 0.6
Cbc
MathProgBase 0.5 0.8
LinQuadOptInterface
Copy link
Member

Choose a reason for hiding this comment

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

This should have a strict bound of LinQuadOptInterface 0.1 0.2

"""
function LQOI.get_variable_lowerbound(instance::ClpOptimizer, col::Int)::Float64
lower = replaceInf(get_col_lower(instance.inner))
return lower[col];
Copy link
Member

Choose a reason for hiding this comment

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

Two comments. Since you only query one element, you don't need to call replaceInf on the whole lowerbound vector. But also, if get_col_lower returns 10+20, why should we change that to Inf? The real bound in Clp is 10+20.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

About the second comment. I am honestly about the reason behind that. I just tried to be consistent with the mathprog wrapper. If we fix it we should do it for both at the same time to avoid inconsistencies, especially for those who would like to move from the mathprog wrapper to MOI wrapper. So I suggest we open a separate issue for this.

"""
function LQOI.get_variable_upperbound(instance::ClpOptimizer, col::Int)::Float64
upper = replaceInf(get_col_upper(instance.inner))
return upper[col];
Copy link
Member

Choose a reason for hiding this comment

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

Ditto here. Also trailing ; is unneeded.

return get_num_rows(instance.inner)
end

function addrow(row, lower, upper, instance, rows, cols, coefs)
Copy link
Member

Choose a reason for hiding this comment

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

The ordering on these arguments is inconsistent with the rest of the calls. They could be typed for readability?

Copy link
Member

Choose a reason for hiding this comment

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

It is also pretty confusing that you pass in the A matrix but only add one row. At the very least it should be documented

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`.
"""
Copy link
Member

Choose a reason for hiding this comment

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

Here and throughout: you don't need this docstring since it is already in LQOI

error("sense must be Cchar(x) where x is in ['L','G',E']")
end

addrow(row, lower, upper, instance, rows, cols, coefs)
Copy link
Member

Choose a reason for hiding this comment

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

I got confused here, see comment with addrow above

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I ve added a comment on the function and types for arguments

upper[row] = coef
chg_row_upper(instance.inner, upper)
else
error("Either row_lower or row_upper must be of abs less than 1e20")
Copy link
Member

Choose a reason for hiding this comment

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

So when adding a constraint it allows bigger coefficients, but it truncates them to 1e20, but when modifying it doesn't? Confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Disallowing for both is needed here. I used it to know the active side of the range

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]
Copy link
Member

Choose a reason for hiding this comment

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

I think it is sufficient to go obj_in[cols] .= coefs instead of the loop

numberInColumn = 0
rows = Int32[]
elements = Float64[]
add_column(instance.inner, numberInColumn, rows, elements, -Inf, +Inf, 0.0)
Copy link
Member

Choose a reason for hiding this comment

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

So Inf bounds are okay here? Not 1e20?

Copy link
Contributor Author

@IssamT IssamT Jun 30, 2018

Choose a reason for hiding this comment

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

The C code will test and truncate it anyway.

Copy link
Member

Choose a reason for hiding this comment

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

This can probably just be add_column(instance.inner, 0, Int32[], Float64[], -Inf, Inf, 0.0)

@IssamT
Copy link
Contributor Author

IssamT commented Jul 2, 2018

The failing test is due to an error inside CLP I think: after deleting a column, getVectorStrats returns a wrong vector. I have reproduced this issue using C interface only and reported it.

https://projects.coin-or.org/Clp/ticket/84

Copy link
Member

@mlubin mlubin left a comment

Choose a reason for hiding this comment

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

The failing test is due to an error inside CLP I think: after deleting a column, getVectorStrats returns a wrong vector. I have reproduced this issue using C interface only and reported it.

That's great, but in my experience there's a 95% chance this bug report will be ignored. The Clp author is more responsive on the mailing list. Disable this test so we can pass on travis and leave a comment explaining the situation.

#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)
Copy link
Member

Choose a reason for hiding this comment

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

Naming: variables in the scope of a function shouldn't start with underscores. _row_upper could be row_upper_vector. Same comment below.

using LinQuadOptInterface
using Clp.ClpCInterface

function replaceInf(x)
Copy link
Member

Choose a reason for hiding this comment

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

naming: no camel case, use replace_inf.

end

function ClpOptimizer(;kwargs...)
# env = Env()
Copy link
Member

Choose a reason for hiding this comment

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

Style: don't leave code commented without an explanation why. If the code doesn't do anything, remove it.

end

"""
Helper function for adding a row. it adds the row in the components
Copy link
Member

Choose a reason for hiding this comment

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

Start docstrings with the function signature.

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,
Copy link
Member

Choose a reason for hiding this comment

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

Naming: add_row.

cols = A.columns
coefs = A.coefficients

nbrows = length(rhs)
Copy link
Member

Choose a reason for hiding this comment

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

Naming: num_rows.

end

"""
get_linear_constraint(m, row::Int)::Tuple{Vector{Int}, Vector{Float64}}
Copy link
Member

Choose a reason for hiding this comment

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

Formatting: indent the signature with 4 spaces so it displays like code.

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)`.
Copy link
Member

Choose a reason for hiding this comment

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

Which model m? There's no m below.

return MOI.InfeasibleNoResult
elseif s == 2
return MOI.UnboundedNoResult
# if s in [0, 1, 2]
Copy link
Member

Choose a reason for hiding this comment

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

Remove or explain why it's commented.

@IssamT
Copy link
Contributor Author

IssamT commented Jul 9, 2018

There is an error on Linux but not on Mac. Can someone with Linux check the origin of this error?

@blegat
Copy link
Member

blegat commented Jul 9, 2018

Yes, linear11 is failing on my Linux computer. You can probably add it in the exclude list. The problem seems to come from Clp and not from the wrapper.
Once I exlude linear11, it passes all the rest of the tests

@IssamT
Copy link
Contributor Author

IssamT commented Jul 11, 2018

Thanks @blegat ! We had the same issue with Cbc (since it uses the C++ Clp ), so we disabled the linear11 test there as well.

@mlubin
Copy link
Member

mlubin commented Jul 28, 2018

@IssamT is this ready to merge?

@IssamT
Copy link
Contributor Author

IssamT commented Jul 28, 2018

@mlubin, Yes. I've just run the tests with the latest master on MOI / LQOI. (But with julia 0.6)

@mlubin
Copy link
Member

mlubin commented Jul 28, 2018

LQOI 0.2 is released, could you update the PR? It's not good for the ecosystem to start being split by LQOI version.

@mlubin
Copy link
Member

mlubin commented Jul 28, 2018

Is removing the upper bound the right fix? We also don't want the package to break when LQOI is updated. @joaquimg @odow

@IssamT
Copy link
Contributor Author

IssamT commented Jul 28, 2018

Sorry @mlubin , but I think I am missing something here.

  • I have tested the package against MOI master and LQOI master.
  • However Travis used LQOI 1.0 for the previous commit because the REQUIRE had a version upper bound for LQOI.
  • I removed LQOI version upper and made this new commit. Hence, travis used LQOI 2.0 to pass the tests.

Which updates of LQOI are you talking about?

@mlubin
Copy link
Member

mlubin commented Jul 28, 2018

LQOI 0.3 could easily contain a change that would break this package. There should be an upper bound on LQOI to prevent users from seeing a broken package, like we do with MOI and MPB dependencies.

@mlubin
Copy link
Member

mlubin commented Jul 28, 2018

The bounds on LQOI look good now. Clp was previously passing tests on 0.7 (https://travis-ci.org/JuliaOpt/Clp.jl/builds/364908514), so the failure looks like a regression. Could you take a quick look at why it's failing?

Copy link
Member

@odow odow left a comment

Choose a reason for hiding this comment

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

This isn't ready to merge yet. Mainly because LQOI has been moving forward and not all changes are reflected here (e.g., regarding interval constraints). And there are a few bugs. Let me know if you have time to fix otherwise I'll go through and tidy up.

config = MOIT.TestConfig()
solver = ClpOptimizer(LogLevel = 0)
# TODO uncomment after adding MOIT.TestConfig.multiple_bounds
# MOIT.basic_constraint_tests(solver, config)
Copy link
Member

Choose a reason for hiding this comment

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

end
# TODO uncomment after adding MOIT.TestConfig.multiple_bounds
# @testset "orderedindicestest" begin
# MOIT.orderedindicestest(solver)
Copy link
Member

Choose a reason for hiding this comment

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

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)
Copy link
Member

Choose a reason for hiding this comment

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

Isn't it just as simple to go
add_rows(model, 1, [row_lower], [row_upper], [0, number_in_row], columns, elements)

_column_starts[2] = number_in_column
_column_upper = [column_upper]
_column_lower = [column_lower]
_objective = [objective]
Copy link
Member

Choose a reason for hiding this comment

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

Ditto here, or at least no underscore variable names

lowerbounds[cols[i]] = values[i]
else
error("sense is " * string(senses[i]) * ", but only " * Cchar('U') *
" and " * Cchar('L') * " senses are supported")
Copy link
Member

Choose a reason for hiding this comment

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

Is this preferred over string interpolation?
"sense is $(senses[i]), but only 'U' and 'L' senses are supported."

error("Either row_lower or row_upper must be of abs less than 1e20")
end

sense = sense[i]
Copy link
Member

Choose a reason for hiding this comment

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

Bug? Is this tested? I shouldn't imagine that this passed...

"""
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
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't account for duplicates. See jump-dev/MathOptInterface.jl#432

numberInColumn = 0
rows = Int32[]
elements = Float64[]
add_column(instance.inner, numberInColumn, rows, elements, -Inf, +Inf, 0.0)
Copy link
Member

Choose a reason for hiding this comment

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

This can probably just be add_column(instance.inner, 0, Int32[], Float64[], -Inf, Inf, 0.0)

return lb
elseif ub < Inf
return ub
else
Copy link
Member

Choose a reason for hiding this comment

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

What about interval constraints? You need to implement get_range

Copy link
Member

Choose a reason for hiding this comment

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

# 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"])
Copy link
Member

Choose a reason for hiding this comment

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

Why are these tests excluded?

@odow
Copy link
Member

odow commented Jul 29, 2018

@IssamT there is a PR with some changes: IssamT#1

@IssamT
Copy link
Contributor Author

IssamT commented Jul 30, 2018

@mlubin, looking quickly at the error on 0.7, apparently it is due to LQOI failing under 0.7, which is due to Nullables.jl failing under 0.7.

Supporting LQOI on both 0.6, 0.7 requires Nullables.

@joaquimg
Copy link
Member

Do you know why we need nullables in LQOI?
I think its always good to avoid dependencies, but if there is no other way lets go for it.

@IssamT
Copy link
Contributor Author

IssamT commented Jul 30, 2018

Nullable is disappearing in julia 0.7 in favor of Union{T, Nothing}. Nullables.jl offer a generic way of using Nullables that hides the difference between 0.6 and 0.7 (see JuliaLang/julia#23642)

However, as far as I can tell, Nullable is only used for single_obj_var::Nullable{MOI.VariableIndex}

I am not sure if that justifies a dependency ...

@odow
Copy link
Member

odow commented Aug 2, 2018

Okay, Nullables was removed from LQOI. Now we just need to wait for MOI to be tagged, then LQOI, then we can merge this with a new bound for LQOI.

We should also refactor ClpOptimizer to Clp.Optimizer ala GLPK and Gurobi.

@codecov-io
Copy link

codecov-io commented Aug 9, 2018

Codecov Report

❗ No coverage uploaded for pull request base (master@a7c6223). Click here to learn what that means.
The diff coverage is 72.56%.

Impacted file tree graph

@@            Coverage Diff            @@
##             master      #27   +/-   ##
=========================================
  Coverage          ?   48.13%           
=========================================
  Files             ?        3           
  Lines             ?      644           
  Branches          ?        0           
=========================================
  Hits              ?      310           
  Misses            ?      334           
  Partials          ?        0
Impacted Files Coverage Δ
src/ClpCInterface.jl 34.62% <100%> (ø)
src/MOIWrapper.jl 72.22% <72.22%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a7c6223...8806d61. Read the comment docs.

@odow
Copy link
Member

odow commented Aug 9, 2018

You just need to add 0.7 on travis and make sure it passes

@testset "Unit Tests" begin
config = MOIT.TestConfig()
solver = Clp.Optimizer(LogLevel = 0)
# MOIT.basic_constraint_tests(solver, config)
Copy link
Member

Choose a reason for hiding this comment

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

This can be enabled

@IssamT
Copy link
Contributor Author

IssamT commented Aug 14, 2018

Is there still something required for this PR?

@odow
Copy link
Member

odow commented Aug 14, 2018

There a lot of deprecation warnings, but they can be addressed in another PR.

@odow odow merged commit afc14ee into jump-dev:master Aug 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

6 participants