diff --git a/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl b/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl index 6d91ffe695..aff3fad8a2 100644 --- a/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl +++ b/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl @@ -269,8 +269,8 @@ function conversion_cost( ::Type{ <:Union{ MOI.VariableIndex, - MOI.ScalarAffineFunction, - MOI.ScalarQuadraticFunction, + MOI.ScalarAffineFunction{Float64}, + MOI.ScalarQuadraticFunction{Float64}, }, }, ) diff --git a/src/Bridges/Objective/Objective.jl b/src/Bridges/Objective/Objective.jl index 68be377bcb..60ffcddf92 100644 --- a/src/Bridges/Objective/Objective.jl +++ b/src/Bridges/Objective/Objective.jl @@ -28,6 +28,7 @@ The coefficient type used is `T`. function add_all_bridges(model, ::Type{T}) where {T} MOI.Bridges.add_bridge(model, FunctionizeBridge{T}) MOI.Bridges.add_bridge(model, QuadratizeBridge{T}) + MOI.Bridges.add_bridge(model, ToScalarNonlinearBridge{T}) MOI.Bridges.add_bridge(model, SlackBridge{T}) MOI.Bridges.add_bridge(model, VectorFunctionizeBridge{T}) MOI.Bridges.add_bridge(model, VectorSlackBridge{T}) diff --git a/src/Bridges/Objective/bridges/FunctionConversionBridge.jl b/src/Bridges/Objective/bridges/FunctionConversionBridge.jl index 92deb99bf9..28cf74bd49 100644 --- a/src/Bridges/Objective/bridges/FunctionConversionBridge.jl +++ b/src/Bridges/Objective/bridges/FunctionConversionBridge.jl @@ -14,7 +14,7 @@ for these pairs of functions: - * [`MOI.ScalarAffineFunction`](@ref)` to [`MOI.ScalarQuadraticFunction`](@ref) + * [`MOI.ScalarAffineFunction`](@ref) to [`MOI.ScalarQuadraticFunction`](@ref) * [`MOI.ScalarQuadraticFunction`](@ref) to [`MOI.ScalarNonlinearFunction`](@ref) * [`MOI.VectorAffineFunction`](@ref) to [`MOI.VectorQuadraticFunction`](@ref) @@ -173,6 +173,34 @@ const QuadratizeBridge{T,G} = const Quadratize{T,OT<:MOI.ModelLike} = SingleBridgeOptimizer{QuadratizeBridge{T},OT} +""" + ToScalarNonlinearBridge{T,G} <: FunctionConversionBridge{T,MOI.ScalarNonlinearFunction,G} + +`ToScalarNonlinearBridge` implements the following reformulations: + + * ``\\min\\{x^\\top \\mathbf{𝑄} x + a^\\top x + b\\}`` into ``\\min\\{f(x)\\}`` + * ``\\max\\{x^\\top \\mathbf{𝑄} x + a^\\top x + b\\}`` into ``\\max\\{f(x)\\}`` + +where `f(x)` is a `MOI.ScalarNonlinearFunction`. + +## Source node + +`ToScalarNonlinearBridge` supports: + + * [`MOI.ObjectiveFunction{G}`](@ref) + +## Target nodes + +`ToScalarNonlinearBridge` creates: + + * One objective node: [`MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}`](@ref) +""" +const ToScalarNonlinearBridge{T,G} = + FunctionConversionBridge{T,MOI.ScalarNonlinearFunction,G} + +const ToScalarNonlinear{T,OT<:MOI.ModelLike} = + SingleBridgeOptimizer{ToScalarNonlinearBridge{T},OT} + """ VectorFunctionizeBridge{T,G} <: FunctionConversionBridge{T,MOI.VectorAffineFunction{T},G} diff --git a/test/Bridges/Objective/ToScalarNonlinearBridge.jl b/test/Bridges/Objective/ToScalarNonlinearBridge.jl new file mode 100644 index 0000000000..2a33005fb8 --- /dev/null +++ b/test/Bridges/Objective/ToScalarNonlinearBridge.jl @@ -0,0 +1,160 @@ +# Copyright (c) 2017: Miles Lubin and contributors +# Copyright (c) 2017: Google Inc. +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestObjectiveToScalarNonlinear + +using Test + +import MathOptInterface as MOI + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end + return +end + +include("../utilities.jl") + +function test_solve_singlevariable_obj() + mock = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Objective.ToScalarNonlinear{Float64}(mock) + MOI.Utilities.set_mock_optimize!( + mock, + (mock::MOI.Utilities.MockOptimizer) -> + MOI.Utilities.mock_optimize!(mock, [1.0], MOI.FEASIBLE_POINT), + ) + MOI.Test.test_objective_ObjectiveFunction_duplicate_terms( + model, + MOI.Test.Config(; + exclude = Any[MOI.DualObjectiveValue, MOI.ConstraintDual], + ), + ) + @test MOI.get(mock, MOI.ObjectiveFunctionType()) == + MOI.ScalarNonlinearFunction + @test MOI.get(model, MOI.ObjectiveFunctionType()) == + MOI.ScalarAffineFunction{Float64} + @test MOI.get(mock, MOI.ObjectiveSense()) == MOI.MIN_SENSE + @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE + vis = MOI.get(model, MOI.ListOfVariableIndices()) + func = 3.0 * vis[1] + 0.0 + + @test MOI.get( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + ) ≈ func + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + @test MOI.get(mock, MOI.ObjectiveSense()) == MOI.MAX_SENSE + @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE + + _test_delete_objective(model, 1, tuple()) + return +end + +function test_solve_result_index() + mock = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Objective.ToScalarNonlinear{Float64}(mock) + MOI.Utilities.set_mock_optimize!( + mock, + (mock::MOI.Utilities.MockOptimizer) -> MOI.Utilities.mock_optimize!( + mock, + MOI.OPTIMAL, + (MOI.FEASIBLE_POINT, [1.0]), + MOI.FEASIBLE_POINT, + (MOI.VariableIndex, MOI.GreaterThan{Float64}) => [1.0], + ), + ) + MOI.Test.test_solve_result_index( + model, + MOI.Test.Config(; + exclude = Any[MOI.DualObjectiveValue, MOI.ConstraintDual], + ), + ) + + return +end + +function test_runtests() + MOI.Bridges.runtests( + MOI.Bridges.Objective.ToScalarNonlinearBridge, + model -> begin + x = MOI.add_variable(model) + aff = MOI.ScalarAffineFunction( + MOI.ScalarAffineTerm.([2.0], [x]), + 1.0, + ) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + aff, + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + end, + model -> begin + x = MOI.add_variable(model) + exp = MOI.ScalarNonlinearFunction( + :+, + [ + MOI.ScalarNonlinearFunction( + :*, + [2.0, MOI.VariableIndex(1)], + ), + 1.0, + ], + ) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}(), + exp, + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + end, + ) + MOI.Bridges.runtests( + MOI.Bridges.Objective.ToScalarNonlinearBridge, + model -> begin + x = MOI.add_variable(model) + aff = MOI.ScalarAffineFunction( + MOI.ScalarAffineTerm.([2.0], [x]), + 1.0, + ) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + aff, + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + end, + model -> begin + x = MOI.add_variable(model) + exp = MOI.ScalarNonlinearFunction( + :+, + [ + MOI.ScalarNonlinearFunction( + :*, + [2.0, MOI.VariableIndex(1)], + ), + 1.0, + ], + ) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}(), + exp, + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + end, + ) + return +end + +end # module + +TestObjectiveToScalarNonlinear.runtests()