- 
                Notifications
    
You must be signed in to change notification settings  - Fork 35
 
Moiwrapper #54
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
Moiwrapper #54
Conversation
| 
           Very cool! CC @IssamT  | 
    
| 
           @mlubin I've already seen the code. We share the same office ;)  | 
    
| 
           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.  | 
    
| 
           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.  | 
    
There was a problem hiding this 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?
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | @@ -0,0 +1,397 @@ | |||
| export CbcOptimizer, copy!, optimize!, get | |||
There was a problem hiding this comment.
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
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | col_lb[colIdx] = -Inf | ||
| col_ub[colIdx] = Inf | ||
| obj[colIdx] = 0.0 | ||
| end | 
There was a problem hiding this comment.
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.)
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | mutable struct CbcModelFormat | ||
| nbRows::Int | ||
| nbCols::Int | ||
| constraint_matrix::Array{Float64,2} | 
There was a problem hiding this comment.
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
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | mapping.varmap[varIndex[i]] = MOI.VariableIndex(i) | ||
| end | ||
| 
               | 
          ||
| zeroOneIndices = Vector{Int}(0) | 
There was a problem hiding this comment.
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[]
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | zeroOneIndices = Vector{Int}(0) | ||
| integerIndices = Vector{Int}(0) | ||
| listOfConstraints = MOI.get(userOptimizer, MOI.ListOfConstraints()) | ||
| nbRows = 0; | 
There was a problem hiding this comment.
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 | 
There was a problem hiding this comment.
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
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | 
               | 
          ||
| 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) | 
There was a problem hiding this comment.
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
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | const CANGETCONSATTR = [] | ||
| 
               | 
          ||
| function MOI.canget(cbcOptimizer::CbcOptimizer, object::MOI.PrimalStatus) | ||
| object.N >= 2 && return false | 
There was a problem hiding this comment.
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?
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | 
               | 
          ||
| function MOI.get(cbcOptimizer::CbcOptimizer, object::MOI.VariablePrimal, ref::Vector{MOI.VariableIndex}) | ||
| variablePrimals = CbcCI.getColSolution(cbcOptimizer.inner) | ||
| askedPrimals = Vector{Float64}() | 
There was a problem hiding this comment.
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)
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | 
               | 
          ||
| 
               | 
          ||
| function MOI.get(cbcOptimizer::CbcOptimizer, object::MOI.ResultCount) | ||
| isProvenInfeasible(cbcOptimizer.inner) && return 0 | 
There was a problem hiding this comment.
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?
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | 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) | 
There was a problem hiding this comment.
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
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | 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) | 
There was a problem hiding this comment.
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)
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | 
               | 
          ||
| ## supports constraints | ||
| 
               | 
          ||
| function MOI.supportsconstraint(cbcOptimizer::CbcOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractScalarSet}) | 
There was a problem hiding this comment.
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{...}}) = trueand it would also make the result of supportsconstraint inferrable so calls to supportsconstraint can be optimized out by the compiler.
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | end | ||
| 
               | 
          ||
| function MOI.supports(cbcOptimizer::CbcOptimizer, object::MOI.ObjectiveFunction{T}) where T <: MOI.AbstractFunction | ||
| return T in SUPPORTED_OBJECTIVES | 
There was a problem hiding this comment.
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(...) = trueand specify what is supported in the type
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | # empty! | ||
| 
               | 
          ||
| function MOI.empty!(cbcOptimizer::CbcOptimizer) | ||
| cbcOptimizer = CbcOptimizer() | 
There was a problem hiding this comment.
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
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | end | ||
| 
               | 
          ||
| 
               | 
          ||
| function MOI.canget(cbcOptimizer::CbcOptimizer, object::Union{MOI.AbstractModelAttribute, MOI.AbstractOptimizerAttribute}) | 
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | mutable struct CbcModelFormat | ||
| nbRows::Int | ||
| nbCols::Int | ||
| constraint_matrix::SparseMatrixCSC{Float64,Int32} | 
There was a problem hiding this comment.
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}
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | 
               | 
          ||
| mutable struct CbcOptimizer <: MOI.AbstractOptimizer | ||
| inner::CbcModel | ||
| # env | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | row_ub::VecOrNothing, | ||
| These are needed by function loadProblem of CbcCInterface. | ||
| ``` | 
There was a problem hiding this comment.
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 ```?
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | 
               | 
          ||
| 
               | 
          ||
| 
               | 
          ||
| # | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing comment
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | end | ||
| 
               | 
          ||
| """ | ||
| Create inner model cbcOptimizer based on abstract model userOptimizer provided by user. | 
There was a problem hiding this comment.
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 :)
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | end | ||
| 
               | 
          ||
| struct CbcModelFormat | ||
| nbRows::Int | 
There was a problem hiding this comment.
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.
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | 
               | 
          ||
| struct CbcModelFormat | ||
| nbRows::Int | ||
| nbCols::Int | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
num_cols
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | end | ||
| 
               | 
          ||
| 
               | 
          ||
| function loadConstraint(ci::MOI.ConstraintIndex, cbcModelFormat::CbcModelFormat, mapping::MOIU.IndexMap, | 
There was a problem hiding this comment.
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.
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | end | ||
| 
               | 
          ||
| 
               | 
          ||
| function loadConstraint(ci::MOI.ConstraintIndex, cbcModelFormat::CbcModelFormat, mapping::MOIU.IndexMap, | 
There was a problem hiding this comment.
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
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | 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 | 
There was a problem hiding this comment.
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.
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | 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 | 
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 contlinear.jl.
There was a problem hiding this comment.
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.
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | end | ||
| end | ||
| 
               | 
          ||
| """ | 
There was a problem hiding this comment.
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.
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | 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) | 
There was a problem hiding this comment.
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.
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | MOI.set!(cbcOptimizer, MOI.ObjectiveSense(), sense) | ||
| 
               | 
          ||
| ## Load the problem to Cbc | ||
| CbcCI.loadProblem(cbcOptimizer.inner, cbcModelFormat.constraint_matrix, | 
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
        
          
                test/MOIWrapper.jl
              
                Outdated
          
        
      | @testset "orderedindicestest" begin | ||
| MOIT.orderedindicestest(optimizer) | ||
| end | ||
| # @testset "canaddconstrainttest" begin ## do not pass due to vector of fariables | 
There was a problem hiding this comment.
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)
There was a problem hiding this 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 | |||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 | 
There was a problem hiding this comment.
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 | 
There was a problem hiding this comment.
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.
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | 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). | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: instantiate.
| end | ||
| end | ||
| 
               | 
          ||
| """ | 
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | 
               | 
          ||
| ci = MOI.get(user_optimizer, MOI.ListOfConstraintIndices{F,S}()) | ||
| 
               | 
          ||
| if S == MOI.ZeroOne | 
There was a problem hiding this comment.
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        
          
                src/MOIWrapper.jl
              
                Outdated
          
        
      | ## Set functions | ||
| 
               | 
          ||
| function MOI.write(cbc_optimizer::CbcOptimizer, filename::String) | ||
| writeMps(cbc_optimizer.inner, filename) | 
There was a problem hiding this comment.
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 | 
There was a problem hiding this comment.
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, | 
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
| 
           @vitornesello @IssamT is this ready to merge?  | 
    
| 
           @mlubin Yes. We have been using it already and we had no problems so far.  | 
    
| 
           Thanks!!  | 
    
No description provided.