@@ -36,6 +36,7 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol})
3636 largest_user_input_dimension = max (largest_user_input_dimension, op. N)
3737 end
3838 d. objective = nothing
39+ d. residual = nothing
3940 d. user_output_buffer = zeros (largest_user_input_dimension)
4041 d. jac_storage = zeros (max (N, largest_user_input_dimension))
4142 d. constraints = _FunctionStorage[]
@@ -47,6 +48,9 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol})
4748 max_expr_with_sub_length = 0
4849 #
4950 main_expressions = [c. expression. nodes for (_, c) in d. data. constraints]
51+ if d. data. residual != = nothing
52+ pushfirst! (main_expressions, something (d. data. residual). nodes)
53+ end
5054 if d. data. objective != = nothing
5155 pushfirst! (main_expressions, something (d. data. objective). nodes)
5256 end
@@ -102,7 +106,9 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol})
102106 end
103107 max_chunk = 1
104108 shared_partials_storage_ϵ = Float64[]
109+ main_offset = 0
105110 if d. data. objective != = nothing
111+ main_offset += 1
106112 expr = something (d. data. objective)
107113 subexpr, linearity = _subexpression_and_linearity (
108114 expr,
@@ -116,7 +122,7 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol})
116122 coloring_storage,
117123 d. want_hess,
118124 d. subexpressions,
119- individual_order[1 ],
125+ individual_order[main_offset ],
120126 subexpression_edgelist,
121127 subexpression_variables,
122128 linearity,
@@ -125,8 +131,32 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol})
125131 max_chunk = max (max_chunk, size (objective. seed_matrix, 2 ))
126132 d. objective = objective
127133 end
134+ if d. data. residual != = nothing
135+ main_offset += 1
136+ expr = something (d. data. residual)
137+ subexpr, linearity = _subexpression_and_linearity (
138+ expr,
139+ moi_index_to_consecutive_index,
140+ shared_partials_storage_ϵ,
141+ d,
142+ )
143+ residual = _FunctionStorage (
144+ subexpr,
145+ N,
146+ coloring_storage,
147+ d. want_hess,
148+ d. subexpressions,
149+ individual_order[main_offset],
150+ subexpression_edgelist,
151+ subexpression_variables,
152+ linearity,
153+ )
154+ max_expr_length = max (max_expr_length, length (expr. nodes))
155+ max_chunk = max (max_chunk, size (residual. seed_matrix, 2 ))
156+ d. residual = residual
157+ end
128158 for (k, (_, constraint)) in enumerate (d. data. constraints)
129- idx = d . data . objective != = nothing ? k + 1 : k
159+ idx = main_offset + k
130160 expr = constraint. expression
131161 subexpr, linearity = _subexpression_and_linearity (
132162 expr,
@@ -211,6 +241,116 @@ function MOI.eval_constraint(d::NLPEvaluator, g, x)
211241 return
212242end
213243
244+ # Forward-evaluate subexpressions and the residual at `x`.
245+ function _forward_pass_residual! (d:: NLPEvaluator , x)
246+ for k in d. subexpression_order
247+ d. subexpression_forward_values[k] =
248+ _forward_eval (d. subexpressions[k], d, x)
249+ end
250+ _forward_eval (something (d. residual). expr, d, x)
251+ return
252+ end
253+
254+ # Read the residual values from forward storage into `F`.
255+ function _read_residual! (F:: AbstractVector , d:: NLPEvaluator )
256+ res = something (d. residual)
257+ range = _storage_range (res. expr. sizes, 1 )
258+ @assert length (F) == length (range)
259+ for (i, j) in enumerate (range)
260+ F[i] = res. expr. forward_storage[j]
261+ end
262+ return
263+ end
264+
265+ """
266+ eval_residual!(d::NLPEvaluator, F, x)
267+
268+ Forward-evaluate the residual at `x` and write the values into `F`.
269+ """
270+ function eval_residual! (d:: NLPEvaluator , F:: AbstractVector , x:: AbstractVector )
271+ if d. residual === nothing
272+ error (" No nonlinear residual." )
273+ end
274+ _forward_pass_residual! (d, x)
275+ _read_residual! (F, d)
276+ return F
277+ end
278+
279+ """
280+ eval_residual_jtprod!(d::NLPEvaluator, Jtv, x, v)
281+
282+ Compute `J(x)' * v` for the residual `F`, writing the result into `Jtv`.
283+ This is one reverse-mode pass with `v` as the seed at the residual root.
284+ """
285+ function eval_residual_jtprod! (
286+ d:: NLPEvaluator ,
287+ Jtv:: AbstractVector ,
288+ x:: AbstractVector ,
289+ v:: AbstractVector ,
290+ )
291+ if d. residual === nothing
292+ error (" No nonlinear residual." )
293+ end
294+ res = something (d. residual)
295+ _forward_pass_residual! (d, x)
296+ for k in d. subexpression_order
297+ _reverse_eval (d. subexpressions[k])
298+ end
299+ _reverse_eval (res. expr, v)
300+ fill! (Jtv, 0.0 )
301+ _extract_reverse_pass (Jtv, d, res)
302+ return Jtv
303+ end
304+
305+ """
306+ residual_dimension(d::NLPEvaluator) -> Int
307+
308+ Number of residual components (i.e. the length of the residual vector).
309+ """
310+ function residual_dimension (d:: NLPEvaluator )
311+ return length (_storage_range (something (d. residual). expr. sizes, 1 ))
312+ end
313+
314+ """
315+ eval_residual_jprod!(d::NLPEvaluator, Jv, x, v)
316+
317+ Compute `J(x) * v` for the residual `F`. ArrayDiff doesn't yet have a
318+ forward-mode JVP, so this materializes the Jacobian via `nresid` reverse-mode
319+ passes (each with a unit-basis seed) and then takes `J * v`.
320+ """
321+ function eval_residual_jprod! (
322+ d:: NLPEvaluator ,
323+ Jv:: AbstractVector ,
324+ x:: AbstractVector ,
325+ v:: AbstractVector ,
326+ )
327+ if d. residual === nothing
328+ error (" No nonlinear residual." )
329+ end
330+ res = something (d. residual)
331+ nresid = residual_dimension (d)
332+ _forward_pass_residual! (d, x)
333+ for k in d. subexpression_order
334+ _reverse_eval (d. subexpressions[k])
335+ end
336+ seed = zeros (Float64, nresid)
337+ row = zeros (Float64, length (v))
338+ fill! (Jv, 0.0 )
339+ for i in 1 : nresid
340+ fill! (seed, 0.0 )
341+ seed[i] = 1.0
342+ _reverse_eval (res. expr, seed)
343+ fill! (row, 0.0 )
344+ _extract_reverse_pass (row, d, res)
345+ s = 0.0
346+ for j in eachindex (v)
347+ s += row[j] * v[j]
348+ end
349+ Jv[i] = s
350+ end
351+ return Jv
352+ end
353+
214354function MOI. jacobian_structure (d:: NLPEvaluator )
215355 J = Tuple{Int64,Int64}[]
216356 for (row, constraint) in enumerate (d. constraints)
0 commit comments