def add_module(self, module): for fname, f in module.__dict__.items(): if fname[0] == '_': continue if isinstance(f, collections.Callable): self.add_function(f) elif math2.is_number(f): self.add_constant(f, fname)
def add(self, expr): ''' add expr :return: bool True if it has been added ''' if expr is None: return False try: k = expr() # the key is the numeric value of expr except (TypeError, ValueError): return False if not math2.is_number(k): return False if type(k) is complex: return False if self.int: # limit to integers... but maybe we're extremely close k = math2.int_or_float(k) if not isinstance(k, int): return False if k < 0: return self.add(-expr) if self.max is not None and k > self.max: return False if k in self: if self.improve and expr.complexity() >= self[k].complexity(): return False self[k] = expr return True
def isconstant(self): ''':return: True if Expr evaluates to a constant number or bool''' res = self() if math2.is_number(res): return True if isinstance(res, bool): return True return False
def eval(self, node): '''safe eval of ast node : only functions and _operators listed above can be used :param node: ast.AST to evaluate :param ctx: dict of varname : value to substitute in node :return: number or expression string ''' if isinstance(node, ast.Num): # <number> return node.n elif isinstance(node, ast.Name): return self.variables.get(node.id, node.id) # return value or var elif isinstance(node, ast.Attribute): return getattr(self.variables, [node.value.id], node.attr) elif isinstance(node, ast.Tuple): return tuple(self.eval(e) for e in node.elts) elif isinstance(node, ast.Call): params = [self.eval(arg) for arg in node.args] if node.func.id not in self.functions: raise NameError('%s function not allowed' % node.func.id) f = self.functions[node.func.id][0] res = f(*params) # try to correct small error return math2.int_or_float(res, 0, 1e-12) elif isinstance(node, ast.BinOp): # <left> <operator> <right> op = self.operators[type(node.op)] left = self.eval(node.left) right = self.eval(node.right) if math2.is_number(left) and math2.is_number(right): res = op[0](left, right) # no correction here ! return res else: return "%s%s%s" % (left, op[_dialect_python], right) elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1 right = self.eval(node.operand) return self.operators[type(node.op)][0](right) elif isinstance(node, ast.Compare): left = self.eval(node.left) for op, right in zip(node.ops, node.comparators): # TODO: find what to do when multiple items in list return self.operators[type(op)][0](left, self.eval(right)) elif isinstance(node, ast.NameConstant): return node.value else: logging.warning(ast.dump(node, False, False)) return self.eval(node.body) # last chance
def eval(self, node): '''safe eval of ast node : only functions and _operators listed above can be used :param node: ast.AST to evaluate :param ctx: dict of varname : value to substitute in node :return: number or expression string ''' if isinstance(node, ast.Num): # <number> return node.n elif isinstance(node, ast.Name): return self.variables.get(node.id, node.id) # return value or var elif isinstance(node, ast.Attribute): return getattr(self[node.value.id], node.attr) elif isinstance(node, ast.Tuple): return tuple(self.eval(e) for e in node.elts) elif isinstance(node, ast.Call): params = [self.eval(arg) for arg in node.args] if not node.func.id in self.functions: raise NameError('%s function not allowed' % node.func.id) f = self.functions[node.func.id][0] res = f(*params) return math2.int_or_float(res, 0, 1e-12) # try to correct small error elif isinstance(node, ast.BinOp): # <left> <operator> <right> op = self.operators[type(node.op)] left = self.eval(node.left) right = self.eval(node.right) if math2.is_number(left) and math2.is_number(right): res = op[0](left, right) # no correction here ! return res else: return "%s%s%s" % (left, op[_dialect_python], right) elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1 right = self.eval(node.operand) return self.operators[type(node.op)][0](right) elif isinstance(node, ast.Compare): left = self.eval(node.left) for op, right in zip(node.ops, node.comparators): # TODO: find what to do when multiple items in list return self.operators[type(op)][0](left, self.eval(right)) elif isinstance(node, ast.NameConstant): return node.value else: logging.warning(ast.dump(node, False, False)) return self.eval(node.body) # last chance
def __eq__(self, other): if math2.is_number(other): try: return self() == other except: return False if not isinstance(other, Expr): other = Expr(other, self.context) return str(self()) == str(other())
def __lt__(self, other): if math2.is_number(other): try: return self() < other except: return False if not isinstance(other, Expr): other = Expr(other, self.context) return float(self()) < float(other())
def __mul__(self, other): if math2.is_number(other): mean = self.mean var = other * self.variance else: # it's a Stat mean = self.mean * other.mean # https://fr.wikipedia.org/wiki/Variance_(statistiques_et_probabilit%C3%A9s)#Produit var = self.variance * other.variance + \ self.variance * other.mean ** 2 + other.variance * self.mean ** 2 return Stats(mean=mean, var=var)
def __add__(self, other): if math2.is_number(other): other = Stats([other]) # https://fr.wikipedia.org/wiki/Variance_(statistiques_et_probabilit%C3%A9s)#Produit try: cov = covariance(self, other) except: cov = 0 mean = (self.mean + other.mean) / 2 # TODO : improve var = self.variance + other.variance + 2 * cov return Stats(mean=mean, var=var)
def strftimedelta(t,fmt='%H:%M:%S'): """ :param t: float seconds or timedelta """ if not math2.is_number(t): t=t.total_seconds() hours, remainder = divmod(t, 3600) minutes, seconds = divmod(remainder, 60) res=fmt.replace('%H','%d'%hours) res=res.replace('%h','%d'%hours if hours else '') res=res.replace('%M','%02d'%minutes) res=res.replace('%m','%d'%minutes if minutes else '') res=res.replace('%S','%02d'%seconds) return res
def __mul__(self,other): if isinstance(other,six.string_types): return self.colorize('black',other) if math2.is_number(other): return self.compose(None,other) if other.nchannels>self.nchannels: return other*self if other.nchannels==1: if self.nchannels==1: return self.compose(None,other.array) rgba=list(self.split('RGBA')) rgba[-1]=rgba[-1]*other return Image(rgba,'RGBA') raise NotImplemented('%s * %s'%(self,other))
def strftimedelta(t, fmt='%H:%M:%S'): """ :param t: float seconds or timedelta """ if not math2.is_number(t): t = t.total_seconds() hours, remainder = divmod(t, 3600) minutes, seconds = divmod(remainder, 60) res = fmt.replace('%H', '%d' % hours) res = res.replace('%h', '%d' % hours if hours else '') res = res.replace('%M', '%02d' % minutes) res = res.replace('%m', '%d' % minutes if minutes else '') res = res.replace('%S', '%02d' % seconds) return res
def __mul__(self, other): if isinstance(other, str): return self.colorize('black', other) if math2.is_number(other): return self.compose(None, other) if other.nchannels > self.nchannels: return other * self if other.nchannels == 1: if self.nchannels == 1: return self.compose(None, other.array) rgba = list(self.split('RGBA')) rgba[-1] = rgba[-1] * other return Image(rgba, 'RGBA') raise NotImplementedError('%s * %s' % (self, other))
def __call__(self, x=None, **kwargs): '''evaluate the Expr at x OR compose self(x())''' if isinstance(x, Expr): # composition return self.applx(x) if itertools2.isiterable(x): return [self(x) for x in x] # return a displayable list if x is not None: kwargs['x'] = x kwargs['self'] = self # allows to call methods such as in Stats self.context.variables = kwargs try: e = self.context.eval(self.body) except TypeError: # some params remain symbolic return self except Exception as error: # ZeroDivisionError, OverflowError return None if math2.is_number(e): return e return Expr(e, self.context)
def __init__(self, init=[], default=0, period=(-math2.inf, +math2.inf)): # Note : started by deriving a list of (point,value), but this leads to a problem: # the value is taken into account in sort order by bisect # so instead of defining one more class with a __cmp__ method, I split both lists if math2.is_number(period): period = (0, period) try: # copy constructor ? self.x = list(init.x) self.y = list(init.y) self.period = period or init.period # allow to force periodicity except AttributeError: self.x = [] self.y = [] self.period = period self.append(period[0], default) self.extend(init) # to initialize context and such stuff super(Piecewise, self).__init__(0) self.body = '?' # should not happen
def __init__(self, f, context=default_context): ''' :param f: function or operator, Expr to copy construct, or formula string ''' self.context = context if isinstance(f, Expr): # copy constructor self.body = f.body return elif isinstance(f, ast.AST): self.body = f return elif inspect.isfunction(f): try: f = get_function_source(f) except ValueError: f = '%s(x)' % f.__name__ elif isinstance(f, collections.Callable): # builtin function f = '%s(x)' % f.__name__ elif f in ('True', 'False'): f = bool(f == 'True') if type(f) is bool: self.body = ast.Num(f) return if type(f) is float: # try to beautify it if math2.is_integer(f): f = math2.rint(f) else: f = plouffe(f) if math2.is_number(f): # store it with full precision self.body = ast.Num(f) return # accept ^ as power operator rather than xor ... f = str(f).replace('^', '**') self.body = compile(f, 'Expr', 'eval', ast.PyCF_ONLY_AST).body
def __init__(self, f, context=default_context): ''' :param f: function or operator, Expr to copy construct, or formula string ''' self.context = context if isinstance(f, Expr): # copy constructor self.body = f.body return elif isinstance(f, ast.AST): self.body = f return elif inspect.isfunction(f): try: f = get_function_source(f) except ValueError: f = '%s(x)' % f.__name__ elif isinstance(f, collections.Callable): # builtin function f = '%s(x)' % f.__name__ elif f in ('True', 'False'): f = bool(f == 'True') if type(f) is bool: self.body = ast.Num(f) return if type(f) is float: # try to beautify it if math2.is_integer(f): f = math2.rint(f) else: f = plouffe(f) if math2.is_number(f): # store it with full precision self.body = ast.Num(f) return f = str(f).replace('^', '**') # accept ^ as power operator rather than xor ... self.body = compile(f, 'Expr', 'eval', ast.PyCF_ONLY_AST).body