blob: b3b2958525d8f04bb2594ba4d68867e850786ebe [file] [log] [blame]
#!/usr/bin/env python3
"""spice_units.py: Converts tuple of (unit, value) into standard unit numeric value."""
import re
# set of metric prefixes and the value needed to multiply by to
# get the "standard" unit for SPICE. Only standard units will
# be written into the SPICE file, for reasons of universal
# compatibility.
prefixtypes = {
"T": 1E12, "tera" : 1E12,
"G": 1E9, "giga" : 1E9,
"M": 1E6, "mega" : 1E6, "MEG": 1E6, "meg": 1E6,
"K": 1E3, "kilo" : 1E3, "k":1E3,
"D": 1E1, "deca" : 1E1,
"d": 1E-1, "deci" : 1E-1,
"c": 1E-2, "centi": 1E-2, "%": 1E-2,
"m": 1E-3, "milli": 1E-3,
"u": 1E-6, "micro": 1E-6, "\u00b5": 1E-6, "ppm": 1E-6,
"n": 1E-9, "nano" : 1E-9, "ppb": 1E-9,
"p": 1E-12, "pico" : 1E-12, "ppt": 1E-12,
"f": 1E-15, "femto": 1E-15,
"a": 1E-18, "atto" : 1E-15,
}
# set of known unit types, including some with suffixes, along with a
# keyword that can be used to limit the search if an expected type for
# the value is known. Keys are used in regular expressions, and so
# may use any regular expression syntax.
unittypes = {
"[Ff]": "capacitance",
"[Ff]arad[s]*": "capacitance",
"\u03a9": "resistance",
"[Oo]hm[s]*": "resistance",
"[Vv]": "voltage",
"[Vv]olt[s]*": "voltage",
"[Aa]": "current",
"[Aa]mp[s]*": "current",
"[Aa]mpere[s]*": "current",
"[Ss]": "time",
"[Ss]econd[s]*": "time",
"[Hh]": "inductance",
"[Hh]enry[s]*": "inductance",
"[Hh]enries": "inductance",
"[Hh]z": "frequency",
"[Hh]ertz": "frequency",
"[Mm]": "distance",
"[Mm]eter[s]*": "distance",
"[\u00b0]*[Cc]": "temperature",
"[\u00b0]*[Cc]elsius": "temperature",
"[\u00b0]*[Kk]": "temperature",
"[\u00b0]*[Kk]elvin": "temperature",
"[Ww]": "power",
"[Ww]att[s]*": "power",
"[Vv]-rms": "noise",
"[Vv]olt[s]*-rms": "noise",
"'[bohd]": "digital",
"": "none"
}
# Convert string to either integer or float, with priority on integer
# If argument is not a string, just return the argument.
def numeric(s):
if isinstance(s, str):
try:
return int(s)
except ValueError:
return float(s)
else:
return s
# Define how to convert SI units to spice values
#
# NOTE: spice_unit_unconvert can act on a tuple of (units, value) where
# value is either a single value or a list of values. spice_unit_convert
# only acts on a tuple with a single value. This is because the only large
# vectors are produced by ngspice, and these values need unconverting back
# into the units specified by the datasheet. Values being converted to
# ngspice units are from the datasheet and are only computed a few at a
# time, so handling vectors is not particularly efficient.
def spice_unit_convert(valuet, restrict=[]):
"""Convert SI units into spice values"""
# valuet is a tuple of (unit, value), where "value" is numeric
# and "unit" is a string. "restrict" may be used to require that
# the value be of a specific class like "time" or "resistance".
# Recursive handling of '/' and multiplicatioon dot in expressions
if '/' in valuet[0]:
parts = valuet[0].split('/', 1)
result = numeric(spice_unit_convert([parts[0], valuet[1]], restrict))
result /= numeric(spice_unit_convert([parts[1], "1.0"], restrict))
return str(result)
if '\u22c5' in valuet[0]: # multiplication dot
parts = valuet[0].split('\u22c5')
result = numeric(spice_unit_convert([parts[0], valuet[1]], restrict))
result *= numeric(spice_unit_convert([parts[1], "1.0"], restrict))
return str(result)
if '\u00b2' in valuet[0]: # squared
part = valuet[0].split('\u00b2')[0]
result = numeric(spice_unit_unconvert([part, valuet[1]], restrict))
result *= numeric(spice_unit_unconvert([part, "1.0"], restrict))
return str(result)
if valuet[0] == "": # null case, no units
return valuet[1]
for unitrec in unittypes: # case of no prefix
if re.match('^' + unitrec + '$', valuet[0]):
if restrict:
if unittypes[unitrec] == restrict.lower():
return valuet[1]
else:
return valuet[1]
for prerec in prefixtypes:
for unitrec in unittypes:
if re.match('^' + prerec + unitrec + '$', valuet[0]):
if restrict:
if unittypes[unitrec] == restrict.lower():
newvalue = numeric(valuet[1]) * prefixtypes[prerec]
return str(newvalue)
else:
newvalue = numeric(valuet[1]) * prefixtypes[prerec]
return str(newvalue)
# Check for "%", which can apply to anything.
if valuet[0][0] == '%':
newvalue = numeric(valuet[1]) * 0.01
return str(newvalue)
if restrict:
raise ValueError('units ' + valuet[0] + ' cannot be parsed as ' + restrict.lower())
else:
# raise ValueError('units ' + valuet[0] + ' cannot be parsed')
# (Assume value is not in SI units and will be passed back as-is)
return valuet[1]
# Define how to convert spice values back into SI units
def spice_unit_unconvert(valuet, restrict=[]):
"""Convert spice values back into SI units"""
# valuet is a tuple of (unit, value), where "value" is numeric
# and "unit" is a string. "restrict" may be used to require that
# the value be of a specific class like "time" or "resistance".
# Recursive handling of '/' and multiplicatioon dot in expressions
if '/' in valuet[0]:
parts = valuet[0].split('/', 1)
result = spice_unit_unconvert([parts[0], valuet[1]], restrict)
if isinstance(result, list):
result = list(item / spice_unit_unconvert([parts[1], 1.0],
restrict) for item in result)
else:
result /= spice_unit_unconvert([parts[1], 1.0], restrict)
return result
if '\u22c5' in valuet[0]: # multiplication dot
parts = valuet[0].split('\u22c5')
result = spice_unit_unconvert([parts[0], valuet[1]], restrict)
if isinstance(result, list):
result = list(item * spice_unit_unconvert([parts[1], 1.0],
restrict) for item in result)
else:
result *= spice_unit_unconvert([parts[1], 1.0], restrict)
return result
if '\u00b2' in valuet[0]: # squared
part = valuet[0].split('\u00b2')[0]
result = spice_unit_unconvert([part, valuet[1]], restrict)
if isinstance(result, list):
result = list(item * spice_unit_unconvert([part, 1.0],
restrict) for item in result)
else:
result *= spice_unit_unconvert([part, 1.0], restrict)
return result
if valuet[0] == "": # null case, no units
return valuet[1]
for unitrec in unittypes: # case of no prefix
if re.match('^' + unitrec + '$', valuet[0]):
if restrict:
if unittypes[unitrec] == restrict.lower():
return valuet[1]
else:
return valuet[1]
for prerec in prefixtypes:
for unitrec in unittypes:
if re.match('^' + prerec + unitrec + '$', valuet[0]):
if restrict:
if unittypes[unitrec] == restrict.lower():
if isinstance(valuet[1], list):
return list(item / prefixtypes[prerec] for item in valuet[1])
else:
return valuet[1] / prefixtypes[prerec]
else:
if isinstance(valuet[1], list):
return list(item / prefixtypes[prerec] for item in valuet[1])
else:
return valuet[1] / prefixtypes[prerec]
# Check for "%", which can apply to anything.
if valuet[0][0] == '%':
if isinstance(valuet[1], list):
return list(item * 100 for item in valuet[1])
else:
return valuet[1] * 100
if restrict:
raise ValueError('units ' + valuet[0] + ' cannot be parsed as ' + restrict.lower())
else:
# raise ValueError('units ' + valuet[0] + ' cannot be parsed')
# (Assume value is not in SI units and will be passed back as-is)
return valuet[1]