3

As an exercise (using Python to process LTSpice exported data), I am trying to determine the values of Rs and Ls that were used in a circuit, from the the measurements of Vin, Vout, and the phase lead/lag between the two.

I simulated a circuit using specific value for Rs and Ls,and then tried to determine what those values were from the measured data. The sample circuit is shown below, as well as the waveforms obtained from it.

circuit diagramwaveforms

LTSpice does not sample at uniform intervals, so a trick to recover uniformedly sampled data is to take an FFT of the signals, and then take another FFT of the FFT'd signals to recover the time domain data, now with uniform sampling (LTSpice does interpolation for the FFT). I do this, and then export this data to a Python script where I try to determine the amplitudes of Vo and Vi, and the phase delay,, between them, and then use those values to identify the Rs and Ls, via the following formulas:

Since vout,

then
enter image description here where V1 and Vo are the amplitudes of sinusoids, and enter image description here

Therefore, solving for the magnitude and phase of Z, I can determine the values of R and L:
enter image description here

enter image description here

Since enter image description here

then

eq10.

The problem is that the answer I get is wrong, and since there is no "noise" in the measurements, I assumed I would easily get the exact answer. Assuming my math above is correct, I think the problem stems from the fact that I can't seem to accurately determine the amplitudes of Vo, Vi, and the phase delay between them from the processed samples.

I've tried differentiation to find the peaks and phase delay: I differentiate the waveforms, and then interpolate the values for time and voltage when a change in sign occurs indicating the max (and mins) of the waveforms. I then average the peak values and the difference between the Vin peaks and the Vo peaks over all the samples to try and reduce the error.

I also tried circular crosscorrelation to find the phase delay, thinking perhaps the error in the phase delay determination was too great.

Finally, I tried fitting the different Vo and Vi samples to sinusoids using Least Squares method.

All three cases fail to give me the correct solution for Ls and Rs, as shown in the table (it includes the ideal, computed solution as well) enter image description here

I can share the data, the python notebook code, etc, if anyone is interested in helping me figure out why this seemingly straightforward exercise is not working for me.

Thank you.

[EDIT] Added some code....

Setup:

vi=data[1:,1]
vo=data[1:,2]
time=data[1:,0]
plt.plot(time,vi)
plt.plot(time,vo)
plt.show()

 deltaT= time[2]-time[1]

Derivative Method:

# Use the derivatve to find the slope change from positive to negative. Select the point half way between previous and current sample for the corresponding values of vo, vi, and time.

# In[28]:


tmpderVo = 0
tmpderVi = 0

peakVotimearray=[]
peakVoarray    =[]
peakVitimearray=[]
peakViarray    =[]
derVoArr       =[]
derViArr       =[]

for i in range(len(time)-1):
    derVo = (vo[i+1]-vo[i])/deltaT  # derivative
    derVoArr.append(derVo)
    if np.sign(tmpderVo)==1:
        if np.sign(derVo) != np.sign(tmpderVo):
            # interpolate time and Vo 
            newtime= time[i]+deltaT/2
            peakVotimearray.append(newtime)
            peakVo = vo[i]+(vo[i+1]-vo[i])/2
            peakVoarray.append(peakVo)

    derVi=(vi[i+1]-vi[i])/deltaT  # derivative
    derViArr.append(derVi)
    if np.sign(tmpderVi)==1:
        if  np.sign(derVi) != np.sign(tmpderVi):
            # interpolate time and Vi  -- half way point 
            newtime= time[i]+deltaT/2
            peakVitimearray.append(newtime)
            peakVi = vi[i]+(vi[i+1]-vi[i])/2
            peakViarray.append(peakVi)

    tmpderVo = derVo
    tmpderVi = derVi

plt.plot(derVoArr[10:100],'-*k')
plt.show()
# Average Vo and Vi peaks
peakVoave= np.mean(np.array(peakVoarray)[10:-10])
stdVoave = np.std(np.array(peakVoarray)[10:-10])
peakViave= np.mean(np.array(peakViarray)[10:-10])
stdViave = np.std(np.array(peakViarray)[10:-10])

# Average Time delay
timedlyarray=np.array(peakVitimearray)-np.array(peakVotimearray)
timedlyave = np.mean(timedlyarray[10:-10])
timedlystd  = np.std(timedlyarray[10:-10])

print('time delay average= ', timedlyave, '\t Time Delay STD = ',timedlystd )
print('Coefficient of Variability of Delay Measurement = ', timedlystd/timedlyave)
print('\nAverage Vo Amplitude = ' , peakVoave,'\t Average Vi Amplitude = ' , peakViave)
print('Vo Amplitude STD = ', stdVoave, '\t Vi Amplitude STD = ', stdViave)
print('\nCoefficient of Variability of Vo Peak Measurement = ', stdVoave/peakVoave)
print('Coefficient of Variability of Vi Peak Measurement = ', stdViave/peakViave)

print('\nSkipped the first 10 values in array for average calculation to avoid any data edge effects\n')


plt.plot(time[periodstart:periodend],vo[periodstart:periodend], time[periodstart:periodend],vi[periodstart:periodend])
# indices for peak arrays no longer match original data indices, need to recover them below
frac = periodstart/len(time)  # what fraction of whole time array are we starting at
offset=int(len(peakVotimearray)*frac) # determine offset into peaktime array, one peak per period
plt.vlines(peakVotimearray[offset:int(offset+len(time[periodstart:periodend])*deltaT/1e-6)], -1, 1, colors='r', linestyles='dashed',linewidth=1)
plt.vlines(peakVitimearray[offset:int(offset+len(time[periodstart:periodend])*deltaT/1e-6)], -1, 1, colors='r', linestyles='dashed',linewidth=1)
plt.title('Sketch of Vi and Vo and their phase relationship')
plt.legend(['Vin','Vo'])
plt.show()

# ### Determine Time Delay using Peaks found via Derivatives
peakdly=timedlyave
peakdlyrad=timedlyave/T*2*np.pi
print(peakdlyrad)

waveform sketch

XCorr Method

from numpy.fft import fft, ifft

def periodic_corr(x, y):
    """Periodic correlation, implemented using the FFT.

    x and y must be real sequences with the same length.
    """
    return ifft(fft(x) * fft(y).conj()).real

xcorr=periodic_corr(vi,vo)
dlyndx= np.argmax(xcorr)
print('Index: ',dlyndx, '\tTime delay: ',dlyndx*deltaT)

plt.plot(time,vi)
plt.plot(time+dlyndx*deltaT,vo)
plt.show()
timedly=dlyndx*deltaT/T*2*np.pi

xcorr waveforms

LS Estimator to find Amplitude

D0 =np.array([np.cos(2*np.pi*f*time),np.sin(2*np.pi*f*time),np.ones(time.size)],'float').transpose()

vin=np.array([vi]).T
vout=np.array([vo]).T
print(np.concatenate([vin,vout], axis=1))

from numpy.linalg import inv

s_hat_vin = np.matmul(inv(np.matmul(D0.T,D0)),np.matmul(D0.T,vin))
s_hat_vo  = np.matmul(inv(np.matmul(D0.T,D0)),np.matmul(D0.T,vout))

vinMag = np.sqrt(s_hat_vin[0]**2+s_hat_vin[1]**2)
vinPh  = -np.arctan2(s_hat_vin[1],s_hat_vin[0])

voMag = np.sqrt(s_hat_vo[0]**2+s_hat_vo[1]**2)
voPh  = -np.arctan2(s_hat_vo[1],s_hat_vo[0])

print(vinMag,vinPh)
print(voMag, voPh)

#plt.plot(time,vo)
plt.plot(time,vinMag*np.cos(2*np.pi*f*time + vinPh) + s_hat_vin[2])
plt.plot(time,voMag *np.cos(2*np.pi*f*time + voPh) + s_hat_vo[2])
plt.plot(time,voMag *np.cos(2*np.pi*f*time + voPh+(vinPh-voPh)) + s_hat_vo[2])
plt.show()

lsm_dly = (vinPh-voPh)

enter image description here

def Zmagphase(vipeak,vopeak,theta,R1):
    """Returns the magnitude and phase of Z."""

    magZ  = (vopeak*R1)/(np.sqrt(vipeak**2 - 2*vipeak*vopeak*np.cos(theta)+ vopeak**2))
    phaseZ = theta - np.arctan2(-vopeak*np.sin(theta),(vipeak-vopeak*np.cos(theta)))

    return [magZ,phaseZ]

def Z2LsRs(mag,ph,f):
    """Determines Ls and Rs from Z in polar form"""
    w= 2*np.pi*f
    Rs = mag*np.cos(ph)
    Ls = mag*np.sin(ph)/(w)

    return [Rs, Ls]

FFT Solution

Fs = 1/deltaT
T= deltaT
N =  len(vi)
freq = Fs/N*np.arange(1,int(N/2)+1)

y=np.fft.fft(vi)
vimagfft=2/N*np.abs(y)[0:int(N/2)+1]
vimagfft=vimagfft[1:]
viphase = np.angle(y)[1:int(N/2)+1]

x=np.fft.fft(vo)
vomagfft=2/N*np.abs(x)[0:int(N/2)+1]
vomagfft=vomagfft[1:]
vophase = np.angle(x)[1:int(N/2)+1]

plt.plot(freq,vimagfft,'-*k', freq, vomagfft, '-*r')
plt.axis([0, 10000000, 0, 10])
plt.autoscale(True, 'y')
plt.show()


viFFT = np.max(vimagfft)
voFFT = np.max(vomagfft)

thetaFFT = vophase[np.argmax(vomagfft)]-viphase[np.argmax(vimagfft)]
print('ViampFFT = ', viFFT, '\t VoampFFT = ' , voFFT)
print('Phase Delay =',thetaFFT)

Results in:

enter image description here

fft_sol

Ideal Solution

from numpy import exp, abs, angle

def polar2z(r,theta):
    return r * exp( 1j * theta )

def z2polar(a,b):
    z = a + 1j * b
    return ( abs(z), angle(z) )

Vin = 1*exp(0j)


Vo=1*exp(0j)*(.118+(2*np.pi*1e6*204e-6j))/(1e3+.118+(2*np.pi*1e6*204e-6j))

Vomag=np.abs(Vo)
Votheta=np.angle(Vo)

magZideal= (Vomag*R1)/(np.sqrt(abs(Vin)**2 - 2*abs(Vin)*Vomag*np.cos(Votheta)+ Vomag**2))
print('Z_magIdeal = ', magZideal)


phZideal = Votheta - np.arctan2(-Vomag*np.sin(Votheta),(abs(Vin)-Vomag*np.cos(Votheta)))
print(phZideal)


R = magZideal*np.cos(phZideal)
L = magZideal*np.sin(phZideal)/(w)
print('R = ',R,'\t', 'L = ',L)

Solution Summary After recommendation from @ocspro about limiting the max step in the LTSpice simulation to 1n, the results are better, although not 100% correct in the identification of the Rs. Perhaps this is due to the high numerical sensitivity of the cos(phaseZ) around the pi/2...not sure how to address this (see xcorr solution). Anyway, it seems all approaches taken yield similar solutions (except for Xcorr where Rs is negative due to calculated phaseZ being slightly greater than pi/2):

Differentation Method diff_method_sol

Xcorr Method xcorr_method_sol

Least Square Method ls_sqr_sol

FFT Method FFT_method_sol

Ideal Solution

Ideal Soln

jrive
  • 639
  • 4
  • 14
  • Step 1: Can you read the LTspice output file into your Python code and just re-plot it to verify you are reading it correctly. Step 2: Can you estimate the magnitude and phase of $V_i$ and $V_o$ correctly? Only after making sure you can do steps 1 and 2 should you worry about step 3: Use $V_i$ and $V_o$ to estimate the circuit variables $R_s$, $L_s$, and $R_1$. – The Photon Mar 03 '20 at 20:51
  • Also, does your Python code assume the frequency of the signal is known a priori, or do you also have to estimate the frequency from the data? – The Photon Mar 03 '20 at 20:52
  • How many point FFT? Have you experimented with different FFT lengths and seen any trend betweenFFT length and accuracy? –  Mar 03 '20 at 21:00
  • @ThePhoton, yes -- the estimates are all good...it is the accuracy that seems to be the issue. Frequency is known ahead of time. – jrive Mar 03 '20 at 21:29
  • Wait, but now that I read more carefully, I notice you said "I think the problem stems from the fact that I can't seem to accurately determine the amplitudes of Vo, Vi, and the phase delay between them from the processed samples.". So don't jump the gun and ask about the formulas that calculate the component values from the amplitudes and phases. Ask why you aren't estimating the amplitudes and phases accurately. (And if you want useful answers, include your code that estimates the amplitudes and phases) – The Photon Mar 03 '20 at 21:33
  • @BrianDrummond, I"m using the FFT capabilities in LTPSice -- I"m not sure how many points it uses. The other alternative is to export the time domain signals "as is" and then do some cubic spline interpolation or something similar to that to and compare the result I get from that approach to the approach where I rely on the interpolation LTspice does on the data in the process of computing the FFTs. LTSpice claims their method has very high precision, though.....I may try the interpolation approach next. Thank you for your response – jrive Mar 03 '20 at 21:35
  • @The Photon --- that is what I"m asking....And, I would like to share all the code, and the data ---do you know how I can do that? – jrive Mar 03 '20 at 21:36
  • There's a button to format code as code (i.e. with a monospace font, indented and with a different background color) in the question editor. If you post a large block of code it will make a scrollable window. But you should try to focus on the code that causes the actual problem, because volunteers mostly won't read through pages and pages of stuff to get to the real problem. – The Photon Mar 03 '20 at 21:38
  • Ok...I added the code....It's too much i think. It would be better if I could share the Jupyter Notebook --it would be clearer. I'm surprised this StackExchange does not provide an easy way to share this kind of stuff. If I could share my data, and my notebook, it would be easier for people to help, I think.... Thank you for your help so far. – jrive Mar 03 '20 at 21:58
  • @BrianDrummond --I just reread your comment, and I realized that you might have been asking about the cross correlation approach which uses the FFT and inverse FFT. Still, I do not know how many points numpy.fft uses.....I'll look at the documentation for it. Thank you. – jrive Mar 03 '20 at 22:01
  • 1
    "LTSpice does not sample at uniform intervals, so a trick to recover uniformedly sampled data is to take an FFT of the signals, and then take another FFT of the FFT'd signals to recover the time domain data" - that seems like a ridiculously complicated way to solve a simple problem. – Bruce Abbott Mar 04 '20 at 02:03
  • @Bruce Abbot not sure I understand your comment. What is the simple solution for solving the non-uniform time-steps from an LTSpice transient analysis? – jrive Mar 04 '20 at 14:05
  • Extract the zero crossing time of each sine wave by getting the smallest positive and negative values and extrapolating between them. – Bruce Abbott Mar 04 '20 at 14:29
  • You can force LTSpice to output samples at uniform intervals, in the .TRANS setup tab, IIRC it's an entry field called something like 'print every xxx' – Neil_UK Sep 24 '20 at 13:14
  • @Neil_UK -- thank you --I looked and I can't figure out where to set it to uniform sampling per your suggestion. The only modifiers are: UIC, steady, nodiscard, startup, and step. And in the actual trans simulation card, I can only do: stop time, time to start saving data, max step, ext dc start at 0, stop sim when ss is reached, step the load current source, and skip initial operating point solution. I'm really interested in this, so if you can figure it out, please let me know. Thank you. – jrive Sep 26 '20 at 17:38
  • @jrive Hmm. I'm convinced it used to work, but it doesn't appear to work now. See this question that I answer, I seem to be very sure there. LTSpice IV does not have this facility. LTSpice XVII claims to, but it's only settable in text, not in the edit simulation GUI, and then it doesn't appear to work now. I'd recommend interpolating the output file to your desired time steps, that will work. I'll carry on experimenting for a bit, it would be useful. – Neil_UK Sep 26 '20 at 18:29
  • @jrive I've now come across this in my searches and see where you're getting your FFT process from, it wasn't clear from your OP that it was internal to LTSpice, or was a documented trick. Do check that the time data exported from this 2xFFT trick is consistent with the time data exported normally, things can go wrong with FFT implementations (excel xy scatter plot automatically interpolates). Also, read through the other answers to the question linked in my previous comment, ltsputil seems worth a look. – Neil_UK Sep 26 '20 at 18:37
  • @jrive I used to use a different spice, Simetrix, which definitely had this facility, called print_step. However, the free version was crippleware, limited to 50 nodes, which was enough for one opamp model, but not two, so completely unscalable, so I switched to LT. It's likely that I never tried to use it in LTSpice, andgot confused between the two simulators. If you read the LTwiki it says the step parameter is of no use. It's a pity in the 2 FFT trick, it won't give you the interpolated data without doing the FFTs! – Neil_UK Sep 26 '20 at 19:24
  • @Bruce Abbot, I still don't understand how extrapolating between the zero crossings gives me uniform sampling..... – jrive Aug 05 '21 at 01:16
  • With pure sine sine waves in a linear circuit all you need to determine phase is the zero crossing points. But you may not have samples exactly on the zero point. So take two consecutive samples, one on one side and the other on the other side of zero, extrapolate linearly between them (almost no difference from a sine wave at this small distance) and find the time at which that line crosses zero. This is much more accurate than trying to measure phase using the peaks of the sine waves. – Bruce Abbott Aug 05 '21 at 05:21

2 Answers2

2

Maybe the accuracy error lies in the number of simulation points? Using the built-in fft-function in LTspice (.four 1000k 1 -1 V(Vi) V(Vo)), I got large discrepancies in magnitude and phase by running your simulation with standard simulation settings.

By decreasing the maximum time step, I managed to get magnitude and phase shift almost identical to the ideal solution

Comparison of max step of 1 ns vs none:

  • Vi = .9999 vs .9803
  • Vo = .7883 vs .7722
  • phase shift = .6625 vs .6475

Screenshot of circuit and log

Alternatively, one can decrease the relative tolerance to force shorter time steps. The automatic stepping is probably rather large due to the simple and linear circuit.

ocspro
  • 400
  • 1
  • 10
  • thank you. Much better. I still get an error in the identified Rs, but much closer than before: Rs_ident =0.204, Ls_ident =.204., using the Least squares method:Vi = [0.99996636] Vo = [0.78834297] Phase Dly = [0.6624794] Zmag = [1281.7480386] Zph = [1.5706335] Rs = [0.20870581] Ls = [0.000204]. Xcorr method doesn't work with the new data, I'm not sure why yet...the phase delay goes crazy. – jrive Mar 04 '20 at 14:41
1

In LTspice, a commonly forgotten aspect with using inductors is its default series resistance of 1 mΩ.
Make sure to explicitly set it to zero to get even better matching results!

enter image description here

Huisman
  • 10,694
  • 2
  • 20
  • 40