Skip to content

Commit 57c0af0

Browse files
committed
Updates from mlubin
1 parent 14200ce commit 57c0af0

File tree

5 files changed

+76
-42
lines changed

5 files changed

+76
-42
lines changed

docs/src/submodules/Nonlinear/overview.md

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ DocTestFilters = [r"MathOptInterface|MOI"]
1515
MathOptInterface.
1616

1717
The `Nonlinear` submodule contains data structures and functions for
18-
working with a nonlinear program in the form of an expression tree. This page
19-
explains the API and describes the rationale behind its design.
18+
working with a nonlinear optimization problem in the form of an expression
19+
graph. This page explains the API and describes the rationale behind its design.
2020

2121
## Standard form
2222

@@ -49,12 +49,13 @@ nonlinear information added to the model.
4949

5050
### Decision variables
5151

52-
Decision variables are represented by [`VariableIndex`](@ref)s. The user is
53-
responsible for creating these.
52+
Decision variables are represented by [`VariableIndex`](@ref)es. The user is
53+
responsible for creating these using `MOI.VariableIndex(i)`, where `i` is the
54+
column associated with the variable.
5455

5556
### [Expressions](@id Nonlinear_Expressions)
5657

57-
The input data-structure is a Julia `Expr`. The input expressions can
58+
The input data structure is a Julia `Expr`. The input expressions can
5859
incorporate [`VariableIndex`](@ref)es, but these must be interpolated into
5960
the expression with `$`:
6061
```jldoctest nonlinear_developer
@@ -83,9 +84,9 @@ then be interpolated into other input expressions.
8384
### [Parameters](@id Nonlinear_Parameters)
8485

8586
In addition to constant literals like `1` or `1.23`, you can create parameters.
86-
Parameter are constants that you can change before passing the expression to the
87-
solver. Create a parameter using [`Nonlinear.add_parameter`](@ref), which
88-
accepts a default value:
87+
Parameters are placeholders whose values can change before passing the
88+
expression to the solver. Create a parameter using
89+
[`Nonlinear.add_parameter`](@ref), which accepts a default value:
8990
```jldoctest nonlinear_developer
9091
julia> p = Nonlinear.add_parameter(data, 1.23)
9192
MathOptInterface.Nonlinear.ParameterIndex(1)
@@ -111,6 +112,10 @@ Set a nonlinear objective using [`Nonlinear.set_objective`](@ref):
111112
```jldoctest nonlinear_developer
112113
julia> Nonlinear.set_objective(data, :($p + $expr + $x))
113114
```
115+
Clear a nonlinear objective by passing `nothing`:
116+
```jldoctest nonlinear_developer
117+
julia> Nonlinear.set_objective(data, nothing)
118+
```
114119

115120
### [Constraints](@id Nonlinear_Constraints)
116121

@@ -186,7 +191,7 @@ MathOptInterface.Nonlinear.ExpressionIndex(3)
186191
```
187192
By default, `Nonlinear` will compute the gradient of the registered
188193
operator using `ForwardDiff.jl`. (Hessian information is not supported.)
189-
Over-ride this by passing a function to compute the gradient:
194+
Override this by passing a function to compute the gradient:
190195
```jldoctest nonlinear_developer
191196
julia> function ∇g(ret, x...)
192197
ret[1] = 2 * x[1] + x[2]
@@ -200,43 +205,61 @@ julia> Nonlinear.register_operator(data, :my_g2, 2, g, ∇g)
200205

201206
### [MathOptInterface](@id Nonlinear_MOI_interface)
202207

203-
`Nonlinear` implements the MathOptInterface API to allow solvers to query the
204-
function and derivative information of our nonlinear model `data`. However,
205-
before we can call [`initialize`](@ref), we need to set an
206-
[`Nonlinear.AbstractAutomaticDifferentiation`](@ref).
208+
MathOptInterface communicates the nonlinear portion of an optimization problem
209+
to solvers using concrete subtypes of [`AbstractNLPEvaluator`](@ref), which
210+
implement the [Nonlinear programming](@ref) API.
211+
212+
[`NonlinearData`](@ref) is a subtype of [`AbstractNLPEvaluator`](@ref), but the
213+
functions of the [Nonlinear programming](@ref) API that it implements depends
214+
upon the chosen [`Nonlinear.AbstractAutomaticDifferentiation`](@ref) backend.
207215

208216
There are two to choose from within MOI, although other packages may add more
209217
options by sub-typing [`Nonlinear.AbstractAutomaticDifferentiation`](@ref):
210218
* [`Nonlinear.ExprGraphOnly`](@ref)
211219

220+
Set the differentiation backend using [`Nonlinear.set_differentiation_backend`](@ref).
212221
If we set [`Nonlinear.ExprGraphOnly`](@ref), then we get access to `:ExprGraph`:
213222
```jldoctest nonlinear_developer
214-
julia> Nonlinear.set_differentiation_backend(data, Nonlinear.ExprGraphOnly(), [x])
223+
julia> Nonlinear.set_differentiation_backend(
224+
data,
225+
Nonlinear.ExprGraphOnly(),
226+
[x],
227+
)
215228
216229
julia> data
217230
NonlinearData with available features:
218231
* :ExprGraph
219232
```
220-
!!! note
221-
[`Nonlinear.set_differentiation_backend`](@ref) requires an ordered list of
222-
the variables that are included in the model. This order corresponds to the
223-
the order of the primal decision vector `x` which is passed to the various
224-
functions in MOI's nonlinear API.
233+
234+
[`Nonlinear.set_differentiation_backend`](@ref) requires an ordered list of the
235+
variables that are included in the model. This order corresponds to the order of
236+
the primal decision vector `x` which is passed to the various functions in MOI's
237+
nonlinear API.
225238

226239
The `:ExprGraph` feature means we can call [`objective_expr`](@ref) and
227240
[`constraint_expr`](@ref) to retrieve the expression graph of the problem.
228241
However, we cannot call gradient terms such as
229242
[`eval_objective_gradient`](@ref) because [`Nonlinear.ExprGraphOnly`](@ref) does
230-
not know how to differentiate a nonlinear expression.
243+
not have the capability to differentiate a nonlinear expression.
244+
245+
Instead of passing [`AbstractNLPEvaluator`](@ref)s directly to solvers,
246+
MathOptInterface instead passes an [`NLPBlockData`](@ref), which wraps an
247+
[`AbstractNLPEvaluator`](@ref) and includes other information such as constraint
248+
bounds and whether the evaluator has a nonlinear objective. Create an
249+
`NLPBlockData`](@ref) as follows:
250+
```jldoctest nonlinear_developer
251+
julia> MOI.NLPBlockData(data)
252+
```
231253

232254
## Expression-graph representation
233255

234256
[`Nonlinear.NonlinearData`](@ref) stores nonlinear expressions in
235257
[`Nonlinear.NonlinearExpression`](@ref)s. This section explains the design of
236258
the expression graph datastructure in [`Nonlinear.NonlinearExpression`](@ref).
237259

238-
Given a nonlinear function like `f(x) = sin(x)^2 + x`, the first step is to
239-
convert it into [Polish prefix notation](https://en.wikipedia.org/wiki/Polish_notation):
260+
Given a nonlinear function like `f(x) = sin(x)^2 + x`, a conceptual aid for
261+
thinking about the graph representation of the expression is to convert it into
262+
[Polish prefix notation](https://en.wikipedia.org/wiki/Polish_notation):
240263
```
241264
f(x, y) = (+ (^ (sin x) 2) x)
242265
```
@@ -365,7 +388,7 @@ each expression, `nodes` and `values`, as well as two constant vectors for the
365388
For our third goal, it is not easy to identify the children of a node, but it is
366389
easy to identify the _parent_ of any node. Therefore, we can use
367390
[`Nonlinear.adjacency_matrix`](@ref) to compute a sparse matrix that maps
368-
children to their parents.
391+
parents to their children.
369392

370393
The tape is also ordered topologically, so that a reverse pass of the nodes
371394
evaluates all children nodes before their parent.
@@ -374,7 +397,7 @@ evaluates all children nodes before their parent.
374397

375398
In practice, `Node` and `NonlinearExpression` are exactly [`Nonlinear.Node`](@ref)
376399
and [`Nonlinear.NonlinearExpression`](@ref). However, [`Nonlinear.NodeType`](@ref)
377-
has more terms to account for comparison operators such as `:>=` and `:<=`,
400+
has more fields to account for comparison operators such as `:>=` and `:<=`,
378401
logic operators such as `:&&` and `:||`, nonlinear parameters, and nested
379402
subexpressions.
380403

docs/src/submodules/Nonlinear/reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,5 @@ Nonlinear.NonlinearConstraint
8181
Nonlinear.adjacency_matrix
8282
Nonlinear.parse_expression
8383
Nonlinear.convert_to_expr
84+
Nonlinear.ordinal_index
8485
```

src/Nonlinear/Nonlinear.jl

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,20 +126,23 @@ function delete(data::NonlinearData, c::ConstraintIndex)
126126
end
127127

128128
"""
129-
row(data::NonlinearData, c::ConstraintIndex)::Int
129+
ordinal_index(data::NonlinearData, c::ConstraintIndex)::Int
130130
131-
Return the 1-indexed row of the constraint index `c` in `data`.
131+
Return the 1-indexed value of the constraint index `c` in `data`.
132132
133133
## Examples
134134
135135
```julia
136136
data = NonlinearData()
137137
x = MOI.VariableIndex(1)
138-
c = add_constraint(data, :(\$x^2 <= 1))
139-
row(data, c) # Returns 1
138+
c1 = add_constraint(data, :(\$x^2 <= 1))
139+
c2 = add_constraint(data, :(\$x^2 <= 1))
140+
ordinal_index(data, c2) # Returns 2
141+
delete(data, c1)
142+
ordinal_index(data, c2) # Returns 1
140143
```
141144
"""
142-
function row(data::NonlinearData, c::ConstraintIndex)
145+
function ordinal_index(data::NonlinearData, c::ConstraintIndex)
143146
# TODO(odow): replace with a cache that maps indices to their 1-indexed
144147
# row in the constraint matrix. But failing that, since we know that
145148
# constraints are added in increasing order and that they can be deleted, we
@@ -286,7 +289,12 @@ function MOI.initialize(data::NonlinearData, features::Vector{Symbol})
286289
end
287290

288291
function MOI.objective_expr(data::NonlinearData)
289-
@assert data.objective !== nothing
292+
if data.objective === nothing
293+
error(
294+
"Unable to query objective_expr because no nonlinear objective " *
295+
"was set",
296+
)
297+
end
290298
return convert_to_expr(data, data.objective; moi_output_format = true)
291299
end
292300

src/Nonlinear/types.jl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -223,12 +223,13 @@ function set_differentiation_backend(
223223
return
224224
end
225225

226-
function MOI.NLPBlockData(
227-
data::NonlinearData,
228-
x::Vector{MOI.VariableIndex},
229-
backend::AbstractAutomaticDifferentiation = ExprGraphOnly(),
230-
)
231-
set_differentiation_backend(data, backend, x)
226+
"""
227+
MOI.NLPBlockData(data::NonlinearData)
228+
229+
Create an [`MOI.NLPBlockData`](@ref) object from a [`NonlinearData`](@ref)
230+
object.
231+
"""
232+
function MOI.NLPBlockData(data::NonlinearData)
232233
return MOI.NLPBlockData(
233234
[_bound(c.set) for (_, c) in data.constraints],
234235
data,

test/Nonlinear/Nonlinear.jl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -749,28 +749,28 @@ function test_features_available_Default()
749749
return
750750
end
751751

752-
function test_add_constraint_rows()
752+
function test_add_constraint_ordinal_index()
753753
data = Nonlinear.NonlinearData()
754754
x = MOI.VariableIndex(1)
755755
constraints = [Nonlinear.add_constraint(data, :($x <= $i)) for i in 1:4]
756756
MOI.initialize(data, Symbol[])
757757
for i in 1:4
758-
@test Nonlinear.row(data, constraints[i]) == i
758+
@test Nonlinear.ordinal_index(data, constraints[i]) == i
759759
@test MOI.is_valid(data, constraints[i])
760760
end
761761
Nonlinear.delete(data, constraints[1])
762762
Nonlinear.delete(data, constraints[3])
763763
MOI.initialize(data, Symbol[])
764764
@test !MOI.is_valid(data, constraints[1])
765765
@test MOI.is_valid(data, constraints[2])
766-
@test Nonlinear.row(data, constraints[2]) == 1
766+
@test Nonlinear.ordinal_index(data, constraints[2]) == 1
767767
@test !MOI.is_valid(data, constraints[3])
768768
@test_throws(
769769
ErrorException("Invalid constraint index $(constraints[3])"),
770-
Nonlinear.row(data, constraints[3]),
770+
Nonlinear.ordinal_index(data, constraints[3]),
771771
)
772772
@test MOI.is_valid(data, constraints[4])
773-
@test Nonlinear.row(data, constraints[4]) == 2
773+
@test Nonlinear.ordinal_index(data, constraints[4]) == 2
774774
return
775775
end
776776

@@ -816,7 +816,8 @@ end
816816
function test_NLPBlockData()
817817
data = Nonlinear.NonlinearData()
818818
x = MOI.VariableIndex(1)
819-
block = MOI.NLPBlockData(data, [x])
819+
Nonlinear.set_differentiation_backend(data, Nonlinear.ExprGraphOnly(), [x])
820+
block = MOI.NLPBlockData(data)
820821
@test block.has_objective == false
821822
@test length(block.constraint_bounds) == 0
822823
return

0 commit comments

Comments
 (0)