Skip to content

Commit c3ebf46

Browse files
authored
Pass copy operation for non-variable constraints (jump-dev#1286)
* Pass copy operation for non-variable constraints * Pass attributes separately
1 parent 77799c6 commit c3ebf46

File tree

7 files changed

+239
-13
lines changed

7 files changed

+239
-13
lines changed

src/Bridges/bridge_optimizer.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,41 @@ function MOI.supports(
320320
return MOI.supports(b.model, attr)
321321
end
322322

323+
function MOIU.pass_nonvariable_constraints(
324+
dest::AbstractBridgeOptimizer,
325+
src::MOI.ModelLike,
326+
idxmap::MOIU.IndexMap,
327+
constraint_types,
328+
pass_cons;
329+
filter_constraints::Union{Nothing,Function} = nothing,
330+
)
331+
not_bridged_types = eltype(constraint_types)[]
332+
bridged_types = eltype(constraint_types)[]
333+
for (F, S) in constraint_types
334+
if is_bridged(dest, F, S)
335+
push!(bridged_types, (F, S))
336+
else
337+
push!(not_bridged_types, (F, S))
338+
end
339+
end
340+
MOIU.pass_nonvariable_constraints(
341+
dest.model,
342+
src,
343+
idxmap,
344+
not_bridged_types,
345+
pass_cons;
346+
filter_constraints = filter_constraints,
347+
)
348+
return MOIU.pass_nonvariable_constraints_fallback(
349+
dest,
350+
src,
351+
idxmap,
352+
bridged_types,
353+
pass_cons;
354+
filter_constraints = filter_constraints,
355+
)
356+
end
357+
323358
function MOI.copy_to(mock::AbstractBridgeOptimizer, src::MOI.ModelLike; kws...)
324359
return MOIU.automatic_copy_to(mock, src; kws...)
325360
end

src/Test/modellike.jl

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,7 @@ function start_values_test(dest::MOI.ModelLike, src::MOI.ModelLike)
783783
end
784784
end
785785

786-
function copytest(dest::MOI.ModelLike, src::MOI.ModelLike)
786+
function copytest(dest::MOI.ModelLike, src::MOI.ModelLike; copy_names = false)
787787
@test MOIU.supports_default_copy_to(src, true) #=copy_names=#
788788
MOI.empty!(src)
789789
MOI.empty!(dest)
@@ -850,13 +850,15 @@ function copytest(dest::MOI.ModelLike, src::MOI.ModelLike)
850850
MOI.Zeros,
851851
)
852852

853-
dict = MOI.copy_to(dest, src, copy_names = false)
853+
dict = MOI.copy_to(dest, src, copy_names = copy_names)
854854

855-
@test !MOI.supports(dest, MOI.Name()) || MOI.get(dest, MOI.Name()) == ""
855+
dest_name(src_name) = copy_names ? src_name : ""
856+
@test !MOI.supports(dest, MOI.Name()) ||
857+
MOI.get(dest, MOI.Name()) == dest_name("ModelName")
856858
@test MOI.get(dest, MOI.NumberOfVariables()) == 4
857859
if MOI.supports(dest, MOI.VariableName(), MOI.VariableIndex)
858-
for vi in v
859-
MOI.get(dest, MOI.VariableName(), dict[vi]) == ""
860+
for i in eachindex(v)
861+
MOI.get(dest, MOI.VariableName(), dict[v[i]]) == dest_name("var$i")
860862
end
861863
end
862864
@test MOI.get(
@@ -908,25 +910,25 @@ function copytest(dest::MOI.ModelLike, src::MOI.ModelLike)
908910
@test (MOI.VectorAffineFunction{Float64}, MOI.Zeros) in loc
909911

910912
@test !MOI.supports(dest, MOI.ConstraintName(), typeof(csv)) ||
911-
MOI.get(dest, MOI.ConstraintName(), dict[csv]) == ""
913+
MOI.get(dest, MOI.ConstraintName(), dict[csv]) == dest_name("csv")
912914
@test MOI.get(dest, MOI.ConstraintFunction(), dict[csv]) ==
913915
MOI.SingleVariable(dict[w])
914916
@test MOI.get(dest, MOI.ConstraintSet(), dict[csv]) == MOI.EqualTo(2.0)
915917
@test !MOI.supports(dest, MOI.ConstraintName(), typeof(cvv)) ||
916-
MOI.get(dest, MOI.ConstraintName(), dict[cvv]) == ""
918+
MOI.get(dest, MOI.ConstraintName(), dict[cvv]) == dest_name("cvv")
917919
@test MOI.get(dest, MOI.ConstraintFunction(), dict[cvv]) ==
918920
MOI.VectorOfVariables(getindex.(Ref(dict), v))
919921
@test MOI.get(dest, MOI.ConstraintSet(), dict[cvv]) == MOI.Nonnegatives(3)
920922
@test !MOI.supports(dest, MOI.ConstraintName(), typeof(csa)) ||
921-
MOI.get(dest, MOI.ConstraintName(), dict[csa]) == ""
923+
MOI.get(dest, MOI.ConstraintName(), dict[csa]) == dest_name("csa")
922924
@test MOI.get(dest, MOI.ConstraintFunction(), dict[csa])
923925
MOI.ScalarAffineFunction(
924926
MOI.ScalarAffineTerm.([1.0, 3.0], [dict[v[3]], dict[v[1]]]),
925927
0.0,
926928
)
927929
@test MOI.get(dest, MOI.ConstraintSet(), dict[csa]) == MOI.LessThan(2.0)
928930
@test !MOI.supports(dest, MOI.ConstraintName(), typeof(cva)) ||
929-
MOI.get(dest, MOI.ConstraintName(), dict[cva]) == ""
931+
MOI.get(dest, MOI.ConstraintName(), dict[cva]) == dest_name("cva")
930932
@test MOI.get(dest, MOI.ConstraintFunction(), dict[cva])
931933
MOI.VectorAffineFunction(
932934
MOI.VectorAffineTerm.(

src/Utilities/cachingoptimizer.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ function _standardize(d::IndexMap)
207207
end
208208

209209
function MOI.copy_to(m::CachingOptimizer, src::MOI.ModelLike; kws...)
210-
return automatic_copy_to(m, src; kws...)
210+
m.state == ATTACHED_OPTIMIZER && reset_optimizer(m)
211+
return MOI.copy_to(m.model_cache, src; kws...)
211212
end
212213
function supports_default_copy_to(model::CachingOptimizer, copy_names::Bool)
213214
return supports_default_copy_to(model.model_cache, copy_names)

src/Utilities/copy.jl

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,62 @@ function copy_constraints(
422422
end
423423
end
424424

425+
function pass_nonvariable_constraints_fallback(
426+
dest::MOI.ModelLike,
427+
src::MOI.ModelLike,
428+
idxmap::IndexMap,
429+
constraint_types,
430+
pass_cons = copy_constraints;
431+
filter_constraints::Union{Nothing,Function} = nothing,
432+
)
433+
for (F, S) in constraint_types
434+
cis_src = MOI.get(src, MOI.ListOfConstraintIndices{F,S}())
435+
if filter_constraints !== nothing
436+
filter!(filter_constraints, cis_src)
437+
end
438+
# do the rest in `pass_cons` which is type stable
439+
pass_cons(dest, src, idxmap, cis_src)
440+
end
441+
end
442+
443+
"""
444+
pass_nonvariable_constraints(
445+
dest::MOI.ModelLike,
446+
src::MOI.ModelLike,
447+
idxmap::IndexMap,
448+
constraint_types,
449+
pass_cons = copy_constraints;
450+
filter_constraints::Union{Nothing,Function} = nothing,
451+
)
452+
453+
For all tuples `(F, S)` in `constraint_types`, copy all constraints of type
454+
`F`-in-`S` from `src` to `dest` mapping the variables indices with `idxmap`.
455+
If `filter_constraints` is not nothing, only indices `ci` such that
456+
`filter_constraints(ci)` is true are copied.
457+
458+
The default implementation calls `pass_nonvariable_constraints_fallback` which
459+
copies the constraints with `pass_cons` and their attributes with `pass_attr`.
460+
A method can be implemented to use a specialized copy for a given type of
461+
`dest`.
462+
"""
463+
function pass_nonvariable_constraints(
464+
dest::MOI.ModelLike,
465+
src::MOI.ModelLike,
466+
idxmap::IndexMap,
467+
constraint_types,
468+
pass_cons = copy_constraints;
469+
filter_constraints::Union{Nothing,Function} = nothing,
470+
)
471+
return pass_nonvariable_constraints_fallback(
472+
dest,
473+
src,
474+
idxmap,
475+
constraint_types,
476+
pass_cons;
477+
filter_constraints = filter_constraints,
478+
)
479+
end
480+
425481
function pass_constraints(
426482
dest::MOI.ModelLike,
427483
src::MOI.ModelLike,
@@ -473,13 +529,20 @@ function pass_constraints(
473529
(F, S) for (F, S) in MOI.get(src, MOI.ListOfConstraints()) if
474530
F != MOI.SingleVariable && F != MOI.VectorOfVariables
475531
]
532+
pass_nonvariable_constraints(
533+
dest,
534+
src,
535+
idxmap,
536+
nonvariable_constraint_types,
537+
pass_cons;
538+
filter_constraints = filter_constraints,
539+
)
476540
for (F, S) in nonvariable_constraint_types
477541
cis_src = MOI.get(src, MOI.ListOfConstraintIndices{F,S}())
478542
if filter_constraints !== nothing
479543
filter!(filter_constraints, cis_src)
480544
end
481545
# do the rest in `pass_cons` which is type stable
482-
pass_cons(dest, src, idxmap, cis_src)
483546
pass_attributes(dest, src, copy_names, idxmap, cis_src, pass_attr)
484547
end
485548
end

src/Utilities/model.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,24 @@ function MOI.empty!(model::AbstractModel{T}) where {T}
752752
return
753753
end
754754

755+
function pass_nonvariable_constraints(
756+
dest::AbstractModel,
757+
src::MOI.ModelLike,
758+
idxmap::IndexMap,
759+
constraint_types,
760+
pass_cons;
761+
filter_constraints::Union{Nothing,Function} = nothing,
762+
)
763+
return pass_nonvariable_constraints(
764+
dest.constraints,
765+
src,
766+
idxmap,
767+
constraint_types,
768+
pass_cons;
769+
filter_constraints = filter_constraints,
770+
)
771+
end
772+
755773
function MOI.copy_to(dest::AbstractModel, src::MOI.ModelLike; kws...)
756774
return automatic_copy_to(dest, src; kws...)
757775
end

src/Utilities/universalfallback.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,41 @@ function MOI.empty!(uf::UniversalFallback)
8686
return
8787
end
8888

89+
function pass_nonvariable_constraints(
90+
dest::UniversalFallback,
91+
src::MOI.ModelLike,
92+
idxmap::IndexMap,
93+
constraint_types,
94+
pass_cons;
95+
filter_constraints::Union{Nothing,Function} = nothing,
96+
)
97+
supported_types = eltype(constraint_types)[]
98+
unsupported_types = eltype(constraint_types)[]
99+
for (F, S) in constraint_types
100+
if MOI.supports_constraint(dest.model, F, S)
101+
push!(supported_types, (F, S))
102+
else
103+
push!(unsupported_types, (F, S))
104+
end
105+
end
106+
pass_nonvariable_constraints(
107+
dest.model,
108+
src,
109+
idxmap,
110+
supported_types,
111+
pass_cons;
112+
filter_constraints = filter_constraints,
113+
)
114+
return pass_nonvariable_constraints_fallback(
115+
dest,
116+
src,
117+
idxmap,
118+
unsupported_types,
119+
pass_cons;
120+
filter_constraints = filter_constraints,
121+
)
122+
end
123+
89124
function MOI.copy_to(uf::UniversalFallback, src::MOI.ModelLike; kws...)
90125
return MOIU.automatic_copy_to(uf, src; kws...)
91126
end

test/Utilities/copy.jl

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ end
4545
MOIT.failcopytestia(model)
4646
MOIT.failcopytestva(model)
4747
MOIT.failcopytestca(model)
48-
MOIT.copytest(model, MOIU.Model{Float64}())
48+
MOIT.copytest(model, MOIU.Model{Float64}(), copy_names = false)
49+
MOIT.copytest(model, MOIU.Model{Float64}(), copy_names = true)
4950
end
5051
@testset "Allocate-Load" begin
5152
@test !MOIU.supports_allocate_load(DummyModel(), false)
@@ -55,7 +56,8 @@ end
5556
MOIT.failcopytestia(mock)
5657
MOIT.failcopytestva(mock)
5758
MOIT.failcopytestca(mock)
58-
MOIT.copytest(mock, MOIU.Model{Float64}())
59+
MOIT.copytest(mock, MOIU.Model{Float64}(), copy_names = false)
60+
MOIT.copytest(mock, MOIU.Model{Float64}(), copy_names = true)
5961
end
6062

6163
struct DummyEvaluator <: MOI.AbstractNLPEvaluator end
@@ -620,3 +622,73 @@ end
620622
MOI.NumberOfConstraints{MOI.SingleVariable,MOI.Integer}(),
621623
) == 0
622624
end
625+
626+
# We create a `OnlyCopyConstraints` that don't implement `add_constraint` but
627+
# implements `pass_nonvariable_constraints` to check that this is passed accross
628+
# all layers without falling back to `pass_nonvariable_constraints_fallback`
629+
# which calls `add_constraint`.
630+
631+
struct OnlyCopyConstraints{F,S} <: MOI.ModelLike
632+
constraints::MOIU.VectorOfConstraints{F,S}
633+
function OnlyCopyConstraints{F,S}() where {F,S}
634+
return new{F,S}(MOIU.VectorOfConstraints{F,S}())
635+
end
636+
end
637+
MOI.empty!(model::OnlyCopyConstraints) = MOI.empty!(model.constraints)
638+
function MOI.supports_constraint(
639+
model::OnlyCopyConstraints,
640+
F::Type{<:MOI.AbstractFunction},
641+
S::Type{<:MOI.AbstractSet},
642+
)
643+
return MOI.supports_constraint(model.constraints, F, S)
644+
end
645+
function MOIU.pass_nonvariable_constraints(
646+
dest::OnlyCopyConstraints,
647+
src::MOI.ModelLike,
648+
idxmap::MOIU.IndexMap,
649+
constraint_types,
650+
pass_cons;
651+
filter_constraints::Union{Nothing,Function} = nothing,
652+
)
653+
return MOIU.pass_nonvariable_constraints(
654+
dest.constraints,
655+
src,
656+
idxmap,
657+
constraint_types,
658+
pass_cons;
659+
filter_constraints = filter_constraints,
660+
)
661+
end
662+
663+
function test_pass_copy(::Type{T}) where {T}
664+
F = MOI.ScalarAffineFunction{T}
665+
S = MOI.EqualTo{T}
666+
S2 = MOI.GreaterThan{T}
667+
src = MOIU.Model{T}()
668+
x = MOI.add_variable(src)
669+
fx = MOI.SingleVariable(x)
670+
MOI.add_constraint(src, T(1) * fx, MOI.EqualTo(T(1)))
671+
MOI.add_constraint(src, T(2) * fx, MOI.EqualTo(T(2)))
672+
MOI.add_constraint(src, T(3) * fx, MOI.GreaterThan(T(3)))
673+
MOI.add_constraint(src, T(4) * fx, MOI.GreaterThan(T(4)))
674+
dest = MOIU.CachingOptimizer(
675+
MOI.Bridges.full_bridge_optimizer(
676+
MOIU.UniversalFallback(
677+
MOIU.GenericOptimizer{T,OnlyCopyConstraints{F,S}}(),
678+
),
679+
T,
680+
),
681+
MOIU.AUTOMATIC,
682+
)
683+
MOI.copy_to(dest, src)
684+
voc = dest.model_cache.model.model.constraints.constraints
685+
@test MOI.get(voc, MOI.NumberOfConstraints{F,S}()) == 2
686+
@test !haskey(dest.model_cache.model.constraints, (F, S))
687+
@test MOI.get(dest, MOI.NumberOfConstraints{F,S2}()) == 2
688+
@test haskey(dest.model_cache.model.constraints, (F, S2))
689+
end
690+
691+
@testset "copy of constraints passed as copy accross layers" begin
692+
test_pass_copy(Int)
693+
test_pass_copy(Float64)
694+
end

0 commit comments

Comments
 (0)