Triode Modeling

  • Model Parameter Extraction
  • Model Parameter Verification

This experiment uses data extracted from a vacuum tube datasheet and scipy.optimize to calculate the Child-Langmuir parameters used for circuit simulation.

$$I_a = K (V_{gk} + D_aV_{ak})^{3/2}$$

We are going to use curve fitting to determine $$K \text{ and } D_A$$

Then we can use Leach's triode SPICE model.


In [14]:
import scipy
from scipy.optimize import curve_fit
import numpy as np
import matplotlib.pyplot as plt
import math

Starting with the Philips ECC83 data sheet, create a PNG of the import this image into engauge

Create 9 curves then use 'curve point tool' to add points to each curve

Change export options, "Raw Xs and Ys" and "One curve on each line", otherwise engauge will do some interrupting of your points export a csv file


In [15]:
%cat data/ecc83-philips-1954.csv


x,Curve1
2.26,0.0001626
9.41,0.0003321
14.94,0.0004691
22.1,0.0005997
28.27,0.0007562
38.35,0.0009455
46.82,0.0010698
54.63,0.001207
65.69,0.0013899
77.07,0.0016054
86.5,0.0018142
103.09,0.0021795
124.87,0.0026818
146.98,0.0031906
166.16,0.0036406
183.06,0.0040515
199.96,0.0044558
216.87,0.0048537

x,Curve2
7.17,3.97e-05
14.66,9.24e-05
23.13,0.0001582
31.28,0.0002175
38.11,0.0003156
43.65,0.0003747
53.09,0.0004795
61.23,0.0005908
71.64,0.0007412
80.76,0.000872
97.35,0.0011335
113.62,0.0014144
129.88,0.0017278
147.77,0.0021063
163.37,0.0024585
180.61,0.0028564
195.56,0.0031956
208.89,0.0035152
223.84,0.0038674
236.85,0.0041869
249.19,0.0045064

x,Curve3
12.39,-1.82e-05
24.46,-4e-06
30.65,9.7e-06
36.84,2.98e-05
43.69,5.65e-05
50.53,8.97e-05
61.94,0.0001558
74.32,0.0002285
82.46,0.0002878
88,0.0003598
101.02,0.0004845
113.39,0.0006417
125.11,0.0007922
136.5,0.0009817
153.42,0.0012692
177.81,0.0017587
201.23,0.0022417
223.67,0.0027245
246.75,0.0032464
268.54,0.0037162

x,Curve4
8.15,-1.21e-05
42.39,-1.51e-05
67.5,-1.9e-05
80.87,8.4e-06
99.44,0.0001142
114.76,0.0002002
130.72,0.0003123
144.4,0.0004371
162.31,0.0006208
175.98,0.000791
188.02,0.0009871
214.05,0.0014378
232.92,0.0018099
252.75,0.0022211
268.04,0.0025603
281.69,0.0028734
296.97,0.0032257

x,Curve5
4.24,6.9e-06
47.94,-8e-06
91.3,3e-06
112.17,-1.3e-06
130.43,7.2e-05
142.81,0.0001057
152.91,0.0001587
165.3,0.000212
174.42,0.0002844
188.43,0.0003832
201.13,0.0004949
227.17,0.0008288
249.3,0.0011882
272.4,0.0015997
291.26,0.0019848
306.87,0.0023436
321.83,0.0026503

x,Curve6
6.85,7e-07
54.46,5.7e-06
104.35,-2.1e-06
143.15,1.9e-06
167.61,1.74e-05
183.91,3.86e-05
191.4,9.13e-05
208.02,0.0001645
220.73,0.0002567
233.76,0.0003684
245.48,0.0004736
266.97,0.0007096
285.2,0.0009972
304.07,0.0013173
323.26,0.001644
338.55,0.0019637

x,Curve7
4.57,5e-07
77.28,8e-06
120,-5e-07
175.11,-1.43e-05
201.52,8e-06
221.73,3.6e-05
235.42,0.0001154
252.04,0.000182
262.79,0.0002741
277.12,0.0003794
294.06,0.00055
307.74,0.0006878
316.2,0.0008056
335.4,0.0010738
353.3,0.0013419
369.9,0.0016099

x,Curve8
15.33,-4.9e-06
100.11,-2.6e-06
157.5,-3.1e-06
191.09,1.34e-05
234.78,-1.46e-05
253.37,2.63e-05
262.82,4.68e-05
274.23,7.39e-05
285.31,0.0001206
296.39,0.0001801
304.86,0.0002265
317.24,0.0003122
325.06,0.0003714
341.99,0.000555
357.95,0.000719
371.3,0.0009022

x,Curve9
15.65,-4.9e-06
80.22,1.8e-06
136.96,1.2e-06
192.72,5e-07
243.59,-7e-07
283.37,-3e-06
295.76,4.8e-06
305.21,5.12e-05
316.29,7.83e-05
327.05,0.0001249
335.85,0.0001778
346.6,0.0002503
357.03,0.0003033
364.19,0.0003495
372.99,0.0004089

Need to create scipy array like this

x = scipy.array([[0,150],[-3.0,300],[-1.0,200],[-2.0,250]])

y = scipy.array([3.2e-3,0.6e-3,2.2e-3,1.2e-3])

from the extracted curves


In [16]:
fname = "data/ecc83-philips-1954.csv"
f = open(fname,'r').readlines()

deltaVgk = -0.5

VgkVak = []
Iak    = []
Vaks   = []

for l in f:
    l = l.strip()
    if len(l): # skip blank lines
        if l[0] == 'x':
            vn = float(l.split("Curve")[1]) - 1.0
            Vgk = vn * deltaVgk
            continue
        else:
            (Vak,i) = l.split(',')
        VgkVak.append([float(Vgk),float(Vak)])
        Iak.append(float(i))
        Vaks.append(float(Vak))

x = scipy.array(VgkVak)
y = scipy.array(Iak)
Vaks = scipy.array(Vaks)

In [17]:
%matplotlib inline
exp = 1.5

def func(x,K,Da):
    rv = []
    for VV in x:
        Vgk = VV[0]
        Vak = VV[1]
        t = Vgk + Da * Vak

        if t > 0:
            Ia = K * t**exp
        else:
            Ia = 0
        # print "IaCalc",Vgk,Vak,t,Ia
        rv.append(Ia)

    return rv

popt, pcov = curve_fit(func, x, y,p0=[0.001,0.01])
#print popt,pcov

(K,Da) = popt

print "K=%.8f   Da=%.8f"%(K,Da)


K=0.00126098   Da=0.01179251

In [18]:
def IaCalc(Vgk,Vak):
    t = Vgk + Da * Vak
    if t > 0:
        Ia = K * t**exp
    else:
        Ia = 0
    # print "IaCalc",Vgk,Vak,t,Ia
    return Ia

Vgk = np.linspace(0,-5,11)
Vak = np.linspace(0,400,21)

vIaCalc = np.vectorize(IaCalc,otypes=[np.float])

Ia = vIaCalc(Vgk[:,None],Vak[None,:])

plt.figure(figsize=(12,6))

for i in range(len(Vgk)):
    plt.plot(Vak,Ia[i],label=Vgk[i])

plt.scatter(Vaks,y,marker="+")
plt.legend(loc='upper left')
plt.suptitle('ECC83 Child-Langmuir Curve-Fit K/Da Model (Philips 1954)', fontsize=14, fontweight='bold')
plt.grid()
plt.ylim((0,0.005))
plt.xlim((0,400))
#plt.savefig("ecc83-philips-1954-curvefit.png")
plt.show()


Trying the Koren's triode phenomenological model.

$$E_1 = \frac{E_P}{k_P} log\left(1 + exp^{k_P (\frac{1}{u} + \frac{E_G}{\sqrt{k_{VB} + {E_P}^2}})}\right)$$$$I_P = \frac{{E_1}^X}{k_{G1}} \left(1+sgn(E_1)\right)$$

Need to fit $X, k_{G1}, k_P, k_{VB}$

SPICE Model
see http://www.normankoren.com/Audio/Tubemodspice_article_2.html#Appendix_A
.SUBCKT 12AX7 1 2 3 ; P G C; NEW MODEL (TRIODE) 
+ PARAMS: MU=100 EX=1.4 KG1=1060 KP=600 KVB=300 RGI=2000 
E1 7 0 VALUE={V(1,3)/KP*LOG(1+EXP(KP*(1/MU+V(2,3)/SQRT(KVB+V(1,3)*V(1,3)))))} 
G1 1 3 VALUE={(PWR(V(7),EX)+PWRS(V(7),EX))/KG1}

In [19]:
mu = 100

def sgn(val):
    if val >= 0:
        return 1
    if val < 0:
        return -1
    
def funcKoren(x,X,kG1,kP,kVB):
    rv = []
    for VV in x:
        EG = VV[0]
        EP = VV[1]
        
        E1 = (EP/kP) * math.log(1 + math.exp(kP*(1/mu + EG / math.sqrt(kVB + EP*EP)))) 
        if E1 > 0:
            IP = (math.pow(E1,X)/kG1)*(1 + sgn(E1))
        else:
            IP = 0
        rv.append(IP)

    return rv

popt, pcov = curve_fit(funcKoren,x,y,p0=[1.3,1000,610,305])
#print popt,pcov

(X,kG1,kP,kVB) = popt

print "X=%.8f   kG1=%.8f   kP=%.8f   kVB=%.8f"%(X,kG1,kP,kVB)

# koren's values 12AX7 mu=100 X=1.4 kG1=1060 kP=600 kVB=300


X=1.25013759   kG1=191.63052993   kP=249.11435609   kVB=24953.24402839

In [20]:
'''
X=1.4
kG1=1060
kP=600
kVB=300
mu=100
'''

def IaCalcKoren(Vgk,Vak):
    global X,kG1,kP,kVB,mu
    E1 = (Vak/kP) * math.log(1 + math.exp(kP*(1/mu + Vgk / math.sqrt(kVB + Vak*Vak)))) 
    Ia = (math.pow(E1,X)/kG1)*(1 + sgn(E1))
    return Ia

Vgk = np.linspace(0,-5,11)
Vak = np.linspace(0,400,21)

vIaCalcKoren = np.vectorize(IaCalcKoren,otypes=[np.float])

Ia = vIaCalcKoren(Vgk[:,None],Vak[None,:])

plt.figure(figsize=(12,6))

for i in range(len(Vgk)):
    plt.plot(Vak,Ia[i],label=Vgk[i])

plt.scatter(Vaks,y,marker="+")
plt.legend(loc='upper left')
plt.suptitle('ECC83 Child-Langmuir Curve-Fit Koren Model (Philips 1954)', fontsize=14, fontweight='bold')
plt.grid()
plt.ylim((0,0.005))
plt.xlim((0,400))
#plt.savefig("ecc83-philips-1954-curvefit.png")
plt.show()



In [ ]: