Skip to content

[Nonlinear] change Nonlinear.add_constraint to accept function and set #1818

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/src/submodules/Nonlinear/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/Nonlinear/evaluator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 25 additions & 8 deletions src/Nonlinear/model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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)
Expand All @@ -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)
```
"""
Expand Down Expand Up @@ -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)
Expand Down
35 changes: 0 additions & 35 deletions src/Nonlinear/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
68 changes: 26 additions & 42 deletions test/Nonlinear/Nonlinear.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down