Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/Bridges/bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,41 @@ function MOI.supports(
return MOI.supports(b.model, attr)
end

function MOIU.pass_nonvariable_constraints(
dest::AbstractBridgeOptimizer,
src::MOI.ModelLike,
idxmap::MOIU.IndexMap,
constraint_types,
pass_cons;
filter_constraints::Union{Nothing,Function} = nothing,
)
not_bridged_types = eltype(constraint_types)[]
bridged_types = eltype(constraint_types)[]
for (F, S) in constraint_types
if is_bridged(dest, F, S)
push!(bridged_types, (F, S))
else
push!(not_bridged_types, (F, S))
end
end
MOIU.pass_nonvariable_constraints(
dest.model,
src,
idxmap,
not_bridged_types,
pass_cons;
filter_constraints = filter_constraints,
)
return MOIU.pass_nonvariable_constraints_fallback(
dest,
src,
idxmap,
bridged_types,
pass_cons;
filter_constraints = filter_constraints,
)
end

function MOI.copy_to(mock::AbstractBridgeOptimizer, src::MOI.ModelLike; kws...)
return MOIU.automatic_copy_to(mock, src; kws...)
end
Expand Down
20 changes: 11 additions & 9 deletions src/Test/modellike.jl
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,7 @@ function start_values_test(dest::MOI.ModelLike, src::MOI.ModelLike)
end
end

function copytest(dest::MOI.ModelLike, src::MOI.ModelLike)
function copytest(dest::MOI.ModelLike, src::MOI.ModelLike; copy_names = false)
@test MOIU.supports_default_copy_to(src, true) #=copy_names=#
MOI.empty!(src)
MOI.empty!(dest)
Expand Down Expand Up @@ -850,13 +850,15 @@ function copytest(dest::MOI.ModelLike, src::MOI.ModelLike)
MOI.Zeros,
)

dict = MOI.copy_to(dest, src, copy_names = false)
dict = MOI.copy_to(dest, src, copy_names = copy_names)

@test !MOI.supports(dest, MOI.Name()) || MOI.get(dest, MOI.Name()) == ""
dest_name(src_name) = copy_names ? src_name : ""
@test !MOI.supports(dest, MOI.Name()) ||
MOI.get(dest, MOI.Name()) == dest_name("ModelName")
@test MOI.get(dest, MOI.NumberOfVariables()) == 4
if MOI.supports(dest, MOI.VariableName(), MOI.VariableIndex)
for vi in v
MOI.get(dest, MOI.VariableName(), dict[vi]) == ""
for i in eachindex(v)
MOI.get(dest, MOI.VariableName(), dict[v[i]]) == dest_name("var$i")
end
end
@test MOI.get(
Expand Down Expand Up @@ -908,25 +910,25 @@ function copytest(dest::MOI.ModelLike, src::MOI.ModelLike)
@test (MOI.VectorAffineFunction{Float64}, MOI.Zeros) in loc

@test !MOI.supports(dest, MOI.ConstraintName(), typeof(csv)) ||
MOI.get(dest, MOI.ConstraintName(), dict[csv]) == ""
MOI.get(dest, MOI.ConstraintName(), dict[csv]) == dest_name("csv")
@test MOI.get(dest, MOI.ConstraintFunction(), dict[csv]) ==
MOI.SingleVariable(dict[w])
@test MOI.get(dest, MOI.ConstraintSet(), dict[csv]) == MOI.EqualTo(2.0)
@test !MOI.supports(dest, MOI.ConstraintName(), typeof(cvv)) ||
MOI.get(dest, MOI.ConstraintName(), dict[cvv]) == ""
MOI.get(dest, MOI.ConstraintName(), dict[cvv]) == dest_name("cvv")
@test MOI.get(dest, MOI.ConstraintFunction(), dict[cvv]) ==
MOI.VectorOfVariables(getindex.(Ref(dict), v))
@test MOI.get(dest, MOI.ConstraintSet(), dict[cvv]) == MOI.Nonnegatives(3)
@test !MOI.supports(dest, MOI.ConstraintName(), typeof(csa)) ||
MOI.get(dest, MOI.ConstraintName(), dict[csa]) == ""
MOI.get(dest, MOI.ConstraintName(), dict[csa]) == dest_name("csa")
@test MOI.get(dest, MOI.ConstraintFunction(), dict[csa]) ≈
MOI.ScalarAffineFunction(
MOI.ScalarAffineTerm.([1.0, 3.0], [dict[v[3]], dict[v[1]]]),
0.0,
)
@test MOI.get(dest, MOI.ConstraintSet(), dict[csa]) == MOI.LessThan(2.0)
@test !MOI.supports(dest, MOI.ConstraintName(), typeof(cva)) ||
MOI.get(dest, MOI.ConstraintName(), dict[cva]) == ""
MOI.get(dest, MOI.ConstraintName(), dict[cva]) == dest_name("cva")
@test MOI.get(dest, MOI.ConstraintFunction(), dict[cva]) ≈
MOI.VectorAffineFunction(
MOI.VectorAffineTerm.(
Expand Down
3 changes: 2 additions & 1 deletion src/Utilities/cachingoptimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ function _standardize(d::IndexMap)
end

function MOI.copy_to(m::CachingOptimizer, src::MOI.ModelLike; kws...)
return automatic_copy_to(m, src; kws...)
m.state == ATTACHED_OPTIMIZER && reset_optimizer(m)
return MOI.copy_to(m.model_cache, src; kws...)
end
function supports_default_copy_to(model::CachingOptimizer, copy_names::Bool)
return supports_default_copy_to(model.model_cache, copy_names)
Expand Down
65 changes: 64 additions & 1 deletion src/Utilities/copy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,62 @@ function copy_constraints(
end
end

function pass_nonvariable_constraints_fallback(
dest::MOI.ModelLike,
src::MOI.ModelLike,
idxmap::IndexMap,
constraint_types,
pass_cons = copy_constraints;
filter_constraints::Union{Nothing,Function} = nothing,
)
for (F, S) in constraint_types
cis_src = MOI.get(src, MOI.ListOfConstraintIndices{F,S}())
if filter_constraints !== nothing
filter!(filter_constraints, cis_src)
end
# do the rest in `pass_cons` which is type stable
pass_cons(dest, src, idxmap, cis_src)
end
end

"""
pass_nonvariable_constraints(
dest::MOI.ModelLike,
src::MOI.ModelLike,
idxmap::IndexMap,
constraint_types,
pass_cons = copy_constraints;
filter_constraints::Union{Nothing,Function} = nothing,
)

For all tuples `(F, S)` in `constraint_types`, copy all constraints of type
`F`-in-`S` from `src` to `dest` mapping the variables indices with `idxmap`.
If `filter_constraints` is not nothing, only indices `ci` such that
`filter_constraints(ci)` is true are copied.

The default implementation calls `pass_nonvariable_constraints_fallback` which
copies the constraints with `pass_cons` and their attributes with `pass_attr`.
A method can be implemented to use a specialized copy for a given type of
`dest`.
"""
function pass_nonvariable_constraints(
dest::MOI.ModelLike,
src::MOI.ModelLike,
idxmap::IndexMap,
constraint_types,
pass_cons = copy_constraints;
filter_constraints::Union{Nothing,Function} = nothing,
)
return pass_nonvariable_constraints_fallback(
dest,
src,
idxmap,
constraint_types,
pass_cons;
filter_constraints = filter_constraints,
)
end

function pass_constraints(
dest::MOI.ModelLike,
src::MOI.ModelLike,
Expand Down Expand Up @@ -473,13 +529,20 @@ function pass_constraints(
(F, S) for (F, S) in MOI.get(src, MOI.ListOfConstraints()) if
F != MOI.SingleVariable && F != MOI.VectorOfVariables
]
pass_nonvariable_constraints(
dest,
src,
idxmap,
nonvariable_constraint_types,
pass_cons;
filter_constraints = filter_constraints,
)
for (F, S) in nonvariable_constraint_types
cis_src = MOI.get(src, MOI.ListOfConstraintIndices{F,S}())
if filter_constraints !== nothing
filter!(filter_constraints, cis_src)
end
# do the rest in `pass_cons` which is type stable
pass_cons(dest, src, idxmap, cis_src)
pass_attributes(dest, src, copy_names, idxmap, cis_src, pass_attr)
end
end
Expand Down
18 changes: 18 additions & 0 deletions src/Utilities/model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,24 @@ function MOI.empty!(model::AbstractModel{T}) where {T}
return
end

function pass_nonvariable_constraints(
dest::AbstractModel,
src::MOI.ModelLike,
idxmap::IndexMap,
constraint_types,
pass_cons;
filter_constraints::Union{Nothing,Function} = nothing,
)
return pass_nonvariable_constraints(
dest.constraints,
src,
idxmap,
constraint_types,
pass_cons;
filter_constraints = filter_constraints,
)
end

function MOI.copy_to(dest::AbstractModel, src::MOI.ModelLike; kws...)
return automatic_copy_to(dest, src; kws...)
end
Expand Down
35 changes: 35 additions & 0 deletions src/Utilities/universalfallback.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,41 @@ function MOI.empty!(uf::UniversalFallback)
return
end

function pass_nonvariable_constraints(
dest::UniversalFallback,
src::MOI.ModelLike,
idxmap::IndexMap,
constraint_types,
pass_cons;
filter_constraints::Union{Nothing,Function} = nothing,
)
supported_types = eltype(constraint_types)[]
unsupported_types = eltype(constraint_types)[]
for (F, S) in constraint_types
if MOI.supports_constraint(dest.model, F, S)
push!(supported_types, (F, S))
else
push!(unsupported_types, (F, S))
end
end
pass_nonvariable_constraints(
dest.model,
src,
idxmap,
supported_types,
pass_cons;
filter_constraints = filter_constraints,
)
return pass_nonvariable_constraints_fallback(
dest,
src,
idxmap,
unsupported_types,
pass_cons;
filter_constraints = filter_constraints,
)
end

function MOI.copy_to(uf::UniversalFallback, src::MOI.ModelLike; kws...)
return MOIU.automatic_copy_to(uf, src; kws...)
end
Expand Down
76 changes: 74 additions & 2 deletions test/Utilities/copy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ end
MOIT.failcopytestia(model)
MOIT.failcopytestva(model)
MOIT.failcopytestca(model)
MOIT.copytest(model, MOIU.Model{Float64}())
MOIT.copytest(model, MOIU.Model{Float64}(), copy_names = false)
MOIT.copytest(model, MOIU.Model{Float64}(), copy_names = true)
end
@testset "Allocate-Load" begin
@test !MOIU.supports_allocate_load(DummyModel(), false)
Expand All @@ -55,7 +56,8 @@ end
MOIT.failcopytestia(mock)
MOIT.failcopytestva(mock)
MOIT.failcopytestca(mock)
MOIT.copytest(mock, MOIU.Model{Float64}())
MOIT.copytest(mock, MOIU.Model{Float64}(), copy_names = false)
MOIT.copytest(mock, MOIU.Model{Float64}(), copy_names = true)
end

struct DummyEvaluator <: MOI.AbstractNLPEvaluator end
Expand Down Expand Up @@ -620,3 +622,73 @@ end
MOI.NumberOfConstraints{MOI.SingleVariable,MOI.Integer}(),
) == 0
end

# We create a `OnlyCopyConstraints` that don't implement `add_constraint` but
# implements `pass_nonvariable_constraints` to check that this is passed accross
# all layers without falling back to `pass_nonvariable_constraints_fallback`
# which calls `add_constraint`.

struct OnlyCopyConstraints{F,S} <: MOI.ModelLike
constraints::MOIU.VectorOfConstraints{F,S}
function OnlyCopyConstraints{F,S}() where {F,S}
return new{F,S}(MOIU.VectorOfConstraints{F,S}())
end
end
MOI.empty!(model::OnlyCopyConstraints) = MOI.empty!(model.constraints)
function MOI.supports_constraint(
model::OnlyCopyConstraints,
F::Type{<:MOI.AbstractFunction},
S::Type{<:MOI.AbstractSet},
)
return MOI.supports_constraint(model.constraints, F, S)
end
function MOIU.pass_nonvariable_constraints(
dest::OnlyCopyConstraints,
src::MOI.ModelLike,
idxmap::MOIU.IndexMap,
constraint_types,
pass_cons;
filter_constraints::Union{Nothing,Function} = nothing,
)
return MOIU.pass_nonvariable_constraints(
dest.constraints,
src,
idxmap,
constraint_types,
pass_cons;
filter_constraints = filter_constraints,
)
end

function test_pass_copy(::Type{T}) where {T}
F = MOI.ScalarAffineFunction{T}
S = MOI.EqualTo{T}
S2 = MOI.GreaterThan{T}
src = MOIU.Model{T}()
x = MOI.add_variable(src)
fx = MOI.SingleVariable(x)
MOI.add_constraint(src, T(1) * fx, MOI.EqualTo(T(1)))
MOI.add_constraint(src, T(2) * fx, MOI.EqualTo(T(2)))
MOI.add_constraint(src, T(3) * fx, MOI.GreaterThan(T(3)))
MOI.add_constraint(src, T(4) * fx, MOI.GreaterThan(T(4)))
dest = MOIU.CachingOptimizer(
MOI.Bridges.full_bridge_optimizer(
MOIU.UniversalFallback(
MOIU.GenericOptimizer{T,OnlyCopyConstraints{F,S}}(),
),
T,
),
MOIU.AUTOMATIC,
)
MOI.copy_to(dest, src)
voc = dest.model_cache.model.model.constraints.constraints
@test MOI.get(voc, MOI.NumberOfConstraints{F,S}()) == 2
@test !haskey(dest.model_cache.model.constraints, (F, S))
@test MOI.get(dest, MOI.NumberOfConstraints{F,S2}()) == 2
@test haskey(dest.model_cache.model.constraints, (F, S2))
end

@testset "copy of constraints passed as copy accross layers" begin
test_pass_copy(Int)
test_pass_copy(Float64)
end