From 946d18d131a3404c984725fbd05cae1bf38d0bc4 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 22 Jan 2024 16:09:04 +1300 Subject: [PATCH] [breaking] Fix setting constant objective function (#581) --- src/Convex.jl | 2 +- src/MOI_wrapper.jl | 6 +-- src/problem_depot/problems/lp.jl | 2 +- src/problem_depot/problems/sdp.jl | 2 +- src/problems.jl | 40 ++++++++++++++----- src/solution.jl | 5 --- test/test_utilities.jl | 65 +++++++++++++++++++++++++------ 7 files changed, 91 insertions(+), 31 deletions(-) diff --git a/src/Convex.jl b/src/Convex.jl index 7cdcf0067..79725f455 100644 --- a/src/Convex.jl +++ b/src/Convex.jl @@ -218,13 +218,13 @@ for (root, _, files) in walkdir(joinpath(@__DIR__, "constraints")) end end -include("problems.jl") include("SparseTape.jl") include("VectorAffineFunctionAsMatrix.jl") include("ComplexTape.jl") include("operate.jl") include("complex_operate.jl") include("real_operate.jl") +include("problems.jl") include("solution.jl") include("MOI_wrapper.jl") diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 34b136136..222978173 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -225,12 +225,12 @@ function MOI.supports( end function MOI.set( - model::Optimizer, + model::Optimizer{T}, ::MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}, func::MOI.ScalarNonlinearFunction, -) +) where {T} cfp = conic_form!(model.context, _expr(model, func)) - obj = scalar_fn(cfp) + obj = _to_scalar_moi(T, cfp) MOI.set(model, MOI.ObjectiveFunction{typeof(obj)}(), obj) return end diff --git a/src/problem_depot/problems/lp.jl b/src/problem_depot/problems/lp.jl index 71b197d43..ef91fcad0 100644 --- a/src/problem_depot/problems/lp.jl +++ b/src/problem_depot/problems/lp.jl @@ -195,7 +195,7 @@ end end handle_problem!(p) if test - @test p.optval === nothing + @test p.optval === 1.0 @test evaluate(sum(neg(x))) ≈ 6 atol = atol rtol = rtol end end diff --git a/src/problem_depot/problems/sdp.jl b/src/problem_depot/problems/sdp.jl index 09a7bb387..b0fffb12b 100644 --- a/src/problem_depot/problems/sdp.jl +++ b/src/problem_depot/problems/sdp.jl @@ -1499,7 +1499,7 @@ function sdp_quantum_relative_entropy_impl( atol rtol = rtol elseif mode == 5 # Satisfiability problem - @test p.optval === nothing + @test p.optval ≈ 0 atol = atol rtol = rtol end end end diff --git a/src/problems.jl b/src/problems.jl index 9469a07d9..c2cfe7aef 100644 --- a/src/problems.jl +++ b/src/problems.jl @@ -143,6 +143,20 @@ function new_conic_form!(context::Context, p::Problem) return conic_form!(context, p.objective) end +function _to_scalar_moi(::Type{T}, x) where {T} + return _to_scalar_moi(T, only(MOI.Utilities.scalarize(x))) +end + +function _to_scalar_moi(::Type{T}, x::SparseTape) where {T} + return _to_scalar_moi(T, to_vaf(x)) +end + +function _to_scalar_moi(::Type{T}, x::Number) where {T<:Real} + return MOI.ScalarAffineFunction{T}(MOI.ScalarAffineTerm{T}[], x) +end + +_to_scalar_moi(::Type{T}, f::MOI.AbstractScalarFunction) where {T} = f + function Context(p::Problem{T}, optimizer_factory) where {T} context = Context{T}(optimizer_factory) cfp = conic_form!(context, p) @@ -153,7 +167,7 @@ function Context(p::Problem{T}, optimizer_factory) where {T} if p.head == :satisfy MOI.set(context.model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) else - obj = scalar_fn(cfp) + obj = _to_scalar_moi(T, cfp) MOI.set(context.model, MOI.ObjectiveFunction{typeof(obj)}(), obj) MOI.set( context.model, @@ -189,16 +203,20 @@ function minimize( return Problem{numeric_type}(:minimize, objective, constraints) end -function minimize(::Value, constraints::Constraint...; numeric_type = Float64) - return satisfy(collect(constraints); numeric_type = numeric_type) +function minimize( + objective::Value, + constraints::Constraint...; + numeric_type = Float64, +) + return minimize(objective, collect(constraints); numeric_type) end function minimize( - ::Value, + objective::Value, constraints::Array{<:Constraint} = Constraint[]; numeric_type = Float64, ) - return satisfy(constraints; numeric_type = numeric_type) + return minimize(constant(objective), constraints; numeric_type) end # Allow users to simply type maximize @@ -218,16 +236,20 @@ function maximize( return Problem{numeric_type}(:maximize, objective, constraints) end -function maximize(::Value, constraints::Constraint...; numeric_type = Float64) - return satisfy(collect(constraints); numeric_type = numeric_type) +function maximize( + objective::Value, + constraints::Constraint...; + numeric_type = Float64, +) + return maximize(objective, collect(constraints); numeric_type) end function maximize( - ::Value, + objective::Value, constraints::Array{<:Constraint} = Constraint[]; numeric_type = Float64, ) - return satisfy(constraints; numeric_type = numeric_type) + return maximize(constant(objective), constraints; numeric_type) end # Allow users to simply type satisfy (if there is no objective) diff --git a/src/solution.jl b/src/solution.jl index 2cf657eeb..5ec64ec87 100644 --- a/src/solution.jl +++ b/src/solution.jl @@ -10,11 +10,6 @@ function add_variables!(model, var::AbstractVariable) end end -scalar_fn(x::Number) = x # for `satisfy` problems? Not sure... -scalar_fn(x) = only(MOI.Utilities.scalarize(x)) -scalar_fn(x::SparseTape) = scalar_fn(to_vaf(x)) -scalar_fn(v::MOI.AbstractScalarFunction) = v - """ solve!(problem::Problem, optimizer_factory; silent_solver = false, diff --git a/test/test_utilities.jl b/test/test_utilities.jl index e0c1cd4ce..fceaee202 100644 --- a/test/test_utilities.jl +++ b/test/test_utilities.jl @@ -90,18 +90,12 @@ function test_complex_objective_function_errors() return end -function test_constant_objective() +function test_satisfy_constant_objective() x = Variable() - for p in [ - satisfy(x == 0, x == 1), - satisfy(Constraint[]), - minimize(0, x == 0), - minimize(0, Constraint[]), - maximize(0, x == 0), - maximize(0, Constraint[]), - ] - @test isnothing(p.objective) - end + p = satisfy(x == 0, x == 1) + @test isnothing(p.objective) + p = satisfy(Constraint[]) + @test isnothing(p.objective) return end @@ -1142,6 +1136,55 @@ function test_tree_interface() return end +function test_scalar_fn_constant_objective() + x = Variable() + p = minimize(2.1, [x >= 1]) + solve!(p, SCS.Optimizer; silent_solver = true) + @test isapprox(p.optval, 2.1; atol = 1e-5) + p = minimize(2.2, x >= 1) + solve!(p, SCS.Optimizer; silent_solver = true) + @test isapprox(p.optval, 2.2; atol = 1e-5) + p = maximize(2.3, [x >= 1]) + solve!(p, SCS.Optimizer; silent_solver = true) + @test isapprox(p.optval, 2.3; atol = 1e-5) + p = maximize(2.4, x >= 1) + solve!(p, SCS.Optimizer; silent_solver = true) + @test isapprox(p.optval, 2.4; atol = 1e-5) + return +end + +function test_scalar_fn_objective_number() + x = Variable() + p = minimize(constant(2), [x >= 1]) + solve!(p, SCS.Optimizer) + @test isapprox(p.optval, 2.0; atol = 1e-5) + return +end + +function test_scalar_fn_objective_variable() + x = Variable() + p = minimize(x, [x >= 1]) + solve!(p, SCS.Optimizer) + @test isapprox(p.optval, 1.0; atol = 1e-5) + return +end + +function test_scalar_fn_objective_affine() + x = Variable() + p = minimize(x + 1, [x >= 1]) + solve!(p, SCS.Optimizer) + @test isapprox(p.optval, 2.0; atol = 1e-5) + return +end + +function test_scalar_fn_objective_square() + x = Variable() + p = minimize(square(x - 2), [x >= 1]) + solve!(p, SCS.Optimizer) + @test isapprox(p.optval, 0.0; atol = 1e-3) + return +end + end # TestUtilities TestUtilities.runtests()