Skip to content

Commit 94046ea

Browse files
authored
Add support for all constraint types (#3)
1 parent 8b79c7c commit 94046ea

2 files changed

Lines changed: 59 additions & 74 deletions

File tree

README.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,3 @@ model = Model(() -> MathOptLazy.Optimizer(HiGHS.Optimizer))
3939
@variable(model, x[1:10] >= 0)
4040
@constraint(model, [i in 1:10], x[i] <= 1, MathOptLazy.Lazy())
4141
```
42-
43-
## Supported constraints
44-
45-
The following lazy constraint types are supported:
46-
47-
* `MOI.ScalarAffineFunction`-in-`MOI.GreaterThan`
48-
* `MOI.ScalarAffineFunction`-in-`MOI.LessThan`

src/MathOptLazy.jl

Lines changed: 59 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -75,32 +75,44 @@ struct _LazyData{F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
7575
end
7676
end
7777

78-
Base.length(x::_LazyData) = length(x.data)
79-
80-
Base.isempty(x::_LazyData) = isempty(x.data)
81-
8278
### Optimizer
8379

8480
"""
85-
Optimizer(inner_fn; kwargs...)
81+
Optimizer(inner_fn; kwargs...) <: MOI.AbstractOptimizer
82+
83+
Construct a new optimizer that wraps the result of
84+
`MOI.instantiate(inner_fn; kwargs...)`.
85+
86+
## Example
87+
88+
```julia
89+
julia> import MathOptLazy
90+
91+
julia> import HiGHS
92+
93+
julia> model = MathOptLazy.Optimizer(HiGHS.Optimizer)
94+
MathOptLazy.Optimizer{Float64, HiGHS.Optimizer}
95+
├ ObjectiveSense: FEASIBILITY_SENSE
96+
├ ObjectiveFunctionType: MOI.ScalarAffineFunction{Float64}
97+
├ NumberOfVariables: 0
98+
└ NumberOfConstraints: 0
99+
100+
julia> model = MathOptLazy.Optimizer(HiGHS.Optimizer; with_bridge_type = Float64)
101+
MathOptLazy.Optimizer{Float64, MOIB.LazyBridgeOptimizer{HiGHS.Optimizer}}
102+
├ ObjectiveSense: FEASIBILITY_SENSE
103+
├ ObjectiveFunctionType: MOI.ScalarAffineFunction{Float64}
104+
├ NumberOfVariables: 0
105+
└ NumberOfConstraints: 0
106+
```
86107
"""
87-
struct Optimizer{T,OT} <: MOI.AbstractOptimizer
108+
struct Optimizer{OT} <: MOI.AbstractOptimizer
88109
inner::OT
89110

90-
saf_gt::_LazyData{MOI.ScalarAffineFunction{T},MOI.GreaterThan{T}}
91-
saf_lt::_LazyData{MOI.ScalarAffineFunction{T},MOI.LessThan{T}}
111+
lazy::Dict{Tuple{Type,Type},_LazyData}
92112

93-
function Optimizer(
94-
inner_fn;
95-
coefficient_type::Type{T} = Float64,
96-
kwargs...,
97-
) where {T}
113+
function Optimizer(inner_fn; kwargs...)
98114
inner = MOI.instantiate(inner_fn; kwargs...)
99-
return new{T,typeof(inner)}(
100-
inner,
101-
_LazyData{MOI.ScalarAffineFunction{T},MOI.GreaterThan{T}}(),
102-
_LazyData{MOI.ScalarAffineFunction{T},MOI.LessThan{T}}(),
103-
)
115+
return new{typeof(inner)}(inner, Dict{Tuple{Type,Type},_LazyData}())
104116
end
105117
end
106118

@@ -141,7 +153,7 @@ function MOI.get(model::Optimizer, attr::_ATTRIBUTES, args...)
141153
return MOI.get(model.inner, attr, args...)
142154
end
143155

144-
function MOI.get(model::Optimizer, attr::_ATTRIBUTES, arg::Vector{T}) where {T}
156+
function MOI.get(model::Optimizer, attr::_ATTRIBUTES, arg::Vector)
145157
return MOI.get.(model, attr, arg)
146158
end
147159

@@ -248,54 +260,48 @@ MOI.compute_conflict!(model::Optimizer) = MOI.compute_conflict!(model.inner)
248260

249261
### LazyConstraints
250262

251-
_data(::Optimizer, ::Type{F}, ::Type{S}) where {F,S} = nothing
252-
253-
function _data(
254-
model::Optimizer{T},
255-
::Type{MOI.ScalarAffineFunction{T}},
256-
::Type{MOI.GreaterThan{T}},
257-
) where {T}
258-
return model.saf_gt
263+
function _maybe_data(
264+
model::Optimizer,
265+
::Type{F},
266+
::Type{S},
267+
)::Union{Nothing,_LazyData{F,S}} where {F,S}
268+
return get(model.lazy, (F, S), nothing)
259269
end
260270

261-
function _data(
262-
model::Optimizer{T},
263-
::Type{MOI.ScalarAffineFunction{T}},
264-
::Type{MOI.LessThan{T}},
265-
) where {T}
266-
return model.saf_lt
271+
function _data(model::Optimizer, ::Type{F}, ::Type{S}) where {F,S}
272+
return get!(_LazyData{F,S}, model.lazy, (F, S))
267273
end
268274

269275
function MOI.supports_constraint(
270276
model::Optimizer,
271277
::Type{F},
272278
::Type{LazyScalarSet{S}},
273279
) where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
274-
return _data(model, F, S) !== nothing &&
275-
MOI.supports_constraint(model, F, S)
280+
return MOI.supports_constraint(model, F, S)
276281
end
277282

278283
function MOI.is_valid(
279284
model::Optimizer,
280285
ci::MOI.ConstraintIndex{F,LazyScalarSet{S}},
281286
) where {F,S}
282-
data = _data(model, F, S)
283-
return data !== nothing && 1 <= ci.value <= length(data)
287+
ret = _maybe_data(model, F, S)
288+
return ret !== nothing && 1 <= ci.value <= length(ret.data)
284289
end
285290

286291
function MOI.get(
287292
model::Optimizer,
288293
::MOI.ListOfConstraintIndices{F,LazyScalarSet{S}},
289294
) where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
290-
n = length(_data(model, F, S))
295+
n = MOI.get(model, MOI.NumberOfConstraints{F,LazyScalarSet{S}}())
291296
return [MOI.ConstraintIndex{F,LazyScalarSet{S}}(i) for i in 1:n]
292297
end
293298

294299
function MOI.get(
295300
model::Optimizer,
296301
::MOI.NumberOfConstraints{F,LazyScalarSet{S}},
297302
) where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
298-
return length(_data(model, F, S))
303+
ret = _maybe_data(model, F, S)
304+
return ret === nothing ? 0 : length(ret.data)
299305
end
300306

301307
function MOI.add_constraint(
@@ -307,7 +313,7 @@ function MOI.add_constraint(
307313
push!(data.data, (f, s.set))
308314
push!(data.active, false)
309315
push!(data.index, MOI.ConstraintIndex{F,S}(0))
310-
return MOI.ConstraintIndex{F,LazyScalarSet{S}}(length(data))
316+
return MOI.ConstraintIndex{F,LazyScalarSet{S}}(length(data.data))
311317
end
312318

313319
function MOI.get(
@@ -334,22 +340,10 @@ function MOI.get(
334340
return LazyScalarSet(_data(model, F, S).data[ci.value][2])
335341
end
336342

337-
function MOI.get(
338-
model::Optimizer{T},
339-
::MOI.ListOfConstraintTypesPresent,
340-
) where {T}
343+
function MOI.get(model::Optimizer, ::MOI.ListOfConstraintTypesPresent)
341344
ret = MOI.get(model.inner, MOI.ListOfConstraintTypesPresent())
342-
if !isempty(model.saf_gt)
343-
push!(
344-
ret,
345-
(MOI.ScalarAffineFunction{T}, LazyScalarSet{MOI.GreaterThan{T}}),
346-
)
347-
end
348-
if !isempty(model.saf_lt)
349-
push!(
350-
ret,
351-
(MOI.ScalarAffineFunction{T}, LazyScalarSet{MOI.LessThan{T}}),
352-
)
345+
for (F, S) in keys(model.lazy)
346+
push!(ret, (F, LazyScalarSet{S}))
353347
end
354348
return ret
355349
end
@@ -359,8 +353,8 @@ function MOI.get(
359353
attr::MOI.NumberOfConstraints{F,S},
360354
) where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
361355
n = MOI.get(model.inner, attr)
362-
if (data = _data(model, F, S)) !== nothing
363-
n -= sum(data.active)
356+
if (data = _maybe_data(model, F, S)) !== nothing
357+
n -= sum((data).active)
364358
end
365359
return n
366360
end
@@ -406,31 +400,29 @@ end
406400

407401
### MOI.optimize!
408402

409-
function MOI.optimize!(model::Optimizer{T}) where {T}
403+
function MOI.optimize!(model::Optimizer)
410404
needs_solve = true
411405
x = MOI.get(model, MOI.ListOfVariableIndices())
412-
X = Dict{MOI.VariableIndex,T}(xi => zero(T) for xi in x)
413406
while needs_solve
414407
needs_solve = false
415408
MOI.optimize!(model.inner)
416409
if MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
417-
for xi in x
418-
X[xi] = MOI.get(model, MOI.VariablePrimal(), xi)
410+
X = Dict(xi => MOI.get(model, MOI.VariablePrimal(), xi) for xi in x)
411+
constraints_added = 0
412+
for v in values(model.lazy)
413+
constraints_added += _add_if_necessary(model, v, X)
419414
end
420-
constraints_added =
421-
_add_if_necessary(model, model.saf_gt, X) +
422-
_add_if_necessary(model, model.saf_lt, X)
423415
needs_solve = constraints_added > 0
424416
end
425417
end
426418
return
427419
end
428420

429421
function _add_if_necessary(
430-
model::Optimizer{T},
422+
model::Optimizer,
431423
data::_LazyData,
432-
x::Dict{MOI.VariableIndex,T},
433-
) where {T}
424+
x::Dict{MOI.VariableIndex},
425+
)
434426
constraints_added = 0
435427
for (i, (f, s)) in enumerate(data.data)
436428
if data.active[i]

0 commit comments

Comments
 (0)