Source code for maabara.uncertainty

import re
import logging
import sympy as sy
import numpy as np
import uncertainties as uc
import math

def ceiling(x,n):
	a = math.ceil(x*10**n)/10**n
	a = round(a,n)
	return a

[docs]class Sheet(object): """ Symbolic propagation of uncertainty Parameters ---------- equation : sympy equation string, optional See :func:`~maabara.uncertainty.Sheet.set_equation` name : string, optional See :func:`~maabara.uncertainty.Sheet.set_name` data : list See :func:`~maabara.uncertainty.Sheet.set_data` """ def __init__(self, equation = "0", name = "", data = []): self.reset() self.set_equation(equation) self.name = name self.set_data(data)
[docs] def reset(self): """ Clear all settings Returns ------- out : boolean True on success. """ self.name = "" self.equation = "0" self.eq_expr = 0 self.err_expr = 0 self.data = [] self.nominal = 0 self.deviation = 0 self.ufloat = False self.messages = [] return True
[docs] def set_name(self,name): """ Set a name Parameters ---------- name : string Name as Latex markup. It will appear in computation equations. Returns ------- out : boolean True on success. """ self.name = name return True
[docs] def n(self, name): """ Alias of :func:`~maabara.uncertainty.Sheet.set_name` """ return self.set_name(name)
[docs] def set_equation(self,equation, name = ""): """ Set equation string Parameters ---------- equation : Sympy equation string Use fundamental functions like ``sin, exp, sqrt, atan`` etc. and ``Rational(a,b)`` to define a fraction a/b. Allowed variable names are alphanumeric and use ``_`` only. They must be specifed by :func:`~maabara.uncertainty.Sheet.set_value`. Please be aware that ``I`` will be interpreted as imaginary unit. name : string, optional See :func:`~maabara.uncertainty.Sheet.set_name` Returns ------- out : boolean True on success. """ if (isinstance(equation, str) & (equation != "")): self.changed_equation = True equation = equation.replace('_','') self.equation = equation self.eq_expr = sy.sympify(equation) if name != "": self.name = name return True
[docs] def eq(self,equation,name = ""): """ Alias of :func:`~maabara.uncertainty.Sheet.set_equation` """ return self.set_equation(equation, name)
[docs] def set_data(self, data): """ Set data manually. Parameters ---------- data : list of tuples [ ( string Variable, float Value, float Error = 0, tex = "") ] Notes ----- It is more recommend to use :func:`~maabara.uncertainty.Sheet.set_value` function to manipulate the data. Returns ------- out : boolean True on success. """ if (len(data) > 0): self.data = data return True
[docs] def get_data(self, line = False, element = False): """ Get data Parameters ---------- line : string, optional Variable name element : {'val', 'dev', or 'tex'}, optional Return mode Returns ------- out : mixed If ``line`` and ``element`` are False complete data is returned. If found ``line`` will be returned as tuple. If ``element`` is specified value (val), deviation (dev) or Latex markup (tex) will be returned. """ if (line != False): index = self._find_in_list(self.data, line) if (index == -1): return False else: if(index[1] == 0): if (element == "val"): return self.data[index[0]][1] elif (element == "dev"): return self.data[index[0]][2] elif (element == "tex"): return self.data[index[0]][3] else: return self.data[index[0]] else: return False else: return self.data
[docs] def set_value(self, symbol, value = False, error = False, tex = False): """ Set uncertain value Parameters ---------- symbol : string Symbol string used in equation value : float, optional Value error : float, optional Deviation tex : string, optional Latex markup replace string. All symbol occurences will be replaced by this markup. """ symbol_replaced = symbol.replace('_','') symbol_clear = ''.join(e for e in symbol_replaced if e.isalnum()) if (symbol_replaced != symbol_clear): raise ValueError("Invalid symbol name, only underscore alphanumeric expression allowed (eg. Example_1). You might use 'tex'-parameter to set more complex expression names") if (symbol_replaced != symbol) & (tex == False): tex = sy.latex(sy.sympify(symbol)) val = (symbol_replaced, value, error, tex) index = self._find_in_list(self.data, symbol) if (index == -1): self.data.append(val) else: if(index[1] == 0): self.data[index[0]] = val else: self.data.append(val)
[docs] def v(self,symbol, value = False, error = False, tex = False): """ Alias of :func:`~maabara.uncertainty.Sheet.set_value` """ return self.set_value(symbol, value, error, tex)
def set_error(self, symbol, error): return self.set_value(symbol, False, error)
[docs] def run(self, equation = False, data = []): """ Runs symbolic error propagation by specified data Parameters ---------- equation : string, optional See :func:`~maabara.uncertainty.Sheet.set_equation` data : list, optional See :func:`~maabara.uncertainty.Sheet.set_data` Returns ------- out : sympy equation, sympy error_equation, ufloat result """ self.set_equation(equation) self.set_data(data) no_deviation = [] if (self.changed_equation): self.err_expr = 0 for var in self.data: #define symbol exec(var[0] + " = sy.Symbol('" + var[0] + "')") if len(var) >= 2 and isinstance(var[2], bool) and var[2] == False: no_deviation.append(var[0]) elif var[2] == 0: ## edit, if error = 0, no errorcalculation is done pass else: # get derivative exec(var[0] + "_der = sy.diff(self.eq_expr,var[0])") # set error variable exec(var[0] + "_err = sy.Symbol('sigma_" + var[0] + "')") exec(var[0] + "_der = " + var[0] + "_der * " + var[0] + "_err") # chunk exec("self.err_expr = self.err_expr + (" + var[0] + "_der)**2") # square root self.err_expr = sy.simplify(sy.sqrt(self.err_expr)) # force rooting self.err_expr = sy.powdenest(self.err_expr, force=True) self.changed_equation = False # error propagation if given nominal = self.eq_expr deviation = self.err_expr for var in self.data: if (len(var) != 4): var = (var[0],False, False, False) if isinstance(float(var[1]), (int,float,long)): try: exec("nominal = nominal.subs('" + var[0] + "'," + str(var[1]) + ")") except: pass try: exec("deviation = deviation.subs('" + var[0] + "', +" + str(var[1]) + ")") except: pass if isinstance(float(var[2]), (int,float,long)): try: exec("deviation = deviation.subs('sigma_" + var[0] + "', " + str(var[2]) + ")") except: pass try: self.nominal = float(nominal) except: self._msg("Could not finish nominal evalution due to missing values, stopped in at \n" + str(nominal), 'warning') self.nominal = 0 try: self.deviation = float(deviation) except: self._msg("Could not finish deviation evalution due to missing values, stopped in at \n" + str(deviation), 'warning') self.deviation = 0 if (len(no_deviation) > 0): self._msg("No deviation for " + ', '.join(no_deviation)) # cast to uncertainties self.ufloat = uc.ufloat(self.nominal,self.deviation) return self.eq_expr, self.err_expr, self.ufloat
[docs] def print_result(self,mode = "default", multiply = "dot"): """ Print results of symbolic error propagation Parameters ---------- mode : {'default', or 'short'} Specifies printing mode. multiply : string, optional Latex markup for multiply symbol. Use ``None`` to supress multipliers Returns ------- out : ufloat Result """ self.run() # outs result_tex = '{:.1uL}'.format(self.ufloat) if (self.name != ""): result_tex = self.name + "=" + result_tex eq_tex = sy.latex(self.eq_expr, mul_symbol=multiply) if (self.name != ""): eq_tex = self.name + "=" + eq_tex error_tex = sy.latex(self.err_expr, mul_symbol=multiply) if (self.name != ""): error_tex = "\sigma_{" + self.name + "}" + "=" + error_tex # replace alias for var in self.data: if (len(var) == 4): if (var[3] != False): var0 = sy.latex(sy.sympify(str(var[0]))) # secure replacement subs = var[3] eq_tex = re.sub(r'\b' + var0 + r'\b', subs.encode('string-escape'), eq_tex) error_tex = re.sub(r'\b' + var0 + r'\b', subs.encode('string-escape'), error_tex) def pdisplay(tex): IPython.display.display((IPython.display.Math(tex))) print tex + '\n' if (mode == "short"): print eq_tex + '\n' print result_tex + '\n' print error_tex + '\n' elif (mode == "default"): try: import IPython.display pdisplay(eq_tex) pdisplay(result_tex) pdisplay(error_tex) except: logging.warn('Could not import IPython.display to render Latex') else: raise ValueError('Invalid Mode') return self.ufloat
[docs] def p(self,mode = "default", multiply = "dot"): """ Alias of :func:`~maabara.uncertainty.Sheet.print_result` """ return self.print_result(mode, multiply)
[docs] def ps(self): """ Alias of :func:`~maabara.uncertainty.Sheet.set_result` in short mode """ return self.print_result("short")
[docs] def get_result(self,mode = "default"): """ Get error propagation Parameters ---------- mode : {'default', 'exact', 'ufloat', or 'tex'} Returns ------- out : mixed """ self.run() if (mode.find("tex",0,3) != -1): tex = '{:L}'.format(self.ufloat) if (str.find(mode,"tex:") == 0): tex = mode[4:] + "=" + tex display(Math(tex)) print tex return tex elif (mode == "ufloat"): return self.ufloat elif (mode == "exact"): tmp = "{:10}".format(self.ufloat) tmp = str.split(tmp,'+/-') return (float(tmp[0]), float(tmp[1])) elif (mode == "print"): print str(self.nominal) + "\t" + str(self.deviation) return (self.nominal, self.deviation) elif (mode == "default"): return (self.nominal, self.deviation) return False;
[docs] def batch(self,data, fields, mode = "default"): """ Batch process a set of values. Parameters ---------- data : array_like NxM or tuple of array_like A tuple will be stacked to columns. Columns represent different variables indexed by ``fields``. Rows will be iterated. fields : string List of columns fields divided by ``|``. Deviations columns must be suffix by ``%``. Use ``*`` to ignore a column form ``data`` mode : {'default', 'ufloat', or 'exact'}, optional Specify ``return`` type Returns ------- out : ndarray Depending on mode. 'default' will return Nx2 array. First column will hold nominal value, second its deviation. 'exact' like default but rounded to significant digits. 'ufloat' will return Nx1 array of ufloats Notes ----- It is possible to set constant values or errors before calling batch method. The batch method will automaticly use the values if not given by the data array. See example below. Examples -------- Retrieve a computation object and set equation >>> stack = ma.uncertainty.Sheet('a*x**3') Data array specifies the sets to compute row-by-row >>> data = [ [0.5, 1. , 0.1], [0.3, 2. ,0.15] ] Batch the data >>> stack.set_value('a', error=0.05) # set constant error for a >>> stack.batch(data, 'a|x|x%') array([[ 0.5 , 0.15811388], [ 2.4 , 0.6720119 ]]) # line-by-line result with uncertainty Try again with a constant value >>> stack.set_value('a',1., 0.05) # define constant a value >>> stack.batch(data, '*|x|x%', 'ufloat') # rerun computation ignoring first data column array([[1.0+/-0.30000000000000004], [8.0+/-1.7999999999999998]], dtype=object) """ data = np.array(data); if(isinstance(data,tuple)): data = np.column_stack(data) # if not a column, transpose it if (len(data.shape) == 1): data = np.column_stack((data[:],)) #reset messages self.messages = [] fields = str.split(fields.strip(),"|") # require same size if (len(fields) != len(data[0])): return False if (mode == 'ufloat'): result = range(len(data)) else: result = np.zeros([len(data),2]) i = 0 for line in data: for field in fields: if(field == "*"): continue if (field.find("%") != -1): continue try: val = line[fields.index(field)] except: val = self.get_data(field,'val') try: dev = line[fields.index(field + '%')] except: dev = self.get_data(field,'dev') tex = self.get_data(field,'tex') self.set_value(field, val, dev, tex) if (mode == 'ufloat'): result[i] = [self.get_result(mode)] elif(mode == 'sigceil'): nominal, deviation = self.get_result(mode = 'default') (prec ,deviation)= uc.PDG_precision(deviation) fdigit = uc.first_digit(deviation) deviation = ceiling(deviation,prec-1-fdigit) nominal = round(nominal,prec-1-fdigit) result[i][0] = nominal result[i][1] = deviation elif(mode == 'siground'): nominal, deviation = self.get_result(mode = 'default') (prec ,deviation)= uc.PDG_precision(deviation) fdigit = uc.first_digit(deviation) deviation = round(deviation,prec-1-fdigit) nominal = round(nominal,prec-1-fdigit) result[i][0] = nominal result[i][1] = deviation else: nominal, deviation = self.get_result(mode) result[i][0] = nominal result[i][1] = deviation i += 1 if (mode == 'ufloat'): return np.array(result) return result
def _msg(self, message, mode = 'default'): # avoid multiple messages try: self.messages.index(message) return except: self.messages.append(message) if (mode == 'warning'): logging.warning(message) else: logging.info(message) def _find_in_list(self,l, elem): for row, i in enumerate(l): try: column = i.index(elem) except ValueError: continue return row, column return -1