jump-dev/Clp.jl

copy_to incorrectly maps scalar constraints

Closed this issue · 3 comments

Edit(@odow):

using Clp
const MOI = Clp.MathOptInterface
model = MOI.Utilities.Model{Float64}()
x = MOI.add_variable(model)
con = [
    MOI.add_constraint(
        model, 
        MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0),
        MOI.EqualTo(1.0),
    )
    for i = 1:2
]
clp = Clp.Optimizer()
index_map = MOI.copy_to(clp, model)
index_map[con[1]] === index_map[con[2]]  # True! But should be false.

Original issue text

I created a simple model in JuMP v0.21.5 (Julia Version 1.5.2):

max 10𝑋1+15𝑋2
Subject to
6𝑋1+20𝑋2≤2400.0
20𝑋1+20𝑋2≤4000.0
4𝑋1+9𝑋2≤900.0
𝑋1≤100.0
𝑋2≤100.0
𝑋1≥0.0
𝑋2≥0.0

After optimizing (Solver: Clp 0.8.1) I do get the correct objective value as well as the correct variable values(X1=100, X2=55.55555). But for the following it seems as there is a problem. In earlier versions of Julia this code used to work.

JuMP.has_duals(LP)
Out: true

JuMP.dual(CapacityRestriction[1]), JuMP.dual(CapacityRestriction[2]), JuMP.dual(CapacityRestriction[3])
Out: (0.0, 0.0, 0.0)

JuMP.shadow_price(CapacityRestriction[1]), JuMP.shadow_price(CapacityRestriction[2]), JuMP.shadow_price(CapacityRestriction[3])
Out: (-0.0, -0.0, -0.0)

The third shadow price is not 0. Why doesn`t it show the correct solution.
Thanks for your help.

odow commented

-0.0 is equivalent to 0.0:

julia> -0.0 == 0.0
true

However, you should never compare floating point numbers with strict equality. Always use isapprox.

I'm going to close because this isn't a bug in JuMP. If you have follow-up questions, please post on the community forum. Before doing so, please read PSA: make it easier to help you, and provide a reproducible example.

odow commented

Or is your point that the dual and shadow price should not be 0? Please provide reproducible code. I don't know which constraints are CapacityRestriction.

Maybe there is something weird going on:

julia> b = [2400, 4000, 900]
3-element Array{Int64,1}:
 2400
 4000
  900

julia> A = [6 20; 20 20; 4 9]
3×2 Array{Int64,2}:
  6  20
 20  20
  4   9

julia> using Clp, JuMP

julia> model = Model(Clp.Optimizer)
A JuMP Model
Feasibility problem with:
Variables: 0
Model mode: AUTOMATIC
CachingOptimizer state: EMPTY_OPTIMIZER
Solver name: Clp

julia> @variable(model, 0 <= x[1:2] <= 100)
2-element Array{VariableRef,1}:
 x[1]
 x[2]

julia> @objective(model, Max, 10 * x[1] + 15 * x[2])
10 x[1] + 15 x[2]

julia> @constraint(model, con, A * x .<= b)
3-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},1}:
 6 x[1] + 20 x[2]  2400.0
 20 x[1] + 20 x[2]  4000.0
 4 x[1] + 9 x[2]  900.0

julia> optimize!(model)
Coin0506I Presolve 2 (-1) rows, 2 (0) columns and 4 (-2) elements
Clp0006I 0  Obj -0 Dual inf 37.499998 (2)
Clp0006I 1  Obj 1833.3333
Clp0000I Optimal - objective value 1833.3333
Coin0511I After Postsolve, objective 1833.3333, infeasibilities - dual 0 (0), primal 0 (0)
Clp0032I Optimal objective 1833.333333 - 1 iterations time 0.002, Presolve 0.00

julia> dual.(con)
3-element Array{Float64,1}:
 -1.6666666666666665
 -1.6666666666666665
 -1.6666666666666665

Compare with pure MOI:

julia> b = [2400.0, 4000.0, 900.0]
3-element Array{Float64,1}:
 2400.0
 4000.0
  900.0

julia> A = [6.0 20.0; 20.0 20.0; 4.0 9.0]
3×2 Array{Float64,2}:
  6.0  20.0
 20.0  20.0
  4.0   9.0

julia> model = MOI.Utilities.Model{Float64}()
MOIU.Model{Float64}

julia> x = MOI.add_variables(model, 2)
2-element Array{MathOptInterface.VariableIndex,1}:
 MathOptInterface.VariableIndex(1)
 MathOptInterface.VariableIndex(2)

julia> MOI.add_constraint.(model, MOI.SingleVariable.(x), MOI.Interval(0.0, 100.0))
2-element Array{MathOptInterface.ConstraintIndex{MathOptInterface.SingleVariable,MathOptInterface.Interval{Float64}},1}:
 MathOptInterface.ConstraintIndex{MathOptInterface.SingleVariable,MathOptInterface.Interval{Float64}}(1)
 MathOptInterface.ConstraintIndex{MathOptInterface.SingleVariable,MathOptInterface.Interval{Float64}}(2)

julia> MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
MAX_SENSE::OptimizationSense = 1

julia> MOI.set(
           model, 
           MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
           MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([10.0, 15.0], x), 0.0),
       )
MathOptInterface.ScalarAffineFunction{Float64}(MathOptInterface.ScalarAffineTerm{Float64}[MathOptInterface.ScalarAffineTerm{Float64}(10.0, MathOptInterface.VariableIndex(1)), MathOptInterface.ScalarAffineTerm{Float64}(15.0, MathOptInterface.VariableIndex(2))], 0.0)

julia> con = [
           MOI.add_constraint(
               model, 
               MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(A[i, :], x), 0.0),
               MOI.LessThan(bi),
           )
           for (i, bi) in enumerate(b)
       ]
3-element Array{MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},1}:
 MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}}(1)
 MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}}(2)
 MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}}(3)

julia> clp = Clp.Optimizer()
Clp.Optimizer

julia> MOI.copy_to(clp, model)
MathOptInterface.Utilities.IndexMap with 7 entries:
  VariableIndex(1)                 => VariableIndex(1)
  VariableIndex(2)                 => VariableIndex(2)
  ConstraintIndex{ScalarAffineFun => ConstraintIndex{ScalarAffineFunction{Float64},LessThan{Float64
  ConstraintIndex{ScalarAffineFun => ConstraintIndex{ScalarAffineFunction{Float64},LessThan{Float64
  ConstraintIndex{ScalarAffineFun => ConstraintIndex{ScalarAffineFunction{Float64},LessThan{Float64
  ConstraintIndex{SingleVariable, => ConstraintIndex{SingleVariable,Interval{Float64}}(2)
  ConstraintIndex{SingleVariable, => ConstraintIndex{SingleVariable,Interval{Float64}}(1)

julia> MOI.optimize!(clp)
Coin0506I Presolve 2 (-1) rows, 2 (0) columns and 4 (-2) elements
Clp0006I 0  Obj -0 Dual inf 37.499998 (2)
Clp0006I 1  Obj 1833.3333
Clp0000I Optimal - objective value 1833.3333
Coin0511I After Postsolve, objective 1833.3333, infeasibilities - dual 0 (0), primal 0 (0)
Clp0032I Optimal objective 1833.333333 - 1 iterations time 0.002, Presolve 0.00

julia> MOI.get.(clp, MOI.ConstraintDual(), con)
3-element Array{Float64,1}:
  0.0
  0.0
 -1.6666666666666665

I edited the package source code as you suggested and now everything works fine. Thanks for your help!