From 558b3c181a10dffa68c9403445ae22fd83345ecc Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 25 Apr 2022 15:44:09 +1200 Subject: [PATCH] [Nonlinear] change Nonlinear.add_constraint to accept function and set --- docs/src/submodules/Nonlinear/overview.md | 4 +- src/Nonlinear/evaluator.jl | 4 +- src/Nonlinear/model.jl | 33 ++++++++--- src/Nonlinear/parse.jl | 35 ------------ test/Nonlinear/Nonlinear.jl | 68 +++++++++-------------- 5 files changed, 55 insertions(+), 89 deletions(-) diff --git a/docs/src/submodules/Nonlinear/overview.md b/docs/src/submodules/Nonlinear/overview.md index ee18aeedee..d4bd6b35d6 100644 --- a/docs/src/submodules/Nonlinear/overview.md +++ b/docs/src/submodules/Nonlinear/overview.md @@ -164,7 +164,7 @@ julia> Nonlinear.set_objective(model, :($p + $expr + $x)); Add a constraint using [`Nonlinear.add_constraint`](@ref): ```jldoctest nonlinear_developer -julia> c = Nonlinear.add_constraint(model, :(1 + sqrt($x) <= 2.0)) +julia> c = Nonlinear.add_constraint(model, :(1 + sqrt($x)), MOI.LessThan(2.0)) MathOptInterface.Nonlinear.ConstraintIndex(1) julia> model @@ -177,7 +177,7 @@ A Nonlinear.Model with: The return value, `c`, is a [`Nonlinear.ConstraintIndex`](@ref) that is a unique identifier for the constraint. Interval constraints are also supported: ```jldoctest nonlinear_developer -julia> c2 = Nonlinear.add_constraint(model, :(-1.0 <= 1 + sqrt($x) <= 2.0)) +julia> c2 = Nonlinear.add_constraint(model, :(1 + sqrt($x)), MOI.Interval(-1.0, 2.0)) MathOptInterface.Nonlinear.ConstraintIndex(2) julia> model diff --git a/src/Nonlinear/evaluator.jl b/src/Nonlinear/evaluator.jl index 2975735277..a1f98ba3dc 100644 --- a/src/Nonlinear/evaluator.jl +++ b/src/Nonlinear/evaluator.jl @@ -18,8 +18,8 @@ Return the 1-indexed value of the constraint index `c` in `evaluator`. ```julia model = Model() x = MOI.VariableIndex(1) -c1 = add_constraint(model, :(\$x^2 <= 1)) -c2 = add_constraint(model, :(\$x^2 <= 1)) +c1 = add_constraint(model, :(\$x^2), MOI.LessThan(1.0)) +c2 = add_constraint(model, :(\$x^2), MOI.LessThan(1.0)) evaluator = Evaluator(model) MOI.initialize(evaluator, Symbol[]) ordinal_index(evaluator, c2) # Returns 2 diff --git a/src/Nonlinear/model.jl b/src/Nonlinear/model.jl index fdfe6a03d3..17fadb728f 100644 --- a/src/Nonlinear/model.jl +++ b/src/Nonlinear/model.jl @@ -76,9 +76,18 @@ function Base.getindex(model::Model, index::ExpressionIndex) end """ - add_constraint(model::Model, input::Expr)::ConstraintIndex + add_constraint( + model::Model, + func, + set::Union{ + MOI.GreaterThan{Float64}, + MOI.LessThan{Float64}, + MOI.Interval{Float64}, + MOI.EqualTo{Float64}, + }, + ) -Parse `input` into a [`Constraint`](@ref) and add to `model`. Returns a +Parse `func` and `set` into a [`Constraint`](@ref) and add to `model`. Returns a [`ConstraintIndex`](@ref) that can be used to delete the constraint or query solution information. @@ -87,12 +96,20 @@ solution information. ```julia model = Model() x = MOI.VariableIndex(1) -c = add_constraint(model, :(\$x^2 <= 1)) +c = add_constraint(model, :(\$x^2), MOI.LessThan(1.0)) ``` """ -function add_constraint(model::Model, input::Expr) - expr, set = _expr_to_constraint(input) - f = parse_expression(model, expr) +function add_constraint( + model::Model, + func, + set::Union{ + MOI.GreaterThan{Float64}, + MOI.LessThan{Float64}, + MOI.Interval{Float64}, + MOI.EqualTo{Float64}, + }, +) + f = parse_expression(model, func) model.last_constraint_index += 1 index = ConstraintIndex(model.last_constraint_index) model.constraints[index] = Constraint(f, set) @@ -109,7 +126,7 @@ Delete the constraint index `c` from `model`. ```julia model = Model() x = MOI.VariableIndex(1) -c = add_constraint(model, :(\$x^2 <= 1)) +c = add_constraint(model, :(\$x^2), MOI.LessThan(1.0)) delete(model, c) ``` """ @@ -139,7 +156,7 @@ and used to modify the value of the parameter. model = Model() x = MOI.VariableIndex(1) p = add_parameter(model, 1.2) -c = add_constraint(model, :(\$x^2 <= \$p)) +c = add_constraint(model, :(\$x^2 - \$p), MOI.LessThan(0.0)) ``` """ function add_parameter(model::Model, value::Float64) diff --git a/src/Nonlinear/parse.jl b/src/Nonlinear/parse.jl index 908e0da12f..8beb888bae 100644 --- a/src/Nonlinear/parse.jl +++ b/src/Nonlinear/parse.jl @@ -223,41 +223,6 @@ function parse_expression( ) end -function _normalize_constraint_expr(lhs::Real, body, rhs::Real) - return Float64(lhs), body, Float64(rhs) -end - -function _normalize_constraint_expr(lhs, body, rhs) - return error( - "Interval constraint contains non-constant left- or right-hand " * - "sides. Reformulate as two separate constraints, or move all " * - "variables into the central term.", - ) -end - -_normalize_constraint_expr(lhs, rhs::Real) = lhs, Float64(rhs) - -_normalize_constraint_expr(lhs, rhs) = Expr(:call, :-, lhs, rhs), 0.0 - -function _expr_to_constraint(expr::Expr) - if isexpr(expr, :comparison) - @assert expr.args[2] == expr.args[4] - @assert expr.args[2] in (:<=, :>=) - lhs, body, rhs = - _normalize_constraint_expr(expr.args[1], expr.args[3], expr.args[5]) - return body, MOI.Interval(lhs, rhs) - end - lhs, rhs = _normalize_constraint_expr(expr.args[2], expr.args[3]) - if expr.args[1] == :<= - return :($lhs - $rhs), MOI.LessThan(0.0) - elseif expr.args[1] == :>= - return :($lhs - $rhs), MOI.GreaterThan(0.0) - else - @assert expr.args[1] == :(==) - return :($lhs - $rhs), MOI.EqualTo(0.0) - end -end - """ convert_to_expr(data::Model, expr::Expression) diff --git a/test/Nonlinear/Nonlinear.jl b/test/Nonlinear/Nonlinear.jl index c03a372610..842e99750e 100644 --- a/test/Nonlinear/Nonlinear.jl +++ b/test/Nonlinear/Nonlinear.jl @@ -295,89 +295,70 @@ end function test_add_constraint_less_than() model = Nonlinear.Model() x = MOI.VariableIndex(1) - input = :($x^2 + 1 <= 1.0) - c = Nonlinear.add_constraint(model, input) - @test model[c].set == MOI.LessThan(0.0) + func = :($x^2 + 1) + set = MOI.LessThan(1.0) + c = Nonlinear.add_constraint(model, func, set) + @test model[c].set == set evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.constraint_expr(evaluator, 1) == :((x[$x]^2 + 1) - 1.0 <= 0.0) + @test MOI.constraint_expr(evaluator, 1) == :(x[$x]^2 + 1 <= 1.0) return end function test_add_constraint_delete() model = Nonlinear.Model() x = MOI.VariableIndex(1) - c1 = Nonlinear.add_constraint(model, :($x^2 + 1 <= 1.0)) - _ = Nonlinear.add_constraint(model, :(sqrt($x) <= 1.0)) + c1 = Nonlinear.add_constraint(model, :($x^2 + 1), MOI.LessThan(1.0)) + _ = Nonlinear.add_constraint(model, :(sqrt($x)), MOI.LessThan(1.0)) evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.constraint_expr(evaluator, 1) == :((x[$x]^2 + 1) - 1.0 <= 0.0) - @test MOI.constraint_expr(evaluator, 2) == :((sqrt(x[$x])) - 1.0 <= 0.0) + @test MOI.constraint_expr(evaluator, 1) == :(x[$x]^2 + 1 <= 1.0) + @test MOI.constraint_expr(evaluator, 2) == :(sqrt(x[$x]) <= 1.0) Nonlinear.delete(model, c1) evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.constraint_expr(evaluator, 1) == :(sqrt(x[$x]) - 1.0 <= 0.0) + @test MOI.constraint_expr(evaluator, 1) == :(sqrt(x[$x]) <= 1.0) @test_throws BoundsError MOI.constraint_expr(evaluator, 2) return end -function test_add_constraint_less_than_normalize() - model = Nonlinear.Model() - x = MOI.VariableIndex(1) - input = :(1 <= 1.0 + $x^2) - c = Nonlinear.add_constraint(model, input) - @test model[c].set == MOI.LessThan(0.0) - evaluator = Nonlinear.Evaluator(model) - MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.constraint_expr(evaluator, 1) == - :(1 - (1.0 + x[$x]^2) - 0.0 <= 0.0) - return -end - function test_add_constraint_greater_than() model = Nonlinear.Model() x = MOI.VariableIndex(1) - input = :($x^2 + 1 >= 1.0) - c = Nonlinear.add_constraint(model, input) - @test model[c].set == MOI.GreaterThan(0.0) + func = :($x^2 + 1) + set = MOI.GreaterThan(1.0) + c = Nonlinear.add_constraint(model, func, set) + @test model[c].set == set evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.constraint_expr(evaluator, 1) == :((x[$x]^2 + 1) - 1.0 >= 0.0) + @test MOI.constraint_expr(evaluator, 1) == :(x[$x]^2 + 1 >= 1.0) return end function test_add_constraint_equal_to() model = Nonlinear.Model() x = MOI.VariableIndex(1) - input = :($x^2 + 1 == 1.0) - c = Nonlinear.add_constraint(model, input) - @test model[c].set == MOI.EqualTo(0.0) + func, set = :($x^2 + 1), MOI.EqualTo(1.0) + c = Nonlinear.add_constraint(model, func, set) + @test model[c].set == set evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.constraint_expr(evaluator, 1) == :((x[$x]^2 + 1) - 1.0 == 0.0) + @test MOI.constraint_expr(evaluator, 1) == :(x[$x]^2 + 1 == 1.0) return end function test_add_constraint_interval() model = Nonlinear.Model() x = MOI.VariableIndex(1) - input = :(-1.0 <= $x^2 + 1 <= 1.0) - c = Nonlinear.add_constraint(model, input) - @test model[c].set == MOI.Interval(-1.0, 1.0) + func, set = :($x^2 + 1), MOI.Interval(-1.0, 1.0) + c = Nonlinear.add_constraint(model, func, set) + @test model[c].set == set evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) @test MOI.constraint_expr(evaluator, 1) == :(-1.0 <= x[$x]^2 + 1 <= 1.0) return end -function test_add_constraint_interval_normalize() - model = Nonlinear.Model() - x = MOI.VariableIndex(1) - input = :(-1.0 + $x <= $x^2 + 1 <= 1.0) - @test_throws(ErrorException, Nonlinear.add_constraint(model, input)) - return -end - function test_eval_univariate_function() r = Nonlinear.OperatorRegistry() @test Nonlinear.eval_univariate_function(r, :+, 1.0) == 1.0 @@ -783,7 +764,10 @@ end function test_add_constraint_ordinal_index() model = Nonlinear.Model() x = MOI.VariableIndex(1) - constraints = [Nonlinear.add_constraint(model, :($x <= $i)) for i in 1:4] + constraints = [ + Nonlinear.add_constraint(model, :($x), MOI.LessThan(1.0 * i)) for + i in 1:4 + ] for i in 1:4 @test MOI.is_valid(model, constraints[i]) end