Skip to content

Commit 2a21a36

Browse files
authored
follow stable solutions/hysteresis (#17)
1 parent ae6a516 commit 2a21a36

File tree

4 files changed

+104
-1
lines changed

4 files changed

+104
-1
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "HarmonicBalance"
22
uuid = "e13b9ff6-59c3-11ec-14b1-f3d2cc6c135e"
33
authors = ["Jan Kosata <[email protected]>", "Javier del Pino <[email protected]>"]
4-
version = "0.4.1"
4+
version = "0.4.3"
55

66
[deps]
77
BijectiveHilbert = "91e7fc40-53cd-4118-bd19-d7fcd1de2a54"

src/HarmonicBalance.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ module HarmonicBalance
4444
include("saving.jl")
4545
include("plotting_static.jl")
4646
include("plotting_interactive.jl")
47+
include("hysteresis_sweep.jl")
4748

4849
include("modules/HC_wrapper.jl")
4950
using .HC_wrapper

src/hysteresis_sweep.jl

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
export plot_1D_solutions_branch
2+
3+
4+
"""Calculate distance between a given state and a stable branch"""
5+
function _closest_branch_index(res::Result,state::Vector{Float64},index::Int64)
6+
#search only among stable solutions
7+
physical = filter_solutions.(
8+
filter_solutions.(
9+
res.solutions, res.classes["physical"]),res.classes["stable"])
10+
11+
steadystates = reduce(hcat,physical[index]) #change structure for easier indexing
12+
distances = vec(sum(abs2.(steadystates .- state),dims=1))
13+
return argmin(replace!(distances, NaN=>Inf))
14+
end
15+
16+
17+
"""Return the indexes and values following stable branches along a 1D sweep.
18+
When a no stable solutions are found (e.g. in a bifurcation), the next stable solution is calculated by time evolving the previous solution (quench).
19+
20+
Keyword arguments
21+
- `y`: Dependent variable expression (parsed into Symbolics.jl) to evaluate the followed solution branches on .
22+
- `sweep`: Direction for the sweeping of solutions. A `right` (`left`) sweep proceeds from the first (last) solution, ordered as the sweeping parameter.
23+
- `tf`: time to reach steady
24+
- `ϵ`: small random perturbation applied to quenched solution, in a bifurcation in order to favour convergence in cases where multiple solutions are identically accessible (e.g. symmetry breaking into two equal amplitude states)
25+
"""
26+
function follow_branch(starting_branch::Int64,res::Result; y="sqrt(u1^2+v1^2)",sweep="right",tf=10000=1E-4)
27+
sweep_directions = ["left", "right"]
28+
sweep sweep_directions || error("Only the following (1D) sweeping directions are allowed: ", sweep_directions)
29+
30+
#filter stable solutions
31+
Y = transform_solutions(res, y)
32+
Ys = filter_solutions.(filter_solutions.(Y, res.classes["physical"]),res.classes["stable"]);
33+
Ys = real.(reduce(hcat,Ys)) #facilitate indexing. Ys is an array (length(sweep))x(#solutions)
34+
35+
followed_branch = zeros(Int64,length(Y)) #followed branch indexes
36+
followed_branch[1] = starting_branch
37+
38+
if sweep == "left"
39+
Ys = reverse(Ys,dims=2)
40+
end
41+
42+
p1 = collect(keys(res.swept_parameters))[1] #parameter values
43+
44+
for i in 2:size(Ys)[2]
45+
s = Ys[followed_branch[i-1],i] #solution amplitude in the current branch and current parameter index
46+
if !isnan(s) #the solution is not unstable or unphysical
47+
followed_branch[i] = followed_branch[i-1]
48+
else #bifurcation found
49+
if sweep == "right"
50+
next_index = i
51+
elseif sweep == "left" #reverse order of iterators
52+
next_index = length(Y)-i+1
53+
end
54+
55+
# create a synthetic starting point out of an unphysical solution: quench and time evolve
56+
#the actual solution is complex there, i.e. non physical. Take real part for the quench.
57+
sol_dict = get_single_solution(res, branch=followed_branch[i-1], index= next_index)
58+
print("bifurcation @ ", p1 ," = ", real(sol_dict[p1])," ")
59+
sol_dict = Dict(zip(keys(sol_dict),real.(values(sol_dict)) .+ 0.0im .+ ϵ*rand(length(values(sol_dict)))))
60+
problem_t = TimeEvolution.ODEProblem(res.problem.eom, steady_solution=sol_dict, timespan=(0,tf))
61+
res_t = TimeEvolution.solve(problem_t,saveat=tf)
62+
63+
followed_branch[i] = _closest_branch_index(res,res_t.u[end],next_index) #closest branch to final state
64+
65+
print("switched branch ", followed_branch[i-1] ," -> ", followed_branch[i],"\n")
66+
end
67+
end
68+
69+
if sweep == "left"
70+
Ys = reverse(Ys,dims=2)
71+
followed_branch = reverse(followed_branch)
72+
end
73+
74+
return followed_branch,Ys
75+
end
76+
77+
78+
"""1D plot with the followed branch highlighted"""
79+
function plot_1D_solutions_branch(starting_branch::Int64,res::Result; x::String, y::String, sweep="right",tf=10000=1E-4, xscale="linear",yscale="linear",plot_only=["physical"],marker_classification="stable",filename=nothing,kwargs...)
80+
plot_1D_solutions(res; x=x, y=y, xscale=xscale,yscale=yscale,
81+
plot_only=plot_only,marker_classification=marker_classification,filename=filename,kwargs...)
82+
83+
followed_branch,Ys = follow_branch(starting_branch,res,y=y,sweep=sweep,tf=tf,ϵ=ϵ)
84+
if sweep == "left"
85+
m = "<"
86+
elseif sweep == "right"
87+
m = ">"
88+
end
89+
90+
Y_followed = [Ys[branch,param_idx] for (param_idx,branch) in enumerate(followed_branch)]
91+
92+
lines = plot(transform_solutions(res, x),Y_followed,c="k",marker=m; kwargs...)
93+
94+
#extract plotted data and return it
95+
xdata,ydata = [line.get_xdata() for line in lines], [line.get_ydata() for line in lines]
96+
markers = [line.get_marker() for line in lines]
97+
save_dict= Dict(string(x) => xdata,string(y)=>ydata,"markers"=>markers)
98+
99+
!isnothing(filename) ? JLD2.save(_jld2_name(filename), save_dict) : nothing
100+
return save_dict
101+
end

src/plotting_static.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ function plot_1D_solutions(res::Result; x::String, y::String, xscale="linear",ys
220220
Nb = sum(.~ignored_idx) #number of branches
221221

222222
palette = plt.rcParams["axes.prop_cycle"].by_key()["color"] # the currently used palette (to match legend and plot colours)
223+
palette = repeat(plt.rcParams["axes.prop_cycle"].by_key()["color"],Nb ÷ length(palette) + 1)[1:Nb]
223224

224225
leg_classes = [plt.Line2D([0], [0]; marker="o", color="w", label=sol_type, markerfacecolor="k", kwargs...),
225226
plt.Line2D([0], [0]; marker="X", color="w", label=not_sol_type,markerfacecolor="k", kwargs...)]

0 commit comments

Comments
 (0)