Giter Club home page Giter Club logo

bifurcationkit.jl's People

Contributors

aayushsabharwal avatar antoine-levitt avatar danielwe avatar datseris avatar dkarrasch avatar fredrikekre avatar isaacsas avatar oxinabox avatar paniash avatar rveltz avatar tkf avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bifurcationkit.jl's Issues

Computing bifurcation diagrams or branch points with only the first derivative

Hello,
I have a 2 variable ODE system for which I want to computer a bifurcation diagram. I managed to get a sketch of it on previous versions:
image
The paths didn't manage to track all of the ways properly, but it seems to have a single steady state, then a region with 5, then a region with 3, then 5 and then 1 again. I think there are 2 branch points and 4 fold points.

I managed to get that sketch using deflated continuation, but I think the code don't work anymore, and it looked kind of weird, so I figured maybe I should try using the bifurcation diagram feature (https://rveltz.github.io/BifurcationKit.jl/dev/BifurcationDiagram/) which computes it recursively (but then that might have some problems because there's a cycle). Possibly branch switching is better (https://rveltz.github.io/BifurcationKit.jl/dev/branchswitching/), since I know more or less what to expect.

However, all of the inputs for these methods require the function and 3 derivatives. Because I get my function via reaction networks I have the function, and the first derivative (the Jacobian, but that is what is being requested, right). Is it possible to perform these with only those two inputs (I tried, but got some error, but it might be I am making some other input mistake). Is there any disadvantage to not providing 2nd and 3rd derivatives?

Hopf continuation failed (simple ODE system)

Hi, great package!
I'm trying to make a Hopf continuation for a simple ODE system:

function simpleSystem!(dx, x, p, t)
	@unpack B, β, Da = p
	x1, x2 = x
	dx[1] = -x1 + Da*(1-x1)*exp(x2)
	dx[2] =	-x2 + B*Da*(1-x1)*exp(x2)-β*(x2)
	dx
end

simpleSystem(x, p) = simpleSystem!(similar(x), x, p, 0)
jet  = BK.getJet(simpleSystem; matrixfree=false)

par_simpleSystem = (B=7.06, β=0.74, Da=0.15)
x0 = [0.4,0.4]

opts_br = ContinuationPar(pMin = 0.13, pMax = 0.16	, ds = -0.001, dsmax = 0.01, nInversion = 20,
	detectBifurcation = 3, maxBisectionSteps = 8, nev = 30, plotEveryStep = 20, maxSteps = 1500,
	theta = 0.03)

br, = continuation(jet[1], jet[2], x0, par_firstOrder, (@lens _.Da), opts_br;
	recordFromSolution = recordFromSolution,
	#bothside = true,
	plot = false, verbosity = 0, normC = norminf)

scene = plot(br)

The branch should be correct, but my Hopf continuation on the two Hopf points failed.
Could you tell me how to get the Hopf continuation work?

Error with DifferentialEquations and multiple callbacks

Hi,

When trying to use Multiple Poincare Shooting, I get the following error. Maybe @ChrisRackauckas has an idea...

julia> res = @time probMPsh(par_hopf)(initpo, initpo)
ERROR: Event repeated at the same time. Please report this error
Stacktrace:
 [1] apply_callback!(::OrdinaryDiffEq.ODEIntegrator{Tsit5,false,Array{Float64,1},Float64,NamedTuple{(:r, :μ, :ν, :c3, :c5),NTuple{5,Float64}},Float64,Float64,Float64,Array{Array{Float64,1},1},ODESolution{Float64,2,Array{Array{Float64,1},1},Nothing,Nothing,Array{Float64,1},Array{Array{Array{Float64,1},1},1},ODEProblem{SubArray{Float64,1,Array{Float64,2},Tuple{Base.Slice{Base.OneTo{Int64}},Int64},true},Tuple{Float64,Float64},false,NamedTuple{(:r, :μ, :ν, :c3, :c5),NTuple{5,Float64}},ODEFunction{false,typeof(Fsl),UniformScaling{Bool},Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing},Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}},DiffEqBase.StandardODEProblem},Tsit5,OrdinaryDiffEq.InterpolationData{ODEFunction{false,typeof(Fsl),UniformScaling{Bool},Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing},Array{Array{Float64,1},1},Array{Float64,1},Array{Array{Array{Float64,1},1},1},OrdinaryDiffEq.Tsit5ConstantCache{Float64,Float64}},DiffEqBase.DEStats},ODEFunction{false,typeof(Fsl),UniformScaling{Bool},Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing},OrdinaryDiffEq.Tsit5ConstantCache{Float64,Float64},OrdinaryDiffEq.DEOptions{Float64,Float64,Float64,Float64,typeof(DiffEqBase.ODE_DEFAULT_NORM),typeof(opnorm),CallbackSet{Tuple{VectorContinuousCallback{PseudoArcLengthContinuation.var"#pSection#599"{typeof(section!)},PseudoArcLengthContinuation.var"#affect!#600",Nothing,typeof(DiffEqBase.INITIALIZE_DEFAULT),Float64,Int64,Nothing}},Tuple{}},typeof(DiffEqBase.ODE_DEFAULT_ISOUTOFDOMAIN),typeof(DiffEqBase.ODE_DEFAULT_PROG_MESSAGE),typeof(DiffEqBase.ODE_DEFAULT_UNSTABLE_CHECK),DataStructures.BinaryHeap{Float64,DataStructures.LessThan},DataStructures.BinaryHeap{Float64,DataStructures.LessThan},Nothing,Nothing,Int64,Array{Float64,1},Array{Float64,1},Array{Float64,1}},Array{Float64,1},Float64,DiffEqBase.CallbackCache{Array{Float64,N} where N,Array{Float64,N} where N}}, ::VectorContinuousCallback{PseudoArcLengthContinuation.var"#pSection#599"{typeof(section!)},PseudoArcLengthContinuation.var"#affect!#600",Nothing,typeof(DiffEqBase.INITIALIZE_DEFAULT),Float64,Int64,Nothing}, ::Float64, ::Float64, ::Int64) at /Users/rveltz/.julia/packages/DiffEqBase/YIwj5/src/callbacks.jl:667

The example is as follows:

using DifferentialEquations, DiffEqOperators, ForwardDiff
	using PseudoArcLengthContinuation, LinearAlgebra, Parameters, Setfield
	const PALC = PseudoArcLengthContinuation

norminf = x -> norm(x, Inf)
function Fsl!(f, u, p)
	@unpack r, μ, ν, c3, c5 = p
	u1 = u[1]
	u2 = u[2]

	ua = u1^2 + u2^2

	f[1] = r * u1 - ν * u2 - ua * (c3 * u1 - μ * u2) - c5 * ua^2 * u1
	f[2] = r * u2 + ν * u1 - ua * (c3 * u2 + μ * u1) - c5 * ua^2 * u2

	return f
end

function Fsl(x, p, t)
	f = similar(x)
	Fsl!(f, x, p)
end

Fode(f, x, p, t) = Fsl!(f, x, p)
Jsl(x, dx, p) = ForwardDiff.derivative(t -> Fsl(x .+ t .* dx, p), 0.)
# JacOde(J, x, p, t) = copyto!(J, Jsl(x, p))

par_sl = (r = 0.5, μ = 0., ν = 1.0, c3 = 1.0, c5 = 0.0,)
u0 = [.001, .001]

par_hopf = (@set par_sl.r = 0.1)
probsundials = ODEProblem(Fsl, u0, (0., 100.), par_hopf)

function section!(out, x)
	M = 4
	for ii=1:M
		th = 2pi*ii/M
		tmp = [-sin(th), cos(th)]
		out[ii] = dot(x, tmp)
	end
	out
end

# standard simple shooting
M = 4
probMPsh = p -> PALC.PoincareShootingProblem(u -> Fsl(u, p, 0.), p, probsundials, Tsit5(), M, section!; atol = 1e-10, rtol = 1e-9)

r = 1.04

initpo = Float64[]
for ii=1:M
	th = 2pi*ii/M
	push!(initpo, r*cos(th))
	push!(initpo, r*sin(th))

end

res = @time probMPsh(par_hopf)(initpo);
norminf(res)
# 0.005197 seconds (61.15 k allocations: 4.358 MiB)
# 0.4953547991423255

however, this returns an error:

res = @time probMPsh(par_hopf)(initpo, initpo);

Allow using other Makie backends

Right now, only GLMakie backend is allowed, for e.g. WGLMakie or CairoMakie, RecipesMakie.jl is not loaded (as per this line)

Also, RecipesMakie.jl seems to only support GLMakie (but that's only my assumption).

Thanks a lot

Can pMax and pMin be used as hard constraints?

I have a problem that p is strictly constrained in a range, say [a, b], (which can be computed). When p is out of this range, the evaluation of the function will fail. In my case, a negative will be given to the log function. Currently, when I set pMin=a+eps(), and pMax=b-eps(), the parameter prediction step will still predict a value outside the range [a, b], which leads to the failing of the whole continuation process.

Sample output is

      From worker 2:	Continuation Step 339
      From worker 2:	Step size = 2.0000e-03
      From worker 2:	Parameter Aα = 8.2503e-01 ⟶  8.2741e-01 [guess]
      From worker 2:	--> Step Converged in 2 Nonlinear Iteration(s)
      From worker 2:	Parameter Aα = 8.2503e-01 ⟶  8.2741e-01
      From worker 2:	Predictor: BifurcationKit.SecantPred()
      From worker 2:	--> Computed 3 eigenvalues in 1 iterations, #unstable = 3
      From worker 2:	──────────────────────────────────────────────────────────────────────
      From worker 2:	Continuation Step 340
      From worker 2:	Step size = 2.0000e-03
      From worker 2:	Parameter Aα = 8.2741e-01 ⟶  8.2979e-01 [guess]

For this problem, my b is 0.829285. As can be seen, the prediction of p in Step 340 is 0.82979 (>0.829285), which leads to the breaking of my continuation process.

I wonder if we can make the [pMin, pMax] constraint hard, so that p never goes outside of it. Or even better, BK can detect the failing of evaluation of the function when the prediction of p is outside the range, and reduce the step size accordingly to force the prediciton of p lies in the range.

example in docs using @reaction_network

Would be good to show users that its possible to use this library with DiffEqBiological

using DiffEqBiological,BifurcationKit
using Setfield,LinearAlgebra,Plots

system = @reaction_network begin
     k₁,		Y  2X
     2k₂,		2X  X + Y
     k₃,		X + Y  Y
     k₄,		X  0
end k₁ k₂ k₃ k₄

# get rate function and jacobian, for use in continuation
F! = oderhsfun(system)
function F(u::Vector{T},p::NamedTuple) where T<:Number
	f = similar(u)
	F!(f,u,[ p[key] for (key, value)  paramsmap(system) ],Inf)
	return f
end

∂Fu! = jacfun(system)
function ∂Fu(u::Vector{T},p::NamedTuple) where T<:Number
	J = zeros(T,length(u),length(u))
	∂Fu!(J,u,[ p[key] for (key, value)  paramsmap(system) ],Inf)
	return J
end

# define the parameter direction that you want to explore
pRange = range(0,12,length=200)
parameter_name = (@lens _.k₁)
p = (k₁=0.0, k₂=1.0, k₃=1.0, k₄=1.5)

# options for branch continuation along parameter direction
options = ContinuationPar( maxSteps = 5000,
	dsmax=10*step(pRange), dsmin=step(pRange), ds=step(pRange),
	pMin = minimum(pRange), pMax=maximum(pRange),
	newtonOptions = NewtonPar(tol = 1e-8),
	saveEigenvectors = false
)

# options for detecting multiple branches
deflation = DeflationOperator(2.0, dot, 1.0, [ zeros(length(species(system))) ])
termination_condition(x, f, J, residual, iteration, niter, options; kwargs...) = residual <1e3
root_search(x,p,id) = x .+ rand(length(x))

##################################### main continuation method
@assert( det(∂Fu(randn(length(species(system))),p))  0, "species must be linearly independent")
branches, = continuation( F, ∂Fu, p, parameter_name, options, deflation; showplot=false,
	callbackN = termination_condition, perturbSolution = root_search )

#################################### display results
plot(size=(500,500),ylabel="steady states", xlabel="parameter $(first(typeof(parameter_name).parameters))")
plot!(branches,lw=3,label="")

Screenshot from 2020-11-05 10-28-51

How to use iterator to make a bifurcation diagram?

Hello,
I am following the instructions under https://rveltz.github.io/PseudoArcLengthContinuation.jl/dev/iterator/ to make a bifurcation diagram of a system I am investigating. While I can get the base example to work by running it, I have problems making it work as I'd like for a more general system (and when it works, I am a bit uncertain why).

In the example, I am using DiffEqBiological to generate a model, from which I then generate the system function and jacobian. The model equation is
dX/dt = v0 + v*(X^n)/(K^n + X^n) - d*X
and I want to plot the value of the system's fixed point(s) as the parameter v varies from 0.1 to 5.0.
Please see the following code:

# Fetch stuff
using PseudoArcLengthContinuation, SparseArrays, LinearAlgebra, Plots, Setfield, UnPack
const PALC = PseudoArcLengthContinuation
using DiffEqBiological # Required to generate my system

# Taken from the docs. Don't know why we are doing this.
normInf = x -> norm(x, Inf)

# Generates my functions
# A reactio network. This generates the stuff for a model system with a single variable and 5 parameters.
rn = @reaction_network begin
	v0+hill(X,v,K,n), 0 --> X
	d, X --> 0
end v0 v K n d

# The reaction network structures contain the right functions, however, they are designed for DifferentialEquations and requires modification.
# The original functions do not return the output, but rather modifes an input vector/matrix with the desired values. Also, the parameters works differently (I tried it the natural way, ran into problems, but I will worry about that when I got the main methodology working).
function F(x,p)
	@unpack v0,v,K,n,d = p
	output = copy(x)
	rn.f(output,x,[v0, v, K, n, d],0.)
	return output
end
function J(x,p)
	@unpack v0,v,K,n,d = p
	output = zeros(length(x),length(x))
	rn.jac(output,x,[v0, v, K, n, d],0.)
	return output
end

# Just taken from the docs. I modified pMin and pMax to 0.1 and 10.0. I sat pMin to 0.1 because that was my best guess at the initial value.
opts = PALC.ContinuationPar(dsmax = 0.1, dsmin = 1e-5, ds = -0.001, maxSteps = 130, pMin = 0.1, pMax = 10.0, saveSolEveryNsteps = 0, computeEigenValues = true, detectBifurcation = true, newtonOptions = NewtonPar(tol = 1e-8, verbose = true))

# Set the parameters. I sat v to 5. because I thought that might be the final value.
pars = (v0=0.1, v=5., K=0.5, n=3., d=1.2)

# Tries to define the iterator. The fixed point of the system for v=0.1 is 0.08333333333333334.
iter = PALC.PALCIterable(F, J, [0.08333333333333334], pars, (@lens _.v), opts; verbosity = 2)

# This is just taken directly from the example.
resp = Float64[]
resx = Float64[]
for state in iter
	push!(resx, getx(state)[1])
	push!(resp, getp(state))
end

#This plot
plot(resp, resx; label = "", xlabel = "v")

Now, this plot gets rather "jagged", and I'd like it smoother. I try setting

opts = PALC.ContinuationPar(dsmax = 0.01, dsmin = 1e-3, ds = -0.001, maxSteps = 1300,...

but this makes the end result weird. I try modifying ds = 0.001, but that makes things worse. What should I do here?

Also, could someone confirm how I set the initial and final values of the parameter, as well as exactly where I should make my initial x0 guess? I got it right here, but I would like to be sure why I am right.

Looking forward to getting this working!
Cheers

Illegal inttoptr?

Hi, every example I have tried to run crashes Julia 1.4.1 on my machine.
In the chan.jl example, this occurs on line 76, or skipping the call to newton, on line 82. Likewise, the script crashes on lines 92 or 101 if the previous crashing calls are skipped.

Some of the crash message:

julia> outfold, _, flag = @time newton(
               F_chan, Jac_mat,
               #index of the fold point
               br, indfold, par, (@lens _.α))
Illegal inttoptr
	  %90 = ptrtoint double addrspace(13)* %88 to i64
Illegal inttoptr
	  %scevgep3536 = ptrtoint double addrspace(13)* %scevgep35 to i64
Illegal inttoptr
	  %100 = inttoptr i64 %99 to i8 addrspace(13)*

signal (6): Aborted
in expression starting at REPL[27]:1
gsignal at /usr/bin/../lib/x86_64-linux-gnu/libc.so.6 (unknown line)
abort at /usr/bin/../lib/x86_64-linux-gnu/libc.so.6 (unknown line)
unknown function (ip: 0x7f3242ef3d04)

Apologies if that isn't helpful; I'm just not sure how to track down the underlying issue.

The matrix-free portion of the example runs without issue (starting from line 108).

Support for NonLinearProblem

Dear authors,

I was following the plotting examples from #27, but was not sure whether there is support for NonLinearProblem. Specifically, I am trying to train a neural network (deep equilibrium models) using the NonLinearProblem wrapper, and it is not clear to me what parameters I should pass into continuation. When I run the following function, this error occurs (any help would be appreciated, especially references to visualization of NonLinearProblems)---

ann = Chain(Dense(1, 2), Dense(2, 1))
p,re = Flux.destructure(ann)
pRange = (0.1,20.)

function solve_ss(x)
    z = re(p)(x)
    function dudt_(u, _p)
        re(_p)(u+x) - u
    end

    ss = NonlinearProblem{false}(dudt_, z, p)
    x = solve(ss, NewtonRaphson(), tol = 1f-5)#.u
    options = ContinuationPar( maxSteps=50000,
        dsmax=(pRange[2]-pRange[1])*1e-3, dsmin=(pRange[2]-pRange[1])*1e-9, ds=(pRange[2]-pRange[1])*1e-9,
        pMin=pRange[1], pMax=pRange[2], detectBifurcation=1,
        newtonOptions = NewtonPar(tol = 1e-8, verbose = false));
    path = continuation((u,p)->fun(u,p,0), (u,p)->fun.jac(u,p,0), z, (@lens _.p), options; tangentAlgo=BorderedPred());
    plot(path[1],grid=false,xguide="$p",plotfold=false,label="",framestyle=:box)
end

ERROR: LoadError: type Array has no field p
Stacktrace:
[1] getproperty(x::Vector{Float32}, f::Symbol)
@ Base ./Base.jl:33
[2] get(obj::Vector{Float32}, l::Setfield.PropertyLens{:p})
@ Setfield ~/.julia/packages/Setfield/NshXm/src/lens.jl:105
[3] iterate(it::ContIterable{var"#61#64", BifurcationKit.var"#143#145"{var"#61#64"}, var"#62#65", Vector{Float32}, Setfield.PropertyLens{:p}, Float64, DefaultLS, DefaultEig{typeof(real)}, BorderedPred, BorderingBLS{DefaultLS, Float64}, BifurcationKit.var"#118#128"{BifurcationKit.var"#118#119#129"}, BifurcationKit.var"#120#130"{BifurcationKit.var"#120#121#131"}, typeof(norm), BifurcationKit.DotTheta{BifurcationKit.var"#122#132"}, typeof(BifurcationKit.finaliseDefault), typeof(BifurcationKit.cbDefault), Nothing, String}; _verbosity::Int64)
@ BifurcationKit ~/.julia/packages/BifurcationKit/Yvq7p/src/Continuation.jl:244
[4] iterate(it::ContIterable{var"#61#64", BifurcationKit.var"#143#145"{var"#61#64"}, var"#62#65", Vector{Float32}, Setfield.PropertyLens{:p}, Float64, DefaultLS, DefaultEig{typeof(real)}, BorderedPred, BorderingBLS{DefaultLS, Float64}, BifurcationKit.var"#118#128"{BifurcationKit.var"#118#119#129"}, BifurcationKit.var"#120#130"{BifurcationKit.var"#120#121#131"}, typeof(norm), BifurcationKit.DotTheta{BifurcationKit.var"#122#132"}, typeof(BifurcationKit.finaliseDefault), typeof(BifurcationKit.cbDefault), Nothing, String})
@ BifurcationKit ~/.julia/packages/BifurcationKit/Yvq7p/src/Continuation.jl:243
[5] continuation(it::ContIterable{var"#61#64", BifurcationKit.var"#143#145"{var"#61#64"}, var"#62#65", Vector{Float32}, Setfield.PropertyLens{:p}, Float64, DefaultLS, DefaultEig{typeof(real)}, BorderedPred, BorderingBLS{DefaultLS, Float64}, BifurcationKit.var"#118#128"{BifurcationKit.var"#118#119#129"}, BifurcationKit.var"#120#130"{BifurcationKit.var"#120#121#131"}, typeof(norm), BifurcationKit.DotTheta{BifurcationKit.var"#122#132"}, typeof(BifurcationKit.finaliseDefault), typeof(BifurcationKit.cbDefault), Nothing, String})
@ BifurcationKit ~/.julia/packages/BifurcationKit/Yvq7p/src/Continuation.jl:458
[6] continuation(Fhandle::Function, Jhandle::Function, x0::Function, par::Vector{Float32}, lens::Setfield.PropertyLens{:p}, contParams::ContinuationPar{Float64, DefaultLS, DefaultEig{typeof(real)}}, linearAlgo::BorderingBLS{DefaultLS, Float64}; bothside::Bool, kwargs::Base.Iterators.Pairs{Symbol, BorderedPred, Tuple{Symbol}, NamedTuple{(:tangentAlgo,), Tuple{BorderedPred}}})
@ BifurcationKit ~/.julia/packages/BifurcationKit/Yvq7p/src/Continuation.jl:0
[7] continuation(Fhandle::Function, Jhandle::Function, x0::Function, par::Vector{Float32}, lens::Setfield.PropertyLens{:p}, contParams::ContinuationPar{Float64, DefaultLS, DefaultEig{typeof(real)}}; linearAlgo::Nothing, kwargs::Base.Iterators.Pairs{Symbol, BorderedPred, Tuple{Symbol}, NamedTuple{(:tangentAlgo,), Tuple{BorderedPred}}})
@ BifurcationKit ~/.julia/packages/BifurcationKit/Yvq7p/src/Continuation.jl:591
[8] continuation(Fhandle::var"#61#64", x0::Function, par::Vector{Float32}, lens::Setfield.PropertyLens{:p}, contParams::ContinuationPar{Float64, DefaultLS, DefaultEig{typeof(real)}}; kwargs::Base.Iterators.Pairs{Symbol, BorderedPred, Tuple{Symbol}, NamedTuple{(:tangentAlgo,), Tuple{BorderedPred}}})
@ BifurcationKit ~/.julia/packages/BifurcationKit/Yvq7p/src/Continuation.jl:594
[9] solve_ss(x::Vector{Int64})
@ Main ~/julia/simpleDEQ.jl:37
[10] top-level scope
@ ~/julia/simpleDEQ.jl:62
[11] include(fname::String)
@ Base.MainInclude ./client.jl:444
[12] top-level scope
@ none:1
in expression starting at /Users/qiyaowei/julia/simpleDEQ.jl:62

Weird bifurcation diagram produced

I am using the bifurcationdiagram on a simple function (with a bistable region. However, there is a weird jump in the diagram (best just seen in the image at the end). It seems something weird gets mixed up.

# Fetch packages
using ModelingToolkit, OrdinaryDiffEq, BifurcationKit, Plots, Setfield, LinearAlgebra

# Declare model.
@parameters t s d T v0 n
@variables X(t) A1(t) A2(t) A3(t)
D = Differential(t)

eqs = [D(X) ~ v0 + ((s*X)^n)/((s*X)^n + (d*A3)^n + 1) - X,
       D(A1) ~ (X-A1)/T,
       D(A2) ~ (A1-A2)/T,
       D(A3) ~ (A2-A3)/T]

@named sys = ODESystem(eqs);

# Prepares input 
params_input = [0.25,1.0,0.1,3,0.1]  # Order is "d,s,v0,n,T", check in "sys.ps". Should be fine.
p_span = (1.,4.);
#odefun = ODEFunction(convert(ODESystem,network),jac=true)
odefun = ODEFunction(sys,jac=true)
F = (u,p) -> odefun(u,p,0)      
J = (u,p) -> odefun.jac(u,p,0)
jet = BifurcationKit.getJet(F, J; matrixfree=false)

u0 = solve(ODEProblem(sys,rand(length(sys.states)),(0.,100.0),params_input),Rosenbrock23()).u[end];

# Sets options
dsmax = 1e-2
dsmin = 1e-5
ds = sqrt(dsmax*dsmin);
maxSteps = 10000
opts_br = ContinuationPar(pMin = p_span[1], pMax = p_span[2], dsmax = dsmax, dsmin = dsmin, ds=ds, maxSteps=maxSteps,
detectBifurcation = 3, nInversion = 6, maxBisectionSteps = 25,nev = 4);

# Calculates the diagram.
bif = bifurcationdiagram(jet..., u0, params_input, (@lens _[2]), 2,
    (x,p,level)->setproperties(opts_br);
    tangentAlgo = SecantPred(),
    recordFromSolution=(x, p) -> x[1], verbosity = 0, plot=false);

# Plots the diagram.
plot(bif)

image

This is my Pkg.status()

  [0f109fa4] BifurcationKit v0.1.8
  [479239e8] Catalyst v10.2.0 `~/.julia/dev/Catalyst`
  [c894b116] DiffEqJump v8.0.0
  [0c46a032] DifferentialEquations v6.20.0
  [961ee093] ModelingToolkit v7.1.3
  [1dea7af3] OrdinaryDiffEq v5.69.0
  [91a5bcdd] Plots v1.24.3
  [f27b6e38] Polynomials v2.0.17
  [295af30f] Revise v3.2.0
  [2913bbd2] StatsBase v0.33.13
  [f3b207a7] StatsPlots v0.14.29
  [789caeaf] StochasticDiffEq v6.41.0

Periodic orbits for reaction networks with nontrivial conservative laws

Thanks for the fantastic package. Briefly, I have some problems trying to find periodic orbits for a system built from Catalyst.jl for which I know a Hopf bifurcation.
I am unsure if the problem comes from building the system, a wrong choice of tolerance parameters or something else.

The system is the following.

using Catalyst, LinearAlgebra

rn = @reaction_network begin
    k1,  X2  X1
    k2,  X1  X2
    k3,  X3  X5
    k4,  X5  X4
    k5,  X4  X6
    k6,  X4  X3
    k7,  X6  X5
    k8,  X1 + X5  X2 + X3
    k9,  X1 + X6  X2 + X4
end k1 k2 k3 k4 k5 k6 k7 k8 k9
Catalyst.reorder_states!(rn, [2,1,3,5,4,6])

The Hopf bifurcation occurs at

Xs = [1.6563516576558746, 3.5232975392793924e8, 1.0, 0.0038843824790898043, 3.7708607296986027e-10, 0.08021161610521155]
Ks = [2.8384448207706775e-9, 2.276606366932523e-10, 1.0000091094346868, 29473.54319518398, 0.01816749288551057, 0.002345143593835089, 2.499174147678733e-5, 1.6010576150026472e9, 0.0005160737862267585]

We can check that the system has nontrivial conservative laws

N = netstoichmat(rn)
W = conservationlaws(N)

So, for example, when we call continuation to compute a bifurcation diagram, we get a "singularity error".
To avoid this error, I build the smooth system as follows.
Question0 (provably for Catalyst.jl people): Does it sound reasonable to build the system in this way?

## Singular system
odesys = convert(ODESystem, rn; combinatoric_ratelaws=false)
odefun = ODEFunction(odesys; jac=true)

## Smooth system
# Conservative laws
states_new = states(rn)
Ts = W * Xs
states_new[2] = Ts[1] - states(rn)[1]
states_new[3] = Ts[2] - sum(states(rn)[4:6])

# Build the new system
odefun_new = odefun(states_new, parameters(rn), odesys.iv)
D = Differential(odesys.iv)
eqs_new = equations(odesys)

ind_new = [1,4,5,6]
eqs_new = [D(states(rn)[i]) ~ odefun_new[i] for i in ind_new]

@named odesys_smooth = ODESystem(
    eqs_new,
    odesys.iv,
    [states(rn)[i] for i in ind_new],
    parameters(rn))

odefun_smooth = ODEFunction(odesys_smooth; jac=true)
F_smooth(u, p) = odefun_smooth(u, p, 0.)
J_smooth(u, p) = odefun_smooth.jac(u, p, 0.)
## Sanity check
Xs_smooth = [Xs[i] for i in [1,4,5,6]]
F_smooth(Xs_smooth, Ks)
det(J_smooth(Xs_smooth, Ks))

Now, I want the bifurcation diagram for X4 w.r.t. parameter k9:

p_idx = 9                     # The index of the bifurcation parameter.
p_span = (0.000, 1.)   # The parameter range for the bifurcation diagram.
plot_var_idx = 4          # The index of the variable we plot in the bifurcation diagram.
# I know about the option `bothside=true`, but I am not sure how to use it, so first I track the solution `Xs` along `k9` using [HomotopyContinuation.jl.](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl)
import HomotopyContinuation
const HC = HomotopyContinuation

# Translate vars and params to HC
hc_vars = HC.Variable.(Symbol.([states(rn)[i] for i in ind_new]))
hc_params = Vector{Any}(undef, length(Ks))
hc_param = HC.Variable(Symbol(Catalyst.parameters(rn)[p_idx]))

# Set all parameters except `k9`, for which we want to track.
hc_params .= Ks
hc_params[p_idx] = hc_param

# Build HC.system with one parameter
hc_eqs_smooth = F_smooth(hc_vars, hc_params)
hc_sys_smooth = HC.System(hc_eqs_smooth; variables=hc_vars, parameters=[hc_param])

# Track `Xs` along `k9`
x₀ = HC.solve(hc_sys_smooth, Xs_smooth; start_parameters=[Ks[p_idx]], target_parameters=[p_span[1]])
Xs_init_smooth = HC.real_solutions(x₀)[1]
# We obtain
Xs_init_smooth = [7.774317758843825, 0.00011544400492545825, 8.034530803010681e-11, 0.08392084801717949]

Now compute the bifurcation diagram

using BifurcationKit, Setfield, Plots
const BK = BifurcationKit

cont_opt =
    ContinuationPar(
        dsmax=1e-2, # Maximum arclength value of the pseudo-arc length continuation method.
        dsmin=1e-7, # Minimum arclength
        ds=1e-3, # Initial arclength
        theta=0.01,
        pMin=p_span[1],    # Minimum p-vale (if hit, the method stops).
        pMax=p_span[2],    # Maximum p-vale (if hit, the method stops).
        maxSteps=1000000,   # The maximum number of steps.
        # Value in {0,1,2,3} determining to what extent bifurcation points are detected
        # (0 nothing, 3 both them and their localization are detected).
        detectBifurcation=3,
        nInversion=6,
        # saveToFile        = true,
        # Parameters to the newton solver (when finding fixed points).
        newtonOptions=NewtonPar(tol=1e-9,
                                verbose=false,
                                maxIter=20,
                                )
    )

params_input = setindex!(copy(Ks), p_span[1], p_idx) # The input parameter values have to start at the first index of our parameter span.
## Sanity check
F_smooth(Xs_init_smooth, params_input)

branches, _ =
    continuation(F_smooth, # The vector field
                 J_smooth, # The Jacobian
                 Xs_init_smooth,
                 params_input,
                 (@lens _[p_idx]),
                 cont_opt;
                 plot=false, # We do not want to display, or plot, intermediary results.
                 recordFromSolution=(x, p) -> (x4 = x[2]), # 2 is the new index for X4
                 verbosity=0,
                 callbackN=(x, f, J, res, iteration, itlinear, options; kwargs...) -> res < 1e7,  # Parameter for the continuation method, see BifurcationKit documentation.
                 # filename = "branch_k9_X4"
                 )
#  1,  hopf at p ≈ +0.00051615 ∈ (+0.00051601, +0.00051615), |δp|=1e-07, [converged], δ = ( 2,  2), step =  35, eigenelements in eig[ 36], ind_ev =   2
#  2,  hopf at p ≈ +0.07592376 ∈ (+0.07592131, +0.07592376), |δp|=2e-06, [converged], δ = (-2, -2), step =  48, eigenelements in eig[ 49], ind_ev =   2

Now, we have two Hopf bifurcations, and I would expect that something like the following code to work:

# Newton parameters for periodic orbits
newton_opt_po = NewtonPar(tol=1e-9,
                          maxIter=25)

# continuation parameters
cont_opt_po = ContinuationPar(
    dsmax=0.1,
    ds=-0.001,
    dsmin=1e-4,
    newtonOptions=(@set newton_opt_po.tol = 1e-8),
    precisionStability=1e-2,
    detectBifurcation=1,
    saveSolEveryStep=1,
)

# define the sup norm
norminf(x) = norm(x, Inf)
Mt = 101 # number of time sections
jet_smooth = BK.getJet(F_smooth, J_smooth; matrixfree=false)

branch_po, utrap =
    continuation(
        jet_smooth...,
        branches,
        1,  # Periodic orbits for the first Hopf point.
        cont_opt,
        PeriodicOrbitTrapProblem(M=Mt);
        # help branching from Hopf
        # usedeflation=true,
        usedeflation=false,
        # algorithm to solve linear associated with periodic orbit problem
        # linearPO=:FullSparseInplace,
        linearPO=:Dense,
        # tangent algorithm along the branch
        tangentAlgo=BorderedPred(),
        recordFromSolution = (x, p) -> (xtt=reshape(x[1:end-1],2,Mt);
                                        return (max = maximum(xtt[1,:]),
                                                min = minimum(xtt[1,:]),
                                                period = x[end])),
        # recordFromSolution=(x, p) -> (x4 = x[2]),
        callbackN=(x, f, J, res, iteration, itlinear, options; kwargs...) -> res < 1e7,  # Parameter for
        # finaliseSolution=(z, tau, step, contResult; prob=nothing, kwargs...) -> begin
        #     # limit the period
        #     z.u[end] < 100
        # end,
        normC=norminf
    )
# ERROR: MethodError: no method matching create_array(::Type{Base.ReshapedArray{

First of all, executing the previous code, I get a method missing error for create_array from SymbolicUtils.jl. The following code seems to solve it.

Question1: Is this method missing error related to how I built the system?

import SymbolicUtils
@inline function SymbolicUtils.Code.create_array(A::Type{<:Base.ReshapedArray{T,N,P,MI}}, S, nd::Val, d::Val, elems...) where {T,N,P,MI}
    SymbolicUtils.Code.create_array(P, S, nd, d, elems...)
end

Now, things seem to work, but Newton does not converge.
While refining a solution with Newton, it does converge.
Question2: Can this converge problem be related to my choice of tolerance parameters?
Or it is just that the added method for create_array in fact does not work?
I would really appreciate any help on this.

## Refinement of Hopf points
outfold, hist, flag =
    newton(F_smooth,
           J_smooth,
           branches, 1; # Refine first point.
           issymmetric=false, Jᵗ=nothing, d2F=nothing,
           normN=norminf, options=branches.contparams.newtonOptions,
           # bdlinsolver = BorderingBLS(options.linsolver),
           startWithEigen=false
           )

How to stop continuation after detecting a specified number of branch points?

I'm not sure if this is the right place to ask this question. If not, please direct me to the appropriate place to ask.

So I'm using continuation to find equilibrium solutions to an ODE. It will go through start_point -> branch1 (a fold point) -> branch2 (another fold point) -> branch3 (a fold point) -> start_point. Due to symmetry of the problem, the first half (start_point->branch1->branch2) is essentially the same as the second half (branch2->branch3->start_point). The solutions are just mirror reflection of the other. Is there a way to force continuation to stop the process after detecting two fold points (ends at branch2) since the second half of the solution is just duplicate of the first?

Trouble with negative parameters

This works

using PseudoArcLengthContinuation, LinearAlgebra, Plots, PyPlot, Optim
const PALC = PseudoArcLengthContinuation

function f(x, alpha)
    [-alpha + x[1]^2]
end

optnewton = NewtonPar(verbose = true)
alpha_min = 1.
alpha_max = 2.
out = [1.]
optcont = ContinuationPar(dsmin = 0.01, dsmax = 0.15, ds= 0.01, pMax = alpha_max, newtonOptions = NewtonPar(tol = 1e-9))
br, _ = continuation(f, out, alpha_min, optcont, plot = true, plotSolution = (x, p;kwargs...) -> (plot!(x; ylabel="solution",label="",kwargs...)), verbosity=3)

But changing -alpha to alpha in the objective function and doing alpha_min=-1. and alpha_max=.5 doesn't.

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

If you'd like for me to do this for you, comment TagBot fix on this issue.
I'll open a PR within a few hours, please be patient!

Running into "AssertionError: Newton failed to converge. Required for the computation of the initial tangent" error when running "continuation()"

I am running into an error, I am not sure what the cause might be, and some clue to that would be really helpful. This is the error message:

AssertionError: Newton failed to converge. Required for the computation of the initial tangent.

Stacktrace:
 [1] iterate(it::ContIterable{var"#1#2", var"#3#4", Vector{Float64}, Vector{Float64}, Setfield.IndexLens{Tuple{Int64}}, Float64, DefaultLS, DefaultEig{typeof(real)}, SecantPred, BorderingBLS{DefaultLS, Float64}, BifurcationKit.var"#448#473"{BifurcationKit.var"#448#449#474"}, var"#5#9", typeof(norm), BifurcationKit.DotTheta{BifurcationKit.var"#446#471"}, typeof(BifurcationKit.finaliseDefault), var"#7#11", Nothing, Nothing}; _verbosity::Int64)
   @ BifurcationKit ~/.julia/packages/BifurcationKit/Yvq7p/src/Continuation.jl:266
 [2] iterate(it::ContIterable{var"#1#2", var"#3#4", Vector{Float64}, Vector{Float64}, Setfield.IndexLens{Tuple{Int64}}, Float64, DefaultLS, DefaultEig{typeof(real)}, SecantPred, BorderingBLS{DefaultLS, Float64}, BifurcationKit.var"#448#473"{BifurcationKit.var"#448#449#474"}, var"#5#9", typeof(norm), BifurcationKit.DotTheta{BifurcationKit.var"#446#471"}, typeof(BifurcationKit.finaliseDefault), var"#7#11", Nothing, Nothing})
   @ BifurcationKit ~/.julia/packages/BifurcationKit/Yvq7p/src/Continuation.jl:243
 [3] continuation(F::var"#1#2", J::var"#3#4", par::Vector{Float64}, lens::Setfield.IndexLens{Tuple{Int64}}, contParams::ContinuationPar{Float64, DefaultLS, DefaultEig{typeof(real)}}, defOp::DeflationOperator{Float64, typeof(dot), Vector{Float64}}; verbosity::Int64, maxBranches::Int64, seekEveryStep::Int64, maxIterDefOp::Int64, showplot::Bool, tangentAlgo::SecantPred, linearAlgo::BorderingBLS{DefaultLS, Float64}, dotPALC::Function, printSolution::var"#5#9", plotSolution::BifurcationKit.var"#448#473"{BifurcationKit.var"#448#449#474"}, perturbSolution::var"#6#10", callbackN::var"#7#11", acceptSolution::BifurcationKit.var"#453#478", updateDeflationOp::BifurcationKit.var"#454#479", normN::typeof(norm))
   @ BifurcationKit ~/.julia/packages/BifurcationKit/Yvq7p/src/DeflatedContinuation.jl:208
 [4] top-level scope
   @ timing.jl:210
 [5] eval
   @ ./boot.jl:360 [inlined]
 [6] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
   @ Base ./loading.jl:1094

For reference this is the full code which generates it:

# Fetch Packages
using DifferentialEquations, Catalyst
using BifurcationKit, Setfield, LinearAlgebra

# Declare Model
rn = @reaction_network begin
    kDeg,       (w,w2,w2v,v,w2v2,vP,σB,w2σB)  ∅
    kDeg,       vPp  phos
    (kBw,kDw),  2w  w2
    (kB1,kD1),  w2 + v  w2v
    (kB2,kD2),  w2v + v  w2v2
    kK1,        w2v  w2 + vP
    kK2,        w2v2  w2v + vP
    (kB3,kD3),  w2 + σB  w2σB
    (kB4,kD4),  w2σB + v  w2v + σB
    (kB5,kD5),  vP + phos  vPp
    kP,         vPp  v + phos
    v0*((1+F*σB)/(K+σB)),     ∅  σB
    λW*v0*((1+F*σB)/(K+σB)),  ∅  w
    λV*v0*((1+F*σB)/(K+σB)),  ∅  v
    (pStress,1), 0 <--> phos
end kBw kDw kD kB1 kB2 kB3 kB4 kB5 kD1 kD2 kD3 kD4 kD5 kK1 kK2 kP kDeg v0 F K λW λV pInit pStress;

# Set Parameter Values
kBw = 3600; kDw = 18; kD = 18;
kB1 = 3600; kB2 = 3600; kB3 = 3600; kB4 = 1800; kB5 = 3600;
kD1 = 18; kD2 = 18; kD3 = 18; kD4 = 1800; kD5 = 18;
kK1 = 36; kK2 = 12; #kK2 reduced to a third to achive arcitability. kK2 = 36 in the original model.
kP = 180; kDeg = 0.7;
v0 = 0.4; F = 30; K = 0.2;
λW = 4; λV = 4.5;

η = 0.05
pInit = 0.001
pStress = 0.4
parameters = [kBw, kDw, kD, kB1, kB2, kB3, kB4, kB5, kD1, kD2, kD3, kD4, kD5, kK1, kK2, kP, kDeg, v0, F, K, λW, λV, pInit, pStress, η];

# Find good u0 (know good initial guess should be needed, but had some other error when I just used a zero vector)
oprob = ODEProblem(rn,ones(length(rn.states)),(0.,50.),parameters)
sol = solve(oprob,Rosenbrock23());

# Prepare Bifurcation Comoutation
#rn = noise_modulation_model.system
odefun = ODEFunction(convert(ODESystem,rn),jac=true)
F = (u,p) -> odefun(u,p,0)      
J = (u,p) -> odefun.jac(u,p,0);
params = parameters;
p_idx = 15       
p_span = (12.,36.)
plot_var_idx = 7;

# Set Bifurcation Options
opts = ContinuationPar( dsmax = 0.05,        # Maximum arclength value of the pseudo-arc length continuation method.
                        dsmin = 1e-4,        # Minimum arclength value of the pseudo-arc length continuation method.
                        ds=0.001,            # Initial arclength value of the pseudo-arc length continuation method (should be positive).
                        maxSteps = 100000,   # The maximum number of steps.
                        pMin = p_span[1],    # Minimum p-vale (if hit, the method stops).
                        pMax = p_span[2],    # Maximum p-vale (if hit, the method stops).
                        detectBifurcation=3, # Value in {0,1,2,3} determening to what extent bofurcation points are detected (0 means nothing is done, 3 both them and there localisation are detected).
                        newtonOptions = NewtonPar(tol = 1e-9, verbose = false, maxIter = 15)); #Parameters to the newton solver (when finding fixed points) see BifurcationKit documentation.

DO = DeflationOperator( 2.0,    # Algorithm parameter required when using deflated continuation, see BifurcationKit documentation.
                        dot,    # Algorithm parameter required when using deflated continuation, see BifurcationKit documentation.
                        1.,     # Algorithm parameter required when using deflated continuation, see BifurcationKit documentation.
                        [sol.u[end]]); # Guess(es) of the fixed point for the initial parameter set. Do not need to be exact.

# Calculate the diagram
params_input = setindex!(copy(params),p_span[1],p_idx);
@time branches, = continuation(F, J, params_input, (@lens _[p_idx]) ,opts , DO,       # Gives our input.
    verbosity = 0, showplot=false,                                                    # We do not want to display, or plot, intermediary results.
    printSolution=(x, p) -> x[plot_var_idx],                                          # How we wish to print the output in the diagram. Here we simply want the value of the target varriable.
    perturbSolution = (x,p,id) -> (x  .+ 0.8 .* rand(length(x))),                     # Parameter for the continuation method, see BifurcationKit documentation.
    callbackN = (x, f, J, res, iteration, itlinear, options; kwargs...) -> res <1e7)  # Parameter for the continuation method, see BifurcationKit documentation.

This is the Pkg.status() output:

  [479239e8] Catalyst v9.0.0
  [0c46a032] DifferentialEquations v6.19.0
  [7c1d4256] DynamicPolynomials v0.3.21
  [a98d9a8b] Interpolations v0.13.4
  [91a5bcdd] Plots v1.22.6
  [2913bbd2] StatsBase v0.33.11
  [f3b207a7] StatsPlots v0.14.28
  [0c5d862f] Symbolics v3.4.3

extend methods to TrackedArray

I would like to use parameter continuation as part of an ML flow. this requires the extension of BoardedArray methods to deal with TrackedArrays

Conflict with DynamicalSystems 1.5 (DiffEqBase .47)

Hi there, I'm creating a repository of code exercises for a class. I provide a Project.toml to make things reproducible, however I need to have the latest version of DynamicalSystems.jl, which requires at least DiffEqBase 6.47.

I want to add an exercise with bifurcation diagrams, however there is a conflict. When I add BifurcationKit I get a downgrade in DynamicalSystems. When I explicitly ask latest version of DynamicalSystems, I get conflict error:

(ExercisesRepo) pkg> add [email protected]
  Resolving package versions...
ERROR: Unsatisfiable requirements detected for package JLD2 [033835bb]:
 JLD2 [033835bb] log:
 ├─possible versions are: [0.1.0-0.1.14, 0.2.0-0.2.4] or uninstalled
 ├─restricted by compatibility requirements with BifurcationKit [0f109fa4] to versions: [0.1.0-0.1.14, 0.2.0-0.2.4]
 │ └─BifurcationKit [0f109fa4] log:
 │   ├─possible versions are: 0.1.0-0.1.1 or uninstalled
 │   ├─restricted to versions * by an explicit requirement, leaving only versions 0.1.0-0.1.1   
 │   └─restricted by compatibility requirements with DataStructures [864edb3b] to versions: 0.1.0 or uninstalled, leaving only versions: 0.1.0
 │     └─DataStructures [864edb3b] log:
 │       ├─possible versions are: [0.9.0, 0.10.0, 0.11.0-0.11.1, 0.12.0, 0.13.0, 0.14.0-0.14.1, 
0.15.0, 0.16.1, 0.17.0-0.17.20, 0.18.0-0.18.7] or uninstalled
 │       ├─restricted by compatibility requirements with OrdinaryDiffEq [1dea7af3] to versions: 
[0.9.0, 0.10.0, 0.11.0-0.11.1, 0.12.0, 0.13.0, 0.14.0-0.14.1, 0.15.0, 0.16.1, 0.17.0-0.17.20, 0.18.0-0.18.7]
 │       │ └─OrdinaryDiffEq [1dea7af3] log:
 │       │   ├─possible versions are: [4.0.0, 4.1.0, 4.2.0, 4.3.0, 4.4.0-4.4.1, 4.5.0, 4.6.0, 4.7.0-4.7.1, 4.8.0-4.8.1, 4.9.0, 4.10.0, 4.11.0-4.11.1, 4.12.0-4.12.4, 4.13.0, 4.14.0, 4.15.0-4.15.1, 4.16.0-4.16.5, 4.17.0-4.17.2, 4.18.0-4.18.3, 4.19.0, 4.20.0, 4.21.0-4.21.1, 5.0.0, 5.1.0-5.1.4, 5.2.0-5.2.1, 5.3.0, 5.4.0-5.4.1, 5.5.0, 5.6.0-5.6.1, 5.7.0-5.7.1, 5.8.0-5.8.1, 5.9.0, 5.10.0, 5.11.0-5.11.1, 5.12.0, 5.13.0, 5.14.0, 5.15.0-5.15.1, 5.16.0, 5.17.0-5.17.2, 5.18.0, 5.19.0, 5.20.0-5.20.1, 5.21.0, 5.22.0, 5.23.0, 5.24.0, 5.25.0, 5.26.0-5.26.8, 5.27.0-5.27.1, 5.28.0-5.28.1, 5.29.0, 5.30.0, 5.31.0, 5.32.0-5.32.2, 5.33.0, 5.34.0-5.34.1, 5.35.0-5.35.5, 5.36.0-5.36.1, 5.37.0, 5.38.0-5.38.3, 5.39.0-5.39.1, 5.40.0, 5.41.0, 5.42.0-5.42.10, 5.43.0] or uninstalled     
 │       │   ├─restricted to versions * by an explicit requirement, leaving only versions [4.0.0, 4.1.0, 4.2.0, 4.3.0, 4.4.0-4.4.1, 4.5.0, 4.6.0, 4.7.0-4.7.1, 4.8.0-4.8.1, 4.9.0, 4.10.0, 4.11.0-4.11.1, 4.12.0-4.12.4, 4.13.0, 4.14.0, 4.15.0-4.15.1, 4.16.0-4.16.5, 4.17.0-4.17.2, 4.18.0-4.18.3, 4.19.0, 4.20.0, 4.21.0-4.21.1, 5.0.0, 5.1.0-5.1.4, 5.2.0-5.2.1, 5.3.0, 5.4.0-5.4.1, 5.5.0, 
5.6.0-5.6.1, 5.7.0-5.7.1, 5.8.0-5.8.1, 5.9.0, 5.10.0, 5.11.0-5.11.1, 5.12.0, 5.13.0, 5.14.0, 5.15.0-5.15.1, 5.16.0, 5.17.0-5.17.2, 5.18.0, 5.19.0, 5.20.0-5.20.1, 5.21.0, 5.22.0, 5.23.0, 5.24.0, 5.25.0, 5.26.0-5.26.8, 5.27.0-5.27.1, 5.28.0-5.28.1, 5.29.0, 5.30.0, 5.31.0, 5.32.0-5.32.2, 5.33.0, 5.34.0-5.34.1, 5.35.0-5.35.5, 5.36.0-5.36.1, 5.37.0, 5.38.0-5.38.3, 5.39.0-5.39.1, 5.40.0, 5.41.0, 5.42.0-5.42.10, 5.43.0]
 │       │   ├─restricted by compatibility requirements with RecursiveArrayTools [731186ca] to versions: [5.27.0-5.27.1, 5.28.0-5.28.1, 5.29.0, 5.30.0, 5.31.0, 5.32.0-5.32.2, 5.33.0, 5.34.0-5.34.1, 5.35.0-5.35.5, 5.36.0-5.36.1, 5.37.0, 5.38.0-5.38.3, 5.39.0-5.39.1, 5.40.0, 5.41.0, 5.42.0-5.42.10, 5.43.0] or uninstalled, leaving only versions: [5.27.0-5.27.1, 5.28.0-5.28.1, 5.29.0, 
5.30.0, 5.31.0, 5.32.0-5.32.2, 5.33.0, 5.34.0-5.34.1, 5.35.0-5.35.5, 5.36.0-5.36.1, 5.37.0, 5.38.0-5.38.3, 5.39.0-5.39.1, 5.40.0, 5.41.0, 5.42.0-5.42.10, 5.43.0]
 │       │   │ └─RecursiveArrayTools [731186ca] log:
 │       │   │   ├─possible versions are: [0.16.0-0.16.3, 0.17.0-0.17.2, 0.18.0-0.18.6, 0.19.0-0.19.1, 0.20.0, 1.0.0-1.0.2, 1.1.0-1.1.1, 1.2.0-1.2.1, 2.0.0-2.0.5, 2.1.0-2.1.2, 2.2.0, 2.3.0-2.3.5, 2.4.0-2.4.4, 2.5.0, 2.6.0, 2.7.0-2.7.2] or uninstalled
 │       │   │   └─restricted by compatibility requirements with BifurcationKit [0f109fa4] to versions: [2.4.4, 2.5.0, 2.6.0, 2.7.0-2.7.2]
 │       │   │     └─BifurcationKit [0f109fa4] log: see above
 │       │   └─restricted by compatibility requirements with DataStructures [864edb3b] to versions: [5.42.4-5.42.10, 5.43.0] or uninstalled, leaving only versions: [5.42.4-5.42.10, 5.43.0]    
 │       │     └─DataStructures [864edb3b] log: see above
 │       ├─restricted by compatibility requirements with DiffEqBase [2b5f629d] to versions: [0.17.0-0.17.20, 0.18.0-0.18.7]
 │       │ └─DiffEqBase [2b5f629d] log:
 │       │   ├─possible versions are: [3.13.2-3.13.3, 4.0.0-4.0.1, 4.1.0, 4.2.0, 4.3.0-4.3.1, 4.4.0, 4.5.0, 4.6.0, 4.7.0, 4.8.0, 4.9.0, 4.10.0-4.10.1, 4.11.0-4.11.1, 4.12.0, 4.13.0, 4.14.0-4.14.1, 4.15.0, 4.16.0, 4.17.0, 4.18.0, 4.19.0, 4.20.0-4.20.3, 4.21.0, 4.21.2-4.21.3, 4.22.0-4.22.2, 4.23.0, 4.23.2-4.23.4, 4.24.0-4.24.3, 4.25.0-4.25.1, 4.26.0-4.26.3, 4.27.0-4.27.1, 4.28.0-4.28.1, 4.29.0-4.29.2, 4.30.0-4.30.2, 4.31.0-4.31.2, 4.32.0, 5.0.0-5.0.1, 5.1.0, 5.2.0-5.2.3, 5.3.0-5.3.2, 5.4.0-5.4.1, 5.5.0-5.5.2, 5.6.0-5.6.4, 5.7.0, 5.8.0-5.8.1, 5.9.0, 5.10.0-5.10.3, 5.11.0-5.11.1, 5.12.0, 5.13.0, 5.14.0-5.14.2, 5.15.0, 5.16.0-5.16.5, 5.17.0-5.17.1, 5.18.0, 5.19.0, 5.20.0-5.20.1, 6.0.0, 6.1.0, 6.2.0-6.2.4, 6.3.0-6.3.6, 6.4.0-6.4.2, 6.5.0-6.5.1, 6.6.0, 6.7.0, 6.8.0, 6.9.0-6.9.4, 6.10.0-6.10.2, 6.11.0, 6.12.0-6.12.5, 6.13.0-6.13.3, 6.14.0-6.14.2, 6.15.0-6.15.2, 6.16.0, 6.17.0-6.17.3, 6.18.0-6.18.1, 6.19.0, 6.20.0, 6.21.0-6.21.1, 6.22.0-6.22.2, 6.23.0, 6.24.0, 6.25.0-6.25.2, 6.26.0, 6.27.0, 6.28.0, 6.29.0-6.29.3, 6.30.0-6.30.4, 6.31.0-6.31.1, 6.32.0-6.32.2, 6.33.0-6.33.1, 6.34.0-6.34.3, 6.35.0-6.35.2, 6.36.0-6.36.4, 6.37.0, 6.38.0-6.38.4, 6.39.0-6.39.1, 6.40.0-6.40.9, 6.41.0-6.41.3, 6.42.0, 6.43.0-6.43.1, 6.44.0-6.44.3, 6.45.0-6.45.1, 6.46.0-6.46.1, 6.47.0-6.47.1, 6.48.0] or uninstalled
 │       │   ├─restricted by compatibility requirements with BifurcationKit [0f109fa4] to versions: [6.38.4, 6.39.0-6.39.1, 6.40.0-6.40.9, 6.41.0-6.41.3, 6.42.0, 6.43.0-6.43.1, 6.44.0-6.44.3, 
6.45.0-6.45.1, 6.46.0-6.46.1, 6.47.0-6.47.1, 6.48.0]
 │       │   │ └─BifurcationKit [0f109fa4] log: see above
 │       │   └─restricted by compatibility requirements with DynamicalSystemsBase [6e36e845] to 
versions: [6.47.0-6.47.1, 6.48.0]
 │       │     └─DynamicalSystemsBase [6e36e845] log:
 │       │       ├─possible versions are: [0.11.0-0.11.3, 0.12.0-0.12.2, 1.0.0-1.0.2, 1.1.0-1.1.1, 1.2.0-1.2.3, 1.2.5, 1.3.0-1.3.1, 1.4.0, 1.5.0-1.5.7, 1.6.0-1.6.1] or uninstalled
 │       │       └─restricted by compatibility requirements with DynamicalSystems [61744808] to 
versions: 1.6.0-1.6.1
 │       │         └─DynamicalSystems [61744808] log:
 │       │           ├─possible versions are: [1.0.0, 1.1.0, 1.2.0-1.2.1, 1.3.0, 1.4.0, 1.5.0] or uninstalled
 │       │           └─restricted to versions 1.5 by an explicit requirement, leaving only versions 1.5.0
 │       └─restricted by compatibility requirements with DiffEqBase [2b5f629d] to versions: 0.18.0-0.18.7
 │         └─DiffEqBase [2b5f629d] log: see above
 ├─restricted by compatibility requirements with DataStructures [864edb3b] to versions: 0.2.0-0.2.4 or uninstalled, leaving only versions: 0.2.0-0.2.4
 │ └─DataStructures [864edb3b] log: see above
 └─restricted by compatibility requirements with BifurcationKit [0f109fa4] to versions: 0.1.13-0.1.14 — no versions left
   └─BifurcationKit [0f109fa4] log: see above

I was looking at your Project.toml which also allows DiffEqBase .47, so I got confused. Further confusion comes from the fact that the problem is JLD2, which isn't required by dynamical systems at all.

AssertionError message in ContinuationPar might be incorrect

Hey,

Maybe I am mistaken but I believe an AssertionError message in ContinuationPar() is off.
When given an odd number for nInversion it raises the error: The option nInversion number must be odd.

@Assert iseven(nInversion) "The option nInversion number must be odd"

Should the number of inversions be even or odd?

Issue with branch switching

using PseudoArcLengthContinuation, LinearAlgebra, Plots, PyPlot, Optim
const PALC = PseudoArcLengthContinuation
using ForwardDiff
using Parameters, Setfield

function f(x, p)
    [p.α*x[1] + x[1]^3 ]
end
function Jf(x, p)
    Jf = zeros(1, 1)
    Jf[1] = p.α+3x[1]^2
    Jf
end

Fmit = f
D(f, x, p, dx) = ForwardDiff.derivative(t->f(x .+ t .* dx, p), 0.)
d1Fmit(x,p,dx1) = D((z, p0) -> Fmit(z, p0), x, p, dx1)
d2Fmit(x,p,dx1,dx2) = D((z, p0) -> d1Fmit(z, p0, dx1), x, p, dx2)
d3Fmit(x,p,dx1,dx2,dx3) = D((z, p0) -> d2Fmit(z, p0, dx1, dx2), x, p, dx3)
jet = (f, d1Fmit, d2Fmit, d3Fmit)
jet = (f, Jf, d2Fmit, d3Fmit)

alpha_min = -10.
alpha_max = 10.
alpha_start = 2.

# options for Newton solver
opt_newton = PALC.NewtonPar(tol = 1e-8, verbose = true, maxIter = 20)

# options for continuation
opts_br = ContinuationPar(dsmin = 0.001, dsmax = 0.05, ds = -0.01, pMax = alpha_max, pMin = alpha_min,
	                  detectBifurcation = 2, nev = 30, plotEveryNsteps = 10, newtonOptions = opt_newton,
	                  maxSteps = 100, precisionStability = 1e-6, nInversion = 4, dsminBisection = 1e-7, maxBisectionSteps = 25)

sol_start, _, _ = newton( f, [1.], (α=.1,), opt_newton)
par_mit ==alpha_start,)

br, _ = @time PALC.continuation(
    Fmit, Jf,
    sol_start, par_mit, (@lens _.α), opts_br;
    # printSolution = (x, p) -> norm(x),
    # plotSolution = (x, p; kwargs...) -> plotsol!(x ; kwargs...),
    plot = false, verbosity = 3)

br2, _ = continuation(jet..., br, 1, setproperties(opts_br, ds = -0.01, maxSteps = 20), plot = false, printSolution = (x,p) -> x[1], verbosity=3)

Plots.plot([br,br2])

Everything is fine when I change ds=-0.01 to ds=0.01, but I can't find the "upper" branch that way. I'm guessing this is because the solver runs back into the bifurcation?

Pitchfork branching code weirdness

MWE:

using PseudoArcLengthContinuation, LinearAlgebra, Plots
const PALC = PseudoArcLengthContinuation
using ForwardDiff
using Parameters, Setfield

function f(x, p)
    [p.α*x[1] + x[1]^3 ]
end
function Jf(x, p)
    Jf = zeros(1, 1)
    Jf[1] = p.α+3x[1]^2
    dx -> Jf*dx
end
Fmit = f

# eigensolver
struct MyStruct <: PALC.AbstractEigenSolver
end
function (m::MyStruct)(J, nev)
    return [(J(1))[1]], [1.], true, 1
end

function D(f, x, p, dx)
    ε=1e-7
    (f(x + ε*dx, p) - f(x - ε*dx, p))/(2ε)
end
d1Fmit(x,p,dx1) = D((z, p0) -> Fmit(z, p0), x, p, dx1)
d2Fmit(x,p,dx1,dx2) = D((z, p0) -> d1Fmit(z, p0, dx1), x, p, dx2)
d3Fmit(x,p,dx1,dx2,dx3) = D((z, p0) -> d2Fmit(z, p0, dx1, dx2), x, p, dx3)
jet = (f, d1Fmit, d2Fmit, d3Fmit)
jet = (f, Jf, d2Fmit, d3Fmit)

alpha_min = -10.
alpha_max = 10.
alpha_start = 2.

# options for Newton solver
opt_newton = PALC.NewtonPar(tol = 1e-8, verbose = true, maxIter = 20, eigsolver = MyStruct())
opt_newton = PALC.NewtonPar(tol = 1e-8, verbose = true, maxIter = 20, eigsolver = MyStruct(), linsolver=GMRESKrylovKit())

# options for continuation
opts_br = ContinuationPar(dsmin = 0.001, dsmax = 0.05, ds = -0.01, pMax = alpha_max, pMin = alpha_min,
	                  detectBifurcation = 2, nev = 30, plotEveryNsteps = 10, newtonOptions = opt_newton,
	                  maxSteps = 100, precisionStability = 1e-6, nInversion = 4, dsminBisection = 1e-7, maxBisectionSteps = 25)

sol_start, _, _ = newton( f, [1.], (α=.1,), opt_newton)
par_mit ==alpha_start,)

br, _ = @time PALC.continuation(
    Fmit, Jf,
    sol_start, par_mit, (@lens _.α), opts_br;
    # printSolution = (x, p) -> norm(x),
    # plotSolution = (x, p; kwargs...) -> plotsol!(x ; kwargs...),
    plot = false, verbosity = 3)

br2, _ = continuation(jet..., br, 1, setproperties(opts_br, ds = -0.01, maxSteps = 20), plot = false, printSolution = (x,p) -> x[1], verbosity=3, Jt=Jf)
br3, _ = continuation(jet..., br, 1, setproperties(opts_br, ds = 0.01, maxSteps = 20), plot = false, printSolution = (x,p) -> x[1], verbosity=3, Jt=Jf)

Plots.plot([br,br2,br3], marker=2)

Also plot just [br, br2] and [br, br3].

The pitchfork is well detected, and the two branches get explored. However,

  1. the base of the two branches is at the same point, whereas I would have expected both branches to have opposite starting points

  2. the starting point is off the horizontal axis by an amount relatively large compared to my ds, whereas I would have expected the starting point to be exactly on the horizontal axis. I guess this is easy to fix by just adding the bifurcation point to the branch.

Are both these expected?

dz = norm(z[n+1]-z[n]) along branch from branch.ds?

Since I am doing a line integral over the steady states returned by continuation I need a precise ds. I believe the ds given in branch.ds is the target ds between dsmin and dsmax that an adaptive time stepping regime aims for? Took me a while to realise I was not getting the correct ds 😅

Hopf continuation failed in a stiff ODE system

function Fitz(u,p)
    x,y = u
    @unpack I,a,b,c = p
    du = similar(u)
    
    du[1] = x-x^3/3-y + I
    du[2] = a*(b*x-c*y)
    
    return du
end

sol0 = [0.1,0.1]

par_Fitz = (I=-2.0,a=0.08,b=1.,c=0.8)

ls = GMRESKrylovKit(dim = 100)
# defaultLS() not defined

optnewton = NewtonPar(tol = 1e-11, verbose = true, linsolver = ls)
out, _, _ = @time newton( Fitz, sol0, par_Fitz, optnewton)

optnew = NewtonPar(verbose = true, tol = 1e-10)
optcont = ContinuationPar(dsmin = 0.01, dsmax = 0.1, ds= 0.05, pMax = 5.1, pMin = -2.1,
    newtonOptions = setproperties(optnew; maxIter = 50, tol = 1e-10), 
	maxSteps = 300, plotEveryStep = 40, 
	detectBifurcation = 3, nInversion = 4, tolBisectionEigenvalue = 1e-12, dsminBisection = 1e-5)

br, _ = @time continuation(Fitz, out, par_Fitz, (@lens _.I),
		optcont; plot = true, verbosity = 0,
		printSolution = (x, p) -> x[1])


Ddt(f, x, p, dx) = ForwardDiff.derivative(t -> f(x .+ t .* dx, p), 0.)
dFbpSecBif(x,p)         =  ForwardDiff.jacobian( z-> Fitz(z,p), x)
d1FbpSecBif(x,p,dx1)         = Ddt((z, p0) -> Fitz(z, p0), x, p, dx1)
d2FbpSecBif(x,p,dx1,dx2)     = Ddt((z, p0) -> d1FbpSecBif(z, p0, dx1), x, p, dx2)
d3FbpSecBif(x,p,dx1,dx2,dx3) = Ddt((z, p0) -> d2FbpSecBif(z, p0, dx1, dx2), x, p, dx3)
jet = (Fitz, dFbpSecBif, d2FbpSecBif, d3FbpSecBif)

# index of the Hopf point in br.bifpoint
norminf = x -> norm(x, Inf)
ind_hopf = 1
hopfpoint, _, flag = @time newton(Fitz, dFbpSecBif,
	br, ind_hopf, par_Fitz, (@lens _.I); normN = norminf)
flag && printstyled(color=:red, "--> We found a Hopf Point at μ = ", hopfpoint.p[1],
    ", from μ = ", br.bifpoint[ind_hopf].param, "\n")

# automatic branch switching from Hopf point
opt_po = NewtonPar(tol = 1e-5, verbose = true, maxIter = 15)
opts_po_cont = ContinuationPar(dsmin = 0.01, dsmax = 0.1, ds = 0.1, pMax = 5.5,pMin=-2.1, maxSteps = 200,
    newtonOptions = opt_po, saveSolEveryStep = 2, plotEveryStep = 1, nev = 11, precisionStability = 1e-4,
	detectBifurcation = 3, dsminBisection = 1e-4, maxBisectionSteps = 10);

# number of time slices for the periodic orbit
M = 150
probFD = PeriodicOrbitTrapProblem(M = M)
br_po, = continuation(
	# arguments for branch switching from the first 
	# Hopf bifurcation point
	jet..., br, 1,
	# arguments for continuation
	opts_po_cont, probFD;
	# OPTIONAL parameters
	# we want to jump on the new branch at phopf + δp
	# ampfactor is a factor to increase the amplitude of the guess
	δp = 0.05, ampfactor = 1.1,
	# specific method for solving linear system
	# of Periodic orbits with trapeze method
	linearPO = :FullLU,
# regular options for continuation
	verbosity = 3,	plot = true, 
	printSolution = (x,p)->x[1], normC = norminf)

fieldnames(typeof(br_po))
plot(br)
plot!(br_po)

I expect to get the following result in AUTO. Though I already have the result, I am looking for a pure Julia package to handle this kind of problem.
Capture

My question is how do I modify the code to get a similar result?

Exchange Plots dependency with Requires...?

When BifurcationKit is installed, the whole Plots library and GR (whose combination is massive) is installed with it.

Perhaps a more lean approach would be to use the Requries package and add conditional dependencies, so that users don't have to get Plots if they don't want to plot things? For example, have a look of how I have done this in DynamicalBilliards.jl, which extends several PyPlot.jl functions:

https://github.com/JuliaDynamics/DynamicalBilliards.jl/blob/master/src/DynamicalBilliards.jl#L51-L63

request to make ContinuationPar and NewtonPar mutable

Requesting to make these structures mutable so that it would be possible to update hyperparameters during optimisation loops.

parameters = ContinuationPar() # inital hyperparameters

for i=1:10 # some optimisation loop
       parameters.ds *= i*parameters.ds # for example changing ds
end

throws ERROR: setfield! immutable struct of type ContinuationPar cannot be changed

Formulate some examples using ModelingToolkit

For better visibility and integration of BifurcationKit with SciML, some examples, in particular DAE and PDE, should be formulated using MTK with its automatic Jc features.

Also, BifurcationKit should have better visibility in the SciML ecosystem.

Trouble with branch switching and matrix-free

using PseudoArcLengthContinuation, LinearAlgebra, Plots
const PALC = PseudoArcLengthContinuation
using ForwardDiff
using Parameters, Setfield

function f(x, p)
    [p.α*x[1] + x[1]^3 ]
end
function Jf(x, p)
    Jf = zeros(1, 1)
    Jf[1] = p.α+3x[1]^2
    dx -> Jf*dx
end

# eigensolver
struct MyStruct <: PALC.AbstractEigenSolver
end
function (m::MyStruct)(J, nev)
    return [(J(1))[1]], [1.], true, 1
end

Fmit = f
D(f, x, p, dx) = ForwardDiff.derivative(t->f(x .+ t .* dx, p), 0.)
function D(f, x, p, dx)
    ε=1e-10
    (f(x + ε*dx, p) - f(x, p))/ε
end
d1Fmit(x,p,dx1) = D((z, p0) -> Fmit(z, p0), x, p, dx1)
d2Fmit(x,p,dx1,dx2) = D((z, p0) -> d1Fmit(z, p0, dx1), x, p, dx2)
d3Fmit(x,p,dx1,dx2,dx3) = D((z, p0) -> d2Fmit(z, p0, dx1, dx2), x, p, dx3)
jet = (f, d1Fmit, d2Fmit, d3Fmit)
jet = (f, Jf, d2Fmit, d3Fmit)

alpha_min = -10.
alpha_max = 10.
alpha_start = 2.

# options for Newton solver
opt_newton = PALC.NewtonPar(tol = 1e-8, verbose = true, maxIter = 20, eigsolver = MyStruct())
# opt_newton = PALC.NewtonPar(tol = 1e-8, verbose = true, maxIter = 20, eigsolver = MyStruct(), linsolver=GMRESKrylovKit())

# options for continuation
opts_br = ContinuationPar(dsmin = 0.001, dsmax = 0.05, ds = -0.01, pMax = alpha_max, pMin = alpha_min,
	                  detectBifurcation = 2, nev = 30, plotEveryNsteps = 10, newtonOptions = opt_newton,
	                  maxSteps = 100, precisionStability = 1e-6, nInversion = 4, dsminBisection = 1e-7, maxBisectionSteps = 25)

sol_start, _, _ = newton( f, [1.], (α=.1,), opt_newton)
par_mit ==alpha_start,)

br, _ = @time PALC.continuation(
    Fmit, Jf,
    sol_start, par_mit, (@lens _.α), opts_br;
    # printSolution = (x, p) -> norm(x),
    # plotSolution = (x, p; kwargs...) -> plotsol!(x ; kwargs...),
    plot = false, verbosity = 3)

br2, _ = continuation(jet..., br, 1, setproperties(opts_br, ds = -0.01, maxSteps = 20), plot = false, printSolution = (x,p) -> x[1], verbosity=3, Jt=Jf)
br3, _ = continuation(jet..., br, 1, setproperties(opts_br, ds = 0.01, maxSteps = 20), plot = false, printSolution = (x,p) -> x[1], verbosity=3, Jt=Jf)

Plots.plot([br,br2,br3])

doesn't work. It does if I uncomment the opt_newton line setting the linear solver. I mention this as a bug because the continuation (without branch switching) seems to work fine, meaning it probably autodetects that the problem is matrix-free but branch switching doesn't?

Change from DiffEqBase to SciMLBase

DiffEqBase is the lowest common denominator for the DiffEq packages, not necessarily the whole SciML ecosystem, and so it has a lot DiffEq dependencies. These are generally not required by downstream packages. If what you're looking for is a way to define problems without having most dependencies, we recommend you use SciMLBase as the dependency since everything like ODEProblem, SteadyStateProblem, etc. is defined there. We basically recommend depending on SciMLBase for problem definitions, and solver packages for specific solvers, but generally most non-SciML packages should not be depending on DiffEqBase directly (given the split of SciMLBase in 2021)

For more details see: https://diffeq.sciml.ai/stable/features/low_dep/ and https://discourse.julialang.org/t/psa-the-right-dependency-to-reduce-from-differentialequations-jl/72757

Let me know if you need any help updating this, though for almost all dependents here it should be a trivial name change as you're actually using pieces from SciMLBase.

Method extractPeriodShooting is not defined in Floquet.jl

Bug report: I was trying to compute a branch of periodic orbits of a forced nonlinear oscillator with shooting when I came across this issue. It happens when the continuation method is used with AbstractShootingProblem and detectBifurcation > 0. The method extractPeriodShooting is called by MonodromyQaD in Floquet.jl, giving the error. Below is a code sample that reproduces the error.

using Revise, Parameters, Setfield, Plots, LinearAlgebra
using DifferentialEquations
using BifurcationKit
const BK = BifurcationKit

# define the sup norm
norminf(x) = norm(x, Inf)

# function to record information from a solution
recordFromSolution(x, p) = (u1 = x[1], u2 = x[2])

####################################################################################################

function F!(f, x, p, t=0)
	@unpack a, b1, b2, d, λ, ω = p
	f[1] = x[2]
	f[2] = - d * x[2] - a * x[1] - b1 * (x[1]^2) - b2 * (x[1]^3) + λ * sin* t)
    return f
end
F(x,p,t) = F!(similar(x),x,p,t)

# Defining the Jacobian
function dF(x,p,t = 0)
	@unpack a, b1, b2, d, λ, ω = p
    M = zeros(2,2)
    M[1,1] = 0
    M[1,2] = 1
    M[2,1] = -a -b1*2*x[1] -b2*3*(x[1]^2)
    M[2,2] = -d
    return M
 end

# parameters of the model
P = @with_kw (a=-1., b1=1., b2=0., d=0.1, λ=0.06, ω=0.81)
par_Helm = P(-1.0, 1.0, 0.0, 0.1, 0.06, 0.81)
par0 = (@set par_Helm.a = -1.15)

# initial condition guess
z0 = [1.; 0.001]
vf = ODEFunction(F; jac = dF)       #ODEfunction with the Jacobian
prob = ODEProblem(vf, z0, (0.0, 1000*2*π/par0.ω), par0)   #ODEProblem encoding
sol = solve(prob, Tsit5())

z0 = sol.u[end]
prob = ODEProblem(vf, z0, (0.0, 2*π/par0.ω), par0)   #ODEProblem encoding
sol = solve(prob, Tsit5())

# this encodes the functional for the Shooting problem
probSh = ShootingProblem(
	# we pass the ODEProblem encoding the flow and the time stepper
	prob, Tsit5(),
	# this is for the phase condition, you can pass your own section as well
	[vec(sol[end])],
	# enable threading
	parallel = true,
	# these are options passed to the ODE time stepper
	atol = 1e-10, rtol = 1e-8)

# Newton interation for low energy level
zp0 = vcat(vec(sol[end]),2*π/par_Helm.ω)

ls = GMRESIterativeSolvers(reltol = 1e-7, N = length(zp0), maxiter = 100)
newtonOpt = NewtonPar(verbose = true, tol = 1e-8, maxIter = 25, linsolver = ls)

# continuation options
opts_cont = ContinuationPar(dsmin = 0.001, dsmax = 1.0, ds= 0.01, pMin = 0.02, pMax = 0.1,
	maxSteps = 50, newtonOptions = newtonOpt, nev = 2,
	# options to detect bifurcations
	detectBifurcation = 3,   # this gives an error!
        detectFold = true, nInversion = 8, maxBisectionSteps = 25,
	precisionStability = 1e-8, saveSolEveryStep = 1)

br, = continuation(
    probSh,
    zp0,
    par0,
    (@lens _.λ),
    opts_cont,
    linearAlgo = MatrixFreeBLS(@set ls.N = ls.N + 1);
    verbosity = 3,
    plot = true,
    recordFromSolution = recordFromSolution,
	normC = norminf
)

Changing `dotPALC` is not reflected in `BorderedPred()`?

Hi Romain,

Let me preface this by saying a huge thank you for your invaluable work on this package. At some point this year I am hoping to release my own amateurish package about numerical continuation methods in atomistic simulation of materials and it will be based around BifurcationKit.jl. I have also cited the paper about this package in a recent preprint.

Onto to the issue - title says it all really. It looks to me like any adjustement in dotPALC is not reflected in the computation of the predictor with BorderedPred(), which in my case leads to the breakdown of the continuation routine.

If in my continuation routine I use theta = 0.5 and

tangentAlgo = BorderedPred(), dotPALC = (x, y) -> dot(x, y)/length(x)

then everything runs well, but to achieve the desired step size control, I need to pass something dsmin = 0.00001, dsmax = 0.001, ds= 0.0004 (which would have to be decreased even further as if I was to increase the size of my computational domain - the number of atoms I simulate).

As noted in the documentation, I would like to instead pass

tangentAlgo = BorderedPred(), dotPALC = (x, y) -> dot(x, y)

but when I do that and run the routine, I get an error PosDefException: matrix is not positive definite; Cholesky factorization failed. If on the other hand I pass

tangentAlgo = SecantPred(), dotPALC = (x, y) -> dot(x, y)

then everything is well again and this time dsmin, dsmax, ds have to be adjusted to more reasonable values, which crucially are (more or less) domain-size independent.

I would really like to use BorderedPred() though and having browsed the source code, it looks like this might have something to do with the function `DotTheta()' in Predictor.jl. I think this should be easily fixable or perhaps I am simply misunderstanding something, but I thought I would draw your attention to it just in case. Thanks!

Why is JLD2 a dependency?

I just got an upgrade conficlt because of an old version of JLD2 and I thought "Where does it come from?" because I've never installed JLD2. Turns out it was coming from here.

It made me wonder, why would such a front-end package be a dependency here?

Unable to create bifurcation diagram using deflated continuations

First, sorry for all the troubles!

I have been trying to create some very simple bifurcation diagrams using deflated continuations; however, I have not been able to get it to work. I think I simply do not understand how to give some of the input properly.

The first example is a very simple system:
dX/dt = p -d*X (p and d are parameters)
I create the system using Catalyst.

For reference, I have been using the deflated continuation example at https://rveltz.github.io/BifurcationKit.jl/dev/DeflatedContinuation/, as well as the @reaction_network example discussion at #27.

In my first try, I simply take the example in the docs, but swaps stuff out for what I got in my system:

# Fetch packages.
using Catalyst
using BifurcationKit, LinearAlgebra, Setfield, SparseArrays, Plots

# Create functions.
rn = @reaction_network begin
    p, 0 --> X
    d, X --> 0
end p d;
odefun = ODEFunction(convert(ODESystem,rn),jac=true)
F = (u,p) -> odefun(u,p,0)
J = (u,p) -> odefun.jac(u,p,0);

# Set initial parameter values.
p = (p=1.,d=0.1);
# Sets initial guess
x0 = 0.5;

# Set solving options
opts = BifurcationKit.ContinuationPar(dsmax = 0.051, dsmin = 1e-3, ds=0.001, maxSteps = 140, pMin = -3., saveSolEveryStep = 0, newtonOptions = NewtonPar(tol = 1e-8, verbose = false), saveEigenvectors = false);

# Set Deflation Options
DO = DeflationOperator(2.0, dot, .001, [[0.]]);

# Create bifurcation diagram
brdc, = continuation(F,J, x0, (@lens _),opts,DO; showplot=false, verbosity = 0);

# Plot the diagram
plot(brdc[1])

This code runs without error, but the output is weird, and rightly so, since the algorithm doesn't have the adequate information. It is when I move on from this I start to be confussed. First, I have a question regarding the creation of the DeflationOperator though:

  • If I understand it right, argument 1 and 3 are simple parameters to the algorithm (knowing little about appropriate values, I simply selected what was used in the example). The second argument is a function (is there any reason I would not use dot?). Finally, the fourth argument is an array with roots which I want to avoid? Exactly how should I give it here? Is it roots which I want to ignore, or is it to help the algorithm to find all the roots at the starting condition, but do I then need to give all of the roots except for one (with the algorithm finding the last one)? In the doc example, a root at 0 is given, but the algorithm finds and uses 2 more. If I don't want to skip any roots, can I give this one as an empty array?

Next, I try to give the systems parameter values as an input as well, I try a few different ones, the only one that succeeds is:

brdc, = continuation(F,J,p, (@lens _.p),opts,DO; showplot=false, verbosity = 0);
  • However, I am unsure why I should, when providing a parameter, no longer provide x0? Other alternatives such as:
brdc, = continuation(F,J,x0, p, (@lens _.p),opts,DO; showplot=false, verbosity = 0);
brdc, = continuation(F,J,p, x0, (@lens _.p),opts,DO; showplot=false, verbosity = 0);

fails

Next, I try a slightly more complicated system:

# Fetch packages.
using Catalyst
using BifurcationKit, LinearAlgebra, Setfield, SparseArrays, Plots

# Create functions.
rn2 = @reaction_network begin
    (v0+(S*X)^n/((S*X)^n+(D*A)^n+1),1), ∅  X
    (τ*X,τ), ∅  A
end v0 S n D τ;
odefun2 = ODEFunction(convert(ODESystem,rn2),jac=true)
F2 = (u,p) -> odefun2(u,p,0)
J2 = (u,p) -> odefun2.jac(u,p,0);

# Set initial parameter values.
p2 = (v0=0.1, S=1., n=3, D=2., τ=0.01);
# Sets initial guess
x02 = [0.5, 0.5];

# Set solving options
opts2 = BifurcationKit.ContinuationPar(dsmax = 0.051, dsmin = 1e-3, ds=0.001, maxSteps = 1000000, pMin = 0.1, pMax = 5., saveSolEveryStep = 0, newtonOptions = NewtonPar(tol = 1e-8, verbose = false), saveEigenvectors = false);

# Set Deflation Options
DO2 = DeflationOperator(2.0, dot, .001, [[0.,0.]]);

# Create bifurcation diagram
brdc2, = continuation(F2,J2,p2, (@lens _.S),opts2,DO2; showplot=false, verbosity = 0);

# Plot the diagram
plot(brdc2[1])

This does give me parts of the diagram as output, but not everything (it is one of those bistable switches, but I only get the first third, up until it is supposed to turn over and go backwards):
bad_bif

I have tried modifying option values, as well as adding

termination_condition(x, f, J, residual, iteration, niter, options; kwargs...) = residual <1e3
root_search(x,p,id) = x .+ rand(length(x))

but didn't get it to work. What is going wrong here, and what should I do to modify?

bifurcationdiagram() function fails to track new branches in pitchfork bifurcation

Hello,
I try to compute a bifurcation diagram using the bifurcationdiagram function. The system is a 2 variable ODE originating from a biochemical reaction network. This is the example:

# Fetch packages.
using BifurcationKit
using Catalyst
using OrdinaryDiffEq
using Plots
using Setfield

# Declare our model.
hillAR(x1,x2,v,K,n) = (v*x1^n)/(x1^n+x2^n+K^n)
ss_network = @reaction_network begin
    v0+hillAR(S*X,D*Y,v,K,n), ∅  X
    v0+hillAR(S*Y,D*X,v,K,n), ∅  Y
    1,(X,Y) end S D v0 v n K;

# Generate system functions.
odefun = ODEFunction(convert(ODESystem,rn),jac=true)
F = (u,p) -> odefun(u,p,0)      
J = (u,p) -> odefun.jac(u,p,0);
jet = BifurcationKit.getJet(F, J; matrixfree=false)

# Select parameters
params_in = [1.0,0.5,0.1,1.0,2,0.5]; 
p_idx = 1; 
p_span = (0.1,2.5)
params = setindex!(copy(params_in),p_span[1],p_idx);

# Set options.
dsmax = params[p_idx]/500
dsmin = min(params[p_idx]/10000,1e-4)
ds = sqrt(dsmax*dsmin);
opts_br = ContinuationPar(pMin = p_span[1], pMax = p_span[2], dsmax = dsmax, dsmin = dsmin, ds=ds, maxSteps = 1000000,
    detectBifurcation = 3,
    nInversion = 6, maxBisectionSteps = 25,
    nev = 3);

# Compute diagram.
bif = bifurcationdiagram(jet..., u0, params, (@lens _[p_idx]), 3,
    (x,p,level)->setproperties(opts_br); 
    tangentAlgo = NaturalPred(),
    recordFromSolution=(x, p) -> x[v_idx], verbosity = 0, plot=false);

# Plot the diagram.
plot(bif)

Now, this produces the print:

##################################################
---> Automatic computation of bifurcation diagram

────────────────────────────────────────────────────────────────────────────────
--> New branch, level = 2, dim(Kernel) = 1, code = 0, from bp #1 at p = 0.858837922561846, type = bp
- #  1,    bp at p ≈ +0.85883792 ∈ (+0.85883753, +0.85883792), |δp|=4e-07, [converged], δ = ( 1,  0), step = 3797, eigenelements in eig[3798], ind_ev =   1
----> From Pitchfork
- #  1,    bp at p ≈ +0.85883792 ∈ (+0.85883753, +0.85883792), |δp|=4e-07, [converged], δ = ( 1,  0), step = 3797, eigenelements in eig[3798], ind_ev =   1
────────────────────────────────────────────────────────────────────────────────
--> New branch, level = 2, dim(Kernel) = 1, code = 0, from bp #2 at p = 1.0061644362337048, type = bp
- #  2,    bp at p ≈ +1.00616444 ∈ (+1.00616439, +1.00616444), |δp|=5e-08, [converged], δ = (-1,  0), step = 4534, eigenelements in eig[4535], ind_ev =   1
----> From Pitchfork
- #  2,    bp at p ≈ +1.00616444 ∈ (+1.00616439, +1.00616444), |δp|=5e-08, [converged], δ = (-1,  0), step = 4534, eigenelements in eig[4535], ind_ev =   1

and the plot:
image

However, the bifurcation diagram should look more like this:
image
Basically, in the two branch (pitchfork?) points, two branches go out, which (via two fold points) later connect to the other pitchfork bifurcation.

The code above generates a single branch, with 4 children, if I plot each

plot(plot(bif.γ),plot(bif.child[1].γ),plot(bif.child[2].γ),plot(bif.child[3].γ),plot(bif.child[4].γ),size=(1200,600))

I get
image

this makes it seem like the solver detects that there are two branches going out from each pitchfork point. However, when it tracks these two, it simply follows the original path back/forward towards the end, creating some overlap.

I have tried changing options in (x,p,level)->setproperties(opts_br), using various combinations of what is used in the first example at https://bifurcationkit.github.io/BifurcationKitDocs.jl/dev/BifurcationDiagram/, however, with no luck. I also tried removing tangentAlgo = NaturalPred(), but for large level Julia crashes, and for smaller when I try to print the bifurcation diagram I get no output.

I have also tried making the sign of ds iterate with the level, using

(x,p,level)->setproperties(opts_br;ds=-ds*(-1)^level)

but it didn't work.

Is it just that I am missing some option to make it properly detect the branches, or is there some other problem?

[DOC] what is the expected output type for the Jacobian?

Hi there,

I'm trying to come up with a simple example for the bifurcation of 1-dimensional system to contribute to the documentation, under guidance of @gszep . I have the example running, by altering the code https://rveltz.github.io/BifurcationKit.jl/dev/iterator/# , but there are some things that are not yet clear enough for me.

What I really want to know is what is the allowed return types for both the vector field f and the Jacobian. The code has:

Jac_m = (x, p) -> diagm(0 => 1.0  .- x.^k)

but it was confusing for me because when I tried other types of Jac_m I got errors (I am not reporting errors here until I first see what is the expected form of Jacobian). I also tried to use the output of ForwardDiff.jacobian, also getting errors.

So, can you please document somewhere centrally in the documentation what is the expected forms for f, J? Not only what are the input arguments, but what is the expected output. The documentation of e.g. https://rveltz.github.io/BifurcationKit.jl/dev/library/#BifurcationKit.continuation does not discuss with clarity what J should be. There you can also provide tips and tricks, as e.g. the above usage of diagm is definitely something advanced for me, I would expected a 1-element matrix as return type in this example.


A comment on how the docstring is structured: it provides:

F = (x, p) -> F(x, p)

This, almost always, is invalid Julia code. If F is an existing function, here you are redefining it as an anonymous function F, and Julia will just error. It also doesn't highlight what is the return type of F, yet repeats what are the expected input arguments.

Why not consider replacing it with the more valid F(x,p)::AbstractVector, or simply explicitly write things out as e.g. "F is a function with input arguments (x, p) returning a vector r that represents the vector field of the dynamics".


Are static vectors supported?

unable to save files when saveToFile=true

I'm trying to save the continuation results by setting the keyword argument saveToFile=true, however it doesn't seen to work. I'm just playing with the temperature model in the tutorial:

using BifurcationKit, LinearAlgebra, Plots, Parameters, Setfield
# Setfield.jl is used to provide the parameter axis @lens
const BK = BifurcationKit

N(x; a = 0.5, b = 0.01) = 1 + (x + a*x^2)/(1 + b*x^2)

function F_chan(x, p)
    @unpack α, β = 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 + α * N(x[i], b = β)
    end
    return f
end

n = 101
sol = [(i-1)*(n-i)/n^2+0.1 for i=1:n]

# set of parameters
par = (α = 3.3, β = 0.01)

optnewton = NewtonPar(tol = 1e-11, verbose = false)

out, = @time newton( F_chan, sol, par, optnewton)

optcont = ContinuationPar(dsmin = 0.01, dsmax = 0.2, ds= 0.1, pMin = 0., pMax = 4.1, saveToFile=true,
    newtonOptions = NewtonPar(maxIter = 10, tol = 1e-9))

br, = continuation(F_chan, out, par, (@lens _.α),
        optcont; plot = false, verbosity = 1)

but I get the following error:

  0.236941 seconds (846.97 k allocations: 45.750 MiB, 98.13% compilation time)
UndefVarError: saveToFile not defined

Stacktrace:
 [1] continuation!(it::ContIterable{typeof(F_chan), BifurcationKit.var"#148#150"{typeof(F_chan)}, Vector{Float64}, NamedTuple{(:α, :β), Tuple{Float64, Float64}}, Setfield.PropertyLens{:α}, Float64, DefaultLS, DefaultEig{typeof(real)}, SecantPred, MatrixBLS{Nothing}, BifurcationKit.var"#126#134"{BifurcationKit.var"#126#127#135"}, BifurcationKit.var"#128#136"{BifurcationKit.var"#128#129#137"}, typeof(norm), BifurcationKit.DotTheta{BifurcationKit.var"#169#171", BifurcationKit.var"#170#172"}, typeof(BifurcationKit.finaliseDefault), typeof(BifurcationKit.cbDefault), Nothing, String}, state::ContState{BorderedArray{Vector{Float64}, Float64}, Float64, Nothing, Nothing, Tuple{Nothing, Nothing}}, contRes::ContResult{NamedTuple{(:x, :param, :itnewton, :itlinear, :ds, :θ, :n_unstable, :n_imag, :stable, :step), Tuple{Float64, Float64, Int64, Int64, Float64, Float64, Int64, Int64, Nothing, Int64}}, Nothing, Nothing, BifurcationKit.SpecialPoint{Float64, NamedTuple{(:x,), Tuple{Float64}}, Vector{Float64}}, Nothing, ContinuationPar{Float64, DefaultLS, DefaultEig{typeof(real)}}, Nothing, NamedTuple{(:α, :β), Tuple{Float64, Float64}}, Setfield.PropertyLens{:α}})
   @ BifurcationKit C:\Users\yhcha\.julia\packages\BifurcationKit\eoTLY\src\Continuation.jl:446
 [2] continuation(it::ContIterable{typeof(F_chan), BifurcationKit.var"#148#150"{typeof(F_chan)}, Vector{Float64}, NamedTuple{(:α, :β), Tuple{Float64, Float64}}, Setfield.PropertyLens{:α}, Float64, DefaultLS, DefaultEig{typeof(real)}, SecantPred, MatrixBLS{Nothing}, BifurcationKit.var"#126#134"{BifurcationKit.var"#126#127#135"}, BifurcationKit.var"#128#136"{BifurcationKit.var"#128#129#137"}, typeof(norm), BifurcationKit.DotTheta{BifurcationKit.var"#169#171", BifurcationKit.var"#170#172"}, typeof(BifurcationKit.finaliseDefault), typeof(BifurcationKit.cbDefault), Nothing, String})
   @ BifurcationKit C:\Users\yhcha\.julia\packages\BifurcationKit\eoTLY\src\Continuation.jl:485
 [3] continuation(Fhandle::Function, Jhandle::Function, x0::Vector{Float64}, par::NamedTuple{(:α, :β), Tuple{Float64, Float64}}, lens::Setfield.PropertyLens{:α}, contParams::ContinuationPar{Float64, DefaultLS, DefaultEig{typeof(real)}}, linearAlgo::MatrixBLS{Nothing}; bothside::Bool, kwargs::Base.Pairs{Symbol, Integer, Tuple{Symbol, Symbol}, NamedTuple{(:plot, :verbosity), Tuple{Bool, Int64}}})
   @ BifurcationKit C:\Users\yhcha\.julia\packages\BifurcationKit\eoTLY\src\Continuation.jl:0
 [4] #continuation#146
   @ C:\Users\yhcha\.julia\packages\BifurcationKit\eoTLY\src\Continuation.jl:567 [inlined]
 [5] #continuation#147
   @ C:\Users\yhcha\.julia\packages\BifurcationKit\eoTLY\src\Continuation.jl:570 [inlined]
 [6] top-level scope
   @ In[153]:32
 [7] eval
   @ .\boot.jl:373 [inlined]
 [8] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
   @ Base .\loading.jl:1196

UndefVarError: getTrajectory not define (when running tutorial)

I am running the tutorial, trying to figure out how to plot periodic orbits in ODE bifurcation diagrams.

I am trying the tutorial in: https://rveltz.github.io/BifurcationKit.jl/dev/tutorials/ode/tutorialsODE/#Neural-mass-equation-(Hopf-aBS) however, the section on "Branch of periodic orbits with Trapezoid method" gives an error:

UndefVarError: getTrajectory not defined

Stacktrace:
  [1] (::var"#5#8")(x::Vector{Float64}, p::NamedTuple{(:prob, :p), Tuple{PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}, Float64}})
    @ Main ./In[5]:7
  [2] #808#809
    @ ~/.julia/packages/BifurcationKit/eoTLY/src/periodicorbit/PeriodicOrbitUtils.jl:81 [inlined]
  [3] #808
    @ ~/.julia/packages/BifurcationKit/eoTLY/src/periodicorbit/PeriodicOrbitUtils.jl:81 [inlined]
  [4] ContResult(it::ContIterable{PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}, BifurcationKit.var"#753#758"{PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}}, Vector{Float64}, NamedTuple{(:α, :τ, :J, :E0, :τD, :U0, :τF, :τS), NTuple{8, Float64}}, Setfield.PropertyLens{:E0}, Float64, BifurcationKit.FloquetWrapperLS{DefaultLS}, FloquetQaD{DefaultEig{typeof(abs)}}, SecantPred, BorderingBLS{BifurcationKit.FloquetWrapperLS{DefaultLS}, Float64}, BifurcationKit.var"#818#822"{BifurcationKit.var"#818#819#823"{PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}, var"#6#9"}}, BifurcationKit.var"#808#812"{BifurcationKit.var"#808#809#813"{PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}, var"#5#8"}}, typeof(norminf), BifurcationKit.DotTheta{BifurcationKit.var"#169#171", BifurcationKit.var"#170#172"}, BifurcationKit.var"#800#804"{BifurcationKit.var"#800#801#805"{PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}, Int64}}, typeof(BifurcationKit.cbDefault), Nothing, String}, state::ContState{BorderedArray{Vector{Float64}, Float64}, Float64, Vector{ComplexF64}, Matrix{ComplexF64}, Tuple{Nothing, Nothing}})
    @ BifurcationKit ~/.julia/packages/BifurcationKit/eoTLY/src/Continuation.jl:238
  [5] continuation(it::ContIterable{PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}, BifurcationKit.var"#753#758"{PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}}, Vector{Float64}, NamedTuple{(:α, :τ, :J, :E0, :τD, :U0, :τF, :τS), NTuple{8, Float64}}, Setfield.PropertyLens{:E0}, Float64, BifurcationKit.FloquetWrapperLS{DefaultLS}, FloquetQaD{DefaultEig{typeof(abs)}}, SecantPred, BorderingBLS{BifurcationKit.FloquetWrapperLS{DefaultLS}, Float64}, BifurcationKit.var"#818#822"{BifurcationKit.var"#818#819#823"{PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}, var"#6#9"}}, BifurcationKit.var"#808#812"{BifurcationKit.var"#808#809#813"{PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}, var"#5#8"}}, typeof(norminf), BifurcationKit.DotTheta{BifurcationKit.var"#169#171", BifurcationKit.var"#170#172"}, BifurcationKit.var"#800#804"{BifurcationKit.var"#800#801#805"{PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}, Int64}}, typeof(BifurcationKit.cbDefault), Nothing, String})
    @ BifurcationKit ~/.julia/packages/BifurcationKit/eoTLY/src/Continuation.jl:482
  [6] continuation(Fhandle::PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}, Jhandle::Function, x0::Vector{Float64}, par::NamedTuple{(:α, :τ, :J, :E0, :τD, :U0, :τF, :τS), NTuple{8, Float64}}, lens::Setfield.PropertyLens{:E0}, contParams::ContinuationPar{Float64, BifurcationKit.FloquetWrapperLS{DefaultLS}, FloquetQaD{DefaultEig{typeof(abs)}}}, linearAlgo::BorderingBLS{BifurcationKit.FloquetWrapperLS{DefaultLS}, Float64}; bothside::Bool, kwargs::Base.Pairs{Symbol, Any, NTuple{7, Symbol}, NamedTuple{(:recordFromSolution, :linearPO, :verbosity, :plot, :plotSolution, :normC, :finaliseSolution), Tuple{BifurcationKit.var"#808#812"{BifurcationKit.var"#808#809#813"{PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}, var"#5#8"}}, Symbol, Int64, Bool, BifurcationKit.var"#818#822"{BifurcationKit.var"#818#819#823"{PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}, var"#6#9"}}, typeof(norminf), BifurcationKit.var"#800#804"{BifurcationKit.var"#800#801#805"{PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}, Int64}}}}})
    @ BifurcationKit ~/.julia/packages/BifurcationKit/eoTLY/src/Continuation.jl:0
  [7] continuationPOTrap(prob::PeriodicOrbitTrapProblem{typeof(TMvf), typeof(dTMvf), Nothing, Nothing, Nothing, Vector{Float64}, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}, orbitguess::Vector{Float64}, par::NamedTuple{(:α, :τ, :J, :E0, :τD, :U0, :τF, :τS), NTuple{8, Float64}}, lens::Setfield.PropertyLens{:E0}, contParams::ContinuationPar{Float64, DefaultLS, DefaultEig{typeof(real)}}, linearAlgo::BorderingBLS{DefaultLS, Float64}; jacobianPO::Symbol, updateSectionEveryStep::Int64, kwargs::Base.Pairs{Symbol, Any, NTuple{6, Symbol}, NamedTuple{(:recordFromSolution, :linearPO, :verbosity, :plot, :plotSolution, :normC), Tuple{var"#5#8", Symbol, Int64, Bool, var"#6#9", typeof(norminf)}}})
    @ BifurcationKit ~/.julia/packages/BifurcationKit/eoTLY/src/periodicorbit/PeriodicOrbitTrapeze.jl:916
  [8] #continuation#763
    @ ~/.julia/packages/BifurcationKit/eoTLY/src/periodicorbit/PeriodicOrbitTrapeze.jl:978 [inlined]
  [9] continuation(F::typeof(TMvf), dF::typeof(dTMvf), d2F::BifurcationKit.var"#d2f#284"{BifurcationKit.var"#d1f#282"{typeof(TMvf)}}, d3F::BifurcationKit.var"#d3f#286"{BifurcationKit.var"#d2f#284"{BifurcationKit.var"#d1f#282"{typeof(TMvf)}}}, br::ContResult{NamedTuple{(:E, :x, :u, :param, :itnewton, :itlinear, :ds, :θ, :n_unstable, :n_imag, :stable, :step), Tuple{Float64, Float64, Float64, Float64, Int64, Int64, Float64, Float64, Int64, Int64, Bool, Int64}}, Vector{ComplexF64}, Matrix{ComplexF64}, BifurcationKit.SpecialPoint{Float64, NamedTuple{(:E, :x, :u), Tuple{Float64, Float64, Float64}}, Vector{Float64}}, Nothing, ContinuationPar{Float64, DefaultLS, DefaultEig{typeof(real)}}, Nothing, NamedTuple{(:α, :τ, :J, :E0, :τD, :U0, :τF, :τS), NTuple{8, Float64}}, Setfield.PropertyLens{:E0}}, ind_bif::Int64, _contParams::ContinuationPar{Float64, DefaultLS, DefaultEig{typeof(real)}}, prob::PeriodicOrbitTrapProblem{Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, DefaultLS, BifurcationKit.TimeMesh{Int64}, Nothing}; Jᵗ::Nothing, δ::Float64, δp::Nothing, ampfactor::Int64, usedeflation::Bool, nev::Int64, updateSectionEveryStep::Int64, kwargs::Base.Pairs{Symbol, Any, NTuple{6, Symbol}, NamedTuple{(:linearPO, :verbosity, :plot, :recordFromSolution, :plotSolution, :normC), Tuple{Symbol, Int64, Bool, var"#5#8", var"#6#9", typeof(norminf)}}})
    @ BifurcationKit ~/.julia/packages/BifurcationKit/eoTLY/src/periodicorbit/PeriodicOrbits.jl:410
 [10] top-level scope
    @ In[5]:21
 [11] eval
    @ ./boot.jl:373 [inlined]
 [12] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
    @ Base ./loading.jl:1196

I get this after copying (and running) all the code in the tutorial

using Revise, ForwardDiff, Parameters, Setfield, Plots, LinearAlgebra
using BifurcationKit
const BK = BifurcationKit

# sup norm
norminf(x) = norm(x, Inf)

# vector field
function TMvf!(dz, z, p, t)
	@unpack J, α, E0, τ, τD, τF, U0 = p
	E, x, u = z
	SS0 = J * u * x * E + E0
	SS1 = α * log(1 + exp(SS0 / α))
	dz[1] = (-E + SS1) / τ
	dz[2] =	(1.0 - x) / τD - u * x * E
	dz[3] = (U0 - u) / τF +  U0 * (1.0 - u) * E
	dz
end

# out of place method
TMvf(z, p) = TMvf!(similar(z), z, p, 0)

# we group the differentials together
dTMvf(z,p) = ForwardDiff.jacobian(x -> TMvf(x,p), z)
jet = BK.getJet(TMvf, dTMvf)

# parameter values
par_tm == 1.5, τ = 0.013, J = 3.07, E0 = -2.0, τD = 0.200, U0 = 0.3, τF = 1.5, τS = 0.007)

# initial condition
z0 = [0.238616, 0.982747, 0.367876]

# continuation options
opts_br = ContinuationPar(pMin = -10.0, pMax = -0.9,
	# parameters to have a smooth result
	ds = 0.04, dsmax = 0.05,
	# this is to detect bifurcation points precisely with bisection
	detectBifurcation = 3,
	# Optional: bisection options for locating bifurcations
	nInversion = 8, maxBisectionSteps = 25, nev = 3)

# continuation of equilibria
br, = continuation(TMvf, dTMvf, z0, par_tm, (@lens _.E0), opts_br;
	recordFromSolution = (x, p) -> (E = x[1], x = x[2], u = x[3]),
	tangentAlgo = BorderedPred(),
	plot = true, normC = norminf)

scene = plot(br, plotfold=false, markersize=3, legend=:topleft)

br

# newton parameters
optn_po = NewtonPar(verbose = true, tol = 1e-8,  maxIter = 10)

# continuation parameters
opts_po_cont = ContinuationPar(dsmax = 0.1, ds= -0.0001, dsmin = 1e-4, pMax = 0., pMin=-5.,
	maxSteps = 110, newtonOptions = (@set optn_po.tol = 1e-7),
	nev = 3, precisionStability = 1e-8, detectBifurcation = 3, plotEveryStep = 10, saveSolEveryStep=1)

# arguments for periodic orbits
args_po = (	recordFromSolution = (x, p) -> (xtt = BK.getTrajectory(p.prob, x, p.p);
		return (max = maximum(xtt.u[1,:]),
				min = minimum(xtt.u[1,:]),
				period = x[end])),
	plotSolution = (x, p; k...) -> begin
		xtt = BK.getTrajectory(p.prob, x, p.p)
		plot!(xtt.t, xtt.u[1,:]; label = "E", k...)
		plot!(xtt.t, xtt.u[2,:]; label = "x", k...)
		plot!(xtt.t, xtt.u[3,:]; label = "u", k...)
		plot!(br,subplot=1, putbifptlegend = false)
		end,
	normC = norminf)

Mt = 200 # number of time sections
	br_potrap, utrap = continuation(jet...,
	# we want to branch form the 4th bif. point
	br, 4, opts_po_cont,
	# we want to use the Trapeze method to locate PO
	PeriodicOrbitTrapProblem(M = Mt);
	# this jacobian is specific to ODEs
	# it is computed using AD and
	# updated inplace
	linearPO = :Dense,
	# regular continuation options
	verbosity = 2,	plot = true,
	args_po...)

scene = plot(br, br_potrap, markersize = 3)
plot!(scene, br_potrap.param, br_potrap.min, label = "")

Indeed, just trying to access the getTrajectory function:

BK.getTrajectory

gives the error. I checked, and I have the latest version of BifurcationKit ( [0f109fa4] BifurcationKit v0.1.11).

Joint function for Evaluation and Jacobian ?

Hi, I am thinking about using your package. My first try would be to join this with my package VoronoiFVM.jl.
I understand that for the problem call back one can either pass F(x,p) or F(x,p) and J(x,p) together.

As in my code I am calculating F and (sparse) J at the same time using ForwardDiff and DiffResults I wonder if it
would be possible to have another option which allows to pass something like F_and_J(x,p) ?

user wants recordfunction kwarg

it would be useful to be able to record vectors of solutions during continuation method. Suppose I am interested in recording the first and third component of my ode system then I would call

Cont.continuation( .... recordfunction = u -> [ u[1], u[3] ] )

If u contained a spatial system then a more interesting recordfunction call could be to segment out a pattern and record some statistics on that pattern.. a fourier transform perhaps?
this should not interfere with printsolution or plotBranch

Docs missing description of what verbosity levels does (?)

A quick thing. Doesn't matter much but realised that it seemed like this was missing, so figured I'd add it here.

Various methods have an option verbosity, which can carry between 0 and 3 (or 5). However, nowhere does it seem to say exactly what these various levels actually do. testing various things mostly solves it, but could possibly be useful to add in the docs some day (although not of high priority).

Use Brent's method instead of bisection for locating bifurcations

Feature request: use Brent's method for bifurcation location. Should be as robust and faster than bisection. There's a julia implementation over at Roots.jl (although the algorithm as described on wikipedia might be sufficient if you don't want to depend on Roots)

Continuation function misses negative branch of saddle-node bifurcation?

I may be misunderstanding something, but it seems that the continuation function is saving incorrect equilibrium values to the ContResult object. In particular, I was looking at the following example.

using ForwardDiff, Parameters, Setfield, Plots, LinearAlgebra, SparseArrays
using BifurcationKit
const BK = BifurcationKit

F(x,p) = @. (x^2 - p)
JacF(x,p) = spdiagm(0=> 2. .* x)

opts = ContinuationPar(detectBifurcation = 3, dsmax = 0.1, dsmin = 1e-3, ds = -0.001, maxSteps = 130, pMin = -3., pMax = 1., saveSolEveryStep = 0, newtonOptions = NewtonPar(tol = 1e-8, verbose = true));

iter = BK.ContIterable(F, JacF, [-0.9], 1., (@lens _), opts; verbosity = 2);

If I run continuation!(iter), the resulting branch covers the positive equilibria (sqrt(p)) twice and misses the negative equilibria (-sqrt(p)) (despite passing a negative initial condition). In contrast, if I iterate the ContInterable by hand, then PALC correctly identifies both positive and negative equilibria.

res = ContResult(iter, iterate(iter)[1])
resx = Float64[]

st = iterate(iter)

while st[1] != nothing
    push!(resx, getx(st[1])[1])
    BK.save!(res, iter, st[1])
    st = iterate(iter, st[1])
end

Now if I examine the res.x and compare with the resx list, the resx list contains the negative equilibria but res.x contains the positive equilibria twice.

Am I missing something here, or is this a bug? Thanks!

Avoiding branches/solutions with negative or imaginary values in "continuation()"

I am creating bifurcation diagrams of a biochemical reaction network (which tracks the concentration of several system components). Since I am really only interested in the equilibrium concentration of one component, I use the option recordFromSolution=(x, p) -> x[7]. The solution then returns some negative branches, but I solve that by only plotting the positive plane.

However, I still get suspiciously much more branches than expected (I only expect a single one, possibly with a Hopf bifurcation). I realised that what might happen is that while; my target variable is always negative, I am finding solutions where some other component is negative (or even imaginary!).

Is there a good way of rejecting branches where any variable fulfils some condition (in this case negative/imaginary values)? My best idea so far is to use something like using https://rveltz.github.io/BifurcationKit.jl/dev/faq/#How-can-I-save-a-solution-every-n-steps,-or-at-specific-parameter-values? to record all values in the solution, and then afterwards go through them all to check. But not sure whenever that would work well, or if there's a better solution.

For reference, this is the current code:

# Fetch Packages
using DifferentialEquations, Catalyst
using BifurcationKit, Setfield, LinearAlgebra

# Declare Model
rn = @reaction_network begin
    kDeg,       (w,w2,w2v,v,w2v2,vP,σB,w2σB)  ∅
    kDeg,       vPp  phos
    (kBw,kDw),  2w  w2
    (kB1,kD1),  w2 + v  w2v
    (kB2,kD2),  w2v + v  w2v2
    kK1,        w2v  w2 + vP
    kK2,        w2v2  w2v + vP
    (kB3,kD3),  w2 + σB  w2σB
    (kB4,kD4),  w2σB + v  w2v + σB
    (kB5,kD5),  vP + phos  vPp
    kP,         vPp  v + phos
    v0*((1+F*σB)/(K+σB)),     ∅  σB
    λW*v0*((1+F*σB)/(K+σB)),  ∅  w
    λV*v0*((1+F*σB)/(K+σB)),  ∅  v
    
    (pStress,1), 0 <--> phos
    1,  vPp  vP 
end kBw kDw kD kB1 kB2 kB3 kB4 kB5 kD1 kD2 kD3 kD4 kD5 kK1 kK2 kP kDeg v0 F K λW λV pInit pStress;

# Set Parameter Values
kBw = 3600; kDw = 18; kD = 18;
kB1 = 3600; kB2 = 3600; kB3 = 3600; kB4 = 1800; kB5 = 3600;
kD1 = 18; kD2 = 18; kD3 = 18; kD4 = 1800; kD5 = 18;
kK1 = 36; kK2 = 12; #kK2 reduced to a third to achive arcitability. kK2 = 36 in the original model.
kP = 180; kDeg = 0.7;
v0 = 0.4; F = 30; K = 0.2;
λW = 4; λV = 4.5;

η = 0.05
pInit = 0.001
pStress = 0.4
parameters = [kBw, kDw, kD, kB1, kB2, kB3, kB4, kB5, kD1, kD2, kD3, kD4, kD5, kK1, kK2, kP, kDeg, v0, F, K, λW, λV, pInit, pStress, η];

# Find good u0 (know good initial guess should be needed, but had some other error when I just used a zero vector)
oprob = ODEProblem(rn,ones(length(rn.states)),(0.,50.),parameters)
sol = solve(oprob,Rosenbrock23());

# Prepare Bifurcation Comoutation
#rn = noise_modulation_model.system
odefun = ODEFunction(convert(ODESystem,rn),jac=true)
F = (u,p) -> odefun(u,p,0)      
J = (u,p) -> odefun.jac(u,p,0);
params = parameters;
p_idx = 15       
p_span = (12.,36.)
plot_var_idx = 7;

# Set Bifurcation Options
opts = ContinuationPar( dsmax = 0.05,        # Maximum arclength value of the pseudo-arc length continuation method.
                        dsmin = 1e-4,        # Minimum arclength value of the pseudo-arc length continuation method.
                        ds=0.001,            # Initial arclength value of the pseudo-arc length continuation method (should be positive).
                        maxSteps = 100000,   # The maximum number of steps.
                        pMin = p_span[1],    # Minimum p-vale (if hit, the method stops).
                        pMax = p_span[2],    # Maximum p-vale (if hit, the method stops).
                        detectBifurcation=3, # Value in {0,1,2,3} determening to what extent bofurcation points are detected (0 means nothing is done, 3 both them and there localisation are detected).
                        newtonOptions = NewtonPar(tol = 1e-9, verbose = false, maxIter = 15)); #Parameters to the newton solver (when finding fixed points) see BifurcationKit documentation.

DO = DeflationOperator( 2.0,    # Algorithm parameter required when using deflated continuation, see BifurcationKit documentation.
                        dot,    # Algorithm parameter required when using deflated continuation, see BifurcationKit documentation.
                        1.,     # Algorithm parameter required when using deflated continuation, see BifurcationKit documentation.
                        [sol.u[end]]); # Guess(es) of the fixed point for the initial parameter set. Do not need to be exact.

# Calculate the diagram
params_input = setindex!(copy(params),p_span[1],p_idx);
@time branches, = continuation(F, J, params_input, (@lens _[p_idx]) ,opts , DO,       # Gives our input.
           verbosity = 0, plot=false, 
           recordFromSolution=(x, p) -> x[plot_var_idx], 
           perturbSolution = (x,p,id) -> (x  .+ 0.8 .* rand()),
           callbackN = BifurcationKit.cbMaxNorm(1e7));  
``

How to save my results to a file

Thanks for this wonderful package! I have some problems trying to save my results to a file. My code is as follows:

using Revise, ForwardDiff, Parameters, Setfield, Plots, LinearAlgebra
using BifurcationKit
const BK = BifurcationKit

# sup norm
norminf(x) = norm(x, Inf)

# vector field
function TMvf!(dz, z, p, t)
	@unpack J, α, E0, τ, τD, τF, U0 = p
	E, x, u = z
	SS0 = J * u * x * E + E0
	SS1 = α * log(1 + exp(SS0 / α))
	dz[1] = (-E + SS1) / τ
	dz[2] =	(1.0 - x) / τD - u * x * E
	dz[3] = (U0 - u) / τF +  U0 * (1.0 - u) * E
	dz
end

# out of place method
TMvf(z, p) = TMvf!(similar(z), z, p, 0)

# we group the differentials together
jet = BK.getJet(TMvf; matrixfree=false)

# parameter values
par_tm = (α = 1.5, τ = 0.013, J = 3.07, E0 = -2.0, τD = 0.200, U0 = 0.3, τF = 1.5, τS = 0.007)

# initial condition
z0 = [0.238616, 0.982747, 0.367876]

# continuation options
opts_br = ContinuationPar(pMin = -10.0, pMax = -0.9,
	# parameters to have a smooth result
	ds = 0.04, dsmax = 0.05,
	# this is to detect bifurcation points precisely with bisection
	detectBifurcation = 3,
	saveToFile    = true,
	# Optional: bisection options for locating bifurcations
	nInversion = 8, maxBisectionSteps = 25, nev = 3)

# continuation of equilibria
br, = continuation(jet[1], jet[2], z0, par_tm, (@lens _.E0), opts_br;
	recordFromSolution = (x, p) -> (E = x[1], x = x[2], u = x[3]),
	finaliseSolution = (z, tau, step, contResult; k...) ->
		(Base.display(contResult.eig[end].eigenvals) ;true),
	tangentAlgo = BorderedPred(),
	filename = "Neural mass equation",
	plot = true, normC = norminf)

scene = plot(br, plotfold=false, markersize=3, legend=:topleft)

This is the error message:

ERROR: LoadError: UndefVarError: saveToFile not defined
Stacktrace:
 [1] continuation!(it::ContIterable{typeof(TMvf), BifurcationKit.var"#Jmat#286"{typeof(TMvf)}, Vector{Float64}, NamedTuple{(:α, :τ, :J, :E0, :τD, :U0, :τF, :τS), NTuple{8, Float64}}, Setfield.PropertyLens{:E0}, Float64, DefaultLS, DefaultEig{typeof(real)}, BorderedPred, MatrixBLS{Nothing}, BifurcationKit.var"#126#134"{BifurcationKit.var"#126#127#135"}, var"#25#28", typeof(norminf), BifurcationKit.DotTheta{BifurcationKit.var"#169#171", BifurcationKit.var"#170#172"}, var"#26#29", typeof(BifurcationKit.cbDefault), Nothing, String}, state::ContState{BorderedArray{Vector{Float64}, Float64}, Float64, Vector{ComplexF64}, Matrix{ComplexF64}, Tuple{Nothing, Nothing}}, contRes::ContResult{NamedTuple{(:E, :x, 
:u, :param, :itnewton, :itlinear, :ds, :θ, :n_unstable, :n_imag, :stable, :step), Tuple{Float64, Float64, Float64, Float64, Int64, Int64, Float64, Float64, Int64, Int64, Bool, Int64}}, Vector{ComplexF64}, Matrix{ComplexF64}, BifurcationKit.SpecialPoint{Float64, NamedTuple{(:E, :x, :u), Tuple{Float64, Float64, Float64}}, Vector{Float64}}, Nothing, ContinuationPar{Float64, DefaultLS, DefaultEig{typeof(real)}}, Nothing, NamedTuple{(:α, :τ, :J, :E0, :τD, :U0, :τF, :τS), NTuple{8, Float64}}, Setfield.PropertyLens{:E0}})
   @ BifurcationKit D:\Julia\Julia-1.6.5\packages\packages\BifurcationKit\eoTLY\src\Continuation.jl:446
 [2] continuation(it::ContIterable{typeof(TMvf), BifurcationKit.var"#Jmat#286"{typeof(TMvf)}, Vector{Float64}, NamedTuple{(:α, :τ, :J, :E0, :τD, :U0, :τF, :τS), NTuple{8, Float64}}, Setfield.PropertyLens{:E0}, Float64, DefaultLS, DefaultEig{typeof(real)}, BorderedPred, MatrixBLS{Nothing}, BifurcationKit.var"#126#134"{BifurcationKit.var"#126#127#135"}, var"#25#28", typeof(norminf), BifurcationKit.DotTheta{BifurcationKit.var"#169#171", BifurcationKit.var"#170#172"}, var"#26#29", typeof(BifurcationKit.cbDefault), Nothing, String})
   @ BifurcationKit D:\Julia\Julia-1.6.5\packages\packages\BifurcationKit\eoTLY\src\Continuation.jl:485
 [3] continuation(Fhandle::Function, Jhandle::Function, x0::Vector{Float64}, par::NamedTuple{(:α, :τ, :J, :E0, :τD, :U0, :τF, :τS), NTuple{8, Float64}}, lens::Setfield.PropertyLens{:E0}, contParams::ContinuationPar{Float64, DefaultLS, DefaultEig{typeof(real)}}, linearAlgo::MatrixBLS{Nothing}; bothside::Bool, kwargs::Base.Iterators.Pairs{Symbol, Any, NTuple{6, Symbol}, NamedTuple{(:recordFromSolution, :finaliseSolution, :tangentAlgo, :filename, :plot, :normC), Tuple{var"#25#28", var"#26#29", BorderedPred, String, Bool, typeof(norminf)}}})
   @ BifurcationKit D:\Julia\Julia-1.6.5\packages\packages\BifurcationKit\eoTLY\src\Continuation.jl:0
 [4] #continuation#146
   @ D:\Julia\Julia-1.6.5\packages\packages\BifurcationKit\eoTLY\src\Continuation.jl:567 [inlined]
 [5] top-level scope
   @ d:\Julia\secondpaper_julia\test.jl:43
in expression starting at d:\Julia\secondpaper_julia\test.jl:43

Questions regarding, and oddities with, the bifurcationdiagram function

So, I have been playing around with the bifurcationdiagram function. It's really cool! However, there are a few things I do not understand, and a few other things which seem weird, which I have not been able to understand from reading the docs.

For a starter, I run this code to generate my bifurcation diagram:

# Fetch the required packages.
using Revise, Plots
using BifurcationKit, Setfield, ForwardDiff
using Catalyst, OrdinaryDifEq
const BK = BifurcationKit

# The input (a system, a parameter set, a target parameter, and a range to varry it over).
system = @reaction_network begin
    v0+v*(S*σ)^n/((S*σ)^n+(D*A)^n+K^n), ∅  σ
    d, σ  ∅
    (τ*σ,τ), ∅  A
end S D τ v v0 K n d;
pRange = (0.1,20.)
parameter_vals = [10.,9.,0.005,2.,0.01,20.,3,0.05]
p = :S
pRange = (0.1,20.)

# Extract required stuff from the input.
fun = ODEFunction(convert(ODESystem,system),jac=true)
parameters = copy(parameter_vals); parameters[1] = pRange[1]
p_idx = findfirst(Symbol.(system.ps) .== p)

# Differentiate the function.
D(f, x, p, dx) = ForwardDiff.derivative(t -> f(x .+ t .* dx, p), 0.)
FbpSecBif(u, p) = fun(u,p,0)
dFbpSecBif(x,p) =  ForwardDiff.jacobian( z-> FbpSecBif(z,p), x)
d1FbpSecBif(x,p,dx1) = D((z, p0) -> FbpSecBif(z, p0), x, p, dx1)
d2FbpSecBif(x,p,dx1,dx2) = D((z, p0) -> d1FbpSecBif(z, p0, dx1), x, p, dx2)
d3FbpSecBif(x,p,dx1,dx2,dx3) = D((z, p0) -> d2FbpSecBif(z, p0, dx1, dx2), x, p, dx3)
jet = (FbpSecBif, dFbpSecBif, d2FbpSecBif, d3FbpSecBif)

# Gets a starting fixed point, sets options.
x0 = solve(ODEProblem(system,[1.,1.],(0.,2000),parameters),Rosenbrock23()).u[end];
opt_newton = NewtonPar(tol = 1e-9, verbose = false, maxIter = 20)
opts_br = ContinuationPar(pMin=pRange[1], pMax=pRange[2], maxSteps = 50000,
            dsmin = (pRange[2]-pRange[1])*1e-9, dsmax = (pRange[2]-pRange[1])*1e-3, ds = (pRange[2]-pRange[1])*1e-9, 
            detectBifurcation = 3, nev = 2, newtonOptions = opt_newton, nInversion = 4, 
            tolBisectionEigenvalue = 1e-8, dsminBisection = 1e-9);

# Creates the bifurcation diagram.
diagram = bifurcationdiagram(jet..., x0, parameters, (@lens _[p_idx]), 2, (args...) -> setproperties(opts_br; pMin=pRange[1], pMax=pRange[2], ds = (pRange[2]-pRange[1])*1e-9, dsmax = (pRange[2]-pRange[1])*1e-3, nInversion = 8, detectBifurcation = 3,dsminBisection =1e-18, tolBisectionEigenvalue=1e-11, maxBisectionSteps=20, newtonOptions = (@set opt_newton.verbose=false));)

now this generates a set of error messages, which seems very worrying:

##################################################
---> Automatic computation of bifurcation diagram

────────────────────────────────────────────────────────────────────────────────
--> New branch level = 2, dim(Kernel) = 1, code = 0, from bp #1 at p = 9.063030706988087
────────────────────────────────────────────────────────────────────────────────
--> New branch level = 2, dim(Kernel) = 1, code = 0, from bp #2 at p = 4.228100778475947
────────────────────────────────────────────────────────────────────────────────
--> New branch level = 2, dim(Kernel) = 2, code = 0, from bp #3 at p = 10.755971961392246
#####################################################
--> Hopf Normal form computation
--> Hopf bifurcation point is: SuperCritical
┌ Error: It seems the point is a Saddle-Node bifurcation. The normal form is (a = 0.0018436142964569494, b1 = 0.015793706727377507, b2 = 0.3066201972182164, b3 = 0.41398034761966024).
└ @ BifurcationKit /home/torkelloman/.julia/packages/BifurcationKit/CO5k1/src/NormalForms.jl:203
┌ Error: It seems the point is a Saddle-Node bifurcation. The normal form is (a = -0.007753919955655971, b1 = -0.002178682035144704, b2 = 0.0016086450861372025, b3 = -0.00015770032398444782).
└ @ BifurcationKit /home/torkelloman/.julia/packages/BifurcationKit/CO5k1/src/NormalForms.jl:203
┌ Error: MethodError(BifurcationKit.var"#multicontinuation##kw"(), ((nev = 2, issymmetric = false, usedeflation = false, Teigvec = Array{Float64,1}, ζs = nothing, δp = nothing, verbosedeflation = false, scaleζ = LinearAlgebra.norm, maxIterDeflation = 50, perturb = identity, plotSolution = BifurcationKit.var"#344#349"{BifurcationKit.var"#344#345#350"{Int64,GenericBifPoint{Float64,Array{Float64,1}},NamedTuple{(:current, :maxlevel),Tuple{Int64,Int64}},BifurcationKit.var"#342#347"{BifurcationKit.var"#342#343#348"},String,BifDiagNode{ContResult{Float64,Array{Complex{Float64},1},Array{Complex{Float64},2},GenericBifPoint{Float64,Array{Float64,1}},Nothing,Nothing,Array{Float64,1},Setfield.IndexLens{Tuple{Int64}}},Array{BifDiagNode,1}}}}(BifurcationKit.var"#344#345#350"{Int64,GenericBifPoint{Float64,Array{Float64,1}},NamedTuple{(:current, :maxlevel),Tuple{Int64,Int64}},BifurcationKit.var"#342#347"{BifurcationKit.var"#342#343#348"},String,BifDiagNode{ContResult{Float64,Array{Complex{Float64},1},Array{Complex{Float64},2},GenericBifPoint{Float64,Array{Float64,1}},Nothing,Nothing,Array{Float64,1},Setfield.IndexLens{Tuple{Int64}}},Array{BifDiagNode,1}}}(3, GenericBifPoint{Float64,Array{Float64,1}}
│   type: Symbol hopf
│   idx: Int64 1413
│   param: Float64 10.755971961392246
│   norm: Float64 35.945080086698646
│   printsol: Float64 35.945080086698646
│   x: Array{Float64}((2,)) [25.417009879598147, 25.417009879598147]
│   tau: BorderedArray{Array{Float64,1},Float64}
│   ind_ev: Int64 2
│   step: Int64 1412
│   status: Symbol converged
│   δ: Tuple{Int64,Int64}
│   precision: Float64 2.4654062258377962e-6
│   interval: Tuple{Float64,Float64}
│ , (current = 1, maxlevel = 2), BifurcationKit.var"#342#347"{BifurcationKit.var"#342#343#348"}(BifurcationKit.var"#342#343#348"()), "0", Bifurcation diagram. Root branch (level 1) has 0 children and is such that:
│ Branch number of points: 1947
│ Branch of Equilibrium
│ Bifurcation points:
│  (ind_ev = index of the bifurcating eigenvalue e.g. `br.eig[idx].eigenvals[ind_ev]`)
│ - #  1,    bp at p ≈ +9.06303071 ∈ (+9.05981691, +9.06303071), |δp|=3e-03, [converged], δ = ( 1,  0), step = 358, eigenelements in eig[359], ind_ev =   1
│ - #  2,    bp at p ≈ +4.22810078 ∈ (+4.22810078, +4.22810078), |δp|=3e-12, [converged], δ = ( 1,  0), step = 568, eigenelements in eig[569], ind_ev =   2
│ - #  3,  hopf at p ≈ +10.75597196 ∈ (+10.75596950, +10.75597196), |δp|=2e-06, [converged], δ = (-2, -2), step = 1412, eigenelements in eig[1413], ind_ev =   2
│ Fold points:
│ - #  1, fold at p ≈ 9.06308297 ∈ (9.06308297, 9.06308297), |δp|=-1e+00, [    guess], δ = ( 0,  0), step = 354, eigenelements in eig[354], ind_ev =   0
│ - #  2, fold at p ≈ 4.22810078 ∈ (4.22810078, 4.22810078), |δp|=-1e+00, [    guess], δ = ( 0,  0), step = 565, eigenelements in eig[565], ind_ev =   0
│ ))), BifurcationKit.multicontinuation, FbpSecBif, dFbpSecBif, Branch number of points: 1947
│ Branch of Equilibrium
│ Bifurcation points:
│  (ind_ev = index of the bifurcating eigenvalue e.g. `br.eig[idx].eigenvals[ind_ev]`)
│ - #  1,    bp at p ≈ +9.06303071 ∈ (+9.05981691, +9.06303071), |δp|=3e-03, [converged], δ = ( 1,  0), step = 358, eigenelements in eig[359], ind_ev =   1
│ - #  2,    bp at p ≈ +4.22810078 ∈ (+4.22810078, +4.22810078), |δp|=3e-12, [converged], δ = ( 1,  0), step = 568, eigenelements in eig[569], ind_ev =   2
│ - #  3,  hopf at p ≈ +10.75597196 ∈ (+10.75596950, +10.75597196), |δp|=2e-06, [converged], δ = (-2, -2), step = 1412, eigenelements in eig[1413], ind_ev =   2
│ Fold points:
│ - #  1, fold at p ≈ 9.06308297 ∈ (9.06308297, 9.06308297), |δp|=-1e+00, [    guess], δ = ( 0,  0), step = 354, eigenelements in eig[354], ind_ev =   0
│ - #  2, fold at p ≈ 4.22810078 ∈ (4.22810078, 4.22810078), |δp|=-1e+00, [    guess], δ = ( 0,  0), step = 565, eigenelements in eig[565], ind_ev =   0
│ , BifurcationKit.HopfBifPoint{Array{Float64,1},Float64,Float64,Array{Float64,1},Setfield.IndexLens{Tuple{Int64}},Array{Complex{Float64},1},Array{Complex{Float64},1},NamedTuple{(:a, :b),Tuple{Complex{Float64},Complex{Float64}}}}([25.417009879598147, 25.417009879598147], 10.755971961392246, 0.015804494505662976, [10.755971961392246, 9.0, 0.005, 2.0, 0.01, 20.0, 3.0, 0.05], (@lens _[1]), Complex{Float64}[-0.9571165934996202 + 0.0im, -0.0842773205962544 + 0.2771735190829004im], Complex{Float64}[-0.5213874537927115 - 0.16204406999169177im, 0.009147349963915332 + 1.8102101966238962im], (a = -0.004816693942145319 + 2.6835568552196435e-5im, b = 5.225986319528161e-5 + 0.0001546547428260476im), :SuperCritical), ContinuationPar{Float64,DefaultLS,DefaultEig{typeof(real)}}
│   dsmin: Float64 1.99e-8
│   dsmax: Float64 0.019899999999999998
│   ds: Float64 1.99e-8
│   theta: Float64 0.5
│   doArcLengthScaling: Bool false
│   gGoal: Float64 0.5
│   gMax: Float64 0.8
│   thetaMin: Float64 0.001
│   a: Float64 0.5
│   tangentFactorExponent: Float64 1.5
│   pMin: Float64 0.1
│   pMax: Float64 20.0
│   maxSteps: Int64 50000
│   finDiffEps: Float64 1.0e-9
│   newtonOptions: NewtonPar{Float64,DefaultLS,DefaultEig{typeof(real)}}
│   η: Float64 150.0
│   saveToFile: Bool false
│   saveSolEveryStep: Int64 0
│   nev: Int64 2
│   saveEigEveryStep: Int64 1
│   saveEigenvectors: Bool true
│   plotEveryStep: Int64 10
│   precisionStability: Float64 1.0e-10
│   detectFold: Bool true
│   detectBifurcation: Int64 3
│   dsminBisection: Float64 1.0e-18
│   nInversion: Int64 8
│   maxBisectionSteps: Int64 20
│   tolBisectionEigenvalue: Float64 1.0e-11
│   detectLoop: Bool false
│ ), 0x0000000000006dde)
└ @ BifurcationKit /home/torkelloman/.julia/packages/BifurcationKit/CO5k1/src/bifdiagram/BifurcationDiagram.jl:115

However, I am still actually able to plot the diagram, and it looks correct:

# Plots the diagram.
plot(diagram)

bif_example

Now the first query is what all these error messages are? What went wrong, but in such a way that the plot still turned out fine?

Next, are there options for modifying the plot diagram? The unstable/stable regions are not very easy to distinguish due to the line thickness and if you change the line thickness (e.g. to make it thicker, and easier to see) the difference disappears. Typically I use a different colour, or dashed lines, for the unstable states. Is there an easy way of doing this? Alternatively, is there a way of extracting the separate branches, as divided by the bifurcation points (I only managed to get the full path)?

Also, the bifurcation diagram labels the bifurcation dots weirdly doesn't look correct, the dots themself are missing in the label (and the dots in the diagram, especially the Hopf one, are very small).

Next, a question. It seems to me that I am setting most of the options twice:

opt_newton = NewtonPar(tol = 1e-9, verbose = false, maxIter = 20)
opts_br = ContinuationPar(pMin=pRange[1], pMax=pRange[2], maxSteps = 50000,
            dsmin = (pRange[2]-pRange[1])*1e-9, dsmax = (pRange[2]-pRange[1])*1e-3, ds = (pRange[2]-pRange[1])*1e-9, 
            detectBifurcation = 3, nev = 2, newtonOptions = opt_newton, nInversion = 4, 
            tolBisectionEigenvalue = 1e-8, dsminBisection = 1e-9);

diagram = bifurcationdiagram( ..., (args...) -> setproperties(opts_br; pMin=pRange[1], pMax=pRange[2], ds = (pRange[2]-pRange[1])*1e-9, dsmax = (pRange[2]-pRange[1])*1e-3, nInversion = 8, detectBifurcation = 3,dsminBisection =1e-18, tolBisectionEigenvalue=1e-11, maxBisectionSteps=20, newtonOptions = (@set opt_newton.verbose=false));)

Would I always want to do this, or could I go for something simpler?

Finally, when I created diagrams using continuation I didn't have to define so many derivatives as here:

D(f, x, p, dx) = ForwardDiff.derivative(t -> f(x .+ t .* dx, p), 0.)
FbpSecBif(u, p) = fun(u,p,0)
dFbpSecBif(x,p) =  ForwardDiff.jacobian( z-> FbpSecBif(z,p), x)
d1FbpSecBif(x,p,dx1) = D((z, p0) -> FbpSecBif(z, p0), x, p, dx1)
d2FbpSecBif(x,p,dx1,dx2) = D((z, p0) -> d1FbpSecBif(z, p0, dx1), x, p, dx2)
d3FbpSecBif(x,p,dx1,dx2,dx3) = D((z, p0) -> d2FbpSecBif(z, p0, dx1, dx2), x, p, dx3)
jet = (FbpSecBif, dFbpSecBif, d2FbpSecBif, d3FbpSecBif)

rather, in those cases, things worked OK with only the Jacobian (which is autogenerated by the @reaction_network macro). Why are these required here? Would it be possible to do without them (and what would the negatives of that be?).

`dTMvf` is an undefined name in the Neural mass equation example

https://rveltz.github.io/BifurcationKit.jl/dev/tutorialsODE/#Neural-mass-equation-(automatic)-1

In the first code block the variable dTMvf is used but nowhere defined so I couldn't run the example.

Going into the source of the of the doc page I saw that all documentation code blocks are just markdown julia blocks. Why is it that they are not documenter @example blocks? Documenter makes it extremely easy to just run code and put its output at the end. This include figures. Simply return the value of plot(br, plotfold=false, markersize=3, legend=:topleft) and it will be displayed.

If the docs are built via running code, this kindof guarantees that the examples are correct and also do what is expected. Furthermore, you do not have to update the output codeblocks, nor figures, if you want to update the example. It happens automatically.

no method matching ( ::DeflationProblem, SArray ... ) for static immutable arrays

Deflated newton works with SizedArray and MArray after patching LinearAlgebra.factorize however DeflationProblem fails to initialise when using SArray. SArray still works with the normal newton without deflation though. Here a minimal working example

using BifurcationKit,Parameters,StaticArrays,ForwardDiff
using BifurcationKit: DeflationOperator
using LinearAlgebra: dot

########################################################### setup
function F(x,params) where T<:Number
	@unpack k = params
	f = first(x)*first(k)
	F = similar(x,typeof(f))

	F[1] = k[2] + x[1] - x[1]^(k[1]+1)/(k[1]+1)
	F[2] = x[1] - x[2]
	return F
end

J(x,p) = ForwardDiff.jacobian( x->F(x,p), x )
p = ( k=[1.0,1.0], )

using LinearAlgebra: lu
import LinearAlgebra: factorize
factorize(A::StaticMatrix) = lu(A) # default factorization for StaticArrays

########################################################### normal arrays
x = [-1.0,42.0]
deflated_root = [Inf,Inf]
deflation = DeflationOperator(1.0, dot, 1.0, [ deflated_root ] )

u, = newton( F, J, x, p, NewtonPar() )                      # success
u, = newton( F, J, x, p, NewtonPar(), deflation )      # success

########################################################### static immutable arrays
x = @SVector[-1.0,42.0]
deflated_root = @SVector[Inf,Inf]
deflation = DeflationOperator(1.0, dot, 1.0, [ deflated_root ] )

u, = newton( F, J, x, p, NewtonPar() )                      # success
u, = newton( F, J, x, p, NewtonPar(), deflation )      # no method matching ( ::DeflationProblem, ::SArray, ... )

########################################################### static mutable arrays
x = @MVector[-1.0,42.0]
deflated_root = @MVector[Inf,Inf]
deflation = DeflationOperator(1.0, dot, 1.0, [ deflated_root ] )

u, = newton( F, J, x, p, NewtonPar() )                      # success
u, = newton( F, J, x, p, NewtonPar(), deflation )      # success

########################################################### sized mutable arrays
x = SizedVector{2}([-1.0,42.0])
deflated_root = SizedVector{2}([Inf,Inf])
deflation = DeflationOperator(1.0, dot, 1.0, [ deflated_root ] )

u, = newton( F, J, x, p, NewtonPar() )                      # success
u, = newton( F, J, x, p, NewtonPar(), deflation )      # success

Tutorial for finding periodic orbits of non-spatial systems

I have a couple of systems for which I have done bifurcation diagrams using bifurcation kit, and there are some Hopf bifurcation points. I would like to also plot the periodic orbits for these. When I check the tutorials, they all seem to be for spatial systems. Is there some tutorial or example for non-spatial systems, or is it plausible to make it work for the non-spatial case by looking at these?

incorrect automatic branch switching at simple branch point

This is a duplicate of the mistake on discourse posted by @sceptri

Hello, I have a simple 2D system and I tried continuation from simple branch points

using Plots, BifurcationKit, Setfield, ForwardDiff, Parameters
const BK = BifurcationKit

function f(x,p)
	@unpack a,g,γ,r,d = p
	M,C = x
	T = 1 - M - C
	dM = @. a * M * C  - (g * M) / (M + T) + γ * M * T
	dC = @. r * T * C - d * C - a * M * C
	return [dM, dC]
end

jet = BK.getJet(f; matrixfree = false)

# Starting variables
start = [0.2, 0.6]
# Default values for parameters
parameters = (a = 0.1, g = 0.01, γ = 0.8, r = 1., d = 0.44)

# Options for Newton-Krylov solver and continuation
optnewton = NewtonPar(tol = 1e-12, maxIter = 100)
options = ContinuationPar(pMin = 0.01, pMax = 1., dsmin = 0.0001, dsmax = 0.05, ds = 0.01,
	maxSteps = 100, nev = 30, nInversion = 8, detectBifurcation = 3, newtonOptions = optnewton,
	dsminBisection = 1e-7, maxBisectionSteps = 100)

# Solving for equilibrium from starting point using Newton-Krylon solver
solution, = newton(f, start, parameters, optnewton)
solution[2]

br, = continuation(f, solution, parameters, (@lens _.g), options, 
	plot = true, 
	recordFromSolution = (x,p) -> (C = x[2], M = x[1])
)

br1, = continuation(jet..., br, 1, setproperties(options;ds = 0.0001, maxSteps = 30), recordFromSolution = (x,p) -> (C = x[2], M = x[1]), bothside = true, plot = true)
br2, = continuation(jet..., br1, 2, setproperties(options; maxSteps = 20, ds = 0.0001), recordFromSolution = (x,p) -> (C = x[2], M = x[1]), bothside = true,plot = true)

plot(br, br1; branchlabel = ["br", "br1"], legend = :topright, ylims = (0,0.8), vars = (:param, :C))
plot(br, br1; branchlabel = ["br", "br1"], legend = :topright, ylims = (0,1), vars = (:param, :M))

The first continuation gives expected results - it picks the second branch and continues on it.
The second aBS should result in a horizontal line C = 0, but it stays on the same branch as it was.

Is there something I’m doing wrong or is it a bug in BifurcationKit.jl?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.