JuliaConstraints / CPLEXCP.jl

Julia interface for CPLEX CP Optimizer
MIT License
3 stars 1 forks source link

Implication operator #11

Closed matheusdiogenesandrade closed 1 year ago

matheusdiogenesandrade commented 1 year ago

Hi there.

This is a continuation of the thread created here.

I am trying to model the constraint formula if x = 10 then p + 5 = q. But, I am not being able to model this with CPLEXCP.jl. We could simply create a variable r.

r = q - p
if x = 10 then r = 5

Which could be implemented as below:

# r - (q - p) = r - q + p
r_eq = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1, -1, 1], [r, q, p]), 0)
# r - q + p = 0
MOI.add_constraint(model, r_eq, MOI.EqualsTo(0))

# if x = 10 then r = 5 
MOI.add_constraint(model, 
    MOI.VectorOfVariables([x, r]), 
    CP.Implication(MOI.EqualTo(10), MOI.EqualTo(5))
# github.com/JuliaConstraints/ConstraintProgrammingExtensions.jl/blob/master/src/Test/test_implication.jl#L16
)

However, by doing so, for each p and q, we would have to create another variable r. Therefore, I am looking for different ways of doing this without creating a new variable. One snippet I tried was this one:

using CPLEXCP
using MathOptInterface
using ConstraintProgrammingExtensions

const MOI = MathOptInterface
const CP = ConstraintProgrammingExtensions

model = CPLEXCP.Optimizer()

x = first(MOI.add_constrained_variable(model, MOI.Integer()))
p = first(MOI.add_constrained_variable(model, MOI.Integer()))
q = first(MOI.add_constrained_variable(model, MOI.Integer()))

# if x = 10 then p + 5 = q

# p - q
affine::MOI.ScalarAffineFunction{Int64} = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Int64}.([1, -1], [p, q]), 0)

# [x, p - q]
terms::MOI.VectorAffineFunction{Int64} = MOI.Utilities.operate(vcat, Int64, x, affine)

# [10, - 5]
implication::CP.Implication{MOI.EqualTo{Int64}, MOI.EqualTo{Int64}} = CP.Implication(MOI.EqualTo(10), MOI.EqualTo(-5))

# x = 10 => p - q = - 5
MOI.add_constraint(model, terms, implication)

MOI.add_constraint(model, x, MOI.EqualTo(10))

MOI.add_constraint(model, p, MOI.GreaterThan(0))
MOI.add_constraint(model, p, MOI.LessThan(10))

MOI.optimize!(model)

@show MOI.get(model, MOI.VariablePrimal(), x)
@show MOI.get(model, MOI.VariablePrimal(), p)
@show MOI.get(model, MOI.VariablePrimal(), q)

Which just worked perfectly well. However, when we change the variables type to Float, things go strange. Let's take the code below.

x = first(MOI.add_constrained_variable(model, MOI.Interval(0.0, 10.0)))
p = first(MOI.add_constrained_variable(model, MOI.Interval(0.0, 10.0)))
q = first(MOI.add_constrained_variable(model, MOI.Interval(0.0, 10.0)))

# if x = 10 then p + 5 = q

# p - q
affine::MOI.ScalarAffineFunction{Float64} = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}.([1, -1], [p, q]), 0.0)

# [x, p - q]
terms::MOI.VectorAffineFunction{Float64} = MOI.Utilities.operate(vcat, Float64, x, affine)

# [10, - 5]
implication::CP.Implication{MOI.EqualTo{Float64}, MOI.EqualTo{Float64}} = CP.Implication(MOI.EqualTo(10.0), MOI.EqualTo(-5.0))

# x = 10 => p - q = - 5
MOI.add_constraint(model, terms, implication)

MOI.add_constraint(model, x, MOI.EqualTo(10))

MOI.optimize!(model)

But, by doing so, I obtained the following error:

ERROR: LoadError: MethodError: no method matching _build_constraint(::CPLEXCP.Optimizer,
                    ::MathOptInterface.VectorAffineFunction{Float64},
                    ::ConstraintProgrammingExtensions.Implication{MathOptInterface.EqualTo{Float64}, MathOptInterface.EqualTo{Float64}})
Closest candidates are:
  _build_constraint(::CPLEXCP.Optimizer, 
                    ::Union{MathOptInterface.VectorAffineFunction{T}, MathOptInterface.VectorOfVariables}, 
                    ::ConstraintProgrammingExtensions.AllDifferent) where T at 
            ~/.julia/packages/CPLEXCP/jbgSe/src/MOI/wrapper_constraints_cp.jl:14
  _build_constraint(::CPLEXCP.Optimizer, 
                    ::Union{MathOptInterface.VectorAffineFunction{T}, MathOptInterface.VectorOfVariables}, 
                    ::ConstraintProgrammingExtensions.Implication{S1, S2}) where {T<:Int64, S1<:MathOptInterface.AbstractSet, S2<:MathOptInterface.AbstractSet} at 
            ~/.julia/packages/CPLEXCP/jbgSe/src/MOI/wrapper_constraints_cp_reification.jl:234
  _build_constraint(::CPLEXCP.Optimizer, ::Union{MathOptInterface.VariableIndex, MathOptInterface.ScalarAffineFunction{T}}, 
                    ::ConstraintProgrammingExtensions.AntiDomain{T}) where T<:Int64 at 
            ~/.julia/packages/CPLEXCP/jbgSe/src/MOI/wrapper_constraints_cp.jl:60
  ...
Stacktrace:
 [1] add_constraint(model::CPLEXCP.Optimizer, 
                    f::MathOptInterface.VectorAffineFunction{Float64}, 
                    s::ConstraintProgrammingExtensions.Implication{MathOptInterface.EqualTo{Float64}, 
                    MathOptInterface.EqualTo{Float64}})
   @ CPLEXCP ~/.julia/packages/CPLEXCP/jbgSe/src/MOI/wrapper_constraints.jl:29
 [2] top-level scope
   @ ~/CPLEXCP_example/test_conditional.jl:38
 [3] include(fname::String)
   @ Base.MainInclude ./client.jl:476
 [4] top-level scope
   @ REPL[1]:1
in expression starting at ~/CPLEXCP_example/test_conditional.jl:38

From the list of presented candidates, the one below seems to be the most suitable.

  _build_constraint(::CPLEXCP.Optimizer, 
                    ::Union{MathOptInterface.VectorAffineFunction{T}, MathOptInterface.VectorOfVariables}, 
                    ::ConstraintProgrammingExtensions.Implication{S1, S2}) where {T<:Int64, S1<:MathOptInterface.AbstractSet, S2<:MathOptInterface.AbstractSet} at 
            ~/.julia/packages/CPLEXCP/jbgSe/src/MOI/wrapper_constraints_cp_reification.jl:234

It seems that the function signature is forcing the affine expressions to be defined within integer coefficients, however, I do not think that the JavaCPOptimizer forces this requirement, at least so far I couldn't find anything.

Am I missing some inner step?

Thanks and regards.

dourouc05 commented 1 year ago

In a sense, it's a "feature" for now, CP.Implication is only implemented for integer types: https://github.com/JuliaConstraints/CPLEXCP.jl/blob/32947f04ec2688b4aca5b74dcedd9bb72749aa01/src/MOI/wrapper_constraints_cp_reification.jl#L219-L251

The fix could be easy: I think it just amounts to https://github.com/JuliaConstraints/CPLEXCP.jl/commit/5716d1dd2f4821ff9321d2fce8232ebe52d365d7. It's currently untested: could you tell me if it's working with that patch?