Skip to content

Commit

Permalink
[Bridges] add ComplementsToScalarNonlinearFunctionBridge
Browse files Browse the repository at this point in the history
  • Loading branch information
odow committed Dec 12, 2024
1 parent 86d17e3 commit ac964c4
Show file tree
Hide file tree
Showing 4 changed files with 471 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/Bridges/Constraint/Constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T}
bridged_model,
ExponentialConeToScalarNonlinearFunctionBridge{T},
)
MOI.Bridges.add_bridge(
bridged_model,
ComplementsToScalarNonlinearFunctionBridge{T},
)
return
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
# Copyright (c) 2017: Miles Lubin and contributors
# Copyright (c) 2017: Google Inc.
#
# Use of this source code is governed by an MIT-style license that can be found
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.

"""
ComplementsToScalarNonlinearFunctionBridge{T,F} <:
Bridges.Constraint.AbstractBridge
`ComplementsToScalarNonlinearFunctionBridge` implements the following
reformulation:
* ``(F, x) \\in \\textsf{Complements}()`` to
```julia
if isfinite(l)
(x - l) * F <= 0.0
else
1.0 * F <= 0.0
end
if isfinite(u)
(x - u) * F <= 0.0
else
-1.0 * F <= 0.0
end
```
## Source node
`ComplementsToScalarNonlinearFunctionBridge` supports:
* `F` in [`MOI.Complements`](@ref)
## Target nodes
`ComplementsToScalarNonlinearFunctionBridge` creates:
* [`MOI.ScalarNonlinearFunction`](@ref) in [`MOI.LessThan{T}`](@ref)
"""
mutable struct ComplementsToScalarNonlinearFunctionBridge{
T,
F<:Union{
MOI.VectorOfVariables,
MOI.VectorAffineFunction{T},
MOI.VectorQuadraticFunction{T},
MOI.VectorNonlinearFunction,
},
} <: AbstractBridge
f::F
ci::Vector{MOI.ConstraintIndex{MOI.ScalarNonlinearFunction,MOI.LessThan{T}}}
bounds::Vector{NTuple{2,T}}
function ComplementsToScalarNonlinearFunctionBridge{T}(
f,
::MOI.Complements,
) where {T}
ci = MOI.ConstraintIndex{MOI.ScalarNonlinearFunction,MOI.LessThan{T}}[]
return new{T,typeof(f)}(f, ci, NTuple{2,T}[])
end
end

const ComplementsToScalarNonlinearFunction{T,OT<:MOI.ModelLike} =
SingleBridgeOptimizer{ComplementsToScalarNonlinearFunctionBridge{T},OT}

function bridge_constraint(
::Type{ComplementsToScalarNonlinearFunctionBridge{T,F}},
model::MOI.ModelLike,
f::F,
s::MOI.Complements,
) where {
T,
F<:Union{
MOI.VectorOfVariables,
MOI.VectorAffineFunction{T},
MOI.VectorQuadraticFunction{T},
MOI.VectorNonlinearFunction,
},
}
# !!! info
# Postpone creation until final_touch.
return ComplementsToScalarNonlinearFunctionBridge{T}(f, s)
end

function MOI.supports_constraint(
::Type{<:ComplementsToScalarNonlinearFunctionBridge{T}},
::Type{F},
::Type{MOI.Complements},
) where {
T,
F<:Union{
MOI.VectorOfVariables,
MOI.VectorAffineFunction{T},
MOI.VectorQuadraticFunction{T},
MOI.VectorNonlinearFunction,
},
}
return true
end

function MOI.Bridges.added_constrained_variable_types(
::Type{<:ComplementsToScalarNonlinearFunctionBridge},
)
return Tuple{Type}[]
end

function MOI.Bridges.added_constraint_types(
::Type{<:ComplementsToScalarNonlinearFunctionBridge{T}},
) where {T}
return Tuple{Type,Type}[(MOI.ScalarNonlinearFunction, MOI.LessThan{T})]
end

function concrete_bridge_type(
::Type{<:ComplementsToScalarNonlinearFunctionBridge{T}},
::Type{F},
::Type{MOI.Complements},
) where {
T,
F<:Union{
MOI.VectorOfVariables,
MOI.VectorAffineFunction{T},
MOI.VectorQuadraticFunction{T},
MOI.VectorNonlinearFunction,
},
}
return ComplementsToScalarNonlinearFunctionBridge{T,F}
end

function MOI.get(
::MOI.ModelLike,
::MOI.ConstraintFunction,
bridge::ComplementsToScalarNonlinearFunctionBridge,
)
return copy(bridge.f)
end

function MOI.get(
::MOI.ModelLike,
::MOI.ConstraintSet,
bridge::ComplementsToScalarNonlinearFunctionBridge,
)
n = MOI.output_dimension(bridge.f)
return MOI.Complements(n)
end

function MOI.delete(
model::MOI.ModelLike,
bridge::ComplementsToScalarNonlinearFunctionBridge,
)
MOI.delete.(model, bridge.ci)
empty!(bridge.bounds)
return
end

function MOI.get(
::ComplementsToScalarNonlinearFunctionBridge,
::MOI.NumberOfVariables,
)::Int64
return 0
end

function MOI.get(
::ComplementsToScalarNonlinearFunctionBridge,
::MOI.ListOfVariableIndices,
)::Vector{MOI.VariableIndex}
return MOI.VariableIndex[]
end

function MOI.get(
bridge::ComplementsToScalarNonlinearFunctionBridge{T},
::MOI.NumberOfConstraints{MOI.ScalarNonlinearFunction,MOI.LessThan{T}},
)::Int64 where {T}
return length(bridge.ci)
end

function MOI.get(
bridge::ComplementsToScalarNonlinearFunctionBridge{T},
::MOI.ListOfConstraintIndices{MOI.ScalarNonlinearFunction,MOI.LessThan{T}},
) where {T}
return copy(bridge.ci)
end

function MOI.Bridges.needs_final_touch(
::ComplementsToScalarNonlinearFunctionBridge,
)
return true
end

function MOI.Bridges.final_touch(
bridge::ComplementsToScalarNonlinearFunctionBridge{T},
model::MOI.ModelLike,
) where {T}
f = collect(MOI.Utilities.eachscalar(bridge.f))
N = div(length(f), 2)
final_touch_called_previously = isempty(bridge.bounds)
for i in 1:N
x = convert(MOI.VariableIndex, f[i+N])
ret = MOI.Utilities.get_bounds(model, T, x)
ret = something(ret, (typemin(T), typemax(T)))
if length(bridge.bounds) < i
# This is the first time calling final_touch
push!(bridge.bounds, ret)
elseif bridge.bounds[i] == ret
# We've called final_touch before, and the bounds match. No need to
# reformulate a second time.
continue
elseif bridge.bounds[i] != ret

Check warning on line 205 in src/Bridges/Constraint/bridges/ComplementsToScalarNonlinearFunctionBridge.jl

View check run for this annotation

Codecov / codecov/patch

src/Bridges/Constraint/bridges/ComplementsToScalarNonlinearFunctionBridge.jl#L205

Added line #L205 was not covered by tests
# There is a stored bound, and the current bounds do not match. This
# means the model has been modified since the previous call to
# final_touch. We need to delete the bridge and start again.
MOI.delete(model, bridge)
MOI.Bridges.final_touch(bridge, model)
return

Check warning on line 211 in src/Bridges/Constraint/bridges/ComplementsToScalarNonlinearFunctionBridge.jl

View check run for this annotation

Codecov / codecov/patch

src/Bridges/Constraint/bridges/ComplementsToScalarNonlinearFunctionBridge.jl#L209-L211

Added lines #L209 - L211 were not covered by tests
end
end
if final_touch_called_previously
return # Nothing to be done
end
for i in 1:N
(l, u), F = bridge.bounds[i], f[i]
x = convert(MOI.VariableIndex, f[N+i])
g_l = if isfinite(l) && iszero(l)
MOI.ScalarNonlinearFunction(:*, Any[x, F])
elseif isfinite(l)
x_l = MOI.ScalarNonlinearFunction(:-, Any[x, l])
MOI.ScalarNonlinearFunction(:*, Any[x_l, F])
elseif F isa MOI.ScalarNonlinearFunction
F
else
MOI.ScalarNonlinearFunction(:+, Any[F])
end
push!(bridge.ci, MOI.add_constraint(model, g_l, MOI.LessThan(zero(T))))
g_u = if isfinite(u) && iszero(u)
MOI.ScalarNonlinearFunction(:*, Any[x, F])

Check warning on line 232 in src/Bridges/Constraint/bridges/ComplementsToScalarNonlinearFunctionBridge.jl

View check run for this annotation

Codecov / codecov/patch

src/Bridges/Constraint/bridges/ComplementsToScalarNonlinearFunctionBridge.jl#L232

Added line #L232 was not covered by tests
elseif isfinite(u)
x_u = MOI.ScalarNonlinearFunction(:-, Any[x, u])
MOI.ScalarNonlinearFunction(:*, Any[x_u, F])
else
MOI.ScalarNonlinearFunction(:*, Any[-one(T), F])
end
push!(bridge.ci, MOI.add_constraint(model, g_u, MOI.LessThan(zero(T))))
end
return
end
13 changes: 13 additions & 0 deletions src/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ constant(::VariableIndex, ::Type{T}) where {T} = zero(T)

Base.copy(x::VariableIndex) = x

Base.isapprox(::Number, ::AbstractScalarFunction; kwargs...) = false
Base.isapprox(::AbstractScalarFunction, ::Number; kwargs...) = false

Base.isapprox(x::VariableIndex, y::VariableIndex; kwargs...) = x == y

"""
Expand Down Expand Up @@ -973,6 +976,16 @@ function Base.convert(
return convert(VariableIndex, convert(ScalarAffineFunction{T}, f))
end

function Base.convert(
::Type{VariableIndex},
f::ScalarNonlinearFunction,
) where {T}
if f.head != :+ && length(f.args) != 1
throw(InexactError(:convert, VariableIndex, f))

Check warning on line 984 in src/functions.jl

View check run for this annotation

Codecov / codecov/patch

src/functions.jl#L984

Added line #L984 was not covered by tests
end
return convert(VariableIndex, only(f.args))
end

# ScalarAffineFunction

function Base.convert(::Type{ScalarAffineFunction{T}}, α::T) where {T}
Expand Down
Loading

0 comments on commit ac964c4

Please sign in to comment.