Skip to content
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

Add ScalarNonlinearFunction support #2059

Merged
merged 35 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
021c39c
WIP: explore ScalarNonlinearFunction support
odow Dec 8, 2022
7f658fd
Add UserDefinedFunction attribute
odow Dec 13, 2022
b6a9fdb
Add VectorNonlinearFunction
odow Feb 1, 2023
6ef997d
Revert VectorNonlinearFunction
odow Feb 21, 2023
fcf50c6
Refactor
odow Mar 3, 2023
ec4018a
Fix formatting
odow Mar 3, 2023
207da14
Update docstrings
odow Mar 3, 2023
51de683
Add test for nonlinear duals
odow Mar 3, 2023
50d48ef
Add operators to docstring
odow Mar 3, 2023
5c93afb
Updates
odow Mar 3, 2023
36c9691
Fix docs
odow Mar 3, 2023
17bb849
Apply suggestions from code review
odow Mar 3, 2023
c0b1632
More tweaks
odow Mar 3, 2023
4e6a810
Add more tests
odow Mar 6, 2023
5708f06
Fix formatting
odow Mar 6, 2023
7912fec
Fix docs
odow Mar 6, 2023
5d2a5ca
Drop type parameter in ScalarNonlinearFunction
odow Mar 6, 2023
b643b1a
Update
odow Mar 6, 2023
4e60dc2
More code coverage
odow Mar 7, 2023
7cc4c75
Fix tests
odow Mar 7, 2023
4009904
Test ListOfSupportedNonlinearOperators
odow Mar 7, 2023
700895f
More docstrings
odow Mar 7, 2023
fce34aa
Apply suggestions from code review
odow Mar 7, 2023
ba54d06
Fix printing
odow Mar 7, 2023
283f74a
Updates
odow Mar 8, 2023
3908638
Fix formatting
odow Mar 8, 2023
3b2c61f
Fix typo
odow Mar 8, 2023
4f22a7b
ListOfSupportedNonlinearOperators is optimizer attribute
odow Mar 15, 2023
67a9034
More parsing of ScalarNonlinearFunction
odow Apr 13, 2023
59c68ba
Fix changing objective to nonlinear
odow Apr 26, 2023
e3b3c39
Update
odow May 2, 2023
f080282
Update
odow May 3, 2023
098c85d
Update
odow May 4, 2023
9b227af
Add another promote_operation method
odow May 19, 2023
95adb08
Add more docs for UserDefinedFunction
odow May 28, 2023
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
1 change: 1 addition & 0 deletions docs/src/manual/standard_form.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The function types implemented in MathOptInterface.jl are:
| [`VariableIndex`](@ref) | ``x_j``, the projection onto a single coordinate defined by a variable index ``j``. |
| [`VectorOfVariables`](@ref) | The projection onto multiple coordinates (that is, extracting a sub-vector). |
| [`ScalarAffineFunction`](@ref) | ``a^T x + b``, where ``a`` is a vector and ``b`` scalar. |
| [`ScalarNonlinearFunction`](@ref) | ``f(x)``, where ``f`` is a nonlinear function. |
| [`VectorAffineFunction`](@ref) | ``A x + b``, where ``A`` is a matrix and ``b`` is a vector. |
| [`ScalarQuadraticFunction`](@ref) | ``\frac{1}{2} x^T Q x + a^T x + b``, where ``Q`` is a symmetric matrix, ``a`` is a vector, and ``b`` is a constant. |
| [`VectorQuadraticFunction`](@ref) | A vector of scalar-valued quadratic functions. |
Expand Down
1 change: 1 addition & 0 deletions docs/src/reference/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ ModifyObjectiveNotAllowed
DeleteNotAllowed
UnsupportedSubmittable
SubmitNotAllowed
UnsupportedNonlinearOperator
```

Note that setting the [`ConstraintFunction`](@ref) of a [`VariableIndex`](@ref)
Expand Down
2 changes: 2 additions & 0 deletions docs/src/reference/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ ListOfOptimizerAttributesSet
ListOfModelAttributesSet
ListOfVariableAttributesSet
ListOfConstraintAttributesSet
UserDefinedFunction
ListOfSupportedNonlinearOperators
```

## Optimizer interface
Expand Down
1 change: 1 addition & 0 deletions docs/src/reference/standard_form.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ ScalarAffineTerm
ScalarAffineFunction
ScalarQuadraticTerm
ScalarQuadraticFunction
ScalarNonlinearFunction
```

## Vector functions
Expand Down
8 changes: 6 additions & 2 deletions src/Bridges/Objective/bridges/slack.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function bridge_objective(
end
constraint = MOI.Utilities.normalize_and_add_constraint(model, f, set)
MOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), slack)
return SlackBridge{T,F,G}(slack, constraint, MOI.constant(f))
return SlackBridge{T,F,G}(slack, constraint, MOI.constant(f, T))
end

function supports_objective_function(
Expand Down Expand Up @@ -166,7 +166,11 @@ function MOI.get(
bridge::SlackBridge{T,F,G},
) where {T,F,G<:MOI.AbstractScalarFunction}
func = MOI.get(model, MOI.ConstraintFunction(), bridge.constraint)
f = MOI.Utilities.operate(+, T, func, bridge.constant)
f = if !iszero(bridge.constant)
MOI.Utilities.operate(+, T, func, bridge.constant)
else
func
end
g = MOI.Utilities.remove_variable(f, bridge.slack)
return MOI.Utilities.convert_approx(G, g)
end
4 changes: 4 additions & 0 deletions src/Nonlinear/model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,7 @@ function evaluate(
end
return storage[1]
end

function MOI.get(model::Model, attr::MOI.ListOfSupportedNonlinearOperators)
return MOI.get(model.operators, attr)
end
60 changes: 60 additions & 0 deletions src/Nonlinear/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,60 @@ end
DEFAULT_UNIVARIATE_OPERATORS

The list of univariate operators that are supported by default.

## Example

```jldoctest
julia> import MathOptInterface as MOI

julia> MOI.Nonlinear.DEFAULT_UNIVARIATE_OPERATORS
72-element Vector{Symbol}:
:+
:-
:abs
:sqrt
:cbrt
:abs2
:inv
:log
:log10
:log2
:airybi
:airyaiprime
:airybiprime
:besselj0
:besselj1
:bessely0
:bessely1
:erfcx
:dawson
```
"""
const DEFAULT_UNIVARIATE_OPERATORS = first.(SYMBOLIC_UNIVARIATE_EXPRESSIONS)

"""
DEFAULT_MULTIVARIATE_OPERATORS

The list of multivariate operators that are supported by default.

## Example

```jldoctest
julia> import MathOptInterface as MOI

julia> MOI.Nonlinear.DEFAULT_MULTIVARIATE_OPERATORS
9-element Vector{Symbol}:
:+
:-
:*
:^
:/
:ifelse
:atan
:min
:max
```
"""
const DEFAULT_MULTIVARIATE_OPERATORS =
[:+, :-, :*, :^, :/, :ifelse, :atan, :min, :max]
Expand Down Expand Up @@ -140,6 +187,19 @@ struct OperatorRegistry
end
end

function MOI.get(
registry::OperatorRegistry,
::MOI.ListOfSupportedNonlinearOperators,
)
ops = vcat(
registry.univariate_operators,
registry.multivariate_operators,
registry.logic_operators,
registry.comparison_operators,
)
return unique(ops)
end

const _FORWARD_DIFF_METHOD_ERROR_HELPER = raw"""
Common reasons for this include:

Expand Down
76 changes: 75 additions & 1 deletion src/Nonlinear/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,58 @@ function parse_expression(::Model, ::Expression, x::Any, ::Int)
)
end

function parse_expression(
data::Model,
expr::Expression,
x::MOI.ScalarNonlinearFunction,
parent_index::Int,
)
stack = Tuple{Int,Any}[(parent_index, x)]
while !isempty(stack)
parent_node, arg = pop!(stack)
if arg isa MOI.ScalarNonlinearFunction
_parse_without_recursion_inner(stack, data, expr, arg, parent_node)
else
# We can use recursion here, because ScalarNonlinearFunction only
# occur in other ScalarNonlinearFunction.
parse_expression(data, expr, arg, parent_node)
end
end
return
end

function _get_node_type(data, x)
id = get(data.operators.univariate_operator_to_id, x.head, nothing)
if length(x.args) == 1 && id !== nothing
return id, MOI.Nonlinear.NODE_CALL_UNIVARIATE
end
id = get(data.operators.multivariate_operator_to_id, x.head, nothing)
if id !== nothing
return id, MOI.Nonlinear.NODE_CALL_MULTIVARIATE
end
id = get(data.operators.comparison_operator_to_id, x.head, nothing)
if id !== nothing
return id, MOI.Nonlinear.NODE_COMPARISON
end
id = get(data.operators.logic_operator_to_id, x.head, nothing)
if id !== nothing
return id, MOI.Nonlinear.NODE_LOGIC
end
return throw(MOI.UnsupportedNonlinearOperator(x.head))
end

function _parse_without_recursion_inner(stack, data, expr, x, parent)
id, node_type = _get_node_type(data, x)
push!(expr.nodes, Node(node_type, id, parent))
parent = length(expr.nodes)
# Args need to be pushed onto the stack in reverse because the stack is a
# first-in last-out datastructure.
for arg in reverse(x.args)
push!(stack, (parent, arg))
end
return
end

function parse_expression(
data::Model,
expr::Expression,
Expand Down Expand Up @@ -108,7 +160,7 @@ function _parse_univariate_expression(
_parse_multivariate_expression(stack, data, expr, x, parent_index)
return
end
error("Unable to parse: $x")
throw(MOI.UnsupportedNonlinearOperator(x.args[1]))
end
push!(expr.nodes, Node(NODE_CALL_UNIVARIATE, id, parent_index))
push!(stack, (length(expr.nodes), x.args[2]))
Expand Down Expand Up @@ -200,6 +252,28 @@ function parse_expression(
return
end

function parse_expression(
data::Model,
expr::Expression,
x::MOI.ScalarAffineFunction,
parent_index::Int,
)
f = convert(MOI.ScalarNonlinearFunction, x)
parse_expression(data, expr, f, parent_index)
return
end

function parse_expression(
data::Model,
expr::Expression,
x::MOI.ScalarQuadraticFunction,
parent_index::Int,
)
f = convert(MOI.ScalarNonlinearFunction, x)
parse_expression(data, expr, f, parent_index)
return
end

function parse_expression(::Model, expr::Expression, x::Real, parent_index::Int)
push!(expr.values, convert(Float64, x)::Float64)
push!(expr.nodes, Node(NODE_VALUE, length(expr.values), parent_index))
Expand Down
18 changes: 17 additions & 1 deletion src/Test/test_basic_constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,17 @@ function _function(
)
end

function _function(
::Type{T},
::Type{MOI.ScalarNonlinearFunction},
x::Vector{MOI.VariableIndex},
) where {T}
return MOI.ScalarNonlinearFunction(
:+,
Any[MOI.ScalarNonlinearFunction(:^, Any[xi, 2]) for xi in x],
)
end

# Default fallback.
_set(::Any, ::Type{S}) where {S} = _set(S)

Expand Down Expand Up @@ -316,7 +327,12 @@ for s in [
]
S = getfield(MOI, s)
functions = if S <: MOI.AbstractScalarSet
(:VariableIndex, :ScalarAffineFunction, :ScalarQuadraticFunction)
(
:VariableIndex,
:ScalarAffineFunction,
:ScalarQuadraticFunction,
:ScalarNonlinearFunction,
)
else
(:VectorOfVariables, :VectorAffineFunction, :VectorQuadraticFunction)
end
Expand Down
Loading