SPI Data Reading, Linear, and Non-linear Fit

From Advanced Labs Wiki
Jump to navigation Jump to search

2014

Here is the program, extending last week's program, that reads in single photon interference data and performs a couple fits. The program reads in data and plots them media:spi_data.png. Then a linear fit is performed and a plot of the data plus this model is generated media:spi_data_linear_model.png. Finally a nonlinear fit is performed and a plot with the two models and the data is generated media:spi_data_linear_and_nonlinear_model.png. The text output of the program should look like this:

_volts = [ 30.  30.  30. ...,  70.  70.  70.]
_ABC = [ 0.  0.  0. ...,  0.  0.  0.]
_AC = [ 3.  4.  7. ...,  6.  3.  2.]
volts = [30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70]
range(N) = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40]
Dim of M = (41, 3)
Dim of Ninv = (41, 41)
Dim of alpha = (3, 3)
Linear fit results 
a = 0.374361111765 +/- 0.015423507935
b = 0.805224133015 +/- 0.0153865196485
c = 3.82291964389 +/- 0.0110404319537
Non-linear Marquardt Search (iteration, chi-sq)
1 4155.25421469
2 3428.96612097
3 3337.99353442
4 3231.38829482
5 2556.16118098
6 837.69911394
7 171.075641211
8 118.588679965
9 117.272843887
10 117.253784918
Non-linear fit results
a = -0.889535513171 +/- 0.0154429366169
b = 0.112274935769 +/- 0.000238019986401
c = -3.0535050281 +/- 0.0751514516099
d = 3.824237946 +/- 0.0110433609376

Note the non-linear model will give a slightly different function from the linear model as the frequency of the interference pattern is being fit (and comes out to be 8.9V instead of 9V assumed in the linear fit). Here is the program.

import numpy as np
import pylab as plt

#Read data
_volts, _A, _B, _C, _AB, _AC, _BC, _ABC = np.loadtxt('spi_indistinguishable.txt',\
                    unpack=True, skiprows=9, usecols=(2,3,4,5,6,7,8,9), \
                    delimiter=',')

print '_volts =', _volts
print '_ABC =', _ABC
print '_AC =', _AC

volts = np.arange(30,71)
print 'volts =', volts
N = len(volts)
AC = np.zeros(N)
AC_err = np.zeros(N)
nsamp = np.zeros(N)

print 'range(N) =', range(N)
for i in range(N):
    #print "i =", i
    #print "voltage =", volts[i]
    indices = np.where(_volts == volts[i])
    #print "indices =", indices
    #print "_volts at indices =", _volts[indices]
    #print "_AC at indices =", _AC[indices]
    AC[i] = np.mean(_AC[indices])
    AC_err[i] = np.std(_AC[indices])
    nsamp[i] = len(_AC[indices])

plt.clf()
plt.errorbar(volts, AC, AC_err/np.sqrt(nsamp), fmt='r.', label='AC')
plt.xlabel('Piezoelectric Voltage (V)')
plt.ylabel('Count (per 0.01 s)')
plt.title('Indistiguishable Paths')
plt.legend(loc='lower left')
plt.savefig('spi_data.png')

#Linear fit y(V) = a*cos(2*pi*V/period) + b*sin(2*pi*V/period) + c
#                   = a*f1(V)+b*f2(V)+c*f3(V)

period = 9.

#Building projection matrix M
cos = np.cos(2*np.pi*volts/period)
sin = np.sin(2*np.pi*volts/period)
one = np.ones(len(volts))
MT = np.array([cos, sin, one])
M = np.transpose(MT)
print "Dim of M =", M.shape

#Building noise inverse matrix
Ninv = np.diag(1/(AC_err**2/nsamp))
print "Dim of Ninv =", Ninv.shape

#Compute the best fit parameters a = (alpha)^-1 beta
alpha = np.dot(MT,np.dot(Ninv,M)) #np.dot is matrix multiplication
print "Dim of alpha =", alpha.shape
beta = np.dot(MT,np.dot(Ninv,AC))
cov  = np.linalg.inv(alpha)
params = np.dot(cov,beta)

print "Linear fit results "
print "a =", params[0], '+/-', np.sqrt(cov[0][0])
print "b =", params[1], '+/-', np.sqrt(cov[1][1])
print "c =", params[2], '+/-', np.sqrt(cov[2][2])

plt.plot(volts, params[0]*cos+params[1]*sin+params[2]*one, label='Linear Model')
plt.legend(loc='lower left')
plt.savefig('spi_data_and_linear_model.png')

#Try non-linear fit -- determine the frequency/period
# model: a*sin(2*pi*b*V+c)+d   (4 parameters)

def y(V,a,b,c,d):
    return a*np.sin(2*np.pi*b*V+c)+d

def dyda(V,a,b,c,d):
    return np.sin(2*np.pi*b*V+c)

def dydb(V,a,b,c,d):
    return a*2*np.pi*V*np.cos(2*np.pi*b*V+c)

def dydc(V,a,b,c,d):
    return a*np.cos(2*np.pi*b*V+c)
    
def dydd(V,a,b,c,d):
    return 1.

def alpha(lam,V,a,b,c,d,var):
    alpha = np.zeros((4,4))
    alpha[0][0] = np.sum(dyda(V,a,b,c,d)**2/var*(1+lam))
    alpha[1][1] = np.sum(dydb(V,a,b,c,d)**2/var*(1+lam))
    alpha[2][2] = np.sum(dydc(V,a,b,c,d)**2/var*(1+lam))
    alpha[3][3] = np.sum(dydd(V,a,b,c,d)**2/var*(1+lam))
    alpha[0,1] = np.sum(dyda(V,a,b,c,d)*dydb(V,a,b,c,d)/var)
    alpha[0,2] = np.sum(dyda(V,a,b,c,d)*dydc(V,a,b,c,d)/var)
    alpha[0,3] = np.sum(dyda(V,a,b,c,d)*dydd(V,a,b,c,d)/var)
    alpha[1,2] = np.sum(dydb(V,a,b,c,d)*dydc(V,a,b,c,d)/var)
    alpha[1,3] = np.sum(dydb(V,a,b,c,d)*dydd(V,a,b,c,d)/var)
    alpha[2,3] = np.sum(dydc(V,a,b,c,d)*dydd(V,a,b,c,d)/var)
    alpha[1,0] = alpha[0,1]
    alpha[2,0] = alpha[0,2]
    alpha[3,0] = alpha[0,3]
    alpha[2,1] = alpha[1,2]
    alpha[3,1] = alpha[1,3]
    alpha[3,2] = alpha[2,3]
    return alpha

def beta(V,a,b,c,d,data,var):
    beta = np.zeros(4)
    beta[0]=np.sum((data-y(V,a,b,c,d))*dyda(V,a,b,c,d)/var)
    beta[1]=np.sum((data-y(V,a,b,c,d))*dydb(V,a,b,c,d)/var)
    beta[2]=np.sum((data-y(V,a,b,c,d))*dydc(V,a,b,c,d)/var)
    beta[3]=np.sum((data-y(V,a,b,c,d))*dydd(V,a,b,c,d)/var)
    return beta

def chisq(V,data,var,a,b,c,d):
    return np.sum((data-y(V,a,b,c,d))**2/var)
    
def marquardt(V, data, var, a0, b0, c0, d0, verbose=False):
    a=a0
    b=b0
    c=c0
    d=d0
    lam = 0.001
    i=0
    while(True):
        chisq0 = chisq(V,data,var,a,b,c,d)
        da,db,dc,dd = np.dot(np.linalg.inv(alpha(lam,V,a,b,c,d,var)), \
                            beta(V,a,b,c,d,data,var))
        chisq1 = chisq(V,data,var,a+da,b+db,c+dc,d+dd)
        dchisq = chisq1-chisq0
        if dchisq > 0: #Uphill, oh no!
            lam *= 10
            continue #go back to top of while loop
        if dchisq < 0: #Downhill, life is good!
            lam /= 10
            a = a+da
            b = b+db
            c = c+dc
            d = d+dd
            i+=1
            if verbose:
                print i, chisq1
            if abs(dchisq/chisq0) < 0.001:
                break #leave the loop
    return a, b, c, d, np.linalg.inv(alpha(1.,V,a,b,c,d,var))

print "Non-linear Marquardt Search (iteration, chi-sq)"
a,b,c,d,cov = marquardt(volts, AC, AC_err**2/nsamp, .5 , .1, 0.,4., verbose=True)
print "Non-linear fit results"
print "a =", a, '+/-', np.sqrt(cov[0][0])
print "b =", b, '+/-', np.sqrt(cov[1][1])
print "c =", c, '+/-', np.sqrt(cov[2][2])
print "d =", d, '+/-', np.sqrt(cov[3][3])

plt.plot(volts, a*np.sin(2*np.pi*b*volts+c)+d, label='Nonlinear Model')
plt.legend(loc='lower left')
plt.savefig("spi_data_linear_and_nonlinear_model.png")