# What is the difference between nonlinear "functions with vector outputs" and nonlinear "operators with vector outputs"?

**URL:** https://discourse.julialang.org/t/what-is-the-difference-between-nonlinear-functions-with-vector-outputs-and-nonlinear-operators-with-vector-outputs/105012
**Category:** Optimization (Mathematical)
**Tags:** jump, vector
**Created:** [October 16, 2023, 12:58pm UTC](https://discourse.julialang.org/t/what-is-the-difference-between-nonlinear-functions-with-vector-outputs-and-nonlinear-operators-with-vector-outputs/105012 "2023-10-16T12:58:35Z")
**Posts on this page:** 3
**Page:** 1

<div class="post-metadata">

### Author: ![justinberi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/justinberi/32/202466_2.png) [@justinberi](https://discourse.julialang.org/u/justinberi)
#### Post date: [October 16, 2023, 12:58pm UTC](https://discourse.julialang.org/t/what-is-the-difference-between-nonlinear-functions-with-vector-outputs-and-nonlinear-operators-with-vector-outputs/105012/1 "2023-10-16T12:58:35Z")

</div>

The JuMP [documentation](https://jump.dev/JuMP.jl/stable/tutorials/nonlinear/tips_and_tricks/#User-defined-operators-with-vector-outputs) and [this answer](https://discourse.julialang.org/t/constraint-with-forwarddiff-gradient/104949/2) suggests that nonlinear user-defined **functions** with vector outputs need to be defined using many `@operators`. _The documentation actually says “User-defined operators …”._

[This post](https://discourse.julialang.org/t/how-to-add-user-defined-nonlinear-functions-as-constraints-with-jump/11436) suggest that nonlinear functions can be used directly. _The date on that post and syntax suggest an old version of JuMP._

The example below also suggest that nonlinear functions can be used directly.

This works

```julia
import Pkg; Pkg.activate(temp=true)
Pkg.add(name="JuMP", version="1.15.1");
Pkg.add("Ipopt");
Pkg.instantiate();

import JuMP
import Ipopt

model = JuMP.Model(Ipopt.Optimizer)

function dy(x)
    return [cos(x[1])*x[2]; sin(x[1]) + sin(x[2])]
end

x0 = [.1;.1]
xc = [1;0.5]

JuMP.@variable(model, x[i=1:2], start = x0[i])
JuMP.@constraint(model, dy(x) == xc)
JuMP.optimize!(model)

print("x: ", JuMP.value.(x))
print("dy(x): ", dy(JuMP.value.(x)))

```

```julia
...
EXIT: Optimal Solution Found.
x: [-0.39325899095511446, 1.0826434588266454]dy(x): [0.9999999999993564, 0.5000000000001374]

```

So does this

```julia
import Pkg; Pkg.activate(temp=true)
Pkg.add(name="JuMP", version="1.15.1");
Pkg.add("Ipopt");
Pkg.instantiate();

import JuMP
import Ipopt

function dy(x)
    return [cos(x[1])*x[2]; sin(x[1]) + sin(x[2])]
end

x0 = [.1;.1]
xc = [1;0.5]

model = JuMP.Model(Ipopt.Optimizer)
JuMP.@variable(model, x[i=1:2], start = x0[i])

JuMP.@operator(model, op_f1, 2, (x...) -> dy(collect(x))[1])
JuMP.@operator(model, op_f2, 2, (x...) -> dy(collect(x))[2])

JuMP.@constraint(model, op_f1(x...) == xc[1])
JuMP.@constraint(model, op_f2(x...) == xc[2])

JuMP.optimize!(model)

print("x: ", JuMP.value.(x))
print("dy(x): ", dy(JuMP.value.(x)))

```

```julia
...
EXIT: Optimal Solution Found.
x: [-0.39325899095511446, 1.0826434588266454]dy(x): [0.9999999999993564, 0.5000000000001374]

```

Is there a preferred syntax?

Is there a reason to prefer the `@operator` macro over the user-defined function?

If we have a nice conclusion I will open a PR to add to the documentation 😃

PS:  
This [for nonlinear](https://discourse.julialang.org/t/jump-with-nonlinear-vector-constraints/97223) and [this for linear](https://discourse.julialang.org/t/are-vectorized-constraints-no-longer-supported-on-jump/103355) are possibly related.

---

<div class="post-metadata">

### Author: ![justinberi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/justinberi/32/202466_2.png) [@justinberi](https://discourse.julialang.org/u/justinberi)
#### Post date: [October 16, 2023, 1:25pm UTC](https://discourse.julialang.org/t/what-is-the-difference-between-nonlinear-functions-with-vector-outputs-and-nonlinear-operators-with-vector-outputs/105012/2 "2023-10-16T13:25:51Z")

</div>

It is possible that my confusion lies entirely with when to use an operator and is unrelated to “vector outputs”. The below example illustrates the need (?) for an `@operator` in the case of taking a derivative.

This does not work

```julia
import Pkg; Pkg.activate(temp=true)
Pkg.add(name="JuMP", version="1.15.1");
Pkg.add("Ipopt");
Pkg.add("ForwardDiff");
Pkg.instantiate();

import JuMP
import Ipopt
import ForwardDiff as FD

f(x) = FD.derivative(sin,x)

model = JuMP.Model(Ipopt.Optimizer)
JuMP.@variable(model, x, start = 0.4)
JuMP.@constraint(model, f(x) == 0.5)
JuMP.optimize!(model)

print("x: ", JuMP.value.(x))

```

```julia
MethodError: no method matching derivative(::typeof(sin), ::JuMP.VariableRef)

Closest candidates are:
  derivative(::F, ::R) where {F, R<:Real}
   @ ForwardDiff ~/.julia/packages/ForwardDiff/PcZ48/src/derivative.jl:12
  derivative(::Any, ::AbstractArray)
   @ ForwardDiff ~/.julia/packages/ForwardDiff/PcZ48/src/derivative.jl:72
  derivative(::Any, ::AbstractArray, ::Real)
   @ ForwardDiff ~/.julia/packages/ForwardDiff/PcZ48/src/derivative.jl:25
  ...

Stacktrace:
 [1] f(x::JuMP.VariableRef)
   @ Main ./In[2]:11
 [2] macro expansion
   @ ~/.julia/packages/MutableArithmetics/K9YPJ/src/rewrite.jl:321 [inlined]
 [3] macro expansion
   @ ~/.julia/packages/JuMP/ToPd2/src/macros.jl:717 [inlined]
 [4] top-level scope
   @ In[2]:15

```

This works

```julia
import Pkg; Pkg.activate(temp=true)
Pkg.add(name="JuMP", version="1.15.1");
Pkg.add("Ipopt");
Pkg.add("ForwardDiff");
Pkg.instantiate();

import JuMP
import Ipopt
import ForwardDiff as FD

f(x) = FD.derivative(sin,x)

model = JuMP.Model(Ipopt.Optimizer)
JuMP.@operator(model, op_f, 1, f)

JuMP.@variable(model, x, start = 0.4)
JuMP.@constraint(model, op_f(x) == 0.5)
JuMP.optimize!(model)

print("x: ", JuMP.value.(x))

```

```julia
...
EXIT: Optimal Solution Found.
x: 1.0471975562573468

```

The [macro](https://jump.dev/JuMP.jl/stable/api/JuMP/#JuMP.NonlinearOperator) and [struct](https://jump.dev/JuMP.jl/stable/api/JuMP/#JuMP.NonlinearOperator) suggests we need to use an operator " When called with non-JuMP types, the struct returns the evaluation of `func(args...)` .".

So the answer to question might be.

1. vector outputs are fine to use as is for JuMP types.
2. use `@operators` when multiple dispatch has not been implemented for JuMP types.

Or I could be wrong again 😃

---

<div class="post-metadata">

### Author: ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)
#### Post date: [October 16, 2023, 3:13pm UTC](https://discourse.julialang.org/t/what-is-the-difference-between-nonlinear-functions-with-vector-outputs-and-nonlinear-operators-with-vector-outputs/105012/3 "2023-10-16T15:13:57Z")

</div>

You’re on the right track.

For some simple algebraic functions, like your `dy`, you can call it directly, including vector valued outputs. Under the hood, JuMP traces the function to create a vector of nonlinear expressions. (Try calling the function in the REPL.)

For functions that we cannot trace, like your gradient function, you must define it as an operator. We either need to be able to automatically differentiate it, or you need to provide the gradient. User defined operators must return a scalar.

If you have any suggestions for how we could improve the documentation please let me know. Its always hard for me to know what bits are confusing 🙂
