diff --git a/docs/src/apireference.md b/docs/src/apireference.md index c9ff0f9e49..ee8ef286ce 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -662,6 +662,7 @@ Utilities.mode The following utilities are available for functions: ```@docs Utilities.eval_variables +Utilities.map_indices Utilities.substitute_variables Utilities.remove_variable Utilities.all_coefficients diff --git a/src/Bridges/bridge_optimizer.jl b/src/Bridges/bridge_optimizer.jl index a4ac0bafbd..5476d1d08b 100644 --- a/src/Bridges/bridge_optimizer.jl +++ b/src/Bridges/bridge_optimizer.jl @@ -933,7 +933,7 @@ function bridged_function(b::AbstractBridgeOptimizer, end # Shortcut to avoid `Variable.throw_if_cannot_unbridge(Variable.bridges(b))` function bridge_function( - ::AbstractBridgeOptimizer, value::MOIU.ObjectOrArrayWithoutIndex) + ::AbstractBridgeOptimizer, value::MOIU.ObjectOrTupleOrArrayWithoutIndex) return value end @@ -983,7 +983,7 @@ function unbridged_function(bridge::AbstractBridgeOptimizer, end # Shortcut to avoid `Variable.throw_if_cannot_unbridge(Variable.bridges(b))` function unbridged_function( - ::AbstractBridgeOptimizer, value::MOIU.ObjectOrArrayWithoutIndex) + ::AbstractBridgeOptimizer, value::MOIU.ObjectOrTupleOrArrayWithoutIndex) return value end diff --git a/src/Utilities/cachingoptimizer.jl b/src/Utilities/cachingoptimizer.jl index 29bcb3b300..b3a287feff 100644 --- a/src/Utilities/cachingoptimizer.jl +++ b/src/Utilities/cachingoptimizer.jl @@ -90,7 +90,7 @@ function reset_optimizer(m::CachingOptimizer, optimizer::MOI.AbstractOptimizer) m.state = EMPTY_OPTIMIZER for attr in MOI.get(m.model_cache, MOI.ListOfOptimizerAttributesSet()) value = MOI.get(m.model_cache, attr) - optimizer_value = attribute_value_map(m.model_to_optimizer_map, value) + optimizer_value = map_indices(m.model_to_optimizer_map, value) MOI.set(m.optimizer, attr, optimizer_value) end return @@ -236,7 +236,7 @@ function MOI.add_constraint(m::CachingOptimizer, func::MOI.AbstractFunction, set if m.state == ATTACHED_OPTIMIZER if m.mode == AUTOMATIC try - cindex_optimizer = MOI.add_constraint(m.optimizer, mapvariables(m.model_to_optimizer_map, func), set) + cindex_optimizer = MOI.add_constraint(m.optimizer, map_indices(m.model_to_optimizer_map, func), set) catch err if err isa MOI.NotAllowedError # It could be MOI.AddConstraintNotAllowed{F', S'} with F' != F @@ -248,7 +248,7 @@ function MOI.add_constraint(m::CachingOptimizer, func::MOI.AbstractFunction, set end end else - cindex_optimizer = MOI.add_constraint(m.optimizer, mapvariables(m.model_to_optimizer_map, func), set) + cindex_optimizer = MOI.add_constraint(m.optimizer, map_indices(m.model_to_optimizer_map, func), set) end end cindex = MOI.add_constraint(m.model_cache, func, set) @@ -262,7 +262,7 @@ end function MOI.modify(m::CachingOptimizer, cindex::CI, change::MOI.AbstractFunctionModification) if m.state == ATTACHED_OPTIMIZER cindex_optimizer = m.model_to_optimizer_map[cindex] - change_optimizer = mapvariables(m.model_to_optimizer_map, change) + change_optimizer = map_indices(m.model_to_optimizer_map, change) if m.mode == AUTOMATIC try MOI.modify(m.optimizer, cindex_optimizer, change_optimizer) @@ -285,10 +285,11 @@ end # the third and fourth arguments of the set methods so that we only support # setting the same type of set or function. function replace_constraint_function_or_set(m::CachingOptimizer, attr, cindex, replacement) + replacement_optimizer = map_indices(m.model_to_optimizer_map, replacement) if m.state == ATTACHED_OPTIMIZER if m.mode == AUTOMATIC try - MOI.set(m.optimizer, attr, m.model_to_optimizer_map[cindex], replacement) + MOI.set(m.optimizer, attr, m.model_to_optimizer_map[cindex], replacement_optimizer) catch err if err isa MOI.NotAllowedError reset_optimizer(m) @@ -297,7 +298,7 @@ function replace_constraint_function_or_set(m::CachingOptimizer, attr, cindex, r end end else - MOI.set(m.optimizer, attr, m.model_to_optimizer_map[cindex], replacement) + MOI.set(m.optimizer, attr, m.model_to_optimizer_map[cindex], replacement_optimizer) end end MOI.set(m.model_cache, attr, cindex, replacement) @@ -307,13 +308,13 @@ function MOI.set(m::CachingOptimizer, ::MOI.ConstraintSet, cindex::CI{F,S}, set: replace_constraint_function_or_set(m, MOI.ConstraintSet(), cindex, set) end -function MOI.set(m::CachingOptimizer, ::MOI.ConstraintFunction, cindex::CI{F,S}, func::F) where {F,S} +function MOI.set(m::CachingOptimizer, ::MOI.ConstraintFunction, cindex::CI{F}, func::F) where F replace_constraint_function_or_set(m, MOI.ConstraintFunction(), cindex, func) end function MOI.modify(m::CachingOptimizer, obj::MOI.ObjectiveFunction, change::MOI.AbstractFunctionModification) if m.state == ATTACHED_OPTIMIZER - change_optimizer = mapvariables(m.model_to_optimizer_map, change) + change_optimizer = map_indices(m.model_to_optimizer_map, change) if m.mode == AUTOMATIC try MOI.modify(m.optimizer, obj, change_optimizer) @@ -368,16 +369,13 @@ end ## CachingOptimizer get and set attributes -# Attributes are mapped through attribute_value_map (defined in copy.jl) before +# Attributes are mapped through `map_indices` (defined in functions.jl) before # they are sent to the optimizer and when they are returned from the optimizer. -# This map currently only translates indices on MOI.AbstractFunction objects -# between the optimizer indices and the (user-facing) model_cache indices. As a result, -# all MOI.AbstractFunctions must implement mapvariables. Other attributes that -# store indices need to be handled with care. +# As a result, values of attributes must implement `map_indices`. function MOI.set(m::CachingOptimizer, attr::MOI.AbstractModelAttribute, value) if m.state == ATTACHED_OPTIMIZER - optimizer_value = attribute_value_map(m.model_to_optimizer_map, value) + optimizer_value = map_indices(m.model_to_optimizer_map, value) if m.mode == AUTOMATIC try MOI.set(m.optimizer, attr, optimizer_value) @@ -401,7 +399,7 @@ function MOI.set(m::CachingOptimizer, index::MOI.Index, value) if m.state == ATTACHED_OPTIMIZER optimizer_index = m.model_to_optimizer_map[index] - optimizer_value = attribute_value_map(m.model_to_optimizer_map, value) + optimizer_value = map_indices(m.model_to_optimizer_map, value) if m.mode == AUTOMATIC try MOI.set(m.optimizer, attr, optimizer_index, optimizer_value) @@ -448,8 +446,8 @@ function MOI.get(model::CachingOptimizer, attr::MOI.AbstractModelAttribute) " optimizer is attached.") end end - return attribute_value_map(model.optimizer_to_model_map, - MOI.get(model.optimizer, attr)) + return map_indices(model.optimizer_to_model_map, + MOI.get(model.optimizer, attr)) else return MOI.get(model.model_cache, attr) end @@ -463,9 +461,9 @@ function MOI.get(model::CachingOptimizer, error("Cannot query $(attr) from caching optimizer because no " * "optimizer is attached.") end - return attribute_value_map(model.optimizer_to_model_map, - MOI.get(model.optimizer, attr, - model.model_to_optimizer_map[index])) + return map_indices(model.optimizer_to_model_map, + MOI.get(model.optimizer, attr, + model.model_to_optimizer_map[index])) else return MOI.get(model.model_cache, attr, index) end @@ -479,10 +477,10 @@ function MOI.get(model::CachingOptimizer, error("Cannot query $(attr) from caching optimizer because no " * "optimizer is attached.") end - return attribute_value_map(model.optimizer_to_model_map, - MOI.get(model.optimizer, attr, - map(index -> model.model_to_optimizer_map[index], - indices))) + return map_indices(model.optimizer_to_model_map, + MOI.get(model.optimizer, attr, + map(index -> model.model_to_optimizer_map[index], + indices))) else return MOI.get(model.model_cache, attr, indices) end @@ -521,7 +519,7 @@ end function MOI.set(model::CachingOptimizer, attr::MOI.AbstractOptimizerAttribute, value) - optimizer_value = attribute_value_map(model.model_to_optimizer_map, value) + optimizer_value = map_indices(model.model_to_optimizer_map, value) if model.optimizer !== nothing MOI.set(model.optimizer, attr, optimizer_value) end @@ -537,8 +535,8 @@ function MOI.get(model::CachingOptimizer, attr::MOI.AbstractOptimizerAttribute) error("Cannot query $(attr) from caching optimizer because no " * "optimizer is attached.") end - return attribute_value_map(model.optimizer_to_model_map, - MOI.get(model.optimizer, attr)) + return map_indices(model.optimizer_to_model_map, + MOI.get(model.optimizer, attr)) end # Force users to specify whether the attribute should be queried from the @@ -566,17 +564,17 @@ end function MOI.get(m::CachingOptimizer, attr::AttributeFromOptimizer{T}) where {T <: MOI.AbstractModelAttribute} @assert m.state == ATTACHED_OPTIMIZER - return attribute_value_map(m.optimizer_to_model_map,MOI.get(m.optimizer, attr.attr)) + return map_indices(m.optimizer_to_model_map,MOI.get(m.optimizer, attr.attr)) end function MOI.get(m::CachingOptimizer, attr::AttributeFromOptimizer{T}, idx::MOI.Index) where {T <: Union{MOI.AbstractVariableAttribute,MOI.AbstractConstraintAttribute}} @assert m.state == ATTACHED_OPTIMIZER - return attribute_value_map(m.optimizer_to_model_map,MOI.get(m.optimizer, attr.attr, m.model_to_optimizer_map[idx])) + return map_indices(m.optimizer_to_model_map,MOI.get(m.optimizer, attr.attr, m.model_to_optimizer_map[idx])) end function MOI.get(m::CachingOptimizer, attr::AttributeFromOptimizer{T}, idx::Vector{<:MOI.Index}) where {T <: Union{MOI.AbstractVariableAttribute,MOI.AbstractConstraintAttribute}} @assert m.state == ATTACHED_OPTIMIZER - return attribute_value_map(m.optimizer_to_model_map,MOI.get(m.optimizer, attr.attr, getindex.(m.model_to_optimizer_map,idx))) + return map_indices(m.optimizer_to_model_map,MOI.get(m.optimizer, attr.attr, getindex.(m.model_to_optimizer_map,idx))) end function MOI.set(m::CachingOptimizer, attr::AttributeFromModelCache{T}, v) where {T <: MOI.AbstractModelAttribute} @@ -589,7 +587,7 @@ end function MOI.set(m::CachingOptimizer, attr::AttributeFromOptimizer{T}, v) where {T <: MOI.AbstractModelAttribute} @assert m.state == ATTACHED_OPTIMIZER - return MOI.set(m.optimizer, attr.attr, attribute_value_map(m.model_to_optimizer_map,v)) + return MOI.set(m.optimizer, attr.attr, map_indices(m.model_to_optimizer_map, v)) end # Map vector of indices into vector of indices or one index into one index @@ -597,7 +595,7 @@ map_indices_to_optimizer(m::CachingOptimizer, idx::MOI.Index) = m.model_to_optim map_indices_to_optimizer(m::CachingOptimizer, indices::Vector{<:MOI.Index}) = getindex.(Ref(m.model_to_optimizer_map), indices) function MOI.set(m::CachingOptimizer, attr::AttributeFromOptimizer{T}, idx, v) where {T <: Union{MOI.AbstractVariableAttribute,MOI.AbstractConstraintAttribute}} @assert m.state == ATTACHED_OPTIMIZER - return MOI.set(m.optimizer, attr.attr, map_indices_to_optimizer(m, idx), attribute_value_map(m.model_to_optimizer_map,v)) + return MOI.set(m.optimizer, attr.attr, map_indices_to_optimizer(m, idx), map_indices(m.model_to_optimizer_map, v)) end function MOI.supports(m::CachingOptimizer, attr::AttributeFromModelCache{T}) where {T <: MOI.AbstractModelAttribute} diff --git a/src/Utilities/copy.jl b/src/Utilities/copy.jl index b56d1c55ff..35d62d1738 100644 --- a/src/Utilities/copy.jl +++ b/src/Utilities/copy.jl @@ -63,7 +63,7 @@ error in case `copy_to` is called with `copy_names` equal to `true`. """ supports_default_copy_to(model::MOI.ModelLike, copy_names::Bool) = false -struct IndexMap +struct IndexMap <: AbstractDict{MOI.Index, MOI.Index} varmap::Dict{MOI.VariableIndex, MOI.VariableIndex} conmap::Dict{MOI.ConstraintIndex, MOI.ConstraintIndex} end @@ -141,7 +141,7 @@ function _pass_attributes(dest::MOI.ModelLike, src::MOI.ModelLike, end value = MOI.get(src, attr, get_args...) if value !== nothing - mapped_value = attribute_value_map(idxmap, value) + mapped_value = map_indices(idxmap, value) pass_attr!(dest, attr, set_args..., mapped_value) end end @@ -220,7 +220,7 @@ function copy_constraints(dest::MOI.ModelLike, src::MOI.ModelLike, idxmap::IndexMap, cis_src::Vector{<:MOI.ConstraintIndex}) f_src = MOI.get(src, MOI.ConstraintFunction(), cis_src) - f_dest = mapvariables.(Ref(idxmap), f_src) + f_dest = map_indices.(Ref(idxmap), f_src) s = MOI.get(src, MOI.ConstraintSet(), cis_src) cis_dest = MOI.add_constraints(dest, f_dest, s) for (ci_src, ci_dest) in zip(cis_src, cis_dest) @@ -264,8 +264,6 @@ function pass_constraints( end end -attribute_value_map(idxmap, f::MOI.AbstractFunction) = mapvariables(idxmap, f) -attribute_value_map(idxmap, attribute_value) = attribute_value function default_copy_to(dest::MOI.ModelLike, src::MOI.ModelLike) Base.depwarn("default_copy_to(dest, src) is deprecated, use default_copy_to(dest, src, true) instead or default_copy_to(dest, src, false) if you do not want to copy names.", :default_copy_to) default_copy_to(dest, src, true) @@ -600,7 +598,7 @@ function allocate_constraints(dest::MOI.ModelLike, src::MOI.ModelLike, for ci_src in cis_src f_src = MOI.get(src, MOI.ConstraintFunction(), ci_src) s = MOI.get(src, MOI.ConstraintSet(), ci_src) - f_dest = mapvariables(idxmap, f_src) + f_dest = map_indices(idxmap, f_src) ci_dest = allocate_constraint(dest, f_dest, s) idxmap.conmap[ci_src] = ci_dest end @@ -611,7 +609,7 @@ function load_constraints(dest::MOI.ModelLike, src::MOI.ModelLike, for ci_src in cis_src ci_dest = idxmap[ci_src] f_src = MOI.get(src, MOI.ConstraintFunction(), ci_src) - f_dest = mapvariables(idxmap, f_src) + f_dest = map_indices(idxmap, f_src) s = MOI.get(src, MOI.ConstraintSet(), ci_src) load_constraint(dest, ci_dest, f_dest, s) end diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index aed2f45ae1..788dc19011 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -15,78 +15,114 @@ function eval_variables end eval_variables(varval::Function, f::SVF) = varval(f.variable) eval_variables(varval::Function, f::VVF) = varval.(f.variables) function eval_variables(varval::Function, f::SAF) - return mapreduce(t->evalterm(varval, t), +, f.terms, init=f.constant) + return mapreduce(t->eval_term(varval, t), +, f.terms, init=f.constant) end function eval_variables(varval::Function, f::VAF) out = copy(f.constants) for t in f.terms - out[t.output_index] += evalterm(varval, t.scalar_term) + out[t.output_index] += eval_term(varval, t.scalar_term) end out end function eval_variables(varval::Function, f::SQF) init = zero(f.constant) - lin = mapreduce(t->evalterm(varval, t), +, f.affine_terms, init=init) - quad = mapreduce(t->evalterm(varval, t), +, f.quadratic_terms, init=init) + lin = mapreduce(t->eval_term(varval, t), +, f.affine_terms, init=init) + quad = mapreduce(t->eval_term(varval, t), +, f.quadratic_terms, init=init) return lin + quad + f.constant end function eval_variables(varval::Function, f::VQF) out = copy(f.constants) for t in f.affine_terms - out[t.output_index] += evalterm(varval, t.scalar_term) + out[t.output_index] += eval_term(varval, t.scalar_term) end for t in f.quadratic_terms - out[t.output_index] += evalterm(varval, t.scalar_term) + out[t.output_index] += eval_term(varval, t.scalar_term) end out end # Affine term -function evalterm(varval::Function, t::MOI.ScalarAffineTerm) +function eval_term(varval::Function, t::MOI.ScalarAffineTerm) return t.coefficient * varval(t.variable_index) end # Quadratic term -function evalterm(varval::Function, t::MOI.ScalarQuadraticTerm) +function eval_term(varval::Function, t::MOI.ScalarQuadraticTerm) tval = t.coefficient * varval(t.variable_index_1) * varval(t.variable_index_2) t.variable_index_1 == t.variable_index_2 ? tval/2 : tval end -function mapvariable(varmap::Function, t::MOI.ScalarAffineTerm) - return MOI.ScalarAffineTerm(t.coefficient, varmap(t.variable_index)) +""" + map_indices(index_map::Function, x) + +Substitute any [`MOI.VariableIndex`](@ref) (resp. [`MOI.ConstraintIndex`](@ref)) +in `x` by the [`MOI.VariableIndex`](@ref) (resp. [`MOI.ConstraintIndex`](@ref)) +of the same type given by `index_map(x)`. + +This function is used by implementations of [`MOI.copy_to`](@ref) on constraint +functions, attribute values and submittable values hence it needs to be +implemented for custom types that are meant to be used as attribute or +submittable value. +""" +function map_indices end + +""" + map_indices(variable_map::AbstractDict{T, T}, x) where {T <: MOI.Index} + +Shortcut for `map_indices(vi -> variable_map[vi], x)`. +""" +function map_indices(variable_map::AbstractDict{T, T}, x) where {T <: MOI.Index} + return map_indices(vi -> variable_map[vi], x) +end + +const ObjectWithoutIndex = Union{Nothing, DataType, Number, Enum, AbstractString, MOI.AnyAttribute, MOI.AbstractSet} +const ObjectOrTupleWithoutIndex = Union{ObjectWithoutIndex, Tuple{Vararg{ObjectWithoutIndex}}} +const ObjectOrTupleOrArrayWithoutIndex = Union{ObjectOrTupleWithoutIndex, AbstractArray{<:ObjectOrTupleWithoutIndex}} + +map_indices(::Function, x::ObjectOrTupleOrArrayWithoutIndex) = x + +map_indices(index_map::Function, vi::MOI.VariableIndex) = index_map(vi) +map_indices(index_map::Function, ci::MOI.ConstraintIndex) = index_map(ci) +function map_indices(index_map::Function, array::AbstractArray{<:MOI.Index}) + return map(index_map, array) +end + +# Terms +function map_indices(index_map::Function, t::MOI.ScalarAffineTerm) + return MOI.ScalarAffineTerm(t.coefficient, index_map(t.variable_index)) end -function mapvariable(varmap::Function, t::MOI.VectorAffineTerm) - return MOI.VectorAffineTerm(t.output_index, mapvariable(varmap, t.scalar_term)) +function map_indices(index_map::Function, t::MOI.VectorAffineTerm) + return MOI.VectorAffineTerm(t.output_index, map_indices(index_map, t.scalar_term)) end -function mapvariable(varmap::Function, t::MOI.ScalarQuadraticTerm) - inds = varmap.((t.variable_index_1, t.variable_index_2)) +function map_indices(index_map::Function, t::MOI.ScalarQuadraticTerm) + inds = index_map.((t.variable_index_1, t.variable_index_2)) return MOI.ScalarQuadraticTerm(t.coefficient, inds...) end -function mapvariable(varmap::Function, t::MOI.VectorQuadraticTerm) - MOI.VectorQuadraticTerm(t.output_index, mapvariable(varmap, t.scalar_term)) +function map_indices(index_map::Function, t::MOI.VectorQuadraticTerm) + MOI.VectorQuadraticTerm(t.output_index, map_indices(index_map, t.scalar_term)) end -function mapvariables(varmap::Function, f::MOI.SingleVariable) - return MOI.SingleVariable(varmap(f.variable)) + +# Functions +function map_indices(index_map::Function, f::MOI.SingleVariable) + return MOI.SingleVariable(index_map(f.variable)) end -function mapvariables(varmap::Function, f::MOI.VectorOfVariables) - return MOI.VectorOfVariables(varmap.(f.variables)) +function map_indices(index_map::Function, f::MOI.VectorOfVariables) + return MOI.VectorOfVariables(index_map.(f.variables)) end -function mapvariables(varmap::Function, f::Union{SAF, VAF}) - typeof(f)(mapvariable.(varmap, f.terms), MOI.constant(f)) +function map_indices(index_map::Function, f::Union{SAF, VAF}) + typeof(f)(map_indices.(index_map, f.terms), MOI.constant(f)) end -function mapvariables(varmap::Function, f::Union{SQF, VQF}) - lin = mapvariable.(varmap, f.affine_terms) - quad = mapvariable.(varmap, f.quadratic_terms) +function map_indices(index_map::Function, f::Union{SQF, VQF}) + lin = map_indices.(index_map, f.affine_terms) + quad = map_indices.(index_map, f.quadratic_terms) return typeof(f)(lin, quad, MOI.constant(f)) end -mapvariables(varmap, f::MOI.AbstractFunction) = mapvariables(vi -> varmap[vi], f) -mapvariables(varmap::Function, change::Union{MOI.ScalarConstantChange, MOI.VectorConstantChange}) = change -function mapvariables(varmap::Function, change::MOI.ScalarCoefficientChange) - return MOI.ScalarCoefficientChange(varmap(change.variable), change.new_coefficient) -end -function mapvariables(varmap::Function, change::MOI.MultirowChange) - return MOI.MultirowChange(varmap(change.variable), change.new_coefficients) + +# Function changes +map_indices(index_map::Function, change::Union{MOI.ScalarConstantChange, MOI.VectorConstantChange}) = change +function map_indices(index_map::Function, change::MOI.ScalarCoefficientChange) + return MOI.ScalarCoefficientChange(index_map(change.variable), change.new_coefficient) end -function mapvariables(varmap, f::MOI.AbstractFunctionModification) - return mapvariables(vi -> varmap[vi], f) +function map_indices(index_map::Function, change::MOI.MultirowChange) + return MOI.MultirowChange(index_map(change.variable), change.new_coefficients) end # For performance reason, we assume that the type of the function does not @@ -106,10 +142,7 @@ or submittable value. """ function substitute_variables end -const ObjectWithoutIndex = Union{Number, Enum, MOI.AnyAttribute, MOI.AbstractSet} -const ObjectOrArrayWithoutIndex = Union{ObjectWithoutIndex, AbstractArray{<:ObjectWithoutIndex}} - -substitute_variables(::Function, x::ObjectOrArrayWithoutIndex) = x +substitute_variables(::Function, x::ObjectOrTupleOrArrayWithoutIndex) = x function substitute_variables(variable_map::Function, term::MOI.ScalarQuadraticTerm{T}) where T @@ -524,7 +557,7 @@ function test_models_equal(model1::MOI.ModelLike, model2::MOI.ModelLike, variabl f2 = MOI.get(model2, MOI.ConstraintFunction(), index2) s1 = MOI.get(model1, MOI.ConstraintSet(), index1) s2 = MOI.get(model2, MOI.ConstraintSet(), index2) - @test isapprox(f1, mapvariables(variablemap_2to1, f2)) + @test isapprox(f1, map_indices(variablemap_2to1, f2)) @test s1 == s2 end attrs1 = MOI.get(model1, MOI.ListOfModelAttributesSet()) @@ -535,7 +568,7 @@ function test_models_equal(model1::MOI.ModelLike, model2::MOI.ModelLike, variabl value2 = MOI.get(model2, attr) if value1 isa MOI.AbstractFunction @test value2 isa MOI.AbstractFunction - @test isapprox(value1, attribute_value_map(variablemap_2to1, value2)) + @test isapprox(value1, map_indices(variablemap_2to1, value2)) else @test !(value2 isa MOI.AbstractFunction) @test value1 == value2 diff --git a/src/Utilities/mockoptimizer.jl b/src/Utilities/mockoptimizer.jl index 0af8212893..0dfc3296b6 100644 --- a/src/Utilities/mockoptimizer.jl +++ b/src/Utilities/mockoptimizer.jl @@ -54,7 +54,7 @@ end const internal_xor_mask = Int64(12345678) xor_index(vi::VI) = VI(xor(vi.value, internal_xor_mask)) xor_index(ci::CI{F,S}) where {F,S} = CI{F,S}(xor(ci.value, internal_xor_mask)) -xor_variables(f) = mapvariables(xor_index, f) +xor_indices(x) = map_indices(xor_index, x) function MockOptimizer(inner_model::MOI.ModelLike; supports_names=true, needs_allocate_load=false, @@ -108,7 +108,7 @@ function MOI.add_constraint(mock::MockOptimizer, func::MOI.AbstractFunction, set::MOI.AbstractSet) if mock.add_con_allowed - ci = MOI.add_constraint(mock.inner_model, xor_variables(func), set) + ci = MOI.add_constraint(mock.inner_model, xor_indices(func), set) return xor_index(ci) else throw(MOI.AddConstraintNotAllowed{typeof(func), typeof(set)}()) @@ -166,7 +166,7 @@ function MOI.set(mock::MockOptimizer, attr::MOI.AbstractOptimizerAttribute, if MOI.is_set_by_optimize(attr) mock.optimizer_attributes[attr] = value else - MOI.set(mock.inner_model, attr, value) + MOI.set(mock.inner_model, attr, xor_indices(value)) end end function MOI.supports(mock::MockOptimizer, attr::MOI.AbstractModelAttribute) @@ -178,39 +178,53 @@ function MOI.set(mock::MockOptimizer, attr::MOI.AbstractModelAttribute, value) if MOI.is_set_by_optimize(attr) mock.model_attributes[attr] = value else - MOI.set(mock.inner_model, attr, value) + MOI.set(mock.inner_model, attr, xor_indices(value)) end end -MOI.set(mock::MockOptimizer, attr::MOI.ObjectiveFunction, value) = MOI.set(mock.inner_model, attr, xor_variables(value)) -MOI.set(mock::MockOptimizer, attr::MOI.AbstractVariableAttribute, idx::MOI.VariableIndex, value) = MOI.set(mock.inner_model, attr, xor_index(idx), value) -MOI.set(mock::MockOptimizer, ::MOI.VariablePrimal, idx::MOI.VariableIndex, value) = (mock.varprimal[xor_index(idx)] = value) -MOI.set(mock::MockOptimizer, ::MockVariableAttribute, idx::MOI.VariableIndex, value) = (mock.varattribute[xor_index(idx)] = value) +function MOI.set(mock::MockOptimizer, attr::MOI.AbstractVariableAttribute, + idx::MOI.VariableIndex, value) + MOI.set(mock.inner_model, attr, xor_index(idx), xor_indices(value)) +end +function MOI.set(mock::MockOptimizer, ::MOI.VariablePrimal, + idx::MOI.VariableIndex, value) + mock.varprimal[xor_index(idx)] = value +end +function MOI.set(mock::MockOptimizer, ::MockVariableAttribute, + idx::MOI.VariableIndex, value) + mock.varattribute[xor_index(idx)] = value +end function MOI.set(mock::MockOptimizer, attr::MOI.AbstractConstraintAttribute, idx::MOI.ConstraintIndex, value) MOI.set(mock.inner_model, attr, xor_index(idx), value) end -MOI.set(mock::MockOptimizer, ::MockConstraintAttribute, idx::MOI.ConstraintIndex, value) = (mock.conattribute[xor_index(idx)] = value) -MOI.set(mock::MockOptimizer, ::MOI.ConstraintDual, idx::MOI.ConstraintIndex, value) = (mock.condual[xor_index(idx)] = value) -MOI.set(mock::MockOptimizer, ::MOI.ConstraintBasisStatus, idx::MOI.ConstraintIndex, value) = (mock.con_basis[xor_index(idx)] = value) +function MOI.set(mock::MockOptimizer, ::MockConstraintAttribute, + idx::MOI.ConstraintIndex, value) + mock.conattribute[xor_index(idx)] = value +end +function MOI.set(mock::MockOptimizer, ::MOI.ConstraintDual, + idx::MOI.ConstraintIndex, value) + mock.condual[xor_index(idx)] = value +end +function MOI.set(mock::MockOptimizer, ::MOI.ConstraintBasisStatus, + idx::MOI.ConstraintIndex, value) + mock.con_basis[xor_index(idx)] = value +end function MOI.get(mock::MockOptimizer, attr::MOI.AbstractOptimizerAttribute) if MOI.is_set_by_optimize(attr) return mock.optimizer_attributes[attr] else - return MOI.get(mock.inner_model, attr) + return xor_indices(MOI.get(mock.inner_model, attr)) end end function MOI.get(mock::MockOptimizer, attr::MOI.AbstractModelAttribute) if MOI.is_set_by_optimize(attr) return mock.model_attributes[attr] else - return MOI.get(mock.inner_model, attr) + return xor_indices(MOI.get(mock.inner_model, attr)) end end -MOI.get(mock::MockOptimizer, attr::Union{MOI.ListOfVariableIndices, - MOI.ListOfConstraintIndices}) = xor_index.(MOI.get(mock.inner_model, attr)) -MOI.get(mock::MockOptimizer, attr::MOI.ObjectiveFunction) = xor_variables(MOI.get(mock.inner_model, attr)) ##### ##### Names @@ -312,7 +326,7 @@ function MOI.get(mock::MockOptimizer, attr::MOI.ConstraintFunction, idx::MOI.ConstraintIndex) # If it is thrown by `mock.inner_model`, the index will be xor'ed. MOI.throw_if_not_valid(mock, idx) - return xor_variables(MOI.get(mock.inner_model, attr, xor_index(idx))) + return xor_indices(MOI.get(mock.inner_model, attr, xor_index(idx))) end function MOI.get(mock::MockOptimizer, attr::MOI.ConstraintDual, idx::MOI.ConstraintIndex{F}) where F @@ -415,7 +429,7 @@ function MOI.modify(mock::MockOptimizer, c::CI, change::MOI.AbstractFunctionModi if !mock.modify_allowed throw(MOI.ModifyConstraintNotAllowed(c, change)) end - MOI.modify(mock.inner_model, xor_index(c), xor_variables(change)) + MOI.modify(mock.inner_model, xor_index(c), xor_indices(change)) end function MOI.set(mock::MockOptimizer, ::MOI.ConstraintSet, @@ -426,14 +440,14 @@ end function MOI.set(mock::MockOptimizer, ::MOI.ConstraintFunction, c::CI{F}, func::F) where F<:MOI.AbstractFunction MOI.set(mock.inner_model, MOI.ConstraintFunction(), xor_index(c), - xor_variables(func)) + xor_indices(func)) end function MOI.modify(mock::MockOptimizer, obj::MOI.ObjectiveFunction, change::MOI.AbstractFunctionModification) if !mock.modify_allowed throw(MOI.ModifyObjectiveNotAllowed(change)) end - MOI.modify(mock.inner_model, obj, xor_variables(change)) + MOI.modify(mock.inner_model, obj, xor_indices(change)) end # TODO: transform @@ -454,16 +468,14 @@ end function allocate_variables(mock::MockOptimizer, nvars) return xor_index.(allocate_variables(mock.inner_model, nvars)) end -allocate(mock::MockOptimizer, attr::MOI.AnyAttribute, value) = allocate(mock.inner_model, attr, value) -allocate(mock::MockOptimizer, attr::MOI.ObjectiveFunction, value) = allocate(mock.inner_model, attr, xor_variables(value)) -allocate(mock::MockOptimizer, attr::MOI.AnyAttribute, idx::MOI.Index, value) = allocate(mock.inner_model, attr, xor_index(idx), value) -allocate_constraint(mock::MockOptimizer, f::MOI.AbstractFunction, s::MOI.AbstractSet) = xor_index(allocate_constraint(mock.inner_model, xor_variables(f), s)) +allocate(mock::MockOptimizer, attr::MOI.AnyAttribute, value) = allocate(mock.inner_model, attr, xor_indices(value)) +allocate(mock::MockOptimizer, attr::MOI.AnyAttribute, idx::MOI.Index, value) = allocate(mock.inner_model, attr, xor_index(idx), xor_indices(value)) +allocate_constraint(mock::MockOptimizer, f::MOI.AbstractFunction, s::MOI.AbstractSet) = xor_index(allocate_constraint(mock.inner_model, xor_indices(f), s)) load_variables(mock::MockOptimizer, nvars) = load_variables(mock.inner_model, nvars) -load(mock::MockOptimizer, attr::MOI.AnyAttribute, value) = load(mock.inner_model, attr, value) -load(mock::MockOptimizer, attr::MOI.ObjectiveFunction, value) = load(mock.inner_model, attr, xor_variables(value)) -load(mock::MockOptimizer, attr::MOI.AnyAttribute, idx::MOI.Index, value) = load(mock.inner_model, attr, xor_index(idx), value) -load_constraint(mock::MockOptimizer, ci::CI, f::MOI.AbstractFunction, s::MOI.AbstractSet) = load_constraint(mock.inner_model, xor_index(ci), xor_variables(f), s) +load(mock::MockOptimizer, attr::MOI.AnyAttribute, value) = load(mock.inner_model, attr, xor_indices(value)) +load(mock::MockOptimizer, attr::MOI.AnyAttribute, idx::MOI.Index, value) = load(mock.inner_model, attr, xor_index(idx), xor_indices(value)) +load_constraint(mock::MockOptimizer, ci::CI, f::MOI.AbstractFunction, s::MOI.AbstractSet) = load_constraint(mock.inner_model, xor_index(ci), xor_indices(f), s) """ set_mock_optimize!(mock::MockOptimizer, opt::Function...) diff --git a/test/Utilities/cachingoptimizer.jl b/test/Utilities/cachingoptimizer.jl index 947b6a5e52..7c1d4d4893 100644 --- a/test/Utilities/cachingoptimizer.jl +++ b/test/Utilities/cachingoptimizer.jl @@ -83,6 +83,40 @@ end @test MOI.get(cached, MOI.TimeLimitSec()) == 0.0 end +@testset "Mapping of variables" begin + mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) + model = MOIU.CachingOptimizer(MOIU.Model{Float64}(), mock) + x = MOI.add_variable(model) + y = first(MOI.get(mock, MOI.ListOfVariableIndices())) + @test !MOI.is_valid(model, y) + @test !MOI.is_valid(mock, x) + fx = MOI.SingleVariable(x) + fy = MOI.SingleVariable(y) + + cfx = MOI.add_constraint(model, fx, MOI.GreaterThan(1.0)) + cfy = first(MOI.get(mock, MOI.ListOfConstraintIndices{ + MOI.SingleVariable, MOI.GreaterThan{Float64}}())) + @test !MOI.is_valid(model, cfy) + @test !MOI.is_valid(mock, cfx) + @test MOI.get(mock, MOI.ConstraintFunction(), cfy) == fy + + c2fx = MOI.add_constraint(model, 2.0fx, MOI.GreaterThan(1.0)) + c2fy = first(MOI.get(mock, MOI.ListOfConstraintIndices{ + MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}())) + @test !MOI.is_valid(model, c2fy) + @test !MOI.is_valid(mock, c2fx) + @test MOI.get(mock, MOI.ConstraintFunction(), c2fy) ≈ 2.0fy + + MOI.set(model, MOI.ConstraintFunction(), c2fx, 3.0fx) + @test MOI.get(mock, MOI.ConstraintFunction(), c2fy) ≈ 3.0fy + + MOI.set(model, MOI.ObjectiveFunction{typeof(fx)}(), fx) + @test MOI.get(mock, MOI.ObjectiveFunction{typeof(fy)}()) == fy + + MOI.set(model, MOI.ObjectiveFunction{typeof(2.0fx)}(), 2.0fx) + @test MOI.get(mock, MOI.ObjectiveFunction{typeof(2.0fy)}()) ≈ 2.0fy +end + @testset "CachingOptimizer MANUAL mode" begin m = MOIU.CachingOptimizer(MOIU.Model{Float64}(), MOIU.MANUAL) @test MOIU.state(m) == MOIU.NO_OPTIMIZER diff --git a/test/Utilities/functions.jl b/test/Utilities/functions.jl index 9b4f101855..e3224f12b0 100644 --- a/test/Utilities/functions.jl +++ b/test/Utilities/functions.jl @@ -140,10 +140,11 @@ end @test MOIU.substitute_variables(vi -> subs[vi], fvq) ≈ subs_vq @test MOIU.substitute_variables(vi -> subs[vi], fvq) ≈ subs_vq end -@testset "mapvariables" begin +@testset "map_indices" begin fsq = MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm.(1.0, [x, y]), MOI.ScalarQuadraticTerm.(1.0, [x, w, w], [z, z, y]), -3.0) - gsq = MOIU.mapvariables(Dict(x => y, y => z, w => w, z => x), fsq) + index_map = Dict(x => y, y => z, w => w, z => x) + gsq = MOIU.map_indices(index_map, fsq) sats = MOI.ScalarAffineTerm.(1.0, [y, z]) sqts = MOI.ScalarQuadraticTerm.(1.0, [y, w, w], [x, x, z]) @test gsq.affine_terms == sats @@ -151,7 +152,7 @@ end @test gsq.constant == -3. fvq = MOI.VectorQuadraticFunction(MOI.VectorAffineTerm.([2, 1], MOI.ScalarAffineTerm.(1.0, [x, y])), MOI.VectorQuadraticTerm.([1, 2, 2], MOI.ScalarQuadraticTerm.(1.0, [x, w, w], [z, z, y])), [-3.0, -2.0]) - gvq = MOIU.mapvariables(Dict(x => y, y => z, w => w, z => x), fvq) + gvq = MOIU.map_indices(index_map, fvq) @test gvq.affine_terms == MOI.VectorAffineTerm.([2, 1], sats) @test gvq.quadratic_terms == MOI.VectorQuadraticTerm.([1, 2, 2], sqts) @test MOIU.constant_vector(gvq) == [-3., -2.]