@@ -49,7 +49,7 @@ function MTKBase.get_initialization_problem_type(
4949end
5050
5151"""
52- analyze_initialization_jacobian(prob; rtol = 1e-8, atol = 0.0, threshold = 1e-3, verbose = true)
52+ analyze_initialization_jacobian(prob; rtol = 1e-8, atol = 0.0, threshold = 1e-3, verbose = true, autodiff = nothing )
5353
5454Diagnose rank deficiency of a system's initialization problem by inspecting the singular
5555value decomposition of its residual Jacobian, and report both the **unknowns** that span
@@ -70,8 +70,11 @@ the offending degrees of freedom explicit:
7070 constrain any additional degree of freedom. These explain why an initialization can
7171 have more equations than unknowns yet still be underdetermined.
7272
73- It evaluates the Jacobian of the initialization residual at the initial guess `u0` (via
74- `ForwardDiff`) and computes its SVD. `prob` may be a problem that carries initialization
73+ It evaluates the Jacobian of the initialization residual at the initial guess `u0` and
74+ computes its SVD. By default the Jacobian is formed by central finite differences, which
75+ reuses the already-compiled residual function; pass an ADTypes backend via `autodiff`
76+ (e.g. `AutoForwardDiff()`) to differentiate through `DifferentiationInterface` instead,
77+ at the cost of compiling the residual for the backend's number types. `prob` may be a problem that carries initialization
7578data (e.g. an `ODEProblem`/`DAEProblem` built from a `System`), or an initialization
7679`NonlinearProblem`/`NonlinearLeastSquaresProblem` directly.
7780
@@ -83,6 +86,11 @@ data (e.g. an `ODEProblem`/`DAEProblem` built from a `System`), or an initializa
8386 - `threshold`: only unknowns/equations whose participation exceeds this value are
8487 reported.
8588 - `verbose`: print a human-readable report.
89+ - `autodiff`: `nothing` (default) computes the Jacobian by central finite differences
90+ with step `cbrt(eps)`, whose truncation error is far below the default rank tolerance
91+ and whose first call avoids recompiling the generated residual (the dominant cost of
92+ dual-number AD on large systems). Alternatively an ADTypes backend evaluated through
93+ `DifferentiationInterface`.
8694
8795# Returns
8896
@@ -106,7 +114,7 @@ unknowns); a `redundancy` of `0` means full row rank (no redundant equations).
106114 rank deficient at a particular operating point, and vice versa.
107115"""
108116function analyze_initialization_jacobian (
109- prob; rtol = 1.0e-8 , atol = 0.0 , threshold = 1.0e-3 , verbose = true
117+ prob; rtol = 1.0e-8 , atol = 0.0 , threshold = 1.0e-3 , verbose = true , autodiff = nothing
110118 )
111119 empty_result = (;
112120 jacobian = nothing , singular_values = Float64[], rank = 0 ,
@@ -133,7 +141,8 @@ function analyze_initialization_jacobian(
133141 else
134142 u -> f (u, p)
135143 end
136- J = ForwardDiff. jacobian (residual, u0)
144+ J = autodiff === nothing ? _central_difference_jacobian (residual, u0) :
145+ DI. jacobian (residual, autodiff, u0)
137146 nrows, ncols = size (J)
138147 fact = svd (J; full = true )
139148 S = fact. S
210219# Return the initialization `NonlinearProblem`/`NonlinearLeastSquaresProblem` carried by
211220# `prob`, or `prob` itself if it is already a nonlinear problem, or `nothing` if there is
212221# no initialization problem to analyze.
222+ # Dense Jacobian by central finite differences. Reuses the residual function already
223+ # compiled for the element type of `u0`; `ForwardDiff.jacobian` instead triggers a
224+ # dual-number recompilation of the generated residual, which dominates the runtime on
225+ # large systems. The O(h^2) truncation error (h = cbrt(eps)) is far below the default
226+ # singular-value tolerance of the rank analysis.
227+ function _central_difference_jacobian (residual, u0)
228+ T = eltype (u0)
229+ up = copy (u0)
230+ um = copy (u0)
231+ J = nothing
232+ for i in eachindex (u0)
233+ h = cbrt (eps (T)) * max (one (T), abs (u0[i]))
234+ up[i] = u0[i] + h
235+ um[i] = u0[i] - h
236+ col = (residual (up) .- residual (um)) ./ (2 h)
237+ J === nothing && (J = similar (col, length (col), length (u0)))
238+ J[:, i] = col
239+ up[i] = u0[i]
240+ um[i] = u0[i]
241+ end
242+ return J === nothing ? zeros (T, 0 , length (u0)) : J
243+ end
244+
213245function _initialization_problem (prob)
214246 prob isa SciMLBase. AbstractNonlinearProblem && return prob
215247 f = prob. f
0 commit comments