Surface NMR processing and inversion GUI
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

adapt.py 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. import numpy as np
  2. from numpy.linalg import lstsq
  3. from numpy.linalg import norm
  4. from numpy import fft
  5. import pylab
  6. from scipy.signal import correlate
  7. def autocorr(x):
  8. #result = np.correlate(x, x, mode='full')
  9. result = correlate(x, x, mode='full')
  10. return result[result.size/2:]
  11. class AdaptiveFilter:
  12. def __init__(self, mu):
  13. self.mu = mu
  14. def adapt_filt_Ref(self, x, R, M, mu, PCA, lambda2=0.95, H0=0):
  15. """ Taken from .m file
  16. This function is written to allow the user to filter a input signal
  17. with an adaptive filter that utilizes 2 reference signals instead of
  18. the standard method which allows for only 1 reference signal.
  19. Author: Rob Clemens Date: 3/16/06
  20. Modified and ported to Python, now takes arbitray number of reference points
  21. """
  22. #from akvo.tressel import pca
  23. import akvo.tressel.pca as pca
  24. if np.shape(x) != np.shape(R[0]): # or np.shape(x) != np.shape(rx1):
  25. print ("Error, non aligned")
  26. exit(1)
  27. if PCA == "Yes":
  28. print("Performing PCA calculation in noise cancellation")
  29. # PCA decomposition on ref channels so signals are less related
  30. R, K, means = pca.pca( R )
  31. # test for in loop reference
  32. #print("Cull nearly zero terms?", np.shape(x), np.shape(R))
  33. #R = R[0:3,:]
  34. #R = R[2:4,:]
  35. #print(" removed zero terms?", np.shape(x), np.shape(R))
  36. #H0 = H0[0:3*np.shape(x)[0]]
  37. #H0 = H0[0:2*np.shape(x)[0]]
  38. if all(H0) == 0:
  39. H = np.zeros( (len(R)*M))
  40. #print ("resetting filter")
  41. else:
  42. H = H0
  43. Rn = np.ones(len(R)*M) / mu
  44. r_ = np.zeros( (len(R), M) )
  45. e = np.zeros(len(x)) # error, desired output
  46. ilambda = lambda2**-1
  47. for z in range(0, len(x)):
  48. # Only look forwards, to avoid distorting the lates times
  49. # (run backwards, if opposite and you don't care about distorting very late time.)
  50. for ir in range(len(R)):
  51. if z < M:
  52. r_[ir,0:z] = R[ir][0:z]
  53. r_[ir,z:M] = 0
  54. else:
  55. # TODO, use np.delete and np.append to speed this up
  56. r_[ir,:] = R[ir][z-M:z]
  57. # reshape
  58. r_n = np.reshape(r_, -1) #concatenate((r_v, r_h ))
  59. #K = np.dot( np.diag(Rn,0), r_n) / (lambda2 + np.dot(r_n*Rn, r_n)) # Create/update K
  60. K = (Rn* r_n) / (lambda2 + np.dot(r_n*Rn, r_n)) # Create/update K
  61. e[z] = x[z] - np.dot(r_n.T, H) # e is the filtered signal, input - r(n) * Filter Coefs
  62. H += K*e[z]; # Update Filter Coefficients
  63. Rn = ilambda*Rn - ilambda*np.dot(np.dot(K, r_n.T), Rn) # Update R(n)
  64. return e, H
  65. def transferFunctionFFT(self, D, R, reg=1e-2):
  66. from akvo.tressel import pca
  67. """
  68. Computes the transfer function (H) between a Data channel and
  69. a number of Reference channels. The Matrices D and R are
  70. expected to be in the frequency domain on input.
  71. | R1'R1 R1'R2 R1'R3| |h1| |R1'D|
  72. | R2'R1 R2'R2 R2'R3| * |h2| = |R2'D|
  73. | R3'R1 R3'R2 R3'R3| |h3| |R3'D|
  74. Returns the corrected array
  75. """
  76. # PCA decomposition on ref channels so signals are less related
  77. #transMatrix, K, means = pca.pca( np.array([rx0, rx1]))
  78. #RR = np.zeros(( np.shape(R[0])[0]*np.shape(R[0])[1], len(R)))
  79. # RR = np.zeros(( len(R), np.shape(R[0])[0]*np.shape(R[0])[1] ))
  80. # for ir in range(len(R)):
  81. # RR[ir,:] = np.reshape(R[ir], -1)
  82. # transMatrix, K, means = pca.pca(RR)
  83. # #R rx0 = transMatrix[0,:]
  84. # # rx1 = transMatrix[1,:]
  85. # for ir in range(len(R)):
  86. # R[ir] = transMatrix[ir,0]
  87. import scipy.linalg
  88. import akvo.tressel.pca as pca
  89. # Compute as many transfer functions as len(R)
  90. # A*H = B
  91. nref = len(R)
  92. H = np.zeros( (np.shape(D)[1], len(R)), dtype=complex )
  93. for iw in range(np.shape(D)[1]):
  94. A = np.zeros( (nref, nref), dtype=complex )
  95. B = np.zeros( (nref) , dtype=complex)
  96. for ii in range(nref):
  97. for jj in range(nref):
  98. # build A
  99. A[ii,jj] = np.dot(R[ii][:,iw], R[jj][:,iw])
  100. # build B
  101. B[ii] = np.dot( R[ii][:,iw], D[:,iw] )
  102. # compute H(iw)
  103. #linalg.solve(a,b) if a is square
  104. #print "A", A
  105. #print "B", B
  106. # TODO, regularise this solve step? So as to not fit the spurious noise
  107. #print np.shape(B), np.shape(A)
  108. #H[iw, :] = scipy.linalg.solve(A,B)
  109. H[iw, :] = scipy.linalg.lstsq(A,B,cond=reg)[0]
  110. #print "lstqt", np.shape(scipy.linalg.lstsq(A,B))
  111. #print "solve", scipy.linalg.solve(A,B)
  112. #H[iw,:] = scipy.linalg.lstsq(A,B) # otherwise
  113. #H = np.zeros( (np.shape(D)[1], ) )
  114. #print H #A, B
  115. Error = np.zeros(np.shape(D), dtype=complex)
  116. for ir in range(nref):
  117. for q in range( np.shape(D)[0] ):
  118. #print "dimcheck", np.shape(H[:,ir]), np.shape(R[ir][q,:] )
  119. Error[q,:] += H[:,ir]*R[ir][q,:]
  120. return D - Error
  121. def adapt_filt_tworefFreq(self, x, rx0, rx1, M, lambda2=0.95):
  122. """ Frequency domain version of above
  123. """
  124. from akvo.tressel import pca
  125. pylab.figure()
  126. pylab.plot(rx0)
  127. pylab.plot(rx1)
  128. # PCA decomposition on ref channels so signals are less related
  129. transMatrix, K, means = pca.pca( np.array([rx0, rx1]))
  130. rx0 = transMatrix[:,0]
  131. rx1 = transMatrix[:,1]
  132. pylab.plot(rx0)
  133. pylab.plot(rx1)
  134. pylab.show()
  135. exit()
  136. if np.shape(x) != np.shape(rx0) or np.shape(x) != np.shape(rx1):
  137. print ("Error, non aligned")
  138. exit(1)
  139. wx = fft.rfft(x)
  140. wr0 = fft.rfft(rx0)
  141. wr1 = fft.rfft(rx1)
  142. H = np.zeros( (2*M), dtype=complex )
  143. ident_mat = np.eye((2*M))
  144. Rn = ident_mat / 0.1
  145. r_v = np.zeros( (M), dtype=complex )
  146. r_h = np.zeros( (M), dtype=complex )
  147. e = np.zeros(len(x), dtype=complex )
  148. ilambda = lambda2**-1
  149. for z in range(0, len(wx)):
  150. # TODO Padd with Zeros or truncate if M >,< arrays
  151. r_v = wr0[::-1][:M]
  152. r_h = wr1[::-1][:M]
  153. r_n = np.concatenate((r_v, r_h ))
  154. K = np.dot(Rn, r_n) / (lambda2 + np.dot(np.dot(r_n.T, Rn), r_n)) # Create/update K
  155. e[z] = wx[z] - np.dot(r_n,H) # e is the filtered signal, input - r(n) * Filter Coefs
  156. H += K * e[z]; # Update Filter Coefficients
  157. Rn = ilambda*Rn - ilambda*K*r_n.T*Rn # Update R(n)
  158. return fft.irfft(e)
  159. def iter_filt_refFreq(self, x, rx0, Ahat=.05, Bhat=.5, k=0.05):
  160. X = np.fft.rfft(x)
  161. X0 = np.copy(X)
  162. RX0 = np.fft.rfft(rx0)
  163. # step 0
  164. Abs2HW = []
  165. alphai = k * (np.abs(Ahat)**2 / np.abs(Bhat)**2)
  166. betai = k * (1. / (np.abs(Bhat)**2) )
  167. Hw = ((1.+alphai) * np.abs(X)**2 ) / (np.abs(X)**2 + betai*(np.abs(RX0)**2))
  168. H = np.abs(Hw)**2
  169. pylab.ion()
  170. pylab.figure()
  171. for i in range(10):
  172. #print "alphai", alphai
  173. #print "betai", betai
  174. #print "Hw", Hw
  175. alphai = k * (np.abs(Ahat)**2 / np.abs(Bhat)**2) * np.product(H, axis=0)
  176. betai = k * (1. / np.abs(Bhat)**2) * np.product(H, axis=0)
  177. # update signal
  178. Hw = ((1.+alphai) * np.abs(X)**2) / (np.abs(X)**2 + betai*np.abs(RX0)**2)
  179. Hw = np.nan_to_num(Hw)
  180. X *= Hw
  181. H = np.vstack( (H, np.abs(Hw)**2) )
  182. #print "Hw", Hw
  183. pylab.cla()
  184. pylab.plot(Hw)
  185. #pylab.plot(np.abs(X))
  186. #pylab.plot(np.abs(RX0))
  187. pylab.draw()
  188. raw_input("wait")
  189. pylab.cla()
  190. pylab.ioff()
  191. #return np.fft.irfft(X0-X)
  192. return np.fft.irfft(X)
  193. def iter_filt_refFreq(self, x, rx0, rx1, Ahat=.1, Bhat=1., k=0.001):
  194. X = np.fft.rfft(x)
  195. X0 = np.copy(X)
  196. RX0 = np.fft.rfft(rx0)
  197. RX1 = np.fft.rfft(rx1)
  198. # step 0
  199. alphai = k * (np.abs(Ahat)**2 / np.abs(Bhat)**2)
  200. betai = k * (1. / (np.abs(Bhat)**2) )
  201. #Hw = ((1.+alphai) * np.abs(X)**2 ) / (np.abs(X)**2 + betai*(np.abs(RX0)**2))
  202. H = np.ones(len(X)) # abs(Hw)**2
  203. #pylab.ion()
  204. #pylab.figure(334)
  205. for i in range(1000):
  206. #print "alphai", alphai
  207. #print "betai", betai
  208. #print "Hw", Hw
  209. alphai = k * (np.abs(Ahat)**2 / np.abs(Bhat)**2) * np.product(H, axis=0)
  210. betai = k * (1. / np.abs(Bhat)**2) * np.product(H, axis=0)
  211. # update signal
  212. Hw = ((1.+alphai) * np.abs(X)**2) / (np.abs(X)**2 + betai*np.abs(RX0)**2)
  213. Hw = np.nan_to_num(Hw)
  214. X *= Hw #.conjugate
  215. #H = np.vstack((H, np.abs(Hw)**2) )
  216. H = np.vstack((H, np.abs(Hw)) )
  217. #print "Hw", Hw
  218. #pylab.cla()
  219. #pylab.plot(Hw)
  220. #pylab.plot(np.abs(X))
  221. #pylab.plot(np.abs(RX0))
  222. #pylab.draw()
  223. #raw_input("wait")
  224. #pylab.cla()
  225. #pylab.ioff()
  226. return np.fft.irfft(X0-X)
  227. #return np.fft.irfft(X)
  228. def Tdomain_DFT(self, desired, input, S):
  229. """ Lifted from Adaptive filtering toolbox. Modefied to accept more than one input
  230. vector
  231. """
  232. # Initialisation Procedure
  233. nCoefficients = S["filterOrderNo"]/2+1
  234. nIterations = len(desired)
  235. # Pre Allocations
  236. errorVector = np.zeros(nIterations, dtype='complex')
  237. outputVector = np.zeros(nIterations, dtype='complex')
  238. # Initial State
  239. coefficientVectorDFT = np.fft.rfft(S["initialCoefficients"])/np.sqrt(float(nCoefficients))
  240. desiredDFT = np.fft.rfft(desired)
  241. powerVector = S["initialPower"]*np.ones(nCoefficients)
  242. # Improve source code regularity, pad with zeros
  243. # TODO, confirm zeros(nCoeffics) not nCoeffics-1
  244. prefixedInput = np.concatenate([np.zeros(nCoefficients-1), np.array(input)])
  245. # Body
  246. pylab.ion()
  247. pylab.figure(11)
  248. for it in range(nIterations): # = 1:nIterations,
  249. regressorDFT = np.fft.rfft(prefixedInput[it:it+nCoefficients][::-1]) /\
  250. np.sqrt(float(nCoefficients))
  251. # Summing two column vectors
  252. powerVector = S["alpha"] * (regressorDFT*np.conjugate(regressorDFT)) + \
  253. (1.-S["alpha"])*(powerVector)
  254. pylab.cla()
  255. #pylab.plot(prefixedInput[::-1], 'b')
  256. #pylab.plot(prefixedInput[it:it+nCoefficients][::-1], 'g', linewidth=3)
  257. #pylab.plot(regressorDFT.real)
  258. #pylab.plot(regressorDFT.imag)
  259. pylab.plot(powerVector.real)
  260. pylab.plot(powerVector.imag)
  261. #pylab.plot(outputVector)
  262. #pylab.plot(errorVector.real)
  263. #pylab.plot(errorVector.imag)
  264. pylab.draw()
  265. #raw_input("wait")
  266. outputVector[it] = np.dot(coefficientVectorDFT.T, regressorDFT)
  267. #errorVector[it] = desired[it] - outputVector[it]
  268. errorVector[it] = desiredDFT[it] - outputVector[it]
  269. #print errorVector[it], desired[it], outputVector[it]
  270. # Vectorized
  271. coefficientVectorDFT += (S["step"]*np.conjugate(errorVector[it])*regressorDFT) /\
  272. (S['gamma']+powerVector)
  273. return np.real(np.fft.irfft(errorVector))
  274. #coefficientVector = ifft(coefficientVectorDFT)*sqrt(nCoefficients);
  275. def Tdomain_DCT(self, desired, input, S):
  276. """ Lifted from Adaptive filtering toolbox. Modefied to accept more than one input
  277. vector. Uses cosine transform
  278. """
  279. from scipy.fftpack import dct
  280. # Initialisation Procedure
  281. nCoefficients = S["filterOrderNo"]+1
  282. nIterations = len(desired)
  283. # Pre Allocations
  284. errorVector = np.zeros(nIterations)
  285. outputVector = np.zeros(nIterations)
  286. # Initial State
  287. coefficientVectorDCT = dct(S["initialCoefficients"]) #/np.sqrt(float(nCoefficients))
  288. desiredDCT = dct(desired)
  289. powerVector = S["initialPower"]*np.ones(nCoefficients)
  290. # Improve source code regularity, pad with zeros
  291. prefixedInput = np.concatenate([np.zeros(nCoefficients-1), np.array(input)])
  292. # Body
  293. #pylab.figure(11)
  294. #pylab.ion()
  295. for it in range(0, nIterations): # = 1:nIterations,
  296. regressorDCT = dct(prefixedInput[it:it+nCoefficients][::-1], type=2)
  297. #regressorDCT = dct(prefixedInput[it+nCoefficients:it+nCoefficients*2+1])#[::-1])
  298. # Summing two column vectors
  299. powerVector = S["alpha"]*(regressorDCT) + (1.-S["alpha"])*(powerVector)
  300. #pylab.cla()
  301. #pylab.plot(powerVector)
  302. #pylab.draw()
  303. outputVector[it] = np.dot(coefficientVectorDCT.T, regressorDCT)
  304. #errorVector[it] = desired[it] - outputVector[it]
  305. errorVector[it] = desiredDCT[it] - outputVector[it]
  306. # Vectorized
  307. coefficientVectorDCT += (S["step"]*errorVector[it]*regressorDCT) #/\
  308. #(S['gamma']+powerVector)
  309. #pylab.plot(errorVector)
  310. #pylab.show()
  311. return dct(errorVector, type=3)
  312. #coefficientVector = ifft(coefficientVectorDCT)*sqrt(nCoefficients);
  313. def Tdomain_CORR(self, desired, input, S):
  314. from scipy.linalg import toeplitz
  315. from scipy.signal import correlate
  316. # Autocorrelation
  317. ac = np.correlate(input, input, mode='full')
  318. ac = ac[ac.size/2:]
  319. R = toeplitz(ac)
  320. # cross correllation
  321. r = np.correlate(desired, input, mode='full')
  322. r = r[r.size/2:]
  323. #r = np.correlate(desired, input, mode='valid')
  324. print ("R", np.shape(R))
  325. print ("r", np.shape(r))
  326. print ("solving")
  327. #H = np.linalg.solve(R,r)
  328. H = np.linalg.lstsq(R,r,rcond=.01)[0]
  329. #return desired - np.dot(H,input)
  330. print ("done solving")
  331. pylab.figure()
  332. pylab.plot(H)
  333. pylab.title("H")
  334. #return desired - np.convolve(H, input, mode='valid')
  335. #return desired - np.convolve(H, input, mode='same')
  336. #return np.convolve(H, input, mode='same')
  337. return desired - np.dot(toeplitz(H), input)
  338. #return np.dot(R, H)
  339. # T = toeplitz(input)
  340. # print "shapes", np.shape(T), np.shape(desired)
  341. # h = np.linalg.lstsq(T, desired)[0]
  342. # print "shapes", np.shape(h), np.shape(input)
  343. # #return np.convolve(h, input, mode='same')
  344. # return desired - np.dot(T,h)
  345. def Fdomain_CORR(self, desired, input, dt, freq):
  346. from scipy.linalg import toeplitz
  347. # Fourier domain
  348. Input = np.fft.rfft(input)
  349. Desired = np.fft.rfft(desired)
  350. T = toeplitz(Input)
  351. #H = np.linalg.solve(T, Desired)
  352. H = np.linalg.lstsq(T, Desired)[0]
  353. # ac = np.correlate(Input, Input, mode='full')
  354. # ac = ac[ac.size/2:]
  355. # R = toeplitz(ac)
  356. #
  357. # r = np.correlate(Desired, Input, mode='full')
  358. # r = r[r.size/2:]
  359. #
  360. # #r = np.correlate(desired, input, mode='valid')
  361. # print "R", np.shape(R)
  362. # print "r", np.shape(r)
  363. # print "solving"
  364. # H = np.linalg.solve(R,r)
  365. # #H = np.linalg.lstsq(R,r)
  366. # #return desired - np.dot(H,input)
  367. # print "done solving"
  368. pylab.figure()
  369. pylab.plot(H.real)
  370. pylab.plot(H.imag)
  371. pylab.plot(Input.real)
  372. pylab.plot(Input.imag)
  373. pylab.plot(Desired.real)
  374. pylab.plot(Desired.imag)
  375. pylab.legend(["hr","hi","ir","ii","dr","di"])
  376. pylab.title("H")
  377. #return desired - np.fft.irfft(Input*H)
  378. return np.fft.irfft(H*Input)
  379. def Tdomain_RLS(self, desired, input, S):
  380. """
  381. A DFT is first performed on the data. Than a RLS algorithm is carried out
  382. for noise cancellation. Related to the RLS_Alt Algoritm 5.3 in Diniz book.
  383. The desired and input signals are assummed to be real time series data.
  384. """
  385. # Transform data into frequency domain
  386. Input = np.fft.rfft(input)
  387. Desired = np.fft.rfft(desired)
  388. # Initialisation Procedure
  389. nCoefficients = S["filterOrderNo"]+1
  390. nIterations = len(Desired)
  391. # Pre Allocations
  392. errorVector = np.zeros(nIterations, dtype="complex")
  393. outputVector = np.zeros(nIterations, dtype="complex")
  394. errorVectorPost = np.zeros(nIterations, dtype="complex")
  395. outputVectorPost = np.zeros(nIterations, dtype="complex")
  396. coefficientVector = np.zeros( (nCoefficients, nIterations+1), dtype="complex" )
  397. # Initial State
  398. coefficientVector[:,1] = S["initialCoefficients"]
  399. S_d = S["delta"]*np.eye(nCoefficients)
  400. # Improve source code regularity, pad with zeros
  401. prefixedInput = np.concatenate([np.zeros(nCoefficients-1, dtype="complex"),
  402. np.array(Input)])
  403. invLambda = 1./S["lambda"]
  404. # Body
  405. pylab.ion()
  406. pylab.figure(11)
  407. for it in range(nIterations):
  408. regressor = prefixedInput[it:it+nCoefficients][::-1]
  409. # a priori estimated output
  410. outputVector[it] = np.dot(coefficientVector[:,it].T, regressor)
  411. # a priori error
  412. errorVector[it] = Desired[it] - outputVector[it]
  413. psi = np.dot(S_d, regressor)
  414. if np.isnan(psi).any():
  415. print ("psi", psi)
  416. exit(1)
  417. pylab.cla()
  418. #pylab.plot(psi)
  419. pylab.plot(regressor.real)
  420. pylab.plot(regressor.imag)
  421. pylab.plot(coefficientVector[:,it].real)
  422. pylab.plot(coefficientVector[:,it].imag)
  423. pylab.legend(["rr","ri", "cr", "ci"])
  424. pylab.draw()
  425. raw_input("paws")
  426. S_d = invLambda * (S_d - np.dot(psi, psi.T) /\
  427. S["lambda"] + np.dot(psi.T, regressor))
  428. coefficientVector[:,it+1] = coefficientVector[:,it] + \
  429. np.conjugate(errorVector[it])*np.dot(S_d, regressor)
  430. # A posteriori estimated output
  431. outputVectorPost[it] = np.dot(coefficientVector[:,it+1].T, regressor)
  432. # A posteriori error
  433. errorVectorPost[it] = Desired[it] - outputVectorPost[it]
  434. errorVectorPost = np.nan_to_num(errorVectorPost)
  435. pylab.figure(11)
  436. print (np.shape(errorVectorPost))
  437. pylab.plot(errorVectorPost.real)
  438. pylab.plot(errorVectorPost.imag)
  439. pylab.show()
  440. print(errorVectorPost)
  441. #return np.fft.irfft(Desired)
  442. return np.fft.irfft(errorVectorPost)
  443. if __name__ == "__main__":
  444. def noise(nu, t, phi):
  445. return np.sin(nu*2.*np.pi*t + phi)
  446. import matplotlib.pyplot as plt
  447. print("Test driver for adaptive filtering")
  448. Filt = AdaptiveFilter(.1)
  449. t = np.arange(0, .5, 1e-4)
  450. omega = 2000 * 2.*np.pi
  451. T2 = .100
  452. n1 = noise(60, t, .2 )
  453. n2 = noise(61, t, .514 )
  454. x = np.sin(omega*t)* np.exp(-t/T2) + 2.3*noise(60, t, .34) + 1.783*noise(31, t, 2.1)
  455. e = Filt.adapt_filt_tworef(x, n1, n2, 200, .98)
  456. plt.plot(t, x)
  457. plt.plot(t, n1)
  458. plt.plot(t, n2)
  459. plt.plot(t, e)
  460. plt.show()