diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index a512d380db..39fffd6169 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -543,6 +543,36 @@ function substitute_variables( return MOI.ScalarNonlinearFunction(f.head, new_args) end +function substitute_variables( + variable_map::F, + f::MOI.VectorOfVariables, +) where {F<:Function} + variables = MOI.VariableIndex[] + for (i, x) in enumerate(f.variables) + y = variable_map(x) + try + push!(variables, convert(MOI.VariableIndex, y)) + catch err + @assert err isa InexactError + error( + """ + Cannot substitute variables in the `VectorOfVariables` \ + function because the result is not representable as a \ + `VectorOfVariables` function. + + The variable `$x` in index `$i` is mapped to the expression \ + `$y`, not directly to another `VariableIndex`. + + Note that variables constrained on creation can be used in a \ + `VectorOfVariables` function only when their bridge maps each \ + one directly to another variable, without a transformation. + """, + ) + end + end + return MOI.VectorOfVariables(variables) +end + function substitute_variables( variable_map::F, f::MOI.VectorAffineFunction{T}, diff --git a/test/Utilities/test_functions.jl b/test/Utilities/test_functions.jl index 9182bb2ee2..01d477af56 100644 --- a/test/Utilities/test_functions.jl +++ b/test/Utilities/test_functions.jl @@ -332,6 +332,32 @@ function test_substitute_variables() return end +function test_substitute_variables_vector_of_variables() + w, x, y, z = MOI.VariableIndex.(1:4) + f1 = MOI.VectorOfVariables([w, x]) + f2 = MOI.VectorOfVariables([y, z]) + v_map = Dict(w => y, x => z) + @test MOI.Utilities.substitute_variables(v -> v_map[v], f1) == f2 + @test_throws( + ErrorException( + """ + Cannot substitute variables in the `VectorOfVariables` function \ + because the result is not representable as a `VectorOfVariables` \ + function. + + The variable `$w` in index `1` is mapped to the expression \ + `$(2.0 * w)`, not directly to another `VariableIndex`. + + Note that variables constrained on creation can be used in a \ + `VectorOfVariables` function only when their bridge maps each \ + one directly to another variable, without a transformation. + """, + ), + MOI.Utilities.substitute_variables(vi -> 2.0 * vi, f1), + ) + return +end + function test_substitute_variables_vector_nonlinear_function() model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) x = MOI.add_variable(model)