Updating Python Code 6.1 (p.182) for Figure 6.7 (p.81) |
<code6.1.py>
The deviation of magnetic compass is fitted by a Fourier series, for which
Dr. Berendsen wrote the following code.
- from scipy import optimize
- # data from compass corrections:
- x = arange(0., 365., 15.)
- y = array([-1.5,-0.5,0.,0.,0.,-0.5,-1.,-2.,
-3.,-2.5,-2.,-1.,
0.,0.5,1.5,2.5,2.0,2.5,
1.5,0.,-0.5,-2.,-2.5,-2.,-1.5])
- def fitfun(x,p):
- phi = x*pi/180.
- result = p[0]
- for i in range(1,5,1):
- result = result+[2*i-1]*np.sin(i*phi)+p[2*i]*np.cos(i*phi)
- return result
- def residual(p): return y-fitfun(x, p)
- pin = [0.]*9 # initial guess
- output = optimize.leastsq(residual, pin)
- print(output)
- pout = output[0] # optimized params
- def fun(x): return fitfun(x, pout) # for plotting
This code was expected to pass, but actually I had to add the line
import numpy as np
and put np as prefix like
np.arange, np.array, np.pi.
For the graphics part left out in the book, I added the following line
import matplotlib.pyplot as plt
and the lines to obtain the graph in the right.
|
<Graphics out>
Data fit including harmonics up to fouth order.
|
<Graphics code>
- t = np.arange(0.,361.,1.)
- plt.xlim(0,360)
- plt.ylim(-4,4)
- plt.plot(t, fun(t))
- for i in range(25):
- plt.plot(x[i],y[i],marker="o",markersize=5,color="blue")
- xs = np.array([x[i], x[i]])
- ys = np.array([y[i]-0.25, y[i]+0.25])
- plt.plot(xs,ys,color="red")
- font1 = {'family':'serif','color':'blue','size':11}
- font2 = {'family':'serif','color':'darkred','size':12}
- plt.title("Fig. 6.7. Compass deviation", fontdict = font1)
- plt.xlabel("Compass reading (degree)", fontdict = font2)
- plt.ylabel("Correction (degree)", fontdict = font2)
- plt.grid()
- plt.show()
|
<Remark1>
- I failed to include one of y data and came up with the message
operands could not be broadcast together with shapes (24,) (25,)
The error should have been fixed more quickly.
- 'optimize.least_squares' should be used for nonlinear problems.
|
<Remark2>
The problem could be solved also with optimize.curve_fit with the following changes.
- def fitfun(x,p0,p1,p2,p3,p4,p5,p6,p7,p8):
- phi = x*np.pi/180.
- s = p0 + p1*np.sin(phi) + p2*np.cos(phi) + p3*np.sin(2*phi) + p4*np.cos(2*phi)
- s += p5*np.sin(3*phi) + p6*np.cos(3*phi) + p7*np.sin(4*phi) + p8*np.cos(4*phi)
- return s
- output = optimize.curve_fit(fitfun, x, y)
- pout = output[0]
- def fun(x): return fitfun(x, *pout)
|
<Remark3>
It is a nice idea to define fitfun in the following way (nfree=9).
Do not forget to define the initial value, pini. Otherwise you may encounter with the message
'Unable to determine number of fit parameters'
- def fitfun(x, *q):
- phi = x*np.pi/180.
- i = 0
- j = 0
- s = q[0]
- while True:
- if i>=nfree-1:
- break
- i += 1
- w = q[i]
- j = (i+1) // 2
- if i%2 != 0:
- s += w*np.sin(j*phi)
- else:
- s += w*np.cos(j*phi)
- return s
- pini = [0.]*nfree
- output = optimize.curve_fit(fitfun, x, y, pini)
|