From 5cf05550ecba11850fb95a1870b64b79108c4645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 27 Oct 2026 12:50:52 +0100 Subject: [PATCH] [Utilities] skip map_indices when the variable map of IndexMap is the identity --- src/Utilities/copy/index_map.jl | 17 +++++++++++++++++ src/Utilities/functions.jl | 6 ++++++ test/Utilities/test_copy.jl | 22 ++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/src/Utilities/copy/index_map.jl b/src/Utilities/copy/index_map.jl index 7fe2aa00d9..34a446f3b1 100644 --- a/src/Utilities/copy/index_map.jl +++ b/src/Utilities/copy/index_map.jl @@ -104,3 +104,20 @@ Base.length(map::IndexMap) = length(map.var_map) + length(map.con_map) function Base.iterate(map::IndexMap, args...) return iterate(Base.Iterators.flatten((map.var_map, map.con_map)), args...) end + +# Objects that may contain `MOI.VariableIndex` but no `MOI.ConstraintIndex`. +const _OnlyVariableIndices = Union{ + MOI.VariableIndex, + MOI.AbstractFunction, + MOI.AbstractFunctionModification, +} + +# The variable map is often the identity, for example, when the destination +# model of `copy_to` creates its variables in the same order as the source +# model. In that case, there is no need to map the variables of `x`. +function map_indices(map::IndexMap, x::X)::X where {X<:_OnlyVariableIndices} + if CleverDicts.is_identity(map.var_map) + return x + end + return map_indices(Base.Fix1(getindex, map), x) +end diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index ba69f0893e..0d78c7089e 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -248,6 +248,12 @@ map_indices(::Any, ::MOI.RawOptimizerAttribute, x) = x )::X where {T<:MOI.Index,X} Shortcut for `map_indices(vi -> variable_map[vi], x)`. + +!!! note + The returned value is not guaranteed to be a copy of `x`. For example, if + `variable_map` is an [`IndexMap`](@ref) whose variable map is the + identity, `x` may be returned without being copied. Therefore, it should + not be modified unless `x` is not used anymore. """ function map_indices( variable_map::AbstractDict{T,T}, diff --git a/test/Utilities/test_copy.jl b/test/Utilities/test_copy.jl index 32bd233693..f8800e494d 100644 --- a/test/Utilities/test_copy.jl +++ b/test/Utilities/test_copy.jl @@ -1122,6 +1122,28 @@ function test_copy_names_unsupported() return end +function test_map_indices_identity_index_map() + src = MOI.Utilities.Model{Float64}() + x = MOI.add_variables(src, 2) + dest = MOI.Utilities.Model{Float64}() + index_map = MOI.copy_to(dest, src) + @test MOI.Utilities.CleverDicts.is_identity(index_map.var_map) + f = 1.0 * x[1] + 2.0 * x[2] + @test MOI.Utilities.map_indices(index_map, f) === f + attr = MOI.ObjectiveFunction{typeof(f)}() + @test MOI.Utilities.map_indices(index_map, attr, f) === f + change = MOI.ScalarCoefficientChange(x[1], 2.0) + @test MOI.Utilities.map_indices(index_map, change) === change + index_map[x[1]] = x[2] + index_map[x[2]] = x[1] + @test !MOI.Utilities.CleverDicts.is_identity(index_map.var_map) + g = MOI.Utilities.map_indices(index_map, f) + @test isapprox(g, 1.0 * x[2] + 2.0 * x[1]) + @test MOI.Utilities.map_indices(index_map, change) == + MOI.ScalarCoefficientChange(x[2], 2.0) + return +end + end # module TestCopy.runtests()