Skip to content

Commit

Permalink
Add SkipModelConvertScalarSetWrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
odow committed Oct 29, 2023
1 parent 35e7713 commit c80efa2
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 4 deletions.
3 changes: 3 additions & 0 deletions docs/src/developers/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,9 @@ Rewriting my_equal_to to ==
When parsing a constraint you can recurse into sub-constraint (for example, the
`{expr}` in `z => {x <= 1}`) by calling [`parse_constraint`](@ref).

To prevent JuMP from promoting the set to the same value type as the model, use
[`SkipModelConvertScalarSetWrapper`](@ref).

### Build

To extend the [`@constraint`](@ref) macro at build time, implement a new
Expand Down
39 changes: 35 additions & 4 deletions src/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1543,16 +1543,47 @@ function relax_with_penalty!(
return relax_with_penalty!(model, Dict(); default = default)
end

struct _DoNotConvertSet{S} <: MOI.AbstractScalarSet
"""
SkipModelConvertScalarSetWrapper(set::MOI.AbstractScalarSet)
JuMP uses [`model_convert`](@ref) to automatically promote [`MOI.AbstractScalarSet`](@ref)
sets to the same [`value_type`](@ref) as the model.
In cases there this is undesirable, wrap the set in `SkipModelConvertScalarSetWrapper`
to pass the set un-changed to the solver.
## Examples
```jldoctest
julia> model = Model();
julia> @variable(model, x);
julia> @constraint(model, x in MOI.EqualTo(1 // 2))
x = 0.5
julia> @constraint(model, x in SkipModelConvertScalarSetWrapper(MOI.EqualTo(1 // 2)))
x = 1//2
```
"""
struct SkipModelConvertScalarSetWrapper{S<:MOI.AbstractScalarSet} <:
MOI.AbstractScalarSet
set::S
end

model_convert(::AbstractModel, set::_DoNotConvertSet) = set
model_convert(::AbstractModel, set::SkipModelConvertScalarSetWrapper) = set

moi_set(c::ScalarConstraint{F,<:_DoNotConvertSet}) where {F} = c.set.set
function moi_set(
c::ScalarConstraint{F,<:SkipModelConvertScalarSetWrapper},
) where {F}
return c.set.set
end

function _build_boolean_equal_to(::Function, lhs::AbstractJuMPScalar, rhs::Bool)
return ScalarConstraint(lhs, _DoNotConvertSet(MOI.EqualTo(rhs)))
return ScalarConstraint(
lhs,
SkipModelConvertScalarSetWrapper(MOI.EqualTo(rhs)),
)
end

function _build_boolean_equal_to(error_fn::Function, ::AbstractJuMPScalar, rhs)
Expand Down
9 changes: 9 additions & 0 deletions test/test_constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1763,4 +1763,13 @@ function test_def_equal_to_operator_bool()
return
end

function test_SkipModelConvertScalarSetWrapper()
model = Model()
@variable(model, x)
set = MOI.EqualTo(1 // 2)
c1 = @constraint(model, x in set)
c2 = @constraint(model, x in SkipModelConvertScalarSetWrapper(set))
@test constraint_object(c1).set === MOI.EqualTo(0.5)
@test constraint_object(c2).set === MOI.EqualTo(1 // 2)
return
end

0 comments on commit c80efa2

Please sign in to comment.