π‘ Temperature model (codim 2)
This is a classical example from the Trilinos library.
This is a simple example in which we aim at solving $\Delta T+\alpha N(T,\beta)=0$ with boundary conditions $T(0) = T(1)=\beta$. This example is coded in examples/chan.jl. We start with some imports:
using Revise, BifurcationKit, LinearAlgebra, Plotsconst BK = BifurcationKitNL(x; a = 0.5, b = 0.01) = 1 + (x + a*x^2)/(1 + b*x^2)We then write our functional:
function F_chan(x, p) (;Ξ±, Ξ²) = p f = similar(x) n = length(x) f[1] = x[1] - Ξ² f[n] = x[n] - Ξ² for i=2:n-1 f[i] = (x[i-1] - 2 * x[i] + x[i+1]) * (n-1)^2 + Ξ± * NL(x[i], b = Ξ²) end return fendWe want to call a Newton solver. We first need an initial guess:
n = 101sol0 = [(i-1)*(n-i)/n^2+0.1 for i=1:n]# set of parameterspar = (Ξ± = 3.3, Ξ² = 0.01)Finally, we need to provide some parameters for the Newton iterations. This is done by calling
optnewton = NewtonPar(tol = 1e-11, verbose = true)We call the Newton solver:
prob = BifurcationProblem(F_chan, sol0, par, (@optic _.Ξ±), # function to plot the solution plot_solution = (x, p; k...) -> plot!(x; ylabel="solution", label="", k...))sol = @time BK.solve( prob, Newton(), optnewton)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Newton step residual linear iterations β
βββββββββββββββ¬βββββββββββββββββββββββ¬βββββββββββββββββ€
β 0 β 2.3440e+01 β 0 β
β 1 β 1.3774e+00 β 1 β
β 2 β 1.6267e-02 β 1 β
β 3 β 2.4336e-06 β 1 β
β 4 β 6.7452e-12 β 1 β
βββββββββββββββ΄βββββββββββββββββββββββ΄βββββββββββββββββ
0.000989 seconds (316 allocations: 2.296 MiB)Note that, in this case, we did not give the Jacobian. It was computed internally using Automatic Differentiation.
We can perform numerical continuation w.r.t. the parameter $\alpha$. This time, we need to provide additional parameters, but now for the continuation method:
optcont = ContinuationPar(dsmin = 0.01, dsmax = 0.2, ds= 0.1, p_min = 0., p_max = 4.2, newton_options = NewtonPar(max_iterations = 10, tol = 1e-9))Next, we call the continuation routine as follows.
br = continuation(prob, PALC(), optcont; plot = true)nothing #hideThe parameter axis lens = @optic _.Ξ± is used to extract the component of par corresponding to Ξ±. Internally, it is used as get(par, lens) which returns 3.3.
You should see
scene = title!("") #hideThe left figure is the norm of the solution as function of the parameter $p=\alpha$, the y-axis can be changed by passing a different recordFromSolution to BifurcationProblem. The top right figure is the value of $\alpha$ as function of the iteration number. The bottom right is the solution for the current value of the parameter. This last plot can be modified by changing the argument plotSolution to BifurcationProblem.
Two Fold points were detected. This can be seen by looking at br.specialpoint, by the black dots on the continuation plots when doing plot(br, plotfold=true) or by typing br in the REPL. Note that the bifurcation points are located in br.specialpoint.
Continuation of Fold points
We get a summary of the branch by doing
br ββ Curve type: EquilibriumCont
ββ Number of points: 58
ββ Type of vectors: Vector{Float64}
ββ Parameter Ξ± starts at 3.3, ends at 4.2
ββ Algo: PALC [Secant]
ββ Special points:
- # 1, bp at Ξ± β +4.04103799 β (+4.04103799, +4.04115545), |Ξ΄p|=1e-04, [converged], Ξ΄ = ( 1, 0), step = 5
- # 2, bp at Ξ± β +3.15565319 β (+3.15565221, +3.15565319), |Ξ΄p|=1e-06, [converged], Ξ΄ = (-1, 0), step = 23
- # 3, endpoint at Ξ± β +4.20000000, step = 57
We can take the first Fold point, which has been guessed during the previous continuation run and locate it precisely. However, this only works well when the jacobian is computed analytically. We use automatic differentiation for that
# index of the Fold bifurcation point in br.specialpointindfold = 2outfold = newton( #index of the fold point br, indfold)BK.converged(outfold) && printstyled(color=:red, "--> We found a Fold Point at Ξ± = ", outfold.u.p, ", Ξ² = 0.01, from ", br.specialpoint[indfold].param,"\n")--> We found a Fold Point at Ξ± = 3.15565073161177, Ξ² = 0.01, from 3.155653188706858We can finally continue this fold point in the plane $(Ξ±, Ξ²)$ by performing a Fold Point continuation. In the present case, we find a Cusp point.
We don't need to call newton first in order to use continuation for the codim 2 curve of bifurcation points.
outfoldco = continuation(br, indfold, # second parameter axis to use for codim 2 curve (@optic _.Ξ²), # we disable the computation of eigenvalues, it makes little sense here ContinuationPar(optcont, detect_bifurcation = 0))scene = plot(outfoldco, plotfold = true, legend = :bottomright)The performances for computing the curve of Fold is not that great. It is because we use the default solver tailored for ODE. If you pass jacobian_ma = :minaug to the last continuation call, you should see a great improvement in performances.
Using GMRES or another linear solver
We continue the previous example but now using Matrix Free methods. The user can pass its own solver by implementing a version of LinearSolver. Some linear solvers have been implemented from KrylovKit.jl and IterativeSolvers.jl (see Linear solvers (LS) for more information), we can use them here. Note that we can also use preconditioners as shown below. The same functionality is present for the eigensolver.
# derivative of NdN(x; a = 0.5, b = 0.01) = (1-b*x^2+2*a*x)/(1+b*x^2)^2# Matrix Free version of the differential of F_chan# Very easy to write since we have F_chan.# We could use Automatic Differentiation as wellfunction dF_chan(x, dx, p) (;Ξ±, Ξ²) = p out = similar(x) n = length(x) out[1] = dx[1] out[n] = dx[n] for i=2:n-1 out[i] = (dx[i-1] - 2 * dx[i] + dx[i+1]) * (n-1)^2 + Ξ± * dN(x[i], b = Ξ²) * dx[i] end return outend# we create a new linear solverls = GMRESKrylovKit(dim = 100)# and pass it to the newton parametersoptnewton_mf = NewtonPar(verbose = true, linsolver = ls, tol = 1e-10)# we change the problem with the new jacobianprob = re_make(prob; # we pass the differential a x, # which is a linear operator in dx J = (x, p) -> (dx -> dF_chan(x, dx, p)) )# we can then call the newton solverout_mf = @time BK.solve(prob, Newton(), optnewton_mf)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Newton step residual linear iterations β
βββββββββββββββ¬βββββββββββββββββββββββ¬βββββββββββββββββ€
β 0 β 2.3440e+01 β 0 β
β 1 β 1.3774e+00 β 68 β
β 2 β 1.6267e-02 β 98 β
β 3 β 2.4336e-06 β 74 β
β 4 β 5.6275e-12 β 63 β
βββββββββββββββ΄βββββββββββββββββββββββ΄βββββββββββββββββ
0.003455 seconds (39.55 k allocations: 3.227 MiB, 0.10% compilation time)We can improve this computation, i.e. reduce the number of Linear-Iterations, by using a preconditioner
using SparseArrays# define preconditioner which is basically ΞP = spdiagm(0 => -2 * (n-1)^2 * ones(n), -1 => (n-1)^2 * ones(n-1), 1 => (n-1)^2 * ones(n-1))P[1,1:2] .= [1, 0.];P[end,end-1:end] .= [0, 1.]# define gmres solver with left preconditionerls = GMRESIterativeSolvers(reltol = 1e-4, N = length(sol.u), restart = 10, maxiter = 10, Pl = lu(P))optnewton_mf = NewtonPar(verbose = true, linsolver = ls, tol = 1e-10)out_mf = @time BK.solve(prob, Newton(), optnewton_mf)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Newton step residual linear iterations β
βββββββββββββββ¬βββββββββββββββββββββββ¬βββββββββββββββββ€
β 0 β 2.3440e+01 β 0 β
β 1 β 1.3777e+00 β 3 β
β 2 β 1.6266e-02 β 3 β
β 3 β 2.3699e-05 β 2 β
β 4 β 4.8929e-09 β 3 β
β 5 β 5.6333e-12 β 4 β
βββββββββββββββ΄βββββββββββββββββββββββ΄βββββββββββββββββ
0.000496 seconds (520 allocations: 1.485 MiB)