@@ -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,110 @@ 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+ @assert ! isnothing (d. residual)
272+ _forward_pass_residual! (d, x)
273+ _read_residual! (F, d)
274+ return F
275+ end
276+
277+ """
278+ eval_residual_jtprod!(d::NLPEvaluator, Jtv, x, v)
279+
280+ Compute `J(x)' * v` for the residual `F`, writing the result into `Jtv`.
281+ This is one reverse-mode pass with `v` as the seed at the residual root.
282+ """
283+ function eval_residual_jtprod! (
284+ d:: NLPEvaluator ,
285+ Jtv:: AbstractVector ,
286+ x:: AbstractVector ,
287+ v:: AbstractVector ,
288+ )
289+ @assert ! isnothing (d. residual)
290+ res = something (d. residual)
291+ _forward_pass_residual! (d, x)
292+ for k in d. subexpression_order
293+ _reverse_eval (d. subexpressions[k])
294+ end
295+ _reverse_eval (res. expr, v)
296+ fill! (Jtv, 0.0 )
297+ _extract_reverse_pass (Jtv, d, res)
298+ return Jtv
299+ end
300+
301+ """
302+ residual_dimension(d::NLPEvaluator) -> Int
303+
304+ Number of residual components (i.e. the length of the residual vector).
305+ """
306+ function residual_dimension (d:: NLPEvaluator )
307+ return length (_storage_range (something (d. residual). expr. sizes, 1 ))
308+ end
309+
310+ """
311+ eval_residual_jprod!(d::NLPEvaluator, Jv, x, v)
312+
313+ Compute `J(x) * v` for the residual `F`. ArrayDiff doesn't yet have a
314+ forward-mode JVP, so this materializes the Jacobian via `nresid` reverse-mode
315+ passes (each with a unit-basis seed) and then takes `J * v`.
316+ """
317+ function eval_residual_jprod! (
318+ d:: NLPEvaluator ,
319+ Jv:: AbstractVector ,
320+ x:: AbstractVector ,
321+ v:: AbstractVector ,
322+ )
323+ @assert ! isnothing (d. residual)
324+ res = something (d. residual)
325+ nresid = residual_dimension (d)
326+ _forward_pass_residual! (d, x)
327+ for k in d. subexpression_order
328+ _reverse_eval (d. subexpressions[k])
329+ end
330+ seed = zeros (Float64, nresid)
331+ row = zeros (Float64, length (v))
332+ fill! (Jv, 0.0 )
333+ for i in 1 : nresid
334+ fill! (seed, 0.0 )
335+ seed[i] = 1.0
336+ _reverse_eval (res. expr, seed)
337+ fill! (row, 0.0 )
338+ _extract_reverse_pass (row, d, res)
339+ s = 0.0
340+ for j in eachindex (v)
341+ s += row[j] * v[j]
342+ end
343+ Jv[i] = s
344+ end
345+ return Jv
346+ end
347+
214348function MOI. jacobian_structure (d:: NLPEvaluator )
215349 J = Tuple{Int64,Int64}[]
216350 for (row, constraint) in enumerate (d. constraints)
0 commit comments