Repository navigation
NonlinearSolveCorrector: NonlinearSolve.jl algorithms as the PALC/Natural corrector - #345
ParadaCarleton wants to merge 19 commits into
Conversation
…gh NonlinearSolve.jl A package extension (NonlinearSolveExt) solves the bordered system of PALC, the fixed-parameter Newton system of Natural and solve(prob, corrector, NewtonPar) with any NonlinearSolve algorithm, one cache per corrector so its Jacobian reuse and factorizations survive between solves. corrector = nothing keeps BifurcationKit's Newton method.
…orrectors via reinit!(...; reuse_jacobian = true)
… steps, bounds for algorithms that support them
…arSolve has project_bounds
…(), the keyword NonlinearSolve settled on
|
That part looks pretty cool, thank you! |
| x, p = palc_state(w), w[end] | ||
| Fx = BK.residual(ctx.prob, x, BK.set(ctx.par, ctx.lens, p)) | ||
| Nx = BK.arc_length_eq(ctx.dotθ, x, ctx.z0u, p - ctx.z0p, ctx.τu, ctx.τp, ctx.θ, ctx.ds) | ||
| return vcat(Fx, Nx) |
There was a problem hiding this comment.
this part is bad for GPU, that is why I developed the BorderedArrays.
There was a problem hiding this comment.
Agreed: vcat(Fx, Nx), w[begin:end-1] and w[end] force a flat array with scalar indexing. I'd use BorderedArray directly, but NonlinearSolve needs u0::AbstractArray, and BorderedArray isn't one.
The closest thing I see is RecursiveArrayTools.ArrayPartition(x, [p]), which is already a BifurcationKit dependency. It's an AbstractVector that NonlinearSolve accepts, and it broadcasts part by part, so x stays a CuArray and there's no scalar indexing or concatenation. p would live in a one-element array of the same type as x. Would that work for you, or would you rather make BorderedArray an AbstractVector?
There was a problem hiding this comment.
I think it would work.
A few remarks though:
- I want to separate
uand parametersp: this pattern appears everywhere: periodic orbit (there is a parameter-like constraint), hopf/ fold continuations, continuation of bifurcations of PO (2 constraints), etc. - if you cannot use
BorderedArray, at least allow for the user to passAbstractArrayand not justAbstractVector, this is handy for solving PDE - we have the issue BifurcationKit not working with ArrayPartitions ? #112
- if you pass an
ArrayPartitionto a nonlinear solve, does it return aArrayPartition?
| bordered_matrix(ctx::PALCContext, J::AbstractMatrix, dFdp) = vcat(hcat(J, dFdp), hcat(LA.adjoint(ctx.row), ctx.c)) | ||
|
|
||
| # products of `[J ∂ₚF; row c]` with `v = (vx, vp)` | ||
| function palc_jvp(v, w, ctx::PALCContext) |
There was a problem hiding this comment.
Do you mean running BifurcationKit's own BLS as the linear solver inside NonlinearSolve? That works, I think: linsolve takes a LinearSolve.jl algorithm, and LinearSolve.LinearSolveFunction wraps any function as one. The function would read J, ∂ₚF and the arclength row from the problem's parameter (the PALCContext) and call BK.solve_bls with the continuation's bls. The bordered system would then be solved with BorderingBLS/MatrixFreeBLS, as in BifurcationKit's Newton, instead of building the dense [J ∂ₚF; row c] (bordered_matrix) or forming its products here. If that's what you had in mind, I'll make that the default and keep the dense matrix only for a user-given dense linsolve.
There was a problem hiding this comment.
aaec980 to
e4910ce
Compare
|
|
||
| #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
| # The problem's Jacobian (and `∂ₚF` for PALC) at the last point asked for, built once for the products and the dense matrix of one iterate | ||
| struct Linearization{Tx, TJ, Tdp} |
| #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
| # Solves of the bordered system of PALC in `(x, p)`. The parameter of the NonlinearProblem is a `PALCContext`. | ||
| # `row` and `c` are the last row `[θ A τu' (1-θ) τp]` of the bordered matrix of `MatrixBLS`. | ||
| struct PALCContext{Tprob, Tpar, Tlens, Tdot, Tu, Tp, Tθ, Trow} |
There was a problem hiding this comment.
PALCCache ?
These are already in ContState
z0u::Tu
z0p::Tp
τu::Tu
τp::Tp
| struct PALCContext{Tprob, Tpar, Tlens, Tdot, Tu, Tp, Tθ, Trow} | ||
| prob::Tprob | ||
| par::Tpar | ||
| lens::Tlens |
| θ::Tθ | ||
| ds::Tp | ||
| row::Trow | ||
| c::Tp |
|
Just out of curiosity, can you add tests for Fold/Hopf continuation please to see if the interface you propose is good enough? |
|
Thanks for the review. Plan, following your remarks:
One catch: BorderedLinearSolve.jl is not registered yet (v0.1.0, requires LinearSolve 5 and SciMLBase 3), so BifurcationKit can only depend on it after a release. Should this PR wait for that, or should it land with BorderedLinearSolve as a weak dependency in the extension in the meantime? |
…tems by BorderedLinearSolve.jl The unknown of PALC is ArrayPartition(x, [p]) for any AbstractArray state x, without vcat or scalar indexing. Its Jacobian is BorderedJacobian, a SciMLOperator given to NonlinearSolve as jac_prototype, whose linear systems are solved by BorderedLinearSolve.jl (keyword `bls`, default BorderingBLS with the `linsolve` of `alg` as the inner solver and one correction sweep) through a LinearSolveFunction in the Newton step of `alg`. This replaces the hand-built bordered matrix and products, and `corrector_jacobian`/`corrector_matrix`: a preconditioner now sees the operator of J alone, with the state in `A.u` and the parameters in `A.p`. PALCContext becomes PALCCache, which reads z0, τ0, ds from the continuation state and the lens from the problem, and documents `row` and `c`. BorderedLinearSolve.jl is a weak dependency (extension trigger) loaded from its git URL through [sources] until it is registered.
…e default corrector
…p from the problem's jet
…lem's jet (R01), not finite differences
…e as the bordered jvp With a matrix-free `J` (a function `dx -> J dx`), PALC's corrector gives NonlinearSolve the product with the whole bordered Jacobian `[J ∂ₚF; row' c]` (`palc_jvp`) and solves it with the iterative linear solver of `alg`, whose `precs` reads the iterate through `corrector_jacobian` and `corrector_matrix`. BorderedLinearSolve.jl is used only when `J` is a matrix. This narrows the use of BorderedLinearSolve (decision 2 of the redesign) to the matrix case. NonlinearSolve rebinds only its own JacobianOperator to each iterate, and BorderedLinearSolve has no API to swap the inner operator of its cache while keeping that cache, so a matrix-free `J` inside BLS would rebuild the preconditioner at every iterate and `jacobian_reuse` would have no effect (the precs test failed). The unknown stays `ArrayPartition(x, [p])`. `J` and `∂ₚF` are computed once per iterate (`iterate_jacobian!`), however many products the Krylov solver takes.
…p_min or p_max A corrector whose solution lies past the bound was rejected as not converged, so PALC halved the step toward the bound without ever reaching it. The corrector now reports convergence from its residual alone; a predictor past the bound is still corrected at the bound by Natural.
…es past p_min or p_max" This reverts commit 10f3a59.
…n or p_max is corrected at that bound from where it ended, as a predictor past the bound is
…r p_max is converged The corrector no longer rejects a solution past the bound; PALC corrects it again at the bound (previous commit). Rejecting it made PALC halve the step and reach the bound only through a later predictor past it. Tested on the circle x² + a² = 1 below its centre, where the corrector raises a past a predictor that is short of p_max: the branch ends on p_max exactly.
…ringBLS clashes with BifurcationKit's
e7a7cbd to
f26ae26
Compare
Adds
NonlinearSolveCorrector(package extensionNonlinearSolveExt, loaded byusing NonlinearSolve), which lets any NonlinearSolve.jl algorithm be the corrector ofPALCandNaturaland the solver ofsolve(prob, corrector, NewtonPar()), with Jacobian reuse within and across correctors, matrix-free Jacobians through a Krylovlinsolve, and[p_min, p_max]kept by algorithms with native bounds; the default corrector (nothing, BifurcationKit's own Newton) is unchanged.test/continuation/test_nonlinearsolve_corrector.jlchecks that the branch matches BifurcationKit's Newton point by point past the fold, and that the reuse, trust-region, bounded and matrix-free configurations stay on it. It stays a draft until SciML/NonlinearSolve.jl#1353 (reinit!(...; reuse_jacobian = true)) is released; the projecting test runs only once NonlinearSolve definesBoundsProjection(SciML/NonlinearSolve.jl#1354).