Skip to content

Conversation

@vitornesello
Copy link
Contributor

No description provided.

@mlubin
Copy link
Member

mlubin commented Jun 18, 2018

Very cool!

CC @IssamT

@IssamT
Copy link
Contributor

IssamT commented Jun 18, 2018

@mlubin I've already seen the code. We share the same office ;)

@mlubin
Copy link
Member

mlubin commented Jun 18, 2018

I expected some correlation given Vitor's location :)

I'm currently traveling but I'll try to take a look at this in the next couple days.

@vitornesello
Copy link
Contributor Author

We only have two issues now.

The first one is whether it should be added to the MOI test config the option of saying that the optimizer cannot get MOI.ConstraintPrimal. For instance, this feature is not implemented in CbcCInterface.jl.

The second one is that it seems to be hard to get MOI.ResultCount with Cbc. Either it returns one solution or it does not return, but neither the status codes nor the solution vector really help. I am trying to use the objective function value but I am not sure if it is safe.

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! I made quite a few comments. Most are just style or functions in Julia you might not be aware of.

The first one is whether it should be added to the MOI test config the option of saying that the optimizer cannot get MOI.ConstraintPrimal

Just exclude any tests that need it for now.

The second one is that it seems to be hard to get MOI.ResultCount with Cbc

What happens if you query the solution vector when Cbc has been interrupted or the problem is infeasible?

@@ -0,0 +1,397 @@
export CbcOptimizer, copy!, optimize!, get
Copy link
Member

Choose a reason for hiding this comment

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

You should only export CbcOptimizer

col_lb[colIdx] = -Inf
col_ub[colIdx] = Inf
obj[colIdx] = 0.0
end
Copy link
Member

Choose a reason for hiding this comment

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

Instead of this just use

row_lb = fill(-Inf, nbRows)
obj = zeros(Float64, nbCols)
constraint_matrix = sparse(Int[], Int[], Float64[], nbRows, nbCols)  # see note about sparse matrix

(Ditto with the other fields.)

mutable struct CbcModelFormat
nbRows::Int
nbCols::Int
constraint_matrix::Array{Float64,2}
Copy link
Member

Choose a reason for hiding this comment

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

You probably want to make this a sparse array instead of a dense array. See ? SparseMatrixCSC

mapping.varmap[varIndex[i]] = MOI.VariableIndex(i)
end

zeroOneIndices = Vector{Int}(0)
Copy link
Member

Choose a reason for hiding this comment

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

You can also go zeroOneIndices = Int[]

zeroOneIndices = Vector{Int}(0)
integerIndices = Vector{Int}(0)
listOfConstraints = MOI.get(userOptimizer, MOI.ListOfConstraints())
nbRows = 0;
Copy link
Member

Choose a reason for hiding this comment

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

Don't need the ;


function MOI.cantransformconstraint(model::CbcOptimizer, c::MOI.ConstraintIndex{F}, ::Type{S}) where {F<:MOI.AbstractFunction, S<:MOI.AbstractSet}
return false
end
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 you can leave out these functions as the default should be false


function MOI.set!(cbcOptimizer::CbcOptimizer, object::MOI.ObjectiveSense, sense::MOI.OptimizationSense)
sense == MOI.MinSense && CbcCI.setObjSense(cbcOptimizer.inner, 1)
sense == MOI.MaxSense && CbcCI.setObjSense(cbcOptimizer.inner, -1)
Copy link
Member

Choose a reason for hiding this comment

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

There is also a MOI.FeasibilitySense

const CANGETCONSATTR = []

function MOI.canget(cbcOptimizer::CbcOptimizer, object::MOI.PrimalStatus)
object.N >= 2 && return false
Copy link
Member

Choose a reason for hiding this comment

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

object.N != 1 && return false?


function MOI.get(cbcOptimizer::CbcOptimizer, object::MOI.VariablePrimal, ref::Vector{MOI.VariableIndex})
variablePrimals = CbcCI.getColSolution(cbcOptimizer.inner)
askedPrimals = 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.

Just use generator syntax instead of creating an empty array then looping:
return [variablePrimals[vi.value] for vi in ref] or
return map(vi->variablePrimals[vi.value], ref)



function MOI.get(cbcOptimizer::CbcOptimizer, object::MOI.ResultCount)
isProvenInfeasible(cbcOptimizer.inner) && return 0
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 unbounded models? Does Cbc return a solution?

listOfConstraints = MOI.get(userOptimizer, MOI.ListOfConstraints())
nbRows = 0;
for (F,S) in listOfConstraints
(F,S) in SUPPORTED_CONSTRAINTS || return MOI.CopyResult(MOI.CopyUnsupportedConstraint, "Cbc MOI Interface does not support constraints of type " * (F,S) * ".", nothing)
Copy link
Member

Choose a reason for hiding this comment

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

You might want do do the loop in a separate function so to make it type stable in this separate function like in
https://github.com/JuliaOpt/MathOptInterface.jl/blob/master/src/Utilities/copy.jl#L123-L125

listOfConstraints = MOI.get(userOptimizer, MOI.ListOfConstraints())
nbRows = 0;
for (F,S) in listOfConstraints
(F,S) in SUPPORTED_CONSTRAINTS || return MOI.CopyResult(MOI.CopyUnsupportedConstraint, "Cbc MOI Interface does not support constraints of type " * (F,S) * ".", nothing)
Copy link
Member

Choose a reason for hiding this comment

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

You can replace (F, S) in SUPPORTED_CONSTRAINTS by MOI.supportsconstraint(cbcOptimizer, F, S)


## supports constraints

function MOI.supportsconstraint(cbcOptimizer::CbcOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractScalarSet})
Copy link
Member

@blegat blegat Jun 19, 2018

Choose a reason for hiding this comment

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

It would be more efficient to do check if it is supported at compile time by doing

MOI.supportsconstraint(::CbcOptimizer, ::Type{<:Union{...}}, ::Type{<:Union{...}}) = true

and it would also make the result of supportsconstraint inferrable so calls to supportsconstraint can be optimized out by the compiler.

end

function MOI.supports(cbcOptimizer::CbcOptimizer, object::MOI.ObjectiveFunction{T}) where T <: MOI.AbstractFunction
return T in SUPPORTED_OBJECTIVES
Copy link
Member

Choose a reason for hiding this comment

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

Same here, it's probably better to do

MOI.supports(...) = true

and specify what is supported in the type

# empty!

function MOI.empty!(cbcOptimizer::CbcOptimizer)
cbcOptimizer = CbcOptimizer()
Copy link
Member

Choose a reason for hiding this comment

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

This does nothing, you need to modify the value of cbcOptimizer given as argument. Doing cbcOptimizer = CbcOptimizer() changes the object to which the local variable cbcOptimizer refers to but does not modify it

end


function MOI.canget(cbcOptimizer::CbcOptimizer, object::Union{MOI.AbstractModelAttribute, MOI.AbstractOptimizerAttribute})
Copy link
Member

Choose a reason for hiding this comment

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

Again, this can be handled by an Union

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.

I'm not sure what we should do about the binary variable bounds.

Does it make sense to add a binary constraint to a variable if it has a variable bound? @blegat @joaquimg ???

mutable struct CbcModelFormat
nbRows::Int
nbCols::Int
constraint_matrix::SparseMatrixCSC{Float64,Int32}
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 be SparseMatrixCSC{Float64, Int}


mutable struct CbcOptimizer <: MOI.AbstractOptimizer
inner::CbcModel
# env
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 needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The mutability is necessary because of the empty! function, which modifies the pointer CbcOptimizer.inner.

Copy link
Member

Choose a reason for hiding this comment

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

@odow might be refering to the # env comment

row_ub::VecOrNothing,
These are needed by function loadProblem of CbcCInterface.
```
Copy link
Member

Choose a reason for hiding this comment

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

Did you mean to make this a docstring with """ instead of ```?




#
Copy link
Member

Choose a reason for hiding this comment

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

Trailing comment

end

"""
Create inner model cbcOptimizer based on abstract model userOptimizer provided by user.
Copy link
Member

Choose a reason for hiding this comment

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

If you indent, in markdown, it means that this is code.
You should rather put an indented signature followed by an unindented description :)

end

struct CbcModelFormat
nbRows::Int
Copy link
Member

@mlubin mlubin Jun 23, 2018

Choose a reason for hiding this comment

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

I know that CbcCInterface has weird code, but camelCase shouldn't be used in new Julia code. num_rows instead.


struct CbcModelFormat
nbRows::Int
nbCols::Int
Copy link
Member

Choose a reason for hiding this comment

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

num_cols

end


function loadConstraint(ci::MOI.ConstraintIndex, cbcModelFormat::CbcModelFormat, mapping::MOIU.IndexMap,
Copy link
Member

Choose a reason for hiding this comment

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

cbc_model_format instead of cbcModelFormat.

end


function loadConstraint(ci::MOI.ConstraintIndex, cbcModelFormat::CbcModelFormat, mapping::MOIU.IndexMap,
Copy link
Member

Choose a reason for hiding this comment

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

load_constraint instead of loadConstraint

mapping::MOIU.IndexMap, f::MOI.ScalarAffineFunction, s::MOI.GreaterThan)
for term in f.terms
cbcModelFormat.constraint_matrix[mapping.conmap[ci].value,
mapping.varmap[term.variable_index].value] += term.coefficient
Copy link
Member

Choose a reason for hiding this comment

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

This is a super inefficient way to build a sparse matrix. Instead, keep a collection of the (row, column, value) triplets and call sparse() to build the matrix at the end.

function loadObj(cbcModelFormat::CbcModelFormat, mapping::MOIU.IndexMap,
f::MOI.ScalarAffineFunction)
for term in f.terms
cbcModelFormat.obj[mapping.varmap[term.variable_index].value] += term.coefficient
Copy link
Member

Choose a reason for hiding this comment

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

This assumes that obj is all zeros at the start, right? Add a comment mentioning this assumption.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When cbc_model_format is created everything is initialized to 0.0. So I do += to handle cases where the user sets an objective function like $x_1 + x_2 + x_1$. These cases are tested in contlinear.jl.

Copy link
Member

Choose a reason for hiding this comment

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

That's all good, but please add a comment about this assumption right here.

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.

Why is the docstring indented? It should be aligned with the function below.

function MOI.set!(cbcOptimizer::CbcOptimizer, object::MOI.ObjectiveSense, sense::MOI.OptimizationSense)
if sense == MOI.MaxSense
CbcCI.setObjSense(cbcOptimizer.inner, -1)
end ## Other senses are set as minimization (cbc default)
Copy link
Member

Choose a reason for hiding this comment

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

What happens this function is called with MaxSense and then MinSense? The MinSense will never be set.

MOI.set!(cbcOptimizer, MOI.ObjectiveSense(), sense)

## Load the problem to Cbc
CbcCI.loadProblem(cbcOptimizer.inner, cbcModelFormat.constraint_matrix,
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to keep the constraint_matrix in memory after it's loaded into Cbc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This object is local to the function copy! where it is defined, isn't it?

Copy link
Member

Choose a reason for hiding this comment

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

You're right, I missed that.

@testset "orderedindicestest" begin
MOIT.orderedindicestest(optimizer)
end
# @testset "canaddconstrainttest" begin ## do not pass due to vector of fariables
Copy link
Member

Choose a reason for hiding this comment

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

Typo: fariables -> variables (also below)

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.

This is a big improvement from before. A few small issues left.

@@ -0,0 +1,358 @@
export CbcOptimizer
Copy link
Member

Choose a reason for hiding this comment

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

CbcCInterface and CbcSolverInterface both define define submodules. We should be consistent, either turn this code into a submodule or drop the submodules from the other files.

Copy link
Contributor

Choose a reason for hiding this comment

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

The consistency is preferred also among the different solvers. maybe we should decide about having the wrapper as a submodule or not, and add a sentence here

http://www.juliaopt.org/MathOptInterface.jl/stable/apimanual.html#Package-Naming-1



using MathOptInterface
using Cbc.CbcCInterface
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's better to use a relative import, i.e., using .CbcCInterface.

include("CbcSolverInterface.jl")
include("MOIWrapper.jl")

using Cbc.CbcMathProgSolverInterface
Copy link
Member

Choose a reason for hiding this comment

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

Also try changing this to a relative import: using .CbcMathProgSolverInterface.

end

"""
Receive a cbc_optimizer which contains the pointer to the cbc C object and instanciate the object cbc_model_format::CbcModelFormat based on user_optimizer::AbstractModel (also provided by the user).
Copy link
Member

Choose a reason for hiding this comment

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

Typo: instantiate.

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.

The first line of the doc string should repeat the signature of the method, e.g.,

"""
    foo(a, b)

This is a docstring.
"""

This is because when you ask for help on MOI.copy!, it concatenates all the docstrings for the possible methods (try it).

Copy link
Contributor

Choose a reason for hiding this comment

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

Fixed


ci = MOI.get(user_optimizer, MOI.ListOfConstraintIndices{F,S}())

if S == 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.

This can be restructured for clarity. Right now it's a bit indirect that these cases can only happen when F is SingleVariable. E.g.,

if F == MOI.SingleVariable
  if S == MOI.ZeroOne
     ...
  elseif S == MOI.Integer
     ...
  end
  ## Single variables are treated by bounds in Cbc, so no
  ## need to add a row
else
    ... # content of F != MOI.SingleVariable case
end

## Set functions

function MOI.write(cbc_optimizer::CbcOptimizer, filename::String)
writeMps(cbc_optimizer.inner, filename)
Copy link
Member

Choose a reason for hiding this comment

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

Error if !endswith("filename", "mps").

"solve_blank_obj",
"solve_qcp_edge_cases",
"solve_qp_edge_cases",
"solve_affine_interval", ## cannot get with strings
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 a problem? Doesn't CachingOptimizer deal with getting by string?

MOI.set!(cbc_optimizer, MOI.ObjectiveSense(), sense)

## Load the problem to Cbc
CbcCI.loadProblem(cbc_optimizer.inner, sparse(cbc_model_format.row_idx, cbc_model_format.col_idx,
Copy link
Member

Choose a reason for hiding this comment

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

Clear row_idx, col_idx, and values after this call to save memory?

Copy link
Contributor

Choose a reason for hiding this comment

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

Fixed

@odow odow mentioned this pull request Jul 3, 2018
@mlubin
Copy link
Member

mlubin commented Jul 28, 2018

@vitornesello @IssamT is this ready to merge?

@vitornesello
Copy link
Contributor Author

@mlubin Yes. We have been using it already and we had no problems so far.

@mlubin mlubin merged commit d0fe5ab into jump-dev:master Jul 28, 2018
@mlubin
Copy link
Member

mlubin commented Jul 28, 2018

Thanks!!

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.

5 participants