Duffing Oscillator Solution

In [1]:
%matplotlib inline
%load_ext autoreload
%autoreload 2
import scipy as sp
import numpy as np
import matplotlib.pyplot as plt
import mousai as ms
from scipy import pi, sin
In [3]:
# Test that all is working.
# f_tol adjusts accuracy. This is smaller than reasonable, but illustrative of usage.
t, x, e, amps, phases = ms.hb_time(ms.duff_osc, sp.array([[0,1,-1]]), .7, f_tol = 1e-12)
print('Equation errors (should be zero): ',e)
print('Constant term of FFT of signal should be zero: ', ms.fftp.fft(x)[0,0])
Equation errors (should be zero):  [[ 1.11022302e-15 -1.68753900e-14  1.33226763e-15]]
Constant term of FFT of signal should be zero:  (-0.10771053458129715+0j)
In [3]:
# Using more harmonics.
t, x, e, amps, phases = ms.hb_time(ms.duff_osc, x0 = sp.array([[0,1,-1]]), omega = .7, num_harmonics= 7)
print('Equation errors (should be zero): ', e)
print('Constant term of FFT of signal should be zero: ', ms.fftp.fft(x)[0,0])
Equation errors (should be zero):  [[  2.04616314e-08  -1.11916120e-08  -4.32641484e-09  -8.27785285e-10
   -1.06717036e-08  -2.49418783e-07  -6.82304595e-07  -1.59994936e-07
   -3.88299430e-11   9.85229565e-09   1.56362223e-09  -1.71778813e-10
    7.32659703e-08   4.91827027e-07   5.65568172e-07]]
Constant term of FFT of signal should be zero:  (7.04098108967e-06+0j)

Sometimes we can improve just by restarting from the prior end point. Sometimes, we just think it’s improved.

In [4]:
t, x, e, amps, phases = ms.hb_time(ms.duff_osc, x0 = x, omega = .7, num_harmonics= 7)
print('Errors: ', e)
print('Constant term of FFT of signal should be zero: ', ms.fftp.fft(x)[0,0])
Errors:  [[  7.29971639e-15   2.38697950e-15   1.02140518e-14   1.55431223e-14
    2.88657986e-14  -4.44089210e-16  -1.30007116e-13  -1.18793864e-14
   -5.61399885e-15  -1.65978342e-14  -2.33146835e-15  -1.99840144e-15
   -2.56461519e-14   7.13873405e-14   1.18960397e-13]]
Constant term of FFT of signal should be zero:  (7.08481329931e-06+0j)
/Users/jslater/anaconda3/lib/python3.6/site-packages/scipy/optimize/nonlin.py:474: RuntimeWarning: invalid value encountered in double_scalars
  and dx_norm/self.x_rtol <= x_norm))
In [5]:
# Let's get a smoother response
time, xc = ms.time_history(t,x)
plt.plot(time,xc.T,t,x.T,'*')
plt.grid(True)
print('The average for this problem is known to be zero, we got', sp.average(x))
The average for this problem is known to be zero, we got 4.72320886587e-07
../../_images/tutorial_demos_Duffing_6_1.png
In [26]:
def duff_osc2(x, v, params):
    omega = params['omega']
    t = params['cur_time']
    return np.array([[-x-.1*x**3-.1*v+1*sin(omega*t)]])
In [27]:
t, x, e, amps, phases = ms.hb_time(duff_osc2, sp.array([[0,1,-1]]), .7, num_harmonics=5)

print(x,e)
print('Constant term of FFT of signal should be zero: ', ms.fftp.fft(x)[0,0])
time, xc = ms.time_history(t,x)

plt.plot(time, xc.T, t, x.T, '*')
plt.grid(True)
[[-0.1854827   0.74408211  1.27869989  1.42305317  1.22766696  0.6400849
  -0.31045409 -1.06650367 -1.39410353 -1.36873107 -0.98898229]] [[  3.18347107e-09  -5.75875847e-10  -6.27391217e-09  -7.88667857e-08
   -2.08381912e-07  -1.90325802e-08   1.15335749e-09   3.95564803e-10
    2.19995009e-08   1.48110614e-07   1.50174076e-07]]
Constant term of FFT of signal should be zero:  (-0.000670319513207+0j)
../../_images/tutorial_demos_Duffing_8_1.png
In [28]:
omega = sp.linspace(0.1,3,200)+1/200
amp = sp.zeros_like(omega)
x = sp.array([[0,-1,1,0,0]])
for i, freq in enumerate(omega):
    #print(i,freq,x)
    try:
        t, x, e, amps, phases = ms.hb_time(duff_osc2, x, freq)#, f_tol = 1e-10)#, callback = resid)
        amp[i]=amps[0]
    except:
        amp[i] = sp.nan
plt.plot(omega, amp)
Out[28]:
[<matplotlib.lines.Line2D at 0x10d892a20>]
../../_images/tutorial_demos_Duffing_9_1.png

The break is an indicative of a break in the branch and is actually a result of the solution being unstable. Not the system, but the solution. By that we mean that while this is considered a solution, it isn’t one that will actually continue in a real situation and another solution will necessarily be found.

A simple solution is to change the starting guess to be away from the solution and see if it finds another one. Indeed that happens.

In [29]:
omega = sp.linspace(0.1,3,90)+1/200
amp = sp.zeros_like(omega)
x = sp.array([[0,-1,1]])
for i, freq in enumerate(omega):
    #print(i,freq,x)
    #print(sp.average(x))
    x = x-sp.average(x)
    try:
        t, x, e, amps, phases = ms.hb_time(duff_osc2, x, freq, num_harmonics=4, verbose = False, f_tol = 1e-6)#, callback = resid)
        amp[i]=amps[0]
    except:
        amp[i] = sp.nan
plt.plot(omega, amp)
Out[29]:
[<matplotlib.lines.Line2D at 0x10d766d68>]
../../_images/tutorial_demos_Duffing_11_1.png
In [30]:
omegal = sp.arange(3,.03,-1/200)+1/200
ampl = sp.zeros_like(omegal)
x = sp.array([[0,-1,1]])
for i, freq in enumerate(omegal):
    # Here we try to obtain solutions, but if they don't work,
    # we ignore them by inserting `np.nan` values.
    x = x-sp.average(x)
    try:
        t, x, e, amps, phases = ms.hb_time(duff_osc2, x, freq, num_harmonics=4, f_tol = 1e-6)#, callback = resid)
        ampl[i]=amps[0]
    except:
        ampl[i] = sp.nan
plt.plot(omegal, ampl)
Out[30]:
[<matplotlib.lines.Line2D at 0x10d581278>]
../../_images/tutorial_demos_Duffing_12_1.png
In [31]:
plt.plot(omegal,ampl)
plt.plot(omega,amp)
#plt.axis([0,3, 0, 10.5])
Out[31]:
[<matplotlib.lines.Line2D at 0x10d6ba208>]
../../_images/tutorial_demos_Duffing_13_1.png
In [32]:
from scipy.optimize import newton_krylov
In [33]:
def duff_amp_resid(a):
    return (mu**2+(sigma-3/8*alpha/omega_0*a**2)**2)*a**2-(k**2)/4/omega_0**2
In [34]:
mu = 0.05 # damping
k = 1 # excitation amplitude
sigma = -0.9 #detuning
omega_0 = 1 # driving frequency
alpha = 0.1 # cubic coefficient
In [35]:
newton_krylov(duff_amp_resid,-.1)
Out[35]:
array(-0.5478691201747141)
In [36]:
sigmas = sp.linspace(-1,3,200)
amplitudes = sp.zeros_like(sigmas)
x = newton_krylov(duff_amp_resid,1)
for i, sigma in enumerate(sigmas):
    try:
        amplitudes[i] = newton_krylov(duff_amp_resid,x)
        x = amplitudes[i]
    except:
        amplitudes[i] = newton_krylov(duff_amp_resid,0)
        x = amplitudes[i]

plt.plot(sigmas,amplitudes)
Out[36]:
[<matplotlib.lines.Line2D at 0x10da1e2e8>]
../../_images/tutorial_demos_Duffing_18_1.png
In [37]:
sigmas = sp.linspace(-1,3,200)
sigmasr = sigmas[::-1]
amplitudesr = sp.zeros_like(sigmas)
x = newton_krylov(duff_amp_resid,3)
for i, sigma in enumerate(sigmasr):
    try:
        amplitudesr[i] = newton_krylov(duff_amp_resid,x)
        x = amplitudesr[i]
    except:
        amplitudesr[i] = sp.nan#newton_krylov(duff_amp_resid,0)
        x = amplitudesr[i]


plt.plot(sigmasr,amplitudesr)
/Users/jslater/anaconda/lib/python3.6/site-packages/scipy/optimize/nonlin.py:474: RuntimeWarning: invalid value encountered in double_scalars
  and dx_norm/self.x_rtol <= x_norm))
Out[37]:
[<matplotlib.lines.Line2D at 0x10dbea828>]
../../_images/tutorial_demos_Duffing_19_2.png
In [38]:
plt.plot(sigmasr,amplitudesr)
plt.plot(sigmas,amplitudes)
Out[38]:
[<matplotlib.lines.Line2D at 0x10d862160>]
../../_images/tutorial_demos_Duffing_20_1.png

Using lambda functions

As an aside, we can use a lambda function to solve a simple equation without much hassle. For example, \(\ddot{x} + 0.1\dot{x}+ x + 0.1 x^3 = \sin(0.7t)\)

In [14]:
def duff_osc2(x, v, params):
    omega = params['omega']
    t = params['cur_time']
    return np.array([[-x-.1*x**3-.1*v+1*sin(omega*t)]])
_,_,_,a,_ = ms.hb_time(duff_osc2, sp.array([[0,1,-1]]), 0.7, num_harmonics=1)
print(a)
[ 1.47796291]
In [20]:
_,_,_,a,_ = ms.hb_time(lambda x,v, params:np.array([[-x-.1*x**3-.1*v+1*sin(0.7*params['cur_time'])]]), sp.array([[0,1,-1]]), .7, num_harmonics=1)
a
Out[20]:
array([ 1.47796291])

Two things to note: 1. Remember that the lambda function has to return an n by 1 array. 2. Time must be referenced as params[‘cur_time’]