Skip to content

Commit 1f3ca0f

Browse files
committed
Add VectorNonlinearFunction
Add convert and parse
1 parent 15a3913 commit 1f3ca0f

File tree

9 files changed

+189
-3
lines changed

9 files changed

+189
-3
lines changed

docs/src/manual/standard_form.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ The function types implemented in MathOptInterface.jl are:
4242
| [`VectorAffineFunction`](@ref) | ``A x + b``, where ``A`` is a matrix and ``b`` is a vector. |
4343
| [`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. |
4444
| [`VectorQuadraticFunction`](@ref) | A vector of scalar-valued quadratic functions. |
45+
| [`VectorNonlinearFunction`](@ref) | ``f(x)``, where ``f`` is a vector-valued nonlinear function. |
4546

4647
Extensions for nonlinear programming are present but not yet well documented.
4748

docs/src/reference/standard_form.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ ScalarQuadraticTerm
2626
ScalarQuadraticFunction
2727
VectorQuadraticTerm
2828
VectorQuadraticFunction
29+
VectorNonlinearFunction
2930
```
3031

3132
### Utilities

src/Nonlinear/parse.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,28 @@ function parse_expression(
239239
return
240240
end
241241

242+
function parse_expression(
243+
data::Model,
244+
expr::Expression,
245+
x::MOI.ScalarAffineFunction{T},
246+
parent::Int,
247+
) where {T}
248+
f = convert(MOI.ScalarNonlinearFunction{T}, x)
249+
parse_expression(data, expr, f, parent)
250+
return
251+
end
252+
253+
function parse_expression(
254+
data::Model,
255+
expr::Expression,
256+
x::MOI.ScalarQuadraticFunction{T},
257+
parent::Int,
258+
) where {T}
259+
f = convert(MOI.ScalarNonlinearFunction{T}, x)
260+
parse_expression(data, expr, f, parent)
261+
return
262+
end
263+
242264
function parse_expression(::Model, expr::Expression, x::Real, parent_index::Int)
243265
push!(expr.values, convert(Float64, x)::Float64)
244266
push!(expr.nodes, Node(NODE_VALUE, length(expr.values), parent_index))

src/Test/test_basic_constraint.jl

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,18 @@ function _function(
7777
)
7878
end
7979

80+
function _function(
81+
::Type{T},
82+
::Type{MOI.VectorNonlinearFunction},
83+
x::Vector{MOI.VariableIndex},
84+
) where {T}
85+
f = MOI.ScalarNonlinearFunction{T}(
86+
:+,
87+
Any[MOI.ScalarNonlinearFunction{T}(:^, Any[xi, 2]) for xi in x],
88+
)
89+
return MOI.VectorNonlinearFunction{T}(Any[f; x])
90+
end
91+
8092
# Default fallback.
8193
_set(::Any, ::Type{S}) where {S} = _set(S)
8294

@@ -324,7 +336,12 @@ for s in [
324336
:ScalarNonlinearFunction,
325337
)
326338
else
327-
(:VectorOfVariables, :VectorAffineFunction, :VectorQuadraticFunction)
339+
(
340+
:VectorOfVariables,
341+
:VectorAffineFunction,
342+
:VectorQuadraticFunction,
343+
:VectorNonlinearFunction,
344+
)
328345
end
329346
for f in functions
330347
func = Symbol("test_basic_$(f)_$(s)")

src/Utilities/functions.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,15 @@ function map_indices(
216216
)
217217
end
218218

219+
function map_indices(
220+
index_map::F,
221+
f::MOI.VectorNonlinearFunction{T},
222+
) where {F<:Function,T}
223+
return MOI.VectorNonlinearFunction{T}(
224+
Any[map_indices(index_map, arg) for arg in f.args],
225+
)
226+
end
227+
219228
# Function changes
220229

221230
function map_indices(
@@ -516,6 +525,8 @@ See also [`scalarize`](@ref).
516525
"""
517526
eachscalar(f::MOI.AbstractVectorFunction) = ScalarFunctionIterator(f)
518527

528+
eachscalar(f::MOI.VectorNonlinearFunction) = f.args
529+
519530
"""
520531
eachscalar(f::MOI.AbstractVector)
521532
@@ -818,6 +829,7 @@ function canonicalize!(
818829
end
819830

820831
canonicalize!(f::MOI.ScalarNonlinearFunction) = f
832+
canonicalize!(f::MOI.VectorNonlinearFunction) = f
821833

822834
"""
823835
canonicalize!(f::Union{ScalarQuadraticFunction, VectorQuadraticFunction})
@@ -3355,6 +3367,15 @@ end
33553367

33563368
is_coefficient_type(::Type{<:MOI.ScalarNonlinearFunction}, ::Type) = false
33573369

3370+
function is_coefficient_type(
3371+
::Type{MOI.VectorNonlinearFunction{T}},
3372+
::Type{T},
3373+
) where {T}
3374+
return true
3375+
end
3376+
3377+
is_coefficient_type(::Type{<:MOI.VectorNonlinearFunction}, ::Type) = false
3378+
33583379
function similar_type(::Type{<:MOI.ScalarAffineFunction}, ::Type{T}) where {T}
33593380
return MOI.ScalarAffineFunction{T}
33603381
end

src/Utilities/model.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -820,7 +820,11 @@ const LessThanIndicatorZero{T} =
820820
MOI.ScalarNonlinearFunction,
821821
),
822822
(MOI.VectorOfVariables,),
823-
(MOI.VectorAffineFunction, MOI.VectorQuadraticFunction)
823+
(
824+
MOI.VectorAffineFunction,
825+
MOI.VectorQuadraticFunction,
826+
MOI.VectorNonlinearFunction,
827+
)
824828
)
825829

826830
@doc raw"""

src/Utilities/parser.jl

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ function _parse_function(ex, ::Type{T} = Float64) where {T}
115115
else
116116
if isexpr(ex, :call, 2) && ex.args[1] == :ScalarNonlinearFunction
117117
return ex
118+
elseif isexpr(ex, :call, 2) && ex.args[1] == :VectorNonlinearFunction
119+
return ex
118120
end
119121
# For simplicity, only accept Expr(:call, :+, ...); no recursive
120122
# expressions
@@ -240,12 +242,24 @@ _parsed_to_moi(model, s::Number) = s
240242

241243
function _parsed_to_moi(model, s::Expr)
242244
if isexpr(s, :call, 2) && s.args[1] == :ScalarNonlinearFunction
243-
return _parsed_to_moi(model, s.args[2])
245+
return _parsed_scalar_to_moi(model, s.args[2])
246+
elseif isexpr(s, :call, 2) && s.args[1] == :VectorNonlinearFunction
247+
return _parsed_vector_to_moi(model, s.args[2])
244248
end
245249
args = Any[_parsed_to_moi(model, arg) for arg in s.args[2:end]]
246250
return MOI.ScalarNonlinearFunction{Float64}(s.args[1], args)
247251
end
248252

253+
function _parsed_scalar_to_moi(model, s::Expr)
254+
args = Any[_parsed_to_moi(model, arg) for arg in s.args[2:end]]
255+
return MOI.ScalarNonlinearFunction{Float64}(s.args[1], args)
256+
end
257+
258+
function _parsed_vector_to_moi(model, s::Expr)
259+
args = Any[_parsed_to_moi(model, arg) for arg in s.args]
260+
return MOI.VectorNonlinearFunction{Float64}(args)
261+
end
262+
249263
for typename in [
250264
:_ParsedScalarAffineTerm,
251265
:_ParsedScalarAffineFunction,

src/Utilities/print.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ _to_string(::_PrintOptions, ::typeof(in)) = @static Sys.iswindows() ? "in" : "
7373
# Functions
7474
#------------------------------------------------------------------------
7575

76+
function _to_string(options::_PrintOptions, ::MOI.ModelLike, c::Real)
77+
return _shorten(options, c)
78+
end
79+
7680
function _to_string(
7781
options::_PrintOptions,
7882
model::MOI.ModelLike,

src/functions.jl

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,46 @@ struct ScalarNonlinearFunction{T} <: AbstractScalarFunction
249249
args::Vector{Any}
250250
end
251251

252+
"""
253+
VectorNonlinearFunction{T<:Number}(args::Vector{Any})
254+
255+
The vector-valued nonlinear function composed of a vector of scalar functions.
256+
257+
## `args`
258+
259+
The vector `args` contains the scalar elements of the nonlinear function. Each
260+
element must be one of the folowing:
261+
262+
* A number of type `T`
263+
* A variable of type [`VariableIndex`](@ref)
264+
* A [`ScalarAffineFunction`](@ref)
265+
* A [`ScalarQuadraticFunction`](@ref)
266+
* A [`ScalarNonlinearFunction`](@ref)
267+
268+
## Example
269+
270+
To represent the function ``f(x) = [sin(x)^2, x]``, do:
271+
272+
```jldoctest
273+
julia> using MathOptInterface; const MOI = MathOptInterface;
274+
275+
julia> x = MOI.VariableIndex(1)
276+
MathOptInterface.VariableIndex(1)
277+
278+
julia> g = MOI.ScalarNonlinearFunction{Float64}(
279+
:^,
280+
Any[MOI.ScalarNonlinearFunction{Float64}(:sin, Any[x]), 2],
281+
)
282+
MathOptInterface.ScalarNonlinearFunction{Float64}(:^, Any[MathOptInterface.ScalarNonlinearFunction{Float64}(:sin, Any[MathOptInterface.VariableIndex(1)]), 2])
283+
284+
julia> MOI.VectorNonlinearFunction{Float64}(Any[g, x])
285+
MathOptInterface.VectorNonlinearFunction{Float64}(Any[MathOptInterface.ScalarNonlinearFunction{Float64}(:^, Any[MathOptInterface.ScalarNonlinearFunction{Float64}(:sin, Any[MathOptInterface.VariableIndex(1)]), 2]), MathOptInterface.VariableIndex(1)])
286+
```
287+
"""
288+
struct VectorNonlinearFunction{T} <: AbstractVectorFunction
289+
args::Vector{Any}
290+
end
291+
252292
# Function modifications
253293

254294
"""
@@ -473,6 +513,22 @@ function Base.isapprox(
473513
return true
474514
end
475515

516+
function Base.isapprox(
517+
f::VectorNonlinearFunction{T},
518+
g::VectorNonlinearFunction{T};
519+
kwargs...,
520+
) where {T}
521+
if length(f.args) != length(g.args)
522+
return false
523+
end
524+
for (fi, gi) in zip(f.args, g.args)
525+
if !isapprox(fi, gi; kwargs...)
526+
return false
527+
end
528+
end
529+
return true
530+
end
531+
476532
function constant(f::Union{ScalarAffineFunction,ScalarQuadraticFunction}, ::Any)
477533
return constant(f)
478534
end
@@ -536,6 +592,10 @@ function Base.copy(f::ScalarNonlinearFunction{T}) where {T}
536592
return ScalarNonlinearFunction{T}(f.head, copy(f.args))
537593
end
538594

595+
function Base.copy(f::VectorNonlinearFunction{T}) where {T}
596+
return VectorNonlinearFunction{T}(copy(f.args))
597+
end
598+
539599
# Define shortcuts for
540600
# VariableIndex -> ScalarAffineFunction
541601
function ScalarAffineFunction{T}(f::VariableIndex) where {T}
@@ -713,3 +773,45 @@ function Base.convert(
713773
[g.constant],
714774
)
715775
end
776+
777+
function Base.convert(
778+
::Type{ScalarNonlinearFunction{T}},
779+
term::ScalarAffineTerm{T},
780+
) where {T}
781+
return ScalarNonlinearFunction{T}(:*, Any[term.coefficient, term.variable])
782+
end
783+
784+
function Base.convert(
785+
F::Type{ScalarNonlinearFunction{T}},
786+
f::ScalarAffineFunction{T},
787+
) where {T}
788+
args = Any[convert(ScalarNonlinearFunction{T}, term) for term in f.terms]
789+
if !iszero(f.constant)
790+
push!(args, f.constant)
791+
end
792+
return ScalarNonlinearFunction{T}(:+, args)
793+
end
794+
795+
function Base.convert(
796+
::Type{ScalarNonlinearFunction{T}},
797+
term::ScalarQuadraticTerm{T},
798+
) where {T}
799+
return ScalarNonlinearFunction{T}(
800+
:*,
801+
Any[term.coefficient, term.variable_1, term.variable_2],
802+
)
803+
end
804+
805+
function Base.convert(
806+
F::Type{ScalarNonlinearFunction{T}},
807+
f::ScalarQuadraticFunction{T},
808+
) where {T}
809+
args = Any[convert(F, term) for term in f.quadratic_terms]
810+
for term in f.affine_terms
811+
push!(args, convert(F, term))
812+
end
813+
if !iszero(f.constant)
814+
push!(args, f.constant)
815+
end
816+
return ScalarNonlinearFunction{T}(:+, args)
817+
end

0 commit comments

Comments
 (0)