From 36f7bfeaf1f7d079adad4ebc77252ece9789d10b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 25 Apr 2025 21:26:55 +0200 Subject: [PATCH 01/12] WIP --- .../ReverseAD/mathoptinterface_api.jl | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl index 1766c25e73..51a5cd1beb 100644 --- a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl +++ b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl @@ -43,12 +43,21 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) d.want_hess = :Hess in requested_features want_hess_storage = (:HessVec in requested_features) || d.want_hess coloring_storage = Coloring.IndexedSet(N) + # max_expr_length = 0 max_expr_with_sub_length = 0 - # - main_expressions = [c.expression.nodes for (_, c) in d.data.constraints] + max_chunk = 1 + main_expressions = Vector{Nonlinear.Node}[] if d.data.objective !== nothing - pushfirst!(main_expressions, something(d.data.objective).nodes) + objective = something(d.data.objective) + max_expr_length = max(max_expr_length, length(objective.nodes)) + max_chunk = max(max_chunk, size(objective.seed_matrix, 2)) + push!(main_expressions, objective.nodes) + end + for (_, c) in d.data.constraints + max_expr_length = max(max_expr_length, length(c.expression.nodes)) + max_chunk = max(max_chunk, size(c.expression.seed_matrix, 2)) + push!(main_expressions, c.expression.nodes) end d.subexpression_order, individual_order = _order_subexpressions( main_expressions, @@ -100,11 +109,15 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) subexpression_edgelist[k] = edgelist end end - max_chunk = 1 if d.data.objective !== nothing objective = _FunctionStorage( - main_expressions[1], - something(d.data.objective).values, + _SubexpressionStorage( + something(d.data.objective), + d.data.expressions[k], + d.subexpression_linearity, + moi_index_to_consecutive_index, + zeros(max_chunk * length(d.data.expressions[k].nodes)), + ) N, coloring_storage, d.want_hess, @@ -113,10 +126,7 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) d.subexpression_linearity, subexpression_edgelist, subexpression_variables, - moi_index_to_consecutive_index, ) - max_expr_length = max(max_expr_length, length(objective.nodes)) - max_chunk = max(max_chunk, size(objective.seed_matrix, 2)) d.objective = objective end for (k, (_, constraint)) in enumerate(d.data.constraints) From af9c36e40daf37c874d81662277ee976465ef049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 25 Apr 2025 23:45:57 +0200 Subject: [PATCH 02/12] wip --- src/Nonlinear/ReverseAD/types.jl | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/src/Nonlinear/ReverseAD/types.jl b/src/Nonlinear/ReverseAD/types.jl index fc599accb0..6f6b33141e 100644 --- a/src/Nonlinear/ReverseAD/types.jl +++ b/src/Nonlinear/ReverseAD/types.jl @@ -43,25 +43,18 @@ struct _SubexpressionStorage end struct _FunctionStorage - nodes::Vector{Nonlinear.Node} - adj::SparseArrays.SparseMatrixCSC{Bool,Int} - const_values::Vector{Float64} - forward_storage::Vector{Float64} - partials_storage::Vector{Float64} - reverse_storage::Vector{Float64} + expr::_SubexpressionStorage grad_sparsity::Vector{Int} # Nonzero pattern of Hessian matrix hess_I::Vector{Int} hess_J::Vector{Int} rinfo::Coloring.RecoveryInfo # coloring info for hessians seed_matrix::Matrix{Float64} - linearity::Linearity # subexpressions which this function depends on, ordered for forward pass. dependent_subexpressions::Vector{Int} function _FunctionStorage( - nodes::Vector{Nonlinear.Node}, - const_values, + expr::_SubexpressionStorage, num_variables, coloring_storage::Coloring.IndexedSet, want_hess::Bool, @@ -70,10 +63,7 @@ struct _FunctionStorage subexpression_linearity, subexpression_edgelist, subexpression_variables, - moi_index_to_consecutive_index, ) - nodes = _replace_moi_variables(nodes, moi_index_to_consecutive_index) - adj = Nonlinear.adjacency_matrix(nodes) N = length(nodes) empty!(coloring_storage) _compute_gradient_sparsity!(coloring_storage, nodes) @@ -102,34 +92,21 @@ struct _FunctionStorage ) seed_matrix = Coloring.seed_matrix(rinfo) return new( - nodes, - adj, - const_values, - zeros(N), # forward_storage, - zeros(N), # partials_storage, - zeros(N), # reverse_storage, + expr, grad_sparsity, hess_I, hess_J, rinfo, seed_matrix, - linearity[1], dependent_subexpressions, ) else return new( - nodes, - adj, - const_values, - zeros(N), # forward_storage, - zeros(N), # partials_storage, - zeros(N), # reverse_storage, grad_sparsity, Int[], Int[], Coloring.RecoveryInfo(), Array{Float64}(undef, 0, 0), - NONLINEAR, dependent_subexpressions, ) end From 96ae9ce02878ff6a3d556fcc3532aa2c8fdc9306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 6 May 2025 23:20:58 +0200 Subject: [PATCH 03/12] Fixes --- src/Nonlinear/ReverseAD/mathoptinterface_api.jl | 1 + src/Nonlinear/ReverseAD/types.jl | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl index 51a5cd1beb..527e0bb6d2 100644 --- a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl +++ b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl @@ -78,6 +78,7 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) d.data.expressions[k], d.subexpression_linearity, moi_index_to_consecutive_index, + zeros(max_chunk * length(d.data.expressions[k].nodes)), d.want_hess, ) d.subexpressions[k] = subex diff --git a/src/Nonlinear/ReverseAD/types.jl b/src/Nonlinear/ReverseAD/types.jl index 6f6b33141e..92bd3dda0f 100644 --- a/src/Nonlinear/ReverseAD/types.jl +++ b/src/Nonlinear/ReverseAD/types.jl @@ -18,6 +18,7 @@ struct _SubexpressionStorage expr::Nonlinear.Expression, subexpression_linearity, moi_index_to_consecutive_index, + partials_storage_ϵ::Vector{Float64}, want_hess::Bool, ) nodes = @@ -36,7 +37,7 @@ struct _SubexpressionStorage zeros(N), # forward_storage, zeros(N), # partials_storage, zeros(N), # reverse_storage, - Float64[], + partials_storage_ϵ, linearity, ) end From 03ec708bd20bef27650872f70fddf044d8025f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 6 May 2025 23:38:54 +0200 Subject: [PATCH 04/12] Fix --- .../ReverseAD/mathoptinterface_api.jl | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl index 527e0bb6d2..b04ae31492 100644 --- a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl +++ b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl @@ -59,6 +59,7 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) max_chunk = max(max_chunk, size(c.expression.seed_matrix, 2)) push!(main_expressions, c.expression.nodes) end + max_chunk = min(max_chunk, MAX_CHUNK) d.subexpression_order, individual_order = _order_subexpressions( main_expressions, [expr.nodes for expr in d.data.expressions], @@ -110,6 +111,7 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) subexpression_edgelist[k] = edgelist end end + shared_partials_storage_ϵ = zeros(max_chunk * max_expr_length) if d.data.objective !== nothing objective = _FunctionStorage( _SubexpressionStorage( @@ -117,14 +119,14 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) d.data.expressions[k], d.subexpression_linearity, moi_index_to_consecutive_index, - zeros(max_chunk * length(d.data.expressions[k].nodes)), - ) + shared_partials_storage_ϵ, + d.want_hess, + ), N, coloring_storage, d.want_hess, d.subexpressions, individual_order[1], - d.subexpression_linearity, subexpression_edgelist, subexpression_variables, ) @@ -135,29 +137,30 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) push!( d.constraints, _FunctionStorage( - main_expressions[idx], - constraint.expression.values, + _SubexpressionStorage( + main_expressions[idx], + d.subexpression_linearity, + moi_index_to_consecutive_index, + shared_partials_storage_ϵ, + d.want_hess, + ), N, coloring_storage, d.want_hess, d.subexpressions, individual_order[idx], - d.subexpression_linearity, subexpression_edgelist, subexpression_variables, - moi_index_to_consecutive_index, ), ) max_expr_length = max(max_expr_length, length(d.constraints[end].nodes)) max_chunk = max(max_chunk, size(d.constraints[end].seed_matrix, 2)) end - max_chunk = min(max_chunk, MAX_CHUNK) max_expr_with_sub_length = max(max_expr_with_sub_length, max_expr_length) if d.want_hess || want_hess_storage d.input_ϵ = zeros(max_chunk * N) d.output_ϵ = zeros(max_chunk * N) # - d.partials_storage_ϵ = zeros(max_chunk * max_expr_length) d.storage_ϵ = zeros(max_chunk * max_expr_with_sub_length) # len = max_chunk * length(d.subexpressions) From d1a0e2eb7ed799318976cee985b755fa11b35db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 6 May 2025 23:52:46 +0200 Subject: [PATCH 05/12] Fixes --- .../ReverseAD/mathoptinterface_api.jl | 27 ++++++++----------- src/Nonlinear/ReverseAD/types.jl | 11 +++----- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl index b04ae31492..bd931010a4 100644 --- a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl +++ b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl @@ -43,23 +43,13 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) d.want_hess = :Hess in requested_features want_hess_storage = (:HessVec in requested_features) || d.want_hess coloring_storage = Coloring.IndexedSet(N) - # max_expr_length = 0 max_expr_with_sub_length = 0 - max_chunk = 1 - main_expressions = Vector{Nonlinear.Node}[] + # + main_expressions = [c.expression.nodes for (_, c) in d.data.constraints] if d.data.objective !== nothing - objective = something(d.data.objective) - max_expr_length = max(max_expr_length, length(objective.nodes)) - max_chunk = max(max_chunk, size(objective.seed_matrix, 2)) - push!(main_expressions, objective.nodes) - end - for (_, c) in d.data.constraints - max_expr_length = max(max_expr_length, length(c.expression.nodes)) - max_chunk = max(max_chunk, size(c.expression.seed_matrix, 2)) - push!(main_expressions, c.expression.nodes) + pushfirst!(main_expressions, something(d.data.objective).nodes) end - max_chunk = min(max_chunk, MAX_CHUNK) d.subexpression_order, individual_order = _order_subexpressions( main_expressions, [expr.nodes for expr in d.data.expressions], @@ -79,7 +69,7 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) d.data.expressions[k], d.subexpression_linearity, moi_index_to_consecutive_index, - zeros(max_chunk * length(d.data.expressions[k].nodes)), + Float64[], d.want_hess, ) d.subexpressions[k] = subex @@ -111,12 +101,12 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) subexpression_edgelist[k] = edgelist end end - shared_partials_storage_ϵ = zeros(max_chunk * max_expr_length) + max_chunk = 1 + shared_partials_storage_ϵ = Float64[] if d.data.objective !== nothing objective = _FunctionStorage( _SubexpressionStorage( something(d.data.objective), - d.data.expressions[k], d.subexpression_linearity, moi_index_to_consecutive_index, shared_partials_storage_ϵ, @@ -130,6 +120,8 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) subexpression_edgelist, subexpression_variables, ) + max_expr_length = max(max_expr_length, length(objective.nodes)) + max_chunk = max(max_chunk, size(objective.seed_matrix, 2)) d.objective = objective end for (k, (_, constraint)) in enumerate(d.data.constraints) @@ -156,11 +148,14 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) max_expr_length = max(max_expr_length, length(d.constraints[end].nodes)) max_chunk = max(max_chunk, size(d.constraints[end].seed_matrix, 2)) end + max_chunk = min(max_chunk, MAX_CHUNK) max_expr_with_sub_length = max(max_expr_with_sub_length, max_expr_length) if d.want_hess || want_hess_storage d.input_ϵ = zeros(max_chunk * N) d.output_ϵ = zeros(max_chunk * N) # + resize!(shared_partials_storage_ϵ, max_chunk * max_expr_length) + fill!(shared_partials_storage_ϵ, 0.0) d.storage_ϵ = zeros(max_chunk * max_expr_with_sub_length) # len = max_chunk * length(d.subexpressions) diff --git a/src/Nonlinear/ReverseAD/types.jl b/src/Nonlinear/ReverseAD/types.jl index 92bd3dda0f..f7743def47 100644 --- a/src/Nonlinear/ReverseAD/types.jl +++ b/src/Nonlinear/ReverseAD/types.jl @@ -61,13 +61,11 @@ struct _FunctionStorage want_hess::Bool, subexpressions::Vector{_SubexpressionStorage}, dependent_subexpressions, - subexpression_linearity, subexpression_edgelist, subexpression_variables, ) - N = length(nodes) empty!(coloring_storage) - _compute_gradient_sparsity!(coloring_storage, nodes) + _compute_gradient_sparsity!(coloring_storage, expr.nodes) for k in dependent_subexpressions _compute_gradient_sparsity!( coloring_storage, @@ -77,11 +75,10 @@ struct _FunctionStorage grad_sparsity = sort!(collect(coloring_storage)) empty!(coloring_storage) if want_hess - linearity = _classify_linearity(nodes, adj, subexpression_linearity) edgelist = _compute_hessian_sparsity( - nodes, - adj, - linearity, + expr.nodes, + expr.adj, + expr.linearity, coloring_storage, subexpression_edgelist, subexpression_variables, From 36539bcf9c47702f267f8fe953df073c42d30f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 8 May 2025 21:44:54 +0200 Subject: [PATCH 06/12] Fixes --- .../ReverseAD/mathoptinterface_api.jl | 39 +++++++++++++++---- src/Nonlinear/ReverseAD/types.jl | 14 +++---- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl index bd931010a4..4de03c632d 100644 --- a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl +++ b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl @@ -65,12 +65,19 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) for k in d.subexpression_order # Only load expressions which actually are used d.subexpression_forward_values[k] = NaN + nodes = d.data.expressions[k] + adj = Nonlinear.adjacency_matrix(nodes) + linearity = if d.want_hess + _classify_linearity(nodes, adj, d.subexpression_linearity)[1] + else + NONLINEAR + end subex = _SubexpressionStorage( d.data.expressions[k], - d.subexpression_linearity, + adj, moi_index_to_consecutive_index, Float64[], - d.want_hess, + linearity[1], ) d.subexpressions[k] = subex d.subexpression_linearity[k] = subex.linearity @@ -104,13 +111,20 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) max_chunk = 1 shared_partials_storage_ϵ = Float64[] if d.data.objective !== nothing + nodes = something(d.data.objective) + adj = Nonlinear.adjacency_matrix(nodes) + linearity = if d.want_hess + _classify_linearity(nodes, adj, d.subexpression_linearity)[1] + else + NONLINEAR + end objective = _FunctionStorage( _SubexpressionStorage( - something(d.data.objective), - d.subexpression_linearity, + nodes, + adj, moi_index_to_consecutive_index, shared_partials_storage_ϵ, - d.want_hess, + linearity[1], ), N, coloring_storage, @@ -119,6 +133,7 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) individual_order[1], subexpression_edgelist, subexpression_variables, + linearity, ) max_expr_length = max(max_expr_length, length(objective.nodes)) max_chunk = max(max_chunk, size(objective.seed_matrix, 2)) @@ -126,15 +141,22 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) end for (k, (_, constraint)) in enumerate(d.data.constraints) idx = d.data.objective !== nothing ? k + 1 : k + nodes = main_expressions[idx] + adj = Nonlinear.adjacency_matrix(nodes) + linearity = if d.want_hess + _classify_linearity(nodes, adj, d.subexpression_linearity)[1] + else + NONLINEAR + end push!( d.constraints, _FunctionStorage( _SubexpressionStorage( - main_expressions[idx], - d.subexpression_linearity, + nodes, + adj, moi_index_to_consecutive_index, shared_partials_storage_ϵ, - d.want_hess, + linearity[1], ), N, coloring_storage, @@ -143,6 +165,7 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) individual_order[idx], subexpression_edgelist, subexpression_variables, + linearity, ), ) max_expr_length = max(max_expr_length, length(d.constraints[end].nodes)) diff --git a/src/Nonlinear/ReverseAD/types.jl b/src/Nonlinear/ReverseAD/types.jl index f7743def47..788577f9d6 100644 --- a/src/Nonlinear/ReverseAD/types.jl +++ b/src/Nonlinear/ReverseAD/types.jl @@ -16,20 +16,14 @@ struct _SubexpressionStorage function _SubexpressionStorage( expr::Nonlinear.Expression, - subexpression_linearity, + adj::SparseArrays.SparseMatrixCSC{Bool,Int}, moi_index_to_consecutive_index, partials_storage_ϵ::Vector{Float64}, - want_hess::Bool, + linearity::Linearity, ) nodes = _replace_moi_variables(expr.nodes, moi_index_to_consecutive_index) - adj = Nonlinear.adjacency_matrix(nodes) N = length(nodes) - linearity = if want_hess - _classify_linearity(nodes, adj, subexpression_linearity)[1] - else - NONLINEAR - end return new( nodes, adj, @@ -63,6 +57,7 @@ struct _FunctionStorage dependent_subexpressions, subexpression_edgelist, subexpression_variables, + linearity::Vector{Linearity}, ) empty!(coloring_storage) _compute_gradient_sparsity!(coloring_storage, expr.nodes) @@ -78,7 +73,7 @@ struct _FunctionStorage edgelist = _compute_hessian_sparsity( expr.nodes, expr.adj, - expr.linearity, + linearity, coloring_storage, subexpression_edgelist, subexpression_variables, @@ -100,6 +95,7 @@ struct _FunctionStorage ) else return new( + expr, grad_sparsity, Int[], Int[], From dcdca64e117d45655891619e3f05ca6eded0e89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 8 May 2025 22:01:12 +0200 Subject: [PATCH 07/12] Fixes --- .../ReverseAD/mathoptinterface_api.jl | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl index 4de03c632d..14e8f106ab 100644 --- a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl +++ b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl @@ -65,15 +65,15 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) for k in d.subexpression_order # Only load expressions which actually are used d.subexpression_forward_values[k] = NaN - nodes = d.data.expressions[k] - adj = Nonlinear.adjacency_matrix(nodes) + expr = d.data.expressions[k] + adj = Nonlinear.adjacency_matrix(expr.nodes) linearity = if d.want_hess - _classify_linearity(nodes, adj, d.subexpression_linearity)[1] + _classify_linearity(expr.nodes, adj, d.subexpression_linearity) else - NONLINEAR + [NONLINEAR] end subex = _SubexpressionStorage( - d.data.expressions[k], + expr, adj, moi_index_to_consecutive_index, Float64[], @@ -111,16 +111,16 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) max_chunk = 1 shared_partials_storage_ϵ = Float64[] if d.data.objective !== nothing - nodes = something(d.data.objective) - adj = Nonlinear.adjacency_matrix(nodes) + expr = something(d.data.objective) + adj = Nonlinear.adjacency_matrix(expr.nodes) linearity = if d.want_hess - _classify_linearity(nodes, adj, d.subexpression_linearity)[1] + _classify_linearity(expr.nodes, adj, d.subexpression_linearity) else - NONLINEAR + [NONLINEAR] end objective = _FunctionStorage( _SubexpressionStorage( - nodes, + expr, adj, moi_index_to_consecutive_index, shared_partials_storage_ϵ, @@ -135,24 +135,24 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) subexpression_variables, linearity, ) - max_expr_length = max(max_expr_length, length(objective.nodes)) + max_expr_length = max(max_expr_length, length(expr.nodes)) max_chunk = max(max_chunk, size(objective.seed_matrix, 2)) d.objective = objective end for (k, (_, constraint)) in enumerate(d.data.constraints) idx = d.data.objective !== nothing ? k + 1 : k - nodes = main_expressions[idx] - adj = Nonlinear.adjacency_matrix(nodes) + expr = constraint.expression + adj = Nonlinear.adjacency_matrix(expr.nodes) linearity = if d.want_hess - _classify_linearity(nodes, adj, d.subexpression_linearity)[1] + _classify_linearity(expr.nodes, adj, d.subexpression_linearity) else - NONLINEAR + [NONLINEAR] end push!( d.constraints, _FunctionStorage( _SubexpressionStorage( - nodes, + expr, adj, moi_index_to_consecutive_index, shared_partials_storage_ϵ, From e0f6ac1c0750b733500dd3d74725e7ef67fc3b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 8 May 2025 22:08:03 +0200 Subject: [PATCH 08/12] Fixes --- .../ReverseAD/forward_over_reverse.jl | 2 +- .../ReverseAD/mathoptinterface_api.jl | 6 ++-- src/Nonlinear/ReverseAD/reverse_mode.jl | 33 +++++++++++-------- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/Nonlinear/ReverseAD/forward_over_reverse.jl b/src/Nonlinear/ReverseAD/forward_over_reverse.jl index 03ea1d89fc..32df4f7458 100644 --- a/src/Nonlinear/ReverseAD/forward_over_reverse.jl +++ b/src/Nonlinear/ReverseAD/forward_over_reverse.jl @@ -38,7 +38,7 @@ function _eval_hessian( scale::Float64, nzcount::Int, )::Int - if ex.linearity == LINEAR + if ex.expr.linearity == LINEAR @assert length(ex.hess_I) == 0 return 0 end diff --git a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl index 14e8f106ab..b23d282f74 100644 --- a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl +++ b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl @@ -168,7 +168,7 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) linearity, ), ) - max_expr_length = max(max_expr_length, length(d.constraints[end].nodes)) + max_expr_length = max(max_expr_length, length(expr.nodes)) max_chunk = max(max_chunk, size(d.constraints[end].seed_matrix, 2)) end max_chunk = min(max_chunk, MAX_CHUNK) @@ -210,7 +210,7 @@ function MOI.eval_objective(d::NLPEvaluator, x) error("No nonlinear objective.") end _reverse_mode(d, x) - return something(d.objective).forward_storage[1] + return something(d.objective).expr.forward_storage[1] end function MOI.eval_objective_gradient(d::NLPEvaluator, g, x) @@ -226,7 +226,7 @@ end function MOI.eval_constraint(d::NLPEvaluator, g, x) _reverse_mode(d, x) for i in 1:length(d.constraints) - g[i] = d.constraints[i].forward_storage[1] + g[i] = d.constraints[i].expr.forward_storage[1] end return end diff --git a/src/Nonlinear/ReverseAD/reverse_mode.jl b/src/Nonlinear/ReverseAD/reverse_mode.jl index a9c91875df..02784afde7 100644 --- a/src/Nonlinear/ReverseAD/reverse_mode.jl +++ b/src/Nonlinear/ReverseAD/reverse_mode.jl @@ -98,10 +98,7 @@ Forward-mode evaluation of an expression tree given in `f`. associate storage with each edge of the DAG. """ function _forward_eval( - # !!! warning - # This Union depends upon _FunctionStorage and _SubexpressionStorage - # having similarly named fields. - f::Union{_FunctionStorage,_SubexpressionStorage}, + f::_SubexpressionStorage, d::NLPEvaluator, x::AbstractVector{T}, )::T where {T} @@ -289,6 +286,8 @@ function _forward_eval( return f.forward_storage[1] end +_forward_eval(f::_FunctionStorage, d, x) = _forward_eval(f.expr, d, x) + """ _reverse_eval(f::Union{_FunctionStorage,_SubexpressionStorage}) @@ -297,12 +296,7 @@ Reverse-mode evaluation of an expression tree given in `f`. * This function assumes `f.partials_storage` is already updated. * This function assumes that `f.reverse_storage` has been initialized with 0.0. """ -function _reverse_eval( - # !!! warning - # This Union depends upon _FunctionStorage and _SubexpressionStorage - # having similarly named fields. - f::Union{_FunctionStorage,_SubexpressionStorage}, -) +function _reverse_eval(f::_SubexpressionStorage) @assert length(f.reverse_storage) >= length(f.nodes) @assert length(f.partials_storage) >= length(f.nodes) # f.nodes is already in order such that parents always appear before @@ -328,6 +322,8 @@ function _reverse_eval( return end +_reverse_eval(f::_FunctionStorage) = _reverse_eval(f.expr) + """ _extract_reverse_pass( g::AbstractVector{T}, @@ -361,9 +357,20 @@ end function _extract_reverse_pass_inner( output::AbstractVector{T}, - # !!! warning - # This Union depends upon _FunctionStorage and _SubexpressionStorage - # having similarly named fields. + f::_FunctionStorage, + subexpressions::AbstractVector{T}, + scale::T, +) where {T} + return _extract_reverse_pass_inner( + output, + f.expr, + subexpressions, + scale, + ) +end + +function _extract_reverse_pass_inner( + output::AbstractVector{T}, f::Union{_FunctionStorage,_SubexpressionStorage}, subexpressions::AbstractVector{T}, scale::T, From a80f0abd697de1e2742b33816cd47e4dd385ee68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 8 May 2025 22:32:35 +0200 Subject: [PATCH 09/12] Fixes --- .../ReverseAD/mathoptinterface_api.jl | 43 +++++++------------ src/Nonlinear/ReverseAD/types.jl | 35 ++++++++++++--- 2 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl index b23d282f74..d6aad2feb4 100644 --- a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl +++ b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl @@ -66,18 +66,11 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) # Only load expressions which actually are used d.subexpression_forward_values[k] = NaN expr = d.data.expressions[k] - adj = Nonlinear.adjacency_matrix(expr.nodes) - linearity = if d.want_hess - _classify_linearity(expr.nodes, adj, d.subexpression_linearity) - else - [NONLINEAR] - end - subex = _SubexpressionStorage( + subex, _ = _subexpression_and_linearity( expr, - adj, moi_index_to_consecutive_index, Float64[], - linearity[1], + d, ) d.subexpressions[k] = subex d.subexpression_linearity[k] = subex.linearity @@ -112,20 +105,14 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) shared_partials_storage_ϵ = Float64[] if d.data.objective !== nothing expr = something(d.data.objective) - adj = Nonlinear.adjacency_matrix(expr.nodes) - linearity = if d.want_hess - _classify_linearity(expr.nodes, adj, d.subexpression_linearity) - else - [NONLINEAR] - end + subexpr, linearity = _subexpression_and_linearity( + expr, + moi_index_to_consecutive_index, + shared_partials_storage_ϵ, + d, + ) objective = _FunctionStorage( - _SubexpressionStorage( - expr, - adj, - moi_index_to_consecutive_index, - shared_partials_storage_ϵ, - linearity[1], - ), + subexpr, N, coloring_storage, d.want_hess, @@ -142,12 +129,12 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) for (k, (_, constraint)) in enumerate(d.data.constraints) idx = d.data.objective !== nothing ? k + 1 : k expr = constraint.expression - adj = Nonlinear.adjacency_matrix(expr.nodes) - linearity = if d.want_hess - _classify_linearity(expr.nodes, adj, d.subexpression_linearity) - else - [NONLINEAR] - end + subexpr, linearity = _subexpression_and_linearity( + expr, + moi_index_to_consecutive_index, + shared_partials_storage_ϵ, + d, + ) push!( d.constraints, _FunctionStorage( diff --git a/src/Nonlinear/ReverseAD/types.jl b/src/Nonlinear/ReverseAD/types.jl index 788577f9d6..4e825dcd7d 100644 --- a/src/Nonlinear/ReverseAD/types.jl +++ b/src/Nonlinear/ReverseAD/types.jl @@ -15,19 +15,17 @@ struct _SubexpressionStorage linearity::Linearity function _SubexpressionStorage( - expr::Nonlinear.Expression, + nodes::Vector{Nonlinear.Node}, adj::SparseArrays.SparseMatrixCSC{Bool,Int}, - moi_index_to_consecutive_index, + const_values::Vector{Float64}, partials_storage_ϵ::Vector{Float64}, linearity::Linearity, ) - nodes = - _replace_moi_variables(expr.nodes, moi_index_to_consecutive_index) N = length(nodes) return new( nodes, adj, - expr.values, + const_values, zeros(N), # forward_storage, zeros(N), # partials_storage, zeros(N), # reverse_storage, @@ -37,6 +35,31 @@ struct _SubexpressionStorage end end +# We don't need to store the full vector of `linearity` but we return +# it because it is needed in `compute_hessian_sparsity`. +function _subexpression_and_linearity( + expr::Nonlinear.Expression, + moi_index_to_consecutive_index, + partials_storage_ϵ::Vector{Float64}, + d, +) + nodes = + _replace_moi_variables(expr.nodes, moi_index_to_consecutive_index) + adj = Nonlinear.adjacency_matrix(nodes) + linearity = if d.want_hess + _classify_linearity(nodes, adj, d.subexpression_linearity) + else + [NONLINEAR] + end + return _SubexpressionStorage( + nodes, + adj, + expr.values, + partials_storage_ϵ, + linearity[1], + ), linearity +end + struct _FunctionStorage expr::_SubexpressionStorage grad_sparsity::Vector{Int} @@ -78,11 +101,13 @@ struct _FunctionStorage subexpression_edgelist, subexpression_variables, ) + @show edgelist hess_I, hess_J, rinfo = Coloring.hessian_color_preprocess( edgelist, num_variables, coloring_storage, ) + @show hess_I, hess_J seed_matrix = Coloring.seed_matrix(rinfo) return new( expr, From 13a9df657d783edee79f879dda4f916b6d0edc81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 8 May 2025 23:16:00 +0200 Subject: [PATCH 10/12] Fixes --- .../ReverseAD/forward_over_reverse.jl | 28 ++++++++--------- .../ReverseAD/mathoptinterface_api.jl | 31 +++++-------------- src/Nonlinear/ReverseAD/types.jl | 3 -- 3 files changed, 20 insertions(+), 42 deletions(-) diff --git a/src/Nonlinear/ReverseAD/forward_over_reverse.jl b/src/Nonlinear/ReverseAD/forward_over_reverse.jl index 32df4f7458..316fa65d1b 100644 --- a/src/Nonlinear/ReverseAD/forward_over_reverse.jl +++ b/src/Nonlinear/ReverseAD/forward_over_reverse.jl @@ -128,13 +128,9 @@ function _hessian_slice_inner(d, ex, ::Type{T}) where {T} _reinterpret_unsafe(T, d.subexpression_forward_values_ϵ) for i in ex.dependent_subexpressions subexpr = d.subexpressions[i] - subexpr_forward_values_ϵ[i] = _forward_eval_ϵ( - d, - subexpr, - _reinterpret_unsafe(T, subexpr.partials_storage_ϵ), - ) + subexpr_forward_values_ϵ[i] = _forward_eval_ϵ(d, subexpr, T) end - _forward_eval_ϵ(d, ex, _reinterpret_unsafe(T, d.partials_storage_ϵ)) + _forward_eval_ϵ(d, ex.expr, T) # do a reverse pass subexpr_reverse_values_ϵ = _reinterpret_unsafe(T, d.subexpression_reverse_values_ϵ) @@ -144,9 +140,8 @@ function _hessian_slice_inner(d, ex, ::Type{T}) where {T} end _reverse_eval_ϵ( output_ϵ, - ex, + ex.expr, _reinterpret_unsafe(T, d.storage_ϵ), - _reinterpret_unsafe(T, d.partials_storage_ϵ), d.subexpression_reverse_values, subexpr_reverse_values_ϵ, 1.0, @@ -159,7 +154,6 @@ function _hessian_slice_inner(d, ex, ::Type{T}) where {T} output_ϵ, subexpr, _reinterpret_unsafe(T, d.storage_ϵ), - _reinterpret_unsafe(T, subexpr.partials_storage_ϵ), d.subexpression_reverse_values, subexpr_reverse_values_ϵ, d.subexpression_reverse_values[j], @@ -173,8 +167,8 @@ end _forward_eval_ϵ( d::NLPEvaluator, ex::Union{_FunctionStorage,_SubexpressionStorage}, - partials_storage_ϵ::AbstractVector{ForwardDiff.Partials{N,T}}, - ) where {N,T} + ::Type{P}, + ) where {N,T,P<:ForwardDiff.Partials{N,T}} Evaluate the directional derivatives of the expression tree in `ex`. @@ -186,10 +180,11 @@ This assumes that `_reverse_model(d, x)` has already been called. """ function _forward_eval_ϵ( d::NLPEvaluator, - ex::Union{_FunctionStorage,_SubexpressionStorage}, - partials_storage_ϵ::AbstractVector{P}, + ex::_SubexpressionStorage, + ::Type{P}, ) where {N,T,P<:ForwardDiff.Partials{N,T}} storage_ϵ = _reinterpret_unsafe(P, d.storage_ϵ) + partials_storage_ϵ = _reinterpret_unsafe(P, ex.partials_storage_ϵ) x_values_ϵ = _reinterpret_unsafe(P, d.input_ϵ) subexpression_values_ϵ = _reinterpret_unsafe(P, d.subexpression_forward_values_ϵ) @@ -370,14 +365,17 @@ end # to compute hessian-vector products. function _reverse_eval_ϵ( output_ϵ::AbstractVector{ForwardDiff.Partials{N,T}}, - ex::Union{_FunctionStorage,_SubexpressionStorage}, + ex::_SubexpressionStorage, reverse_storage_ϵ, - partials_storage_ϵ, subexpression_output, subexpression_output_ϵ, scale::T, scale_ϵ::ForwardDiff.Partials{N,T}, ) where {N,T} + partials_storage_ϵ = _reinterpret_unsafe( + ForwardDiff.Partials{N,T}, + ex.partials_storage_ϵ, + ) @assert length(reverse_storage_ϵ) >= length(ex.nodes) @assert length(partials_storage_ϵ) >= length(ex.nodes) if ex.nodes[1].type == Nonlinear.NODE_VARIABLE diff --git a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl index d6aad2feb4..21c6804825 100644 --- a/src/Nonlinear/ReverseAD/mathoptinterface_api.jl +++ b/src/Nonlinear/ReverseAD/mathoptinterface_api.jl @@ -138,13 +138,7 @@ function MOI.initialize(d::NLPEvaluator, requested_features::Vector{Symbol}) push!( d.constraints, _FunctionStorage( - _SubexpressionStorage( - expr, - adj, - moi_index_to_consecutive_index, - shared_partials_storage_ϵ, - linearity[1], - ), + subexpr, N, coloring_storage, d.want_hess, @@ -364,11 +358,7 @@ function MOI.eval_hessian_lagrangian_product(d::NLPEvaluator, h, x, v, σ, μ) subexpr_forward_values_ϵ = reinterpret(T, d.subexpression_forward_values_ϵ) for i in d.subexpression_order subexpr = d.subexpressions[i] - subexpr_forward_values_ϵ[i] = _forward_eval_ϵ( - d, - subexpr, - reinterpret(T, subexpr.partials_storage_ϵ), - ) + subexpr_forward_values_ϵ[i] = _forward_eval_ϵ(d, subexpr, T) end # we only need to do one reverse pass through the subexpressions as well subexpr_reverse_values_ϵ = reinterpret(T, d.subexpression_reverse_values_ϵ) @@ -377,16 +367,11 @@ function MOI.eval_hessian_lagrangian_product(d::NLPEvaluator, h, x, v, σ, μ) fill!(d.storage_ϵ, 0.0) fill!(output_ϵ, zero(T)) if d.objective !== nothing - _forward_eval_ϵ( - d, - something(d.objective), - reinterpret(T, d.partials_storage_ϵ), - ) + _forward_eval_ϵ(d, something(d.objective).expr, T) _reverse_eval_ϵ( output_ϵ, - something(d.objective), - reinterpret(T, d.storage_ϵ), - reinterpret(T, d.partials_storage_ϵ), + something(d.objective).expr, + _reinterpret_unsafe(T, d.storage_ϵ), d.subexpression_reverse_values, subexpr_reverse_values_ϵ, σ, @@ -394,12 +379,11 @@ function MOI.eval_hessian_lagrangian_product(d::NLPEvaluator, h, x, v, σ, μ) ) end for (i, con) in enumerate(d.constraints) - _forward_eval_ϵ(d, con, reinterpret(T, d.partials_storage_ϵ)) + _forward_eval_ϵ(d, con.expr, T) _reverse_eval_ϵ( output_ϵ, - con, + con.expr, reinterpret(T, d.storage_ϵ), - reinterpret(T, d.partials_storage_ϵ), d.subexpression_reverse_values, subexpr_reverse_values_ϵ, μ[i], @@ -413,7 +397,6 @@ function MOI.eval_hessian_lagrangian_product(d::NLPEvaluator, h, x, v, σ, μ) output_ϵ, subexpr, reinterpret(T, d.storage_ϵ), - reinterpret(T, subexpr.partials_storage_ϵ), d.subexpression_reverse_values, subexpr_reverse_values_ϵ, d.subexpression_reverse_values[j], diff --git a/src/Nonlinear/ReverseAD/types.jl b/src/Nonlinear/ReverseAD/types.jl index 4e825dcd7d..27daa2adaa 100644 --- a/src/Nonlinear/ReverseAD/types.jl +++ b/src/Nonlinear/ReverseAD/types.jl @@ -101,13 +101,11 @@ struct _FunctionStorage subexpression_edgelist, subexpression_variables, ) - @show edgelist hess_I, hess_J, rinfo = Coloring.hessian_color_preprocess( edgelist, num_variables, coloring_storage, ) - @show hess_I, hess_J seed_matrix = Coloring.seed_matrix(rinfo) return new( expr, @@ -172,7 +170,6 @@ mutable struct NLPEvaluator <: MOI.AbstractNLPEvaluator # so the length should be multiplied by the maximum number of epsilon components disable_2ndorder::Bool # don't offer Hess or HessVec want_hess::Bool - partials_storage_ϵ::Vector{Float64} # (longest expression excluding subexpressions) storage_ϵ::Vector{Float64} # (longest expression including subexpressions) input_ϵ::Vector{Float64} # (number of variables) output_ϵ::Vector{Float64} # (number of variables) From d292932ac9a3705b94c0cb9ec6cac71c97416891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 8 May 2025 23:16:08 +0200 Subject: [PATCH 11/12] Fix format --- src/Nonlinear/ReverseAD/forward_over_reverse.jl | 6 ++---- src/Nonlinear/ReverseAD/reverse_mode.jl | 7 +------ src/Nonlinear/ReverseAD/types.jl | 6 +++--- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/Nonlinear/ReverseAD/forward_over_reverse.jl b/src/Nonlinear/ReverseAD/forward_over_reverse.jl index 316fa65d1b..fa7b158bb2 100644 --- a/src/Nonlinear/ReverseAD/forward_over_reverse.jl +++ b/src/Nonlinear/ReverseAD/forward_over_reverse.jl @@ -372,10 +372,8 @@ function _reverse_eval_ϵ( scale::T, scale_ϵ::ForwardDiff.Partials{N,T}, ) where {N,T} - partials_storage_ϵ = _reinterpret_unsafe( - ForwardDiff.Partials{N,T}, - ex.partials_storage_ϵ, - ) + partials_storage_ϵ = + _reinterpret_unsafe(ForwardDiff.Partials{N,T}, ex.partials_storage_ϵ) @assert length(reverse_storage_ϵ) >= length(ex.nodes) @assert length(partials_storage_ϵ) >= length(ex.nodes) if ex.nodes[1].type == Nonlinear.NODE_VARIABLE diff --git a/src/Nonlinear/ReverseAD/reverse_mode.jl b/src/Nonlinear/ReverseAD/reverse_mode.jl index 02784afde7..87f9ce2046 100644 --- a/src/Nonlinear/ReverseAD/reverse_mode.jl +++ b/src/Nonlinear/ReverseAD/reverse_mode.jl @@ -361,12 +361,7 @@ function _extract_reverse_pass_inner( subexpressions::AbstractVector{T}, scale::T, ) where {T} - return _extract_reverse_pass_inner( - output, - f.expr, - subexpressions, - scale, - ) + return _extract_reverse_pass_inner(output, f.expr, subexpressions, scale) end function _extract_reverse_pass_inner( diff --git a/src/Nonlinear/ReverseAD/types.jl b/src/Nonlinear/ReverseAD/types.jl index 27daa2adaa..5dad1b8d48 100644 --- a/src/Nonlinear/ReverseAD/types.jl +++ b/src/Nonlinear/ReverseAD/types.jl @@ -43,8 +43,7 @@ function _subexpression_and_linearity( partials_storage_ϵ::Vector{Float64}, d, ) - nodes = - _replace_moi_variables(expr.nodes, moi_index_to_consecutive_index) + nodes = _replace_moi_variables(expr.nodes, moi_index_to_consecutive_index) adj = Nonlinear.adjacency_matrix(nodes) linearity = if d.want_hess _classify_linearity(nodes, adj, d.subexpression_linearity) @@ -57,7 +56,8 @@ function _subexpression_and_linearity( expr.values, partials_storage_ϵ, linearity[1], - ), linearity + ), + linearity end struct _FunctionStorage From 965deaaae6c4d46681396869cee066722b5323fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 9 May 2025 00:25:22 +0200 Subject: [PATCH 12/12] Remove methods --- src/Nonlinear/ReverseAD/reverse_mode.jl | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Nonlinear/ReverseAD/reverse_mode.jl b/src/Nonlinear/ReverseAD/reverse_mode.jl index 87f9ce2046..84cc3581d1 100644 --- a/src/Nonlinear/ReverseAD/reverse_mode.jl +++ b/src/Nonlinear/ReverseAD/reverse_mode.jl @@ -39,20 +39,20 @@ function _reverse_mode(d::NLPEvaluator, x) _forward_eval(d.subexpressions[k], d, x) end if d.objective !== nothing - _forward_eval(d.objective::_FunctionStorage, d, x) + _forward_eval(something(d.objective).expr, d, x) end for con in d.constraints - _forward_eval(con, d, x) + _forward_eval(con.expr, d, x) end # Phase II for k in d.subexpression_order _reverse_eval(d.subexpressions[k]) end if d.objective !== nothing - _reverse_eval(d.objective::_FunctionStorage) + _reverse_eval(something(d.objective).expr) end for con in d.constraints - _reverse_eval(con) + _reverse_eval(con.expr) end # If a JuMP model uses the legacy nonlinear interface, then JuMP constructs # a NLPEvaluator at the start of a call to `JuMP.optimize!` and it passes in @@ -81,7 +81,7 @@ end """ _forward_eval( - f::Union{_FunctionStorage,_SubexpressionStorage}, + f::_SubexpressionStorage, d::NLPEvaluator, x::AbstractVector{T}, ) where {T} @@ -286,10 +286,8 @@ function _forward_eval( return f.forward_storage[1] end -_forward_eval(f::_FunctionStorage, d, x) = _forward_eval(f.expr, d, x) - """ - _reverse_eval(f::Union{_FunctionStorage,_SubexpressionStorage}) + _reverse_eval(f::_SubexpressionStorage) Reverse-mode evaluation of an expression tree given in `f`. @@ -322,8 +320,6 @@ function _reverse_eval(f::_SubexpressionStorage) return end -_reverse_eval(f::_FunctionStorage) = _reverse_eval(f.expr) - """ _extract_reverse_pass( g::AbstractVector{T},