Parsing LTspice output

  1. Output of the AC analysis
  2. Output of any analysis with a scan on parameters

1. Output of the AC analysis

When doing AC analysis LTspice output text files are presented with 2 columns tab ('\t') separated:

  • frequency
  • voltage (real and imaginary part, comma separated)

This is how the file is organized:

Freq.   V(n002)
1.00000000000000e+001   7.89537182408547e-005,1.25658745335168e-002
1.01157945425990e+001   8.07927122987158e-005,1.27113688143752e-002
1.02329299228075e+001   8.26745385298576e-005,1.28585474288659e-002
1.03514216667934e+001   8.46001944587697e-005,1.30074298662202e-002
1.04712854805090e+001   8.65707008374969e-005,1.31580358408370e-002
[...]

A direct reading with numpy loadtxt is possibile importing the line as a string and then creating the 2 arrays

In [1]:
import numpy as np
import matplotlib.pyplot as plt
In [2]:
data = np.loadtxt('passaalto_ltspice_ac.txt', skiprows=1, dtype=str)

# create:
# - the freq array from the first column
# - the v array (of complex) manipulating the second column

freq = data[:,0].astype(float)
v = np.array([complex(float(s.split(',')[0]),float(s.split(',')[1])) for s in data[:,1]])

After that, gain and phase values are ready (e.g. for plotting):

In [3]:
# defining a function to be reused
def plot(freq,v):
    fig, ax1 = plt.subplots(dpi=100)
    ax1.set_xscale('log')
    ax2 = ax1.twinx()
    ax1.plot(freq, v.real, label='amplitude', color='r')
    ax2.plot(freq, np.arctan(v.imag/v.real)/np.pi*180, label='phase', color='b')
    ax1.set_xlabel('Frequency [Hz]')
    ax1.set_ylabel('Voltage [V]', color='r')
    ax2.set_ylabel('Phase [deg.]', color='b')
    ax1.legend(loc='center left', frameon=False)
    ax2.legend(loc='center right', frameon=False)
    return 

plot(freq,v)

Alternative solutions

Just to see how things can be done differently

1. Parsing the file line by line

In [4]:
freq = []
v = []

# opening the text file, process line by line
with open('passaalto_ltspice_ac.txt') as f:
    for i,l in enumerate(f.readlines()):
        # skip the header
        if i>0:
            # each line is a string, parse it filling other string variables
            col1 = l.rstrip().split('\t')[0]  # string with the frequency
            col2 = l.rstrip().split('\t')[1]  # string with real and imaginary part
            col_r = col2.split(',')[0]        # real
            col_im= col2.split(',')[1]        # imag
            # cast the values and fill the lists
            freq.append(float(col1))
            v.append(complex(f'{col_r}+{col_im}j'))
            
# create numpy arrays
freq = np.array(freq)
v = np.array(v)
In [5]:
plot(freq,v)

2. Using genfromtxt and a converter function

In [6]:
# This "dictionary of functions" define a set of rules to be applied column by column
conv_function = {0: lambda s:float(s),  # this is simply casting the string to a float (1st field of the file)
                 1: lambda s: complex(s.decode().split(',')[0]+'+'+s.decode().split(',')[1]+'j') # the 2nd field has to be parsed and cast to a complex
                }

# the genfromtxt is driven by the "converters" on how to properly cast the found strings to correct data types (1 float and 1 complex)
data = np.genfromtxt('passaalto_ltspice_ac.txt', delimiter='\t',skip_header=1,converters=conv_function)

freq, v = data['f0'], data['f1']  # f0 and f1 are default names for the structured array which has been created by the genfromtxt method
In [7]:
plot(freq,v)

2. Output of any analysis with a scan on parameters

In [8]:
import numpy as np
import matplotlib.pyplot as plt

When doing analysis scanning on some paramter LTspice output appends each step data to the same file.

For instance, in the case of a scan on a "Rl" parameter (R load) with 2 signals in the file (Vin, Vout):

time    V(n002) V(n003)
Step Information: Rl=470  (Run: 1/5)
0.000000000000000e+000  0.000000e+000   -6.369808e-001
1.562499990503952e-010  9.817477e-007   -6.369802e-001
2.499999984806323e-009  1.570796e-005   -6.369672e-001
1.683874996888335e-005  1.056037e-001   -5.432294e-001
[...]
3.997307499968883e-003  -1.691667e-002  -6.513905e-001
4.000000000000000e-003  -9.797175e-016  -6.369818e-001
Step Information: Rl=1K  (Run: 2/5)
0.000000000000000e+000  0.000000e+000   -6.589904e-001
6.249999962015806e-010  3.926991e-006   -6.589870e-001
[...]

Moreover, the number of lines per step (sampled data) changes from one step to another!

Therefore, the file has to be parsed line by line to get these numbers before importing into usable arrays

In [9]:
filename = 'transistor2_clipping2.txt'

###  Parsing on the file line by line
#   The number of lines-per-step is stored into one list,
#   During the loop the piece of string containing the paramter values is stored into another list

current_step=-1
step_row_count=[]
step_param_values=[]
with open(filename) as f:
    for l in f.readlines():
        if l.split(' ')[0]=='Step':
            current_step += 1
            step_row_count.append(0)
            step_param_values.append(l.split(' ')[2])
        if current_step>-1 and l.split(' ')[0]!='Step':
            step_row_count[current_step] += 1
# Results    
print('Lines for each step:',step_row_count)
print('Params values:',step_param_values)
Lines for each step: [448, 492, 441, 339, 327]
Params values: ['Rl=470', 'Rl=1K', 'Rl=2K', 'Rl=5K', 'Rl=10K']

Now the file can ben scanned used loadtxt iteratively in order to import the data into different arrays

In [10]:
data_list=[]

for i,nrow in enumerate(step_row_count):
    # compute exact number of lines to be skipped and to be imported
    lines_to_be_skipped = 2 + sum(step_row_count[0:i]) + i
    lines_to_be_read    = step_row_count[i]
    data_list.append(np.loadtxt(filename, delimiter='\t', skiprows=lines_to_be_skipped, max_rows=lines_to_be_read ))

The data_list list contains one numpy array for each step

In [11]:
plt.subplots(figsize=(12,6),dpi=100)

for i,data in enumerate(data_list):
    plt.plot(data[:,0],data[:,1])
    plt.plot(data[:,0],data[:,2], label=step_param_values[i])
    
plt.legend()
plt.show()