Skip to content

Commit d421519

Browse files
authored
Limit cycle classes (#55)
* fixed loading for version changes * ordering revamped to have fixed order * limit cycle unique classification * 2d result test added
1 parent 925c757 commit d421519

File tree

10 files changed

+71
-40
lines changed

10 files changed

+71
-40
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.6.1"
4+
version = "0.6.2"
55

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

src/classification.jl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,19 @@ classify_solutions!(res, "sqrt(u1^2 + v1^2) > 1.0" , "large_amplitude")
2121
2222
"""
2323
function classify_solutions!(res::Result, condition::String, name::String; physical=true)
24+
values = classify_solutions(res, condition; physical=physical)
25+
res.classes[name] = values
26+
end
27+
28+
29+
function classify_solutions(res::Result, condition::String; physical=true)
2430
expr = Num(eval(Meta.parse(condition)))
2531
function cond_func(s::OrderedDict, res)
2632
physical && !is_physical(s, res) && return false
2733
s = [key => real(s[key]) for key in keys(s)] # make values real
2834
Bool(substitute_all(expr, s).val)
2935
end
30-
values = classify_solutions(res, cond_func)
31-
res.classes[name] = values
36+
classify_solutions(res, cond_func)
3237
end
3338

3439

@@ -43,7 +48,7 @@ end
4348
"Return an array of booleans classifying the solution in `res`
4449
according to `f` (`f` takes a solution dictionary, return a boolean)"
4550
function classify_solutions(res::Result, f::Function)
46-
values = similar(res.solutions, Vector{Bool})
51+
values = similar(res.solutions, BitVector)
4752
for (idx, soln) in enumerate(res.solutions)
4853
values[idx] = [f(get_single_solution(res, index=idx, branch=b), res) for b in 1:length(soln)]
4954
end

src/modules/LimitCycles.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ using Symbolics
55
using DocStringExtensions
66

77
include("LimitCycles/Hopf.jl")
8+
include("LimitCycles/analysis.jl")
89

910
end

src/modules/LimitCycles/Hopf.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export replace_Hopf_variable!
44

55
using HarmonicBalance: is_rearranged, rearrange_standard, _remove_brackets
66
using HarmonicBalance.LinearResponse: get_implicit_Jacobian, get_Jacobian
7-
import HarmonicBalance: is_stable, is_physical, is_Hopf_unstable, order_branches!, classify_binaries!
7+
import HarmonicBalance: is_stable, is_physical, is_Hopf_unstable, order_branches!, classify_binaries!, find_branch_order
88
#import HarmonicBalance.get_steady_states; export get_steady_states
99

1010

@@ -97,7 +97,13 @@ function _classify_limit_cycles!(res::Result, Δω::Num)
9797
res.classes[c][idx] .*= abs.(getindex.(res.solutions[idx], Δω_idx)) .> 1E-10
9898
end
9999

100-
order_branches!(res, ["physical", "stable", "Hopf"]) # shuffle the branches to have relevant ones first
100+
classify_unique!(res, Δω)
101+
102+
unique_stable = find_branch_order(map(.*, res.classes["stable"], res.classes["unique"]))
103+
104+
# branches which are unique but never stable
105+
unique_unstable = setdiff(find_branch_order(map(.*, res.classes["unique"], res.classes["physical"])), unique_stable)
106+
order_branches!(res, vcat(unique_stable, unique_unstable)) # shuffle to have relevant branches first
101107
end
102108

103109

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
3+
import HarmonicBalance.classify_solutions
4+
5+
function classify_unique!(res::Result, Δω; class_name="unique")
6+
7+
# 1st degeneracy: arbitrary sign of Δω
8+
c1 = classify_solutions(res, string(Δω) * ">= 0")
9+
10+
# 2nd degeneracy: ambiguity in the fixed phase, manifests as the sign of var
11+
var = HarmonicBalance._remove_brackets(get_Hopf_variables(res.problem.eom, Δω)[1])
12+
c2 = classify_solutions(res, string(var) * ">= 0")
13+
14+
res.classes[class_name] = map(.*, c1, c2)
15+
end

src/saving.jl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ function save(filename, x::Result)
1919
x_nofunc = deepcopy(x)
2020

2121
# compiled functions cause problems in saving: ignore J now, compile when loading
22-
null_J() = 0
23-
x_nofunc.jacobian = null_J
22+
x_nofunc.jacobian = 0
2423
JLD2.save(_jld2_name(filename), Dict("object" => x_nofunc))
2524
end
2625

@@ -37,7 +36,6 @@ reinstated in the `HarmonicBalance` namespace.
3736
"""
3837
function load(filename)
3938
loaded = JLD2.load(filename)
40-
4139
if haskey(loaded,"object") #otherwise save data is from a plot
4240
loaded = loaded["object"]
4341

src/solve_homotopy.jl

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ function _classify_default!(result)
120120
classify_solutions!(result, is_physical, "physical")
121121
classify_solutions!(result, is_stable, "stable")
122122
classify_solutions!(result, is_Hopf_unstable, "Hopf")
123-
order_branches!(result, ["physical", "stable", "Hopf"]) # shuffle the branches to have relevant ones first
123+
order_branches!(result, ["physical", "stable"]) # shuffle the branches to have relevant ones first
124124
classify_binaries!(result) # assign binaries to solutions depending on which branches are stable
125125
end
126126

@@ -160,37 +160,38 @@ function compile_matrix(matrix, variables, fixed_parameters)
160160
end
161161

162162

163-
"Order the solution branches in `res` such that close classified positively by `class` are first."
164-
function order_branches!(res::Result, class::String)
165-
indices = findall( x-> x==1, any.(classify_branch(res, class)))
166-
order = cat(indices, setdiff(1:length(res.solutions[1]), indices), dims=1) # permutation of indices which puts stable branches first
167-
reorder_solutions!(res, order)
163+
"Find a branch order according `classification`. Place branches where true occurs earlier first."
164+
function find_branch_order(classification::Vector{BitVector})
165+
branches = [getindex.(classification, k) for k in 1:length(classification[1])] # array of branches
166+
indices = replace(findfirst.(branches), nothing => Inf)
167+
negative = findall(x -> x == Inf, indices) # branches not true anywhere - leave out
168+
order = setdiff(sortperm(indices), negative)
168169
end
169170

170-
"Order the solution branches in `res` such that close classified positively by `classes` are first.
171-
The order of classes has descending precedence."
172-
function order_branches!(s::Result, classes::Vector{String})
173-
for class in reverse(classes)
174-
order_branches!(s, class)
175-
end
171+
find_branch_order(classification::Array) = collect(1:length(classification[1])) # no ordering for >1D
172+
173+
"Order the solution branches in `res` such that close classified positively by `classes` are first."
174+
function order_branches!(res::Result, classes::Vector{String})
175+
for class in classes
176+
order_branches!(res, find_branch_order(res.classes[class]))
177+
end
176178
end
177179

180+
order_branches!(res::Result, class::String) = order_branches!(res, [class])
181+
178182
"Reorder the solutions in `res` to match the index permutation `order`."
179-
function reorder_solutions!(res::Result, order::Vector{Int64})
180-
res.solutions = reorder_array(res.solutions, order)
183+
function order_branches!(res::Result, order::Vector{Int64})
184+
res.solutions = _reorder_nested(res.solutions, order)
181185
for key in keys(res.classes)
182-
res.classes[key] = reorder_array(res.classes[key], order)
186+
res.classes[key] = _reorder_nested(res.classes[key], order)
183187
end
184188
end
185189

186-
"Reorder EACH ELEMENT of `a` to match the index permutation `order`."
187-
function reorder_array(a::Array, order::Vector{Int64})
188-
a[1] isa Array || return a
189-
new_array = similar(a)
190-
for (i,el) in enumerate(a)
191-
new_array[i] = el[order]
192-
end
193-
return new_array
190+
"Reorder EACH ELEMENT of `a` to match the index permutation `order`. If length(order) < length(array), the remanining positions are kept."
191+
function _reorder_nested(a::Array, order::Vector{Int64})
192+
a[1] isa Union{Array, BitVector} || return a
193+
order = length(order) == length(a) ? order : vcat(order, setdiff(1:length(a[1]), order)) # pad if needed
194+
new_array = [el[order] for el in a]
194195
end
195196

196197

src/types.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ mutable struct Result
223223
If problem.jacobian is a symbolic matrix, this holds a compiled function.
224224
If problem.jacobian was `false`, this holds a function that rearranges the equations to find J
225225
only after numerical values are inserted (preferable in cases where the symbolic J would be very large)."
226-
jacobian::Function
226+
jacobian::Union{Function, Int64}
227227

228228
Result(sol,swept, fixed, problem, classes, J) = new(sol, swept, fixed, problem, classes, J)
229229
Result(sol,swept, fixed, problem, classes) = new(sol, swept, fixed, problem, classes)

test/parametron.jl

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,24 @@ using Test
77

88
natural_equation = d(d(x,t),t) + γ*d(x,t) + Ω^2*(1-λ*cos(2*ω*t+ψ))*x + α * x^3 +η *d(x,t) * x^2
99
forces = F*cos*t+θ)
10-
dEOM = HarmonicBalance.DifferentialEquation(natural_equation + forces, x)
11-
HarmonicBalance.add_harmonic!(dEOM, x, ω)
12-
harmonic_eq = HarmonicBalance.get_harmonic_equations(dEOM, slow_time=T, fast_time=t);
10+
dEOM = DifferentialEquation(natural_equation + forces, x)
11+
add_harmonic!(dEOM, x, ω)
12+
harmonic_eq = get_harmonic_equations(dEOM, slow_time=T, fast_time=t);
1313
p = HarmonicBalance.Problem(harmonic_eq);
1414

1515

16-
fixed_parameters ==> 1.0=> 1E-2, λ => 5E-2, F => 1E-3, α => 1., η=>0.3, θ => 0, ψ => 0)
17-
sweep = ω => LinRange(0.9, 1.1, 100)
18-
soln = HarmonicBalance.get_steady_states(p, sweep, fixed_parameters)
16+
fixed ==> 1.0=> 1E-2, λ => 5E-2, F => 1E-3, α => 1., η=>0.3, θ => 0, ψ => 0)
17+
varied = ω => LinRange(0.9, 1.1, 100)
18+
res = get_steady_states(p, varied, fixed)
1919

2020
# save the result, try and load in the next step
2121
#current_path = @__DIR__
22-
#HarmonicBalance.save(current_path * "/parametron_result.jld2", soln)
22+
#HarmonicBalance.save(current_path * "/parametron_result.jld2", res)
23+
24+
# try to run a 2D calculation
25+
fixed ==> 1.0=> 1E-2, F => 1E-3, α => 1., η=>0.3, θ => 0, ψ => 0)
26+
varied ==> LinRange(0.9, 1.1, 10), λ => LinRange(0.01, 0.05, 10))
27+
res = get_steady_states(p, varied, fixed)
2328

2429

2530
###

test/parametron_result.jld2

21.9 KB
Binary file not shown.

0 commit comments

Comments
 (0)