| #!/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] |