Finding the staedy states of a Duffing oscillator
Here we show the workflow of HarmonicBalance.jl on a simple example - the driven Duffing oscillator. The equation of motion for the displacement
In general, there is no analytical solution to the differential equation. Fortunately, some harmonics are more important than others. By truncating the infinite-dimensional Fourier space to a set of judiciously chosen harmonics, we may obtain a soluble system. For the Duffing resonator, we can well try to only consider the drive frequency
which constraints the spectrum of
First we need to declare the symbolic variables (the excellent Symbolics.jl is used here).
using HarmonicBalance
@variables α ω ω0 F γ t x(t) # declare constant variables and a function x(t)
Next, we have to input the equations of motion. This will be stored as a DifferentialEquation
. The input needs to specify that only x
is a mathematical variable, the other symbols are parameters:
diff_eq = DifferentialEquation(d(x,t,2) + ω0^2*x + α*x^3 + γ*d(x,t) ~ F*cos(ω*t), x)
System of 1 differential equations
Variables: x(t)
Harmonic ansatz: x(t) => ;
Differential(t)(Differential(t)(x(t))) + Differential(t)(x(t))*γ + x(t)*(ω0^2) + (x(t)^3)*α ~ F*cos(t*ω)
One harmonic
The harmonic ansatz needs to be specified now – we expand x
in a single frequency
add_harmonic!(diff_eq, x, ω) # specify the ansatz x = u(T) cos(ωt) + v(T) sin(ωt)
The object diff_eq
now contains all the necessary information to convert the differential equation to the algebraic harmonic equations (coupled polynomials in
harmonic_eq = get_harmonic_equations(diff_eq)
A set of 2 harmonic equations
Variables: u1(T), v1(T)
Parameters: ω, α, γ, ω0, F
Harmonic ansatz:
x(t) = u1(T)*cos(ωt) + v1(T)*sin(ωt)
Harmonic equations:
(2//1)*Differential(T)(v1(T))*ω + Differential(T)(u1(T))*γ - u1(T)*(ω^2) + u1(T)*(ω0^2) + v1(T)*γ*ω + (3//4)*(u1(T)^3)*α + (3//4)*u1(T)*(v1(T)^2)*α ~ F
Differential(T)(v1(T))*γ - (2//1)*Differential(T)(u1(T))*ω - u1(T)*γ*ω - v1(T)*(ω^2) + v1(T)*(ω0^2) + (3//4)*(u1(T)^2)*v1(T)*α + (3//4)*(v1(T)^3)*α ~ 0//1
The variables u1
and v1
were declared automatically to construct the harmonic ansatz. The slow time variable T
describes variation of the quadratures on timescales much slower than ω
. For a steady state, all derivatives w.r.t T
vanish, leaving only algebraic equations to be solved.
We are ready to start plugging in numbers! Let us find steady states by solving harmonic_eq
for numerical parameters. Homotopy continuation is especially suited to solving over a range of parameter values. Here we will solve over a range of driving frequencies ω
– these are stored as Pairs{Sym, Vector{Float64}}
:
varied = ω => range(0.9, 1.2, 100); # range of parameter values
ω => 0.9:0.0030303030303030303:1.2
The other parameters we be fixed – these are declared as Pairs{Sym, Float64}
pairs:
fixed = (α => 1., ω0 => 1.0, F => 0.01, γ => 0.01); # fixed parameters
(α => 1.0, ω0 => 1.0, F => 0.01, γ => 0.01)
Now everything is ready to crank the handle. get_steady_states
solves our harmonic_eq
using the varied and fixed parameters:
result = get_steady_states(harmonic_eq, varied, fixed)
A steady state result for 100 parameter points
Solution branches: 3
of which real: 3
of which stable: 2
Classes: stable, physical, Hopf, binary_labels
The algorithm has found 3 solution branches in total (out of the hypothetically admissible
We now want to visualize the results. Here we plot the solution amplitude,
plot(result, "sqrt(u1^2 + v1^2)")
This is the expected response curve for the Duffing equation.
Using multiple harmonics
In the above section, we truncated the Fourier space to a single harmonic
for small
which gives a response of the form
We argued that frequency conversion takes place, to first order from
Note that this is not a perturbative treatment! The harmonics
add_harmonic!(diff_eq, x, [ω, 3ω]) # specify the two-harmonics ansatz
harmonic_eq = get_harmonic_equations(diff_eq)
A set of 4 harmonic equations
Variables: u1(T), v1(T), u2(T), v2(T)
Parameters: ω, ω0, γ, α, F
Harmonic ansatz:
x(t) = u1(T)*cos(ωt) + v1(T)*sin(ωt) + u2(T)*cos(3ωt) + v2(T)*sin(3ωt)
Harmonic equations:
(2//1)*Differential(T)(v1(T))*ω + Differential(T)(u1(T))*γ - u1(T)*(ω^2) + u1(T)*(ω0^2) + v1(T)*γ*ω + (3//4)*(u1(T)^3)*α + (3//4)*(u1(T)^2)*u2(T)*α + (3//2)*u1(T)*(v2(T)^2)*α + (3//2)*u1(T)*v2(T)*v1(T)*α + (3//4)*u1(T)*(v1(T)^2)*α + (3//2)*u1(T)*(u2(T)^2)*α - (3//4)*(v1(T)^2)*u2(T)*α ~ F
Differential(T)(v1(T))*γ - (2//1)*Differential(T)(u1(T))*ω - u1(T)*γ*ω - v1(T)*(ω^2) + v1(T)*(ω0^2) + (3//4)*(u1(T)^2)*v2(T)*α + (3//4)*(u1(T)^2)*v1(T)*α - (3//2)*u1(T)*v1(T)*u2(T)*α + (3//2)*(v2(T)^2)*v1(T)*α - (3//4)*v2(T)*(v1(T)^2)*α + (3//4)*(v1(T)^3)*α + (3//2)*v1(T)*(u2(T)^2)*α ~ 0//1
Differential(T)(u2(T))*γ + (6//1)*Differential(T)(v2(T))*ω + (3//1)*v2(T)*γ*ω - (9//1)*u2(T)*(ω^2) + u2(T)*(ω0^2) + (1//4)*(u1(T)^3)*α + (3//2)*(u1(T)^2)*u2(T)*α - (3//4)*u1(T)*(v1(T)^2)*α + (3//4)*(v2(T)^2)*u2(T)*α + (3//2)*(v1(T)^2)*u2(T)*α + (3//4)*(u2(T)^3)*α ~ 0//1
-(6//1)*Differential(T)(u2(T))*ω + Differential(T)(v2(T))*γ - (9//1)*v2(T)*(ω^2) + v2(T)*(ω0^2) - (3//1)*u2(T)*γ*ω + (3//2)*(u1(T)^2)*v2(T)*α + (3//4)*(u1(T)^2)*v1(T)*α + (3//4)*(v2(T)^3)*α + (3//2)*v2(T)*(v1(T)^2)*α + (3//4)*v2(T)*(u2(T)^2)*α - (1//4)*(v1(T)^3)*α ~ 0//1
The variables u1
, v1
now encode ω
and u2
, v2
encode 3ω
. We see this system is much harder to solve as we now have 4 harmonic variables, resulting in 4 coupled cubic equations. A maximum of
result = get_steady_states(harmonic_eq, varied, fixed)
plot(result, "sqrt(u1^2 + v1^2)")
For the above parameters (where a perturbative treatment would have been reasonable), the principal response at
p1=plot(result, "sqrt(u1^2 + v1^2)", legend=false)
p2=plot(result, "sqrt(u2^2 + v2^2)")
plot(p1, p2)
The non-perturbative nature of the ansatz allows us to capture some behaviour which is not a mere extension of the usual single-harmonic Duffing response. Suppose we drive a strongly nonlinear resonator at frequency
fixed = (α => 10., ω0 => 3, F => 5, γ=>0.01) # fixed parameters
varied = ω => range(0.9, 1.4, 100) # range of parameter values
result = get_steady_states(harmonic_eq, varied, fixed)
A steady state result for 100 parameter points
Solution branches: 7
of which real: 2
of which stable: 2
Classes: stable, physical, Hopf, binary_labels
Although 9 branches were found in total, only 3 remain physical (real-valued). Let us visualise the amplitudes corresponding to the two harmonics,
p1 = plot(result, "sqrt(u1^2 + v1^2)", legend=false)
p2 = plot(result, "sqrt(u2^2 + v2^2)")
plot(p1, p2)
The contributions of