Skip to content

Commit ac964c4

Browse files
committed
[Bridges] add ComplementsToScalarNonlinearFunctionBridge
1 parent 86d17e3 commit ac964c4

File tree

4 files changed

+471
-0
lines changed

4 files changed

+471
-0
lines changed

src/Bridges/Constraint/Constraint.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T}
113113
bridged_model,
114114
ExponentialConeToScalarNonlinearFunctionBridge{T},
115115
)
116+
MOI.Bridges.add_bridge(
117+
bridged_model,
118+
ComplementsToScalarNonlinearFunctionBridge{T},
119+
)
116120
return
117121
end
118122

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
# Copyright (c) 2017: Miles Lubin and contributors
2+
# Copyright (c) 2017: Google Inc.
3+
#
4+
# Use of this source code is governed by an MIT-style license that can be found
5+
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.
6+
7+
"""
8+
ComplementsToScalarNonlinearFunctionBridge{T,F} <:
9+
Bridges.Constraint.AbstractBridge
10+
11+
`ComplementsToScalarNonlinearFunctionBridge` implements the following
12+
reformulation:
13+
14+
* ``(F, x) \\in \\textsf{Complements}()`` to
15+
```julia
16+
if isfinite(l)
17+
(x - l) * F <= 0.0
18+
else
19+
1.0 * F <= 0.0
20+
end
21+
if isfinite(u)
22+
(x - u) * F <= 0.0
23+
else
24+
-1.0 * F <= 0.0
25+
end
26+
```
27+
28+
## Source node
29+
30+
`ComplementsToScalarNonlinearFunctionBridge` supports:
31+
32+
* `F` in [`MOI.Complements`](@ref)
33+
34+
## Target nodes
35+
36+
`ComplementsToScalarNonlinearFunctionBridge` creates:
37+
38+
* [`MOI.ScalarNonlinearFunction`](@ref) in [`MOI.LessThan{T}`](@ref)
39+
"""
40+
mutable struct ComplementsToScalarNonlinearFunctionBridge{
41+
T,
42+
F<:Union{
43+
MOI.VectorOfVariables,
44+
MOI.VectorAffineFunction{T},
45+
MOI.VectorQuadraticFunction{T},
46+
MOI.VectorNonlinearFunction,
47+
},
48+
} <: AbstractBridge
49+
f::F
50+
ci::Vector{MOI.ConstraintIndex{MOI.ScalarNonlinearFunction,MOI.LessThan{T}}}
51+
bounds::Vector{NTuple{2,T}}
52+
function ComplementsToScalarNonlinearFunctionBridge{T}(
53+
f,
54+
::MOI.Complements,
55+
) where {T}
56+
ci = MOI.ConstraintIndex{MOI.ScalarNonlinearFunction,MOI.LessThan{T}}[]
57+
return new{T,typeof(f)}(f, ci, NTuple{2,T}[])
58+
end
59+
end
60+
61+
const ComplementsToScalarNonlinearFunction{T,OT<:MOI.ModelLike} =
62+
SingleBridgeOptimizer{ComplementsToScalarNonlinearFunctionBridge{T},OT}
63+
64+
function bridge_constraint(
65+
::Type{ComplementsToScalarNonlinearFunctionBridge{T,F}},
66+
model::MOI.ModelLike,
67+
f::F,
68+
s::MOI.Complements,
69+
) where {
70+
T,
71+
F<:Union{
72+
MOI.VectorOfVariables,
73+
MOI.VectorAffineFunction{T},
74+
MOI.VectorQuadraticFunction{T},
75+
MOI.VectorNonlinearFunction,
76+
},
77+
}
78+
# !!! info
79+
# Postpone creation until final_touch.
80+
return ComplementsToScalarNonlinearFunctionBridge{T}(f, s)
81+
end
82+
83+
function MOI.supports_constraint(
84+
::Type{<:ComplementsToScalarNonlinearFunctionBridge{T}},
85+
::Type{F},
86+
::Type{MOI.Complements},
87+
) where {
88+
T,
89+
F<:Union{
90+
MOI.VectorOfVariables,
91+
MOI.VectorAffineFunction{T},
92+
MOI.VectorQuadraticFunction{T},
93+
MOI.VectorNonlinearFunction,
94+
},
95+
}
96+
return true
97+
end
98+
99+
function MOI.Bridges.added_constrained_variable_types(
100+
::Type{<:ComplementsToScalarNonlinearFunctionBridge},
101+
)
102+
return Tuple{Type}[]
103+
end
104+
105+
function MOI.Bridges.added_constraint_types(
106+
::Type{<:ComplementsToScalarNonlinearFunctionBridge{T}},
107+
) where {T}
108+
return Tuple{Type,Type}[(MOI.ScalarNonlinearFunction, MOI.LessThan{T})]
109+
end
110+
111+
function concrete_bridge_type(
112+
::Type{<:ComplementsToScalarNonlinearFunctionBridge{T}},
113+
::Type{F},
114+
::Type{MOI.Complements},
115+
) where {
116+
T,
117+
F<:Union{
118+
MOI.VectorOfVariables,
119+
MOI.VectorAffineFunction{T},
120+
MOI.VectorQuadraticFunction{T},
121+
MOI.VectorNonlinearFunction,
122+
},
123+
}
124+
return ComplementsToScalarNonlinearFunctionBridge{T,F}
125+
end
126+
127+
function MOI.get(
128+
::MOI.ModelLike,
129+
::MOI.ConstraintFunction,
130+
bridge::ComplementsToScalarNonlinearFunctionBridge,
131+
)
132+
return copy(bridge.f)
133+
end
134+
135+
function MOI.get(
136+
::MOI.ModelLike,
137+
::MOI.ConstraintSet,
138+
bridge::ComplementsToScalarNonlinearFunctionBridge,
139+
)
140+
n = MOI.output_dimension(bridge.f)
141+
return MOI.Complements(n)
142+
end
143+
144+
function MOI.delete(
145+
model::MOI.ModelLike,
146+
bridge::ComplementsToScalarNonlinearFunctionBridge,
147+
)
148+
MOI.delete.(model, bridge.ci)
149+
empty!(bridge.bounds)
150+
return
151+
end
152+
153+
function MOI.get(
154+
::ComplementsToScalarNonlinearFunctionBridge,
155+
::MOI.NumberOfVariables,
156+
)::Int64
157+
return 0
158+
end
159+
160+
function MOI.get(
161+
::ComplementsToScalarNonlinearFunctionBridge,
162+
::MOI.ListOfVariableIndices,
163+
)::Vector{MOI.VariableIndex}
164+
return MOI.VariableIndex[]
165+
end
166+
167+
function MOI.get(
168+
bridge::ComplementsToScalarNonlinearFunctionBridge{T},
169+
::MOI.NumberOfConstraints{MOI.ScalarNonlinearFunction,MOI.LessThan{T}},
170+
)::Int64 where {T}
171+
return length(bridge.ci)
172+
end
173+
174+
function MOI.get(
175+
bridge::ComplementsToScalarNonlinearFunctionBridge{T},
176+
::MOI.ListOfConstraintIndices{MOI.ScalarNonlinearFunction,MOI.LessThan{T}},
177+
) where {T}
178+
return copy(bridge.ci)
179+
end
180+
181+
function MOI.Bridges.needs_final_touch(
182+
::ComplementsToScalarNonlinearFunctionBridge,
183+
)
184+
return true
185+
end
186+
187+
function MOI.Bridges.final_touch(
188+
bridge::ComplementsToScalarNonlinearFunctionBridge{T},
189+
model::MOI.ModelLike,
190+
) where {T}
191+
f = collect(MOI.Utilities.eachscalar(bridge.f))
192+
N = div(length(f), 2)
193+
final_touch_called_previously = isempty(bridge.bounds)
194+
for i in 1:N
195+
x = convert(MOI.VariableIndex, f[i+N])
196+
ret = MOI.Utilities.get_bounds(model, T, x)
197+
ret = something(ret, (typemin(T), typemax(T)))
198+
if length(bridge.bounds) < i
199+
# This is the first time calling final_touch
200+
push!(bridge.bounds, ret)
201+
elseif bridge.bounds[i] == ret
202+
# We've called final_touch before, and the bounds match. No need to
203+
# reformulate a second time.
204+
continue
205+
elseif bridge.bounds[i] != ret
206+
# There is a stored bound, and the current bounds do not match. This
207+
# means the model has been modified since the previous call to
208+
# final_touch. We need to delete the bridge and start again.
209+
MOI.delete(model, bridge)
210+
MOI.Bridges.final_touch(bridge, model)
211+
return
212+
end
213+
end
214+
if final_touch_called_previously
215+
return # Nothing to be done
216+
end
217+
for i in 1:N
218+
(l, u), F = bridge.bounds[i], f[i]
219+
x = convert(MOI.VariableIndex, f[N+i])
220+
g_l = if isfinite(l) && iszero(l)
221+
MOI.ScalarNonlinearFunction(:*, Any[x, F])
222+
elseif isfinite(l)
223+
x_l = MOI.ScalarNonlinearFunction(:-, Any[x, l])
224+
MOI.ScalarNonlinearFunction(:*, Any[x_l, F])
225+
elseif F isa MOI.ScalarNonlinearFunction
226+
F
227+
else
228+
MOI.ScalarNonlinearFunction(:+, Any[F])
229+
end
230+
push!(bridge.ci, MOI.add_constraint(model, g_l, MOI.LessThan(zero(T))))
231+
g_u = if isfinite(u) && iszero(u)
232+
MOI.ScalarNonlinearFunction(:*, Any[x, F])
233+
elseif isfinite(u)
234+
x_u = MOI.ScalarNonlinearFunction(:-, Any[x, u])
235+
MOI.ScalarNonlinearFunction(:*, Any[x_u, F])
236+
else
237+
MOI.ScalarNonlinearFunction(:*, Any[-one(T), F])
238+
end
239+
push!(bridge.ci, MOI.add_constraint(model, g_u, MOI.LessThan(zero(T))))
240+
end
241+
return
242+
end

src/functions.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ constant(::VariableIndex, ::Type{T}) where {T} = zero(T)
6868

6969
Base.copy(x::VariableIndex) = x
7070

71+
Base.isapprox(::Number, ::AbstractScalarFunction; kwargs...) = false
72+
Base.isapprox(::AbstractScalarFunction, ::Number; kwargs...) = false
73+
7174
Base.isapprox(x::VariableIndex, y::VariableIndex; kwargs...) = x == y
7275

7376
"""
@@ -973,6 +976,16 @@ function Base.convert(
973976
return convert(VariableIndex, convert(ScalarAffineFunction{T}, f))
974977
end
975978

979+
function Base.convert(
980+
::Type{VariableIndex},
981+
f::ScalarNonlinearFunction,
982+
) where {T}
983+
if f.head != :+ && length(f.args) != 1
984+
throw(InexactError(:convert, VariableIndex, f))
985+
end
986+
return convert(VariableIndex, only(f.args))
987+
end
988+
976989
# ScalarAffineFunction
977990

978991
function Base.convert(::Type{ScalarAffineFunction{T}}, α::T) where {T}

0 commit comments

Comments
 (0)