diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 5cf614923..293946710 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -8,6 +8,7 @@ struct Optimizer{T,M} <: MOI.AbstractOptimizer moi_to_convex::OrderedCollections.OrderedDict{MOI.VariableIndex,Any} convex_to_moi::Dict{UInt64,Vector{MOI.VariableIndex}} constraint_map::Vector{MOI.ConstraintIndex} + function Optimizer(context::Context{T,M}) where {T,M} return new{T,M}( context, @@ -211,6 +212,10 @@ function MOI.add_constraint( set::MOI.AbstractVectorSet, ) where {T} constraint = Constraint(_expr(model, func), set) + if vexity(constraint) == Convex.NotDcp() + msg = "\n\n[Convex.jl] The constraint is not convex according to the axioms of Disciplined Convex Programming.\n\n" + throw(MOI.AddConstraintNotAllowed{typeof(func),typeof(set)}(msg)) + end add_constraint!(model.context, constraint) push!(model.constraint_map, model.context.constr_to_moi_inds[constraint]) return MOI.ConstraintIndex{typeof(func),typeof(set)}( @@ -231,10 +236,19 @@ end function MOI.set( model::Optimizer{T}, - ::MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}, + attr::MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}, func::MOI.ScalarNonlinearFunction, ) where {T} - cfp = conic_form!(model.context, _expr(model, func)) + obj_fn = _expr(model, func) + vex = vexity(obj_fn) + if MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE + vex = -vex + end + if vex in (Convex.NotDcp(), Convex.ConcaveVexity()) + msg = "\n\n[Convex.jl] The objective is not convex according to the axioms of Disciplined Convex Programming.\n\n" + throw(MOI.SetAttributeNotAllowed(attr, msg)) + end + cfp = conic_form!(model.context, obj_fn) obj = _to_scalar_moi(T, cfp) MOI.set(model, MOI.ObjectiveFunction{typeof(obj)}(), obj) return diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 8a4e4b207..66f8c7dd5 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -108,6 +108,60 @@ function test_scalar_nonlinear_function() return end +function test_not_dcp_constraint() + inner = Convex.Optimizer(ECOS.Optimizer) + model = MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + MOI.Bridges.full_bridge_optimizer(inner, Float64), + ) + x = MOI.add_variable(model) + f = MOI.ScalarNonlinearFunction(:^, Any[x, 2]) + MOI.add_constraint(model, f, MOI.GreaterThan(1.0)) + F, S = MOI.VectorNonlinearFunction, MOI.Nonnegatives + @test_throws( + MOI.AddConstraintNotAllowed{F,S}, + MOI.Utilities.attach_optimizer(model), + ) + return +end + +function test_not_dcp_objective() + inner = Convex.Optimizer(ECOS.Optimizer) + model = MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + MOI.Bridges.full_bridge_optimizer(inner, Float64), + ) + x = MOI.add_variable(model) + f = MOI.ScalarNonlinearFunction(:^, Any[x, 2]) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + attr = MOI.ObjectiveFunction{typeof(f)}() + MOI.set(model, attr, f) + @test_throws( + MOI.SetAttributeNotAllowed{typeof(attr)}, + MOI.Utilities.attach_optimizer(model), + ) + return +end + +function test_not_dcp_objective_min() + inner = Convex.Optimizer(ECOS.Optimizer) + model = MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + MOI.Bridges.full_bridge_optimizer(inner, Float64), + ) + x = MOI.add_variable(model) + g = MOI.ScalarNonlinearFunction(:^, Any[x, 2]) + f = MOI.ScalarNonlinearFunction(:-, Any[g]) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + attr = MOI.ObjectiveFunction{typeof(f)}() + MOI.set(model, attr, f) + @test_throws( + MOI.SetAttributeNotAllowed{typeof(attr)}, + MOI.Utilities.attach_optimizer(model), + ) + return +end + end # TestMOIWrapper TestMOIWrapper.runtests()