Notes
@nosent pyspice.py
<< head docstring>>
<< head >>
<< copyright >>
<< release notes >>
v0.2
v0.1
structures
todo
<< global imports >>
<< global vars >>
Option processing
options
classes
base classes
class SpiceElement
__init__
__str__
drop
class Passive2NodeElement
__init__
__str__
drop
class Active2NodeElement
__init__
__str__
class Active4NodeElement
__init__
__str__
element classes
class CommentLine
__init__
class ControlElement
__init__
class Capacitor
__init__
isparallel
combine
class Inductor
__init__
isparallel
combine
class Mosfet
__init__
__str__
isparallel
combine
class Resistor
__init__
class Vsource
__init__
class Isource
__init__
helpers
unit
debug
info
warning
drop_2node
write_2node
read_netlist
<< docstring >>
<< imports >>
class ElementError
__init__
__str__
classify
main
@
These classes are used to break down the spectrum of SPICE elements
into 'classes' of elements. I.e. 2 node passives, 2-node sources, 4-node sources,
and so on. The elements found in a real netlist are based on these types.
@c
@
This is a(n incomplete) definition of the various SPICE elements.
@c
class Active2NodeElement(SpiceElement):
"""Base class for active 2-node elements.
Assumes SPICE element line:
xXXX n1 n2 value p1=val p2=val ...
Inherits:
None
Redefines:
None
"""
@others
def __init__(self,line,num):
if isinstance(line,str):
line=line.split()
self.line=line
self.type='active2'
self.num=num
self.n1=_current_scope+line[1]
self.n2=_current_scope+line[2]
self.value=unit(line[3])
self.param=dict() #store x=y as dictionary
for p in line[4:]:
k,v=p.split('=')
self.param[k]=unit(v)
def __str__(self):
s=StringIO()
print>>s, self.line[0],self.n1,self.n2,self.value,
for k,v in self.param.iteritems():
#are there instances when 0 is (in)significant?
#if v==0: continue
print>>s, k+'='+str(v),
return wrapper.fill(s.getvalue())
class Vsource(Active2NodeElement):
"""Assumes SPICE element line:
vXXX n1 n2 value p1=val p2=val ...
"""
@others
def __init__(self,line,num):
Active2NodeElement.__init__(self,line,num)
self.type='vsource'
class Isource(Active2NodeElement):
"""Assumes SPICE element line:
iXXX n1 n2 value p1=val p2=val ...
"""
@others
def __init__(self,line,num):
Active2NodeElement.__init__(self,line,num)
self.type='isource'
class Active4NodeElement(SpiceElement):
"""Base class for active 4-node elements (xCyS).
Assumes SPICE element line:
xXXX n1 n2 value p1=val p2=val ...
Inherits:
None
Redefines:
None
"""
@others
def __init__(self,line,num):
if isinstance(line,str):
line=line.split()
self.line=line
self.type='active4'
self.num=num
self.n1=_current_scope+line[1]
self.n2=_current_scope+line[2]
self.n3=_current_scope+line[3]
self.n4=_current_scope+line[4]
self.value=unit(line[5])
self.param=dict() #store x=y as dictionary
for p in line[6:]:
k,v=p.split('=')
self.param[k]=unit(v)
def __str__(self):
s=StringIO()
print>>s, self.line[0],self.n1,self.n2,self.n3,self.n4,self.value,
for k,v in self.param.iteritems():
#are there instances when 0 is (in)significant?
#if v==0: continue
print>>s, k+'='+str(v),
return wrapper.fill(s.getvalue())
Release Notes, changelog, whatever it turns out as...
-----------------------------------------------------
@others
pyspice v0.1:
-------------
Initial release.
Only worked for netlist containing MOSFETs and Capacitors.
pyspice.py v0.2:
----------------
-At least default (pass through) handling of all element types.
NOTE: For combining, this uses a global node name scheme. In other
words: subcircuits, libraries, etc. are not in a separate node
namespace as they should be, beware.
-Changed structure of classes (in LEO), there are base classes that contain
common attributes and element classes that define the specific behavior.
-This version _should_ work with any netlist and only touch M's and C's, YMMV.
-Work is ongoing on the class structure and most important IMO is getting netlist
hierarchy implemented.
"""
<< head >>
<< copyright >>
<< release notes >>
@others
"""
@nocolor
class SpiceElement:
"""Base class for SPICE elements.
Methods:
__init__(self,line,num) -> SpiceElement
__str__(self) -> string spice line
drop() -> False
"""
@others
def __init__(self,line,num=None):
"""SpiceElement constructor
line - netlist expanded line
type - first 'word'
num - netlist line number (for keeping roughly the same
order when printing modified netlist)
"""
#accept lists of 'words' also; BE CAREFUL with this, though
if isinstance(line,list):
line=' '.join(line)
self.line=line
self.type=line[0]
self.num=num
def __str__(self):
return wrapper.fill(self.line)
def drop(self,val=0,mode='<'):
"""Template for dropping elements that defaults to NO if
not overidden in the element class"""
return False
class CommentLine(SpiceElement):
"""SPICE Comment line (/^\*.*/)
"""
@others
def __init__(self,line,num):
SpiceElement.__init__(self,line,num)
self.type='comment'
class ControlElement(SpiceElement):
"""Control statement object, no processing for now.
Note: this will eventially be a base class for the real control elements
Note: currently has no knowledge of blocks (.lib/.endl, .subckt/.ends)
has only ONE node namespace, make sure subckt's have unique node names!
"""
@others
def __init__(self,line,num):
SpiceElement.__init__(self,line,num)
self.type='control'
class Passive2NodeElement(SpiceElement):
"""Base class for 2-node elements.
Assumes SPICE element line:
xXXX n1 n2 value p1=val p2=val ...
Inherits:
None
Redefines:
drop(self,val,mode) -> bool
"""
@others
def __init__(self,line,num):
if isinstance(line,str):
line=line.split()
self.line=line
self.type='passive2'
self.num=num
self.n1=_current_scope+line[1]
self.n2=_current_scope+line[2]
self.value=unit(line[3])
self.param=dict() #store x=y as dictionary
for p in line[4:]:
k,v=p.split('=')
self.param[k]=unit(v)
def __str__(self):
s=StringIO()
print>>s, self.line[0],self.n1,self.n2,self.value,
for k,v in self.param.iteritems():
#are there instances when 0 is (in)significant?
#if v==0: continue
print>>s, k+'='+str(v),
return wrapper.fill(s.getvalue())
def drop(self,val=0.0,mode='<'):
"""Indicate whether to drop the element from the list.
Occurs iff (val 'mode' self.value)
Can this be converted to specifying an arbitrary binary function?
This may allow a more elegant comparison.
e.g. mode=< instead of mode='<' or mode=cap_smaller(x,y)
"""
if mode=='<':
if self.value<val: return True
elif mode=='<=':
if self.value<=val: return True
elif mode=='>':
if self.value>val: return True
elif mode=='>=':
if self.value>=val: return True
else:
return False
return False #shouldn't get here, but...
class Capacitor(Passive2NodeElement):
"""Assumes SPICE element line:
cXXX n1 n2 value p1=val p2=val ...
Provides:
isparallel(other)
combine(other)
"""
@others
def __init__(self,line,num):
Passive2NodeElement.__init__(self,line,num)
self.type='capacitor'
def isparallel(self,other):
"""Returns True if instance is parallel with other instance
"""
if self.n1==other.n1 and self.n2==other.n2:
return True
elif self.n1==other.n2 and self.n2==other.n1:
return True
else:
return False
def combine(self,other):
"""Adds values if capacitors are in parallel, returns True if
it combined them.
NOTE: Does not currently touch param dictionary when combining,
just the values. How should this be done? Maybe combine iff
params are identical to avoid problems?
"""
global _ncombine_capacitors
if self.isparallel(other):
self.value+=other.value
_ncombine_capacitors+=1
return True
else:
return False
class Mosfet(SpiceElement):
"""Mosfet constructor takes an array derived from the
netlist line
"""
@others
def __init__(self,line,num):
if isinstance(line,str):
line=line.split()
self.line=line
self.type='mosfet'
self.num=num
self.d=line[1]
self.g=line[2]
self.s=line[3]
self.b=line[4]
self.model=line[5]
self.param=dict()
for p in line[6:]:
k,v=p.split('=')
self.param[k]=unit(v)
self.w=self.param['w']
self.l=self.param['l']
def __str__(self):
s=StringIO()
print>>s, self.line[0], self.d, self.g, self.s, self.b, self.model,
for k,v in self.param.iteritems():
if v==0: continue
print>>s, k+'='+str(v),
return wrapper.fill(s.getvalue())
def isparallel(self,other):
"""Returns True if transistors are parallel
"""
# check gate, substrate, and model first
if self.g==other.g and self.b==other.b and self.model==other.model:
#source and drain can be reversed
if self.d==other.d and self.s==other.s:
return True
elif self.d==other.s and self.s==other.d:
return True
else:
return False
else:
return False
def combine(self,other):
"""Combines adds other to self iff the transistors are identical,
will NOT combine if W/L is different. Parameter 'M' is incremented on
self, other is left alone.
Returns True if it combined the transistors.
Increments global _ncombine_mosfets for information.
NOTE: This currently merely adds the parameters (except w, l, and m)
without regard to their meaning. Here is the place to specially handle
certain FET parameters. Average certain parameters?
"""
global _ncombine_mosfets
if self.isparallel(other):
#combine iff W/L (for original FET) is same also
if self.w==other.w and self.l==other.l:
for k,v in other.param.iteritems():
if k=='w' or k=='l' or k=='m': continue
self.param[k]+=v
if ('m' in self.param.keys()) or ('m' in other.param.keys()):
#add other's M parameter or increment
self.param['m']+=other.param.get('m',1)
else:
self.param['m']=2
_ncombine_mosfets+=1
return True
else:
return False
class Resistor(Passive2NodeElement):
"""Assumes SPICE element line:
rXXX n1 n2 value p1=val p2=val ...
"""
@others
def __init__(self,line,num):
Passive2NodeElement.__init__(self,line,num)
self.type='resistor'
def info(message):
"""Print information to stderr."""
print>>stderr,'Info:',message
def debug(message):
"""Print debugging info to stderr."""
print>>stderr,'Debug:',message
def warning(message,elm=None,num=None):
"""Print warning to stderr. If elm and num defined,
print different message"""
if elm and num:
message=_opt.infile+":"+str(num)+" '"+elm+\
"' type not defined yet, passing through..."
print>>stderr,'Warning:',message
def drop_2node(elm,val,mode='<',type=None, verbose=True):
"""Drop 2-node elements in elm list according to val.
mode = '<' | '>'
type - print info on what and how many it dropped
Note: only works correctly for capacitors for now
"""
new_elm=[]
val=float(val)
for i in elm:
if mode=='<' and i.value>=val:
new_elm.append(i)
elif mode=='>' and i.value<=val:
new_elm.append(i)
else:
continue
#Print info about how many it dropped
if verbose:
infostr='Dropped '+str((len(elm)-len(new_elm)))+' '
if type and type[-1]=='s':
info(infostr+type)
elif type:
info(infostr+type+'s')
else:
info(infostr+'elements')
return new_elm
##
# write 2-node SPICE elements to file
# node pair is specified by key in dict:
# "node0,node1"
##
#NOTE:
# this function is dying a slow, painful death. Each element is
# getting its own custom __str__() method for writing to netlists.
#
def write_2node(elm, type=None, ofp=sys.stdout, comment=None):
"""Write 2-node SPICE elements to file
elm - dictionary of elements
type - SPICE element name
ofp - output file pointer
comment - comment string at head of elm list
Note: "type" must begin with a SPICE element letter, the rest is printed
as identifying information, e.g. linductor, capacitor, mosfet.
"""
if not type:
raise SyntaxError('Must define a SPICE element name')
i=1
if comment:
print>>ofp,'\n**\n* '+comment+'\n**'
for k,v in elm.iteritems():
node=k.split(',')
print>>ofp, type[0]+str(i).rjust(3,'0'),node[0],node[1],v
i+=1
infostr='Wrote '+str(i)+' '+type
if type[-1]=='s':
info(infostr)
else:
info(infostr+'s')
##
# Read netlist from open file object
# -make sure if reading from stdin to read only once and
# use this function to make sure you read the entire netlist
##
def read_netlist(fname):
<< docstring >>
<< imports >>
if isinstance(fname,file):
ifp=fname
else:
ifp=open(fname,'rU')
# netlist=ifp.readlines()
nline=0
lines=[] #raw expanded netlist
#
re_param=re.compile(r"(\S*)\s*=\s*(\S*)")
for line in ifp:
line=line.strip('\r\n')
#pass through empty lines
if not len(line.split()):
#convert empty line to comment as a placeholder
lines.append('*')
nline+=1
continue #next please...
#pass through comments, they stay asis
elif line[0]=='*':
lines.append(line)
nline+=1
continue #next please...
#case is unimportant in SPICE
line=line.lower()
#remove whitespace in parameter assignments
# to prepare for x.split(' ') that happens later:
# 'as = 3e-12' => 'as=3e-12'
line=re.sub(re_param,r'\1=\2',line)
if line[0]!='+': #beginning of SPICE line
lines.append(line)
nline+=1
else: #line continuation
line=line[1:]
lines[-1]=lines[-1]+line
return lines
class ElementError(LookupError):
@others
def __init__(self,elm='???'):
self.elm=elm
def __str__(self):
return str('No class defined for this element: '+self.elm)
def classify(net):
"""Reads expanded netlist and classifies each line,
calls appropriate function to combine nodes.
net - list of unwrapped SPICE netlist lines
"""
# Is there a better (faster) way of doing this? Maybe generating a dict
# of handler functions and calling based on the first character. At any
# rate, it would make the classification a constant-time operation.
import string
elements=dict()
global _counts
#initialize to all element types
for ch in '*.'+string.lowercase:
elements[ch]=[]
_counts[ch]=0
num=-1 #line counter
for line in net:
num+=1
arr=line.split()
x=arr[0][0]
# Comment
if x=='*':
elements[x].append(CommentLine(line,num))
_counts[x]+=1
# Control line
elif x=='.':
elements[x].append(ControlElement(line,num))
_counts[x]+=1
elif x=='a':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='b':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
# Capacitor
elif x=='c':
elm=Capacitor(line,num)
#don't combine if it's the first encountered
if _counts[x]==0:
elements[x].append(elm)
else:
#combine if parallel or add if unique
if elements[x][-1].combine(elm):
pass
else:
elements[x].append(elm)
_counts[x]+=1
elif x=='d':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='e':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='f':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='g':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='h':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
# Current Source
elif x=='i':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='j':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='k':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
# Inductor
elif x=='l':
elm=Inductor(line,num)
#don't combine if it's the first encountered
if _counts[x]==0:
elements[x].append(elm)
else:
#combine if parallel or add if unique
if elements[x][-1].combine(elm):
pass
else:
elements[x].append(elm)
_counts[x]+=1
# Mosfet
elif x=='m':
elm=Mosfet(line,num)
if _counts[x]==0:
#don't combine if it's the first encountered
elements[x].append(elm)
mosfets.append(elm)
else:
#search list backwards to find a parallel one
#in case they aren't adjacent, faster?
i=len(elements[x])-1
while i>=0:
if elements[x][i].combine(elm):
break
else:
i-=1
#found unique MOSFET
if i==-1:
elements[x].append(elm)
_counts['m']+=1
elif x=='n':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='o':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='p':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='q':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
# Resistor
elif x=='r':
elm=Resistor(line,num)
elements[x].append(elm)
_counts['r']+=1
elif x=='s':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='t':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='u':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
# Voltage Source
elif x=='v':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='w':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
# Subcircuit
elif x=='x':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='y':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='z':
elements[x].append(SpiceElement(line,num))
warning('',elm=arr[0],num=num)
_counts[x]+=1
elif x=='+':
raise Error('No line continuations (+) allowed.')
else:
raise Error('Encountered unknown element: '+line)
#add 'special' elements to netlist
#debugging/information header
print>>ofp,'* Input Element counts'
for k,v in _counts.iteritems():
if v!=0: print>>ofp, '*',k,'-',v
#return classified netlist
return elements
##
# Fancy option processing
##
def options(args=sys.argv):
"""Define options and parse argument list.
"""
global ifp, ofp
usage="""%prog [options]"""
desc="""This script reads a SPICE input files and processes it according
to the options given. It is especially useful for processing netlists
from the output of layout extractors by combining parallel C's and
FET's. More features added upon request."""
parser = OptionParser(usage=usage,description=desc)
parser.add_option('-i','--infile',dest='infile',default='stdin',
help='Input SPICE file to be processed'
', (default: %default)')
parser.add_option('-o','--outifle',dest='outfile',default='stdout',
help='Output file for changes'
', (default: %default)')
parser.add_option('-d','--dropcap',dest='dropcap',
default='10', metavar='X',
help='Drop all capacitors smaller than X fF'
', (default: %default)')
parser.add_option('-v','--verbose',dest='v',action='store_true',
default=True,
help='Show info and debugging messages (default)')
parser.add_option('-q','--quiet',dest='v',action='store_false',
help='Suppress all messages on stderr')
parser.add_option('-w','--linewidth',dest='linewidth', default=75,
help='Max. line width for netlist (default: %default)')
(opt, args) = parser.parse_args()
#infile
if opt.infile=='stdin':
ifp=sys.stdin
if opt.v: info('Input: stdin')
else:
try:
ifp=open(opt.infile,'rU') #python's universal line ending mode
if opt.v: info('Input: '+opt.infile)
except IOError, (errno,strerror):
print>>stderr, "IOError(%s): %s '%s'" % (errno,strerror,opt.infile)
#outfile
if opt.outfile=='stdout':
ofp=sys.stdout
if opt.v: info('Output: stdout')
else:
try:
ofp=open(opt.outfile,'w')
if opt.v: info('Output: '+opt.outfile)
except IOError, (errno,strerror):
print>>stderr, "IOError(%s): %s '%s'" % (errno,strerror,opt.infile)
#dropcap
opt.dropcap=float(opt.dropcap)
opt.dropcap=opt.dropcap*1e-15
if opt.v: info('Dropping caps < '+str(opt.dropcap)+' F')
#return the option object
return opt
####
#
# test code
#
####
def main():
global _opt
opt = options()
_opt=opt
import textwrap
#set line continuation style and max width
wrapper=textwrap.TextWrapper(subsequent_indent='+ ',width=opt.linewidth)
print>>ofp,"* pyspice.py: by Dan White <etihwnad at gmail dot com>"
print>>ofp,"* mail me bug reports, fixes, and comments if you find this useful"
print>>ofp,"* ----------------------------------------------------------------"
netlist = read_netlist(ifp)
nlist_new=classify(netlist)
info('Combined %i parallel capacitors' % _ncombine_capacitors)
info('Combined %i parallel mosfets' % _ncombine_mosfets)
nlist_new['c']=drop_2node(nlist_new['c'], opt.dropcap, type='capacitor')
#create a new list of netlist objects
#in order of first appearance
all=[]
print '*\n* Output Element counts'
for type in nlist_new.keys():
num=len(nlist_new[type])
if num:
print '*',type,'-',num
for elm in nlist_new[type]:
all.append(elm)
#python2.4 specific operation
#sort netlist by order of appearance in orig. netlist
all.sort(cmp=lambda x,y: cmp(x.num,y.num))
for x in all:
print>>ofp,x
Copyright Dan White 2006
Licensed by the GPL, see http://www.whiteaudio.com/soft/COPYING or the current
GNU GPL license for details.
pyspice.py v0.2
SPICE pre-processor that combines parallel elements (e.g. capacitors, mosfets)
for GREATLY reduced simulation time. Uses the 'M' parameter of MOSFETS
when combining parallel fets. Makes simulating fingered transistors easier and
faster. This module is in a constant state of flux because I am using and
modifying it for my needs.
Combine parallel capacitors
Drop combined caps smaller than X fF
Usage:
pyspice.py [options] [-i infile] [-o outfile]
Use pyspice.py -h for all the options.
Data structures: (outdated)
lines - list of tuples: (original_line, line.split(), line_type)
caps - dictionary of node pairs and capacitance between nodes
TODO:
xpreserve comments in position
-element class definitions
xinductor
xv-source
xi-source
-e-source (VCVS)
-preserve node namespaces (within subckts, libraries, etc.)
-find illegal SPICE node name character to prepend namespace
e.g. @sub1.node1
-handle control statements specially
-.model
-.dc, .ac, .tran
-.lib/.endl handling
-.meas
-.print
-.probe
-.param
-.option
-.global
-.ic
-.subckt/.ends
-.prot/.unprot
-.alter blocks
-.end
-others?
-option to find/evaluate .param statements?
-HSPICE takes params, ngspice doesn't
-logic to find when an explicit number is specified (e.g. 10k) or
when it is a parameter (e.g. 'value')
-create dict of .params and use as substitution keys in:
k=v model parameters
node names
element values (Rxx n1 n2 'value'; Cxx n1 n2 'fc/2')
others?
-option to make a flat output (inline everything):
.lib statements
.include statements
.model statements
others?
-store node dictionary to keep track of elements connected to that node
-allows tracking of R-C nodes to drop C or R based on time constant
-allows more sophisticated dropping/combining with series elements
specifically R+R+R+R chains -> equivalent R+- (roughly) for faster sims
C C C C
##
# Global Shorthands
##
stderr=sys.stderr
#set line continuation style and max width
wrapper=textwrap.TextWrapper(subsequent_indent='+ ',width=75)
#not used?
#element lists
_defined_types=['comment',
'control',
'capacitor',
'mosfet',
'resistor']
capacitors=[]
resistors=[]
mosfets=[]
#end not used?
#global variables
_counts=dict() #keyed by spice element letter
_ncombine_capacitors=0
_ncombine_inductors=0
_ncombine_mosfets=0
_ncombine_res=0
#namespace tracking
# -not implemented yet
_current_scope=''
import sys, getopt, textwrap, warnings
from optparse import OptionParser
import re
try:
from cStringIO import StringIO as StringIO
except:
from StringIO import StringIO as StringIO
@first #!/usr/bin/env python
@language python
@tabwidth -4
<< head docstring >>
<< global imports >>
<< global vars >>
@
Debug options: string of debugging elements to print
* - comments
follow SPICE element names (c-capacitor, l-inductor)
@c
dbg=''
@others
#magic script-maker
if __name__ == '__main__':
main()
"""Read a SPICE netlist from the open file pointer
read_netlist(filename) -> array lines
Returns a list of expanded lines (without continuation '+')
Keeps case of comments, all other lines are lowercased
return:
netlist (list) of SPICE netlist lines
"""
import re
def unit(s):
"""Takes a string and returns the equivalent float.
'3.0u' -> 3.0e-6"""
mult={'t':1.0e12,
'g':1.0e9,
'meg':1.0e6,
'k':1.0e3,
'mil':25.4e-6,
'm':1.0e-3,
'u':1.0e-6,
'n':1.0e-9,
'p':1.0e-12,
'f':1.0e-15}
m=re.search('^([0-9e\+\-\.]+)(t|g|meg|k|mil|m|u|n|p|f)?',s.lower())
if m.group(2):
return float(m.group(1))*mult[m.group(2)]
else:
return float(m.group(1))
class Inductor(Passive2NodeElement):
"""Assumes SPICE element line:
cXXX n1 n2 value p1=val p2=val ...
Provides:
isparallel(other)
combine(other)
"""
@others
def __init__(self,line,num):
Passive2NodeElement.__init__(self,line,num)
self.type='inductor'
def isparallel(self,other):
"""Returns True if instance is parallel with other instance
"""
if self.n1==other.n1 and self.n2==other.n2:
return True
elif self.n1==other.n2 and self.n2==other.n1:
return True
else:
return False
def combine(self,other):
"""Combines values if inductors are in parallel, returns True if
it combined them.
NOTE:
-Does not currently touch param dictionary when combining,
just the values. How should this be done?
"""
global _ncombine_inductors
if self.isparallel(other):
self.value=(self.value*other.value)/(self.value+other.value)
_ncombine_inductors+=1
return True
else:
return False