def _array(self, name, matrix, allocate=False, operator='=', atomic=False, ignore_symbols=[]): opscount = 0 code = "" # Testing on Godbolt with GCC 9.1 and ICPC shows that # pow(x, 0.5) generates fewer instructions than # sqrt(x), so will swap. However, leave R here in case # it gets used in expansions in future for some reason. light_ignore = [] if sp.symbols('R') in matrix.free_symbols: code += f'{self.precision} R = sqrt(x*x + y*y + z*z);\n' light_ignore.append('R') if sp.symbols('Rinv') in matrix.free_symbols: code += f'{self.precision} Rinv = pow(x*x + y*y + z*z, -0.5);\n' light_ignore.append('Rinv') if allocate: code += f'{self.precision} {name}[{len(matrix)}];\n' if not self.debug: # print('Printing with CSE') iterator = SymbolIterator(name) # print(f'ignoring {name} in cse') sub_expressions, rmatrix = cse(matrix, optimizations=opts, symbols=iterator, ignore=(ignore_symbols), light_ignore=light_ignore) rmatrix = sp.Matrix(rmatrix) for i, (var, sub_expr) in enumerate(sub_expressions): opscount += count_ops(sub_expr) code += f'{self.precision} ' + self.printer.doprint( sub_expr, assign_to=var) + "\n" opscount += count_ops(rmatrix) tmp = self.printer.doprint(rmatrix, assign_to=name).replace('=', operator) else: # print('Printing without CSE') opscount += count_ops(matrix) tmp = self.printer.doprint(matrix, assign_to=name).replace('=', operator) if atomic: lines = tmp.split('\n') for l in lines: code += '#pragma omp atomic\n' code += l + '\n' else: code += tmp + '\n' return code, opscount
def test_simplify_ratio(): # roots of x**3-3*x+5 roots = ['(5/2 + 21**(1/2)/2)**(1/3)*(1/2 - I*3**(1/2)/2)' ' + 1/((1/2 - I*3**(1/2)/2)*(5/2 + 21**(1/2)/2)**(1/3))', '(5/2 + 21**(1/2)/2)**(1/3)*(1/2 + I*3**(1/2)/2)' ' + 1/((1/2 + I*3**(1/2)/2)*(5/2 + 21**(1/2)/2)**(1/3))', '-1/(5/2 + 21**(1/2)/2)**(1/3) - (5/2 + 21**(1/2)/2)**(1/3)'] for r in roots: r = S(r) assert count_ops(simplify(r, ratio=1)) <= count_ops(r) # If ratio=oo, simplify() is always applied: assert simplify(r, ratio=oo) is not r
def test_simplify_ratio(): # roots of x**3-3*x+5 roots = ['(1/2 - sqrt(3)*I/2)*(sqrt(21)/2 + 5/2)**(1/3) + 1/((1/2 - ' 'sqrt(3)*I/2)*(sqrt(21)/2 + 5/2)**(1/3))', '1/((1/2 + sqrt(3)*I/2)*(sqrt(21)/2 + 5/2)**(1/3)) + ' '(1/2 + sqrt(3)*I/2)*(sqrt(21)/2 + 5/2)**(1/3)', '-(sqrt(21)/2 + 5/2)**(1/3) - 1/(sqrt(21)/2 + 5/2)**(1/3)'] for r in roots: r = S(r) assert count_ops(simplify(r, ratio=1)) <= count_ops(r) # If ratio=oo, simplify() is always applied: assert simplify(r, ratio=oo) is not r
def my_measure(expr): from sympy import sqrt, simplify, count_ops, oo from sympy import Symbol, S count = 0 # Discourage powers by giving POW a weight of 10 count += count_ops(expr, visual=True).subs(Symbol('EXP'), 100) count += count_ops(expr, visual=True).subs(Symbol('HEAVISIDE'), 10) #count = count_ops(expr, visual=True).subs(Symbol('HEAVISIDE'), 1) # Every other operation gets a weight of 1 (the default) count = count.replace(Symbol, type(S.One)) return count
def sympy_key(expr): """Get the key for ordering SymPy expressions. This function assumes that the given expression is already sympified. """ return count_ops(expr), default_sort_key(expr)
def process_diff_list(diff_list, label, superscript): if len(diff_list) == 0: return 0 elif len(diff_list) == 1: return diff_list[0].pre_factor * Diff(diff_list[0].argument, label, superscript) result = 0 matches = [] for i in range(1, len(diff_list)): match_result = match_diff_splits(diff_list[i], diff_list[0]) if match_result is not None: matches.append((i, match_result)) if len(matches) == 0: result += diff_list[0].pre_factor * Diff(diff_list[0].argument, label, superscript) else: other_idx, match_result = sorted( matches, key=lambda e: sp.count_ops(e[1]))[0] new_argument = diff_list[0].argument * diff_list[other_idx].argument result += (diff_list[0].pre_factor / diff_list[other_idx].argument) * Diff( new_argument, label, superscript) if match_result == 0: del diff_list[other_idx] else: diff_list[other_idx].pre_factor = match_result * diff_list[ 0].argument result += process_diff_list(diff_list[1:], label, superscript) return result
def expression_complexity(expr, complexity=None): ''' Returns the complexity of an expression (either string or sympy) The complexity is defined as 1 for each arithmetic operation except divide which is 2, and all other operations are 20. This can be overridden using the complexity argument. Note: calling this on a statement rather than an expression is likely to lead to errors. Parameters ---------- expr: `sympy.Expr` or str The expression. complexity: None or dict (optional) A dictionary mapping expression names to their complexity, to overwrite default behaviour. Returns ------- complexity: int The complexity of the expression. ''' subs = {'ADD':1, 'DIV':2, 'MUL':1, 'SUB':1} if complexity is not None: subs.update(complexity) ops = sympy.count_ops(expr, visual=True) for atom in ops.atoms(): if hasattr(atom, 'name'): subs[atom.name] = 20 # unknown operations assumed to have a large cost return ops.evalf(subs=subs)
def test_simplify_measure(): measure1 = lambda expr: len(str(expr)) measure2 = lambda expr: -count_ops(expr ) # Return the most complicated result expr = (x + 1) / (x + sin(x)**2 + cos(x)**2) assert measure1(simplify(expr, measure=measure1)) <= measure1(expr) assert measure2(simplify(expr, measure=measure2)) <= measure2(expr)
def subs_matrix_verbose(A_expr,subs,simultaneous=False): print "flashlight.sympy: subs_matrix_verbose(...) begin..." A_subs_expr = sympy.Matrix.zeros(A_expr.rows,A_expr.cols) for r in range(A_expr.rows): for c in range(A_expr.cols): print " ",r,c,len(subs),sympy.count_ops(A_expr[r,c]) A_subs_expr[r,c] = A_expr[r,c].subs(subs,simultaneous=simultaneous) print "flashlight.sympy: subs_matrix_verbose(...) end." return A_subs_expr
def test_simplify_measure(): measure1 = lambda expr: len(str(expr)) measure2 = lambda expr: -count_ops(expr) # Return the most complicated result expr = (x + 1)/(x + sin(x)**2 + cos(x)**2) assert measure1(simplify(expr, measure=measure1)) <= measure1(expr) assert measure2(simplify(expr, measure=measure2)) <= measure2(expr) expr2 = Eq(sin(x)**2 + cos(x)**2, 1) assert measure1(simplify(expr2, measure=measure1)) <= measure1(expr2) assert measure2(simplify(expr2, measure=measure2)) <= measure2(expr2)
def erank(list_L): # rearrange list of eqns by length # by putting shortest eqns last, system will prefer to solve # shorter equations (i.e. prefer shorter solutions where two exist) # since the sorting is from lower to higher # it should not be reversed when putting into the list - D.Z. sorted_ls = [] list_d = {} for e in list_L: count = int(sp.count_ops(e.RHS)) + int(sp.count_ops(e.LHS)) if count not in list_d.keys(): list_d[count] = [] list_d[count].append(e) keys = list_d.keys() keys = sorted(keys, reverse= False) for key in keys: sorted_ls.extend(list_d[key]) return sorted_ls
def is_zero(self, n=100): """ simplifies zero candidates with count_ops < n """ Z = self.nonzero_tuples() for c, idcs in Z: if sp.count_ops(c) < n: c = sp.simplify(c) if c != 0: return False return True
def try_solving_moving_endpoints_of_spline(): import sympy as sy sy.init_printing() a0, a1, a2, a3, k0, k1, j0, j1, new_t = sy.symbols( 'a0, a1, a2, a3, k0, k1, j0, j1, new_t') # Q: How much simpler is it if we only move one end? #j0 = k0 # A: Wow, much simpler! 30+ ops instead of 80+ #j1 = k1 # A: GADS, not much simpler at all, still 80+ operations. years = new_t * (j1 - j0) + j0 old_t = (years - k0) / (k1 - k0) d = (((a3 * old_t + a2) * old_t) + a1) * old_t + a0 #d = sy.factor(d) #d = sy.expand(d) #d = sy.simplify(d) d = sy.expand(d) d = sy.collect(d, new_t) b0 = d.coeff(new_t, 0) b1 = d.coeff(new_t, 1) b2 = d.coeff(new_t, 2) b3 = d.coeff(new_t, 3) commons, outputs = sy.cse( [b0, b1, b2, b3], sy.numbered_symbols('u'), #optimizations='basic', ) n = 0 for symbol, expr in commons: n += sy.count_ops(expr) print(symbol, '=', expr) print() for i, expr in enumerate(outputs): n += sy.count_ops(expr) print('b{} = {}'.format(i, expr)) print('Total operations: {}'.format(n))
def test_issue_2827_trigsimp_methods(): measure1 = lambda expr: len(str(expr)) measure2 = lambda expr: -count_ops(expr) # Return the most complicated result expr = (x + 1)/(x + sin(x)**2 + cos(x)**2) ans = Matrix([1]) M = Matrix([expr]) assert trigsimp(M, method='fu', measure=measure1) == ans assert trigsimp(M, method='fu', measure=measure2) != ans # all methods should work with Basic expressions even if they # aren't Expr M = Matrix.eye(1) assert all(trigsimp(M, method=m) == M for m in 'fu matching groebner old'.split()) # watch for E in exptrigsimp, not only exp() eq = 1/sqrt(E) + E assert exptrigsimp(eq) == eq
def test_issue_2827_trigsimp_methods(): measure1 = lambda expr: len(str(expr)) measure2 = lambda expr: -count_ops(expr) # Return the most complicated result expr = (x + 1) / (x + sin(x)**2 + cos(x)**2) ans = Matrix([1]) M = Matrix([expr]) assert trigsimp(M, method='fu', measure=measure1) == ans assert trigsimp(M, method='fu', measure=measure2) != ans # all methods should work with Basic expressions even if they # aren't Expr M = Matrix.eye(1) assert all( trigsimp(M, method=m) == M for m in 'fu matching groebner old'.split()) # watch for E in exptrigsimp, not only exp() eq = 1 / sqrt(E) + E assert exptrigsimp(eq) == eq
def TestTraverseExpr(tmpExp): # Inspired from sympy-master/sympy/core/expr.py: assert isinstance(tmpExp, sympy.Expr) #fraction) #Equality) assert tmpExp.is_Mul #fraction) #Equality) print("Number of operators on the rhs exp: %s" % \ sympy.count_ops(tmpExp, visual=True)) args = tmpExp.as_ordered_factors() #order=order) print("args = %s" % args) print("args[1] = %s" % args[1]) assert args[1].is_Pow args1 = args[1].args print args1 print args1[0] assert args1[1] == -1 #print("args1 (the denominator factors) = %s" % str(args1)) #print type(args[1]) assert args[1].is_Mul
def render_node(self, node): expr = NodeRenderer(use_vectorisation_idx=False).render_node(node) if is_scalar_expression(expr, self.variables) and not has_non_float(expr, self.variables): if expr in self.optimisations: name = self.optimisations[expr] else: # Do not pull out very simple expressions (including constants # and numbers) sympy_expr = str_to_sympy(expr) if sympy.count_ops(sympy_expr, visual=False) < 2: return expr self.n += 1 name = '_lio_const_'+str(self.n) self.optimisations[expr] = name return name else: return NodeRenderer.render_node(self, node)
def render_node(self, node): expr = NodeRenderer(use_vectorisation_idx=False).render_node(node) if is_scalar_expression(expr, self.variables) and not has_non_float( expr, self.variables): if expr in self.optimisations: name = self.optimisations[expr] else: # Do not pull out very simple expressions (including constants # and numbers) sympy_expr = str_to_sympy(expr) if sympy.count_ops(sympy_expr, visual=False) < 2: return expr self.n += 1 name = '_lio_const_' + str(self.n) self.optimisations[expr] = name return name else: return NodeRenderer.render_node(self, node)
def get_expr_complexity(expr): expr = parse_expr(expr) compl = 0 is_atomic_number = lambda expr: expr.is_Atom and expr.is_number numbers_expr = [subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression)] for j in numbers_expr: try: compl = compl + get_number_DL_snapped(float(j)) except: compl = compl + 1000000 n_variables = len(expr.free_symbols) n_operations = len(count_ops(expr,visual=True).free_symbols) if n_operations!=0 or n_variables!=0: compl = compl + (n_variables+n_operations)*np.log2((n_variables+n_operations)) return compl
def expression_complexity(expr, complexity=None): ''' Returns the complexity of an expression (either string or sympy) The complexity is defined as 1 for each arithmetic operation except divide which is 2, and all other operations are 20. This can be overridden using the complexity argument. Note: calling this on a statement rather than an expression is likely to lead to errors. Parameters ---------- expr: `sympy.Expr` or str The expression. complexity: None or dict (optional) A dictionary mapping expression names to their complexity, to overwrite default behaviour. Returns ------- complexity: int The complexity of the expression. ''' if isinstance(expr, str): # we do this because sympy.count_ops doesn't handle inequalities (TODO: handle sympy as well str) for op in ['<=', '>=', '==', '<', '>']: expr = expr.replace(op, '+') # work around bug with rand() and randn() (TODO: improve this) expr = expr.replace('rand()', 'rand(0)') expr = expr.replace('randn()', 'randn(0)') subs = {'ADD': 1, 'DIV': 2, 'MUL': 1, 'SUB': 1} if complexity is not None: subs.update(complexity) ops = sympy.count_ops(expr, visual=True) for atom in ops.atoms(): if hasattr(atom, 'name'): subs[atom. name] = 20 # unknown operations assumed to have a large cost return ops.evalf(subs=subs)
def Read(fileName): fin = open(fileName) while (True): str = fin.readline() if str: #pass str = str.rstrip(rstripStr) else: break if str.startswith("#"): continue for index, v in enumerate(defVars): cmpStr = v + " = " #print("cmpStr = %s" % cmpStr) #" - v" if str.startswith(cmpStr): #TestTraverseExpr(tmpExp) tmpExp = Parse(str[len(cmpStr):]) lhs = sympy.Symbol(v) print(tmpExp - lhs) lhs, rhs = PreprocessEq(lhs, tmpExp) #assert False #eqList[index] = Parse(str[len(cmpStr) : ] + " - " + v) eqList[index] = rhs - lhs #print("eqList[index] = %s" % str(eqList[index])) print("eqList[%d] = %s" % (index, eqList[index])) #print(separatevars(expr=eqList[index], symbols=[xdst, xsrc])) print("Number of operators: %s" % \ sympy.count_ops(eqList[index], visual=True)) GenCode(eqList[index])
def expression_complexity(expr, complexity=None): ''' Returns the complexity of an expression (either string or sympy) The complexity is defined as 1 for each arithmetic operation except divide which is 2, and all other operations are 20. This can be overridden using the complexity argument. Note: calling this on a statement rather than an expression is likely to lead to errors. Parameters ---------- expr: `sympy.Expr` or str The expression. complexity: None or dict (optional) A dictionary mapping expression names to their complexity, to overwrite default behaviour. Returns ------- complexity: int The complexity of the expression. ''' if isinstance(expr, str): # we do this because sympy.count_ops doesn't handle inequalities (TODO: handle sympy as well str) for op in ['<=', '>=', '==', '<', '>']: expr = expr.replace(op, '+') # work around bug with rand() and randn() (TODO: improve this) expr = expr.replace('rand()', 'rand(0)') expr = expr.replace('randn()', 'randn(0)') subs = {'ADD':1, 'DIV':2, 'MUL':1, 'SUB':1} if complexity is not None: subs.update(complexity) ops = sympy.count_ops(expr, visual=True) for atom in ops.atoms(): if hasattr(atom, 'name'): subs[atom.name] = 20 # unknown operations assumed to have a large cost return ops.evalf(subs=subs)
def Read(fileName): fin = open(fileName) while (True): str = fin.readline() if str: #pass str = str.rstrip(rstripStr) else: break if str.startswith("#"): continue for index, v in enumerate(defVars): cmpStr = v + " = " #print("cmpStr = %s" % cmpStr) #" - v" if str.startswith(cmpStr): #TestTraverseExpr(tmpExp) tmpExp = Parse(str[len(cmpStr) : ]) lhs = sympy.Symbol(v) print (tmpExp - lhs) lhs, rhs = PreprocessEq(lhs, tmpExp) #assert False #eqList[index] = Parse(str[len(cmpStr) : ] + " - " + v) eqList[index] = rhs - lhs #print("eqList[index] = %s" % str(eqList[index])) print("eqList[%d] = %s" % (index, eqList[index])) #print(separatevars(expr=eqList[index], symbols=[xdst, xsrc])) print("Number of operators: %s" % \ sympy.count_ops(eqList[index], visual=True)) GenCode(eqList[index])
def count_ops(self, visual=None): """wrapper for count_ops that returns the operation count.""" from sympy import count_ops return count_ops(self, visual) return sum(a.count_ops(visual) for a in self.args)
from sympy import simplify, cos, sin, trigsimp, cancel from sympy import sqrt, count_ops, oo, symbols, log from sympy.abc import x, y expr = (2*x + 3*x**2)/(4*x*sin(y)**2 + 2*x*cos(y)**2) expr simplify(expr) trigsimp(expr) cancel(_) root = 4/(sqrt(2)+3) simplify(root, ratio=1) == root count_ops(simplify(root, ratio=oo)) > count_ops(root) x, y = symbols('x y', positive=True) expr2 = log(x) + log(y) + log(x)*log(1/y) expr3 = simplify(expr2) expr3 count_ops(expr2) count_ops(expr3) print(count_ops(expr2, visual=True)) print(count_ops(expr3, visual=True))
def cgen_ncomp(ncomp=3, nporder=2, aggstat=False, debug=False): """Generates a C function for ncomp (int) number of components. The jth key component is always in the first position and the kth key component is always in the second. The number of enrichment stages (NP) is calculated via a taylor series approximation. The order of this approximation may be set with nporder. Only values of 1 or 2 are allowed. The aggstat argument determines whether the status messages should be aggreated and printed at the end or output as the function executes. """ start_time = time.time() stat = _aggstatus('', "generating {0} component enrichment".format(ncomp), aggstat) r = range(0, ncomp) j = 0 k = 1 # setup-symbols alpha = Symbol('alpha', positive=True, real=True) LpF = Symbol('LpF', positive=True, real=True) PpF = Symbol('PpF', positive=True, real=True) TpF = Symbol('TpF', positive=True, real=True) SWUpF = Symbol('SWUpF', positive=True, real=True) SWUpP = Symbol('SWUpP', positive=True, real=True) NP = Symbol('NP', positive=True, real=True) # Enrichment Stages NT = Symbol('NT', positive=True, real=True) # De-enrichment Stages NP0 = Symbol('NP0', positive=True, real=True) # Enrichment Stages Initial Guess NT0 = Symbol('NT0', positive=True, real=True) # De-enrichment Stages Initial Guess NP1 = Symbol('NP1', positive=True, real=True) # Enrichment Stages Computed Value NT1 = Symbol('NT1', positive=True, real=True) # De-enrichment Stages Computed Value Mstar = Symbol('Mstar', positive=True, real=True) MW = [Symbol('MW[{0}]'.format(i), positive=True, real=True) for i in r] beta = [alpha**(Mstar - MWi) for MWi in MW] # np_closed helper terms NP_b = Symbol('NP_b', real=True) NP_2a = Symbol('NP_2a', real=True) NP_sqrt_base = Symbol('NP_sqrt_base', real=True) xF = [Symbol('xF[{0}]'.format(i), positive=True, real=True) for i in r] xPi = [Symbol('xP[{0}]'.format(i), positive=True, real=True) for i in r] xTi = [Symbol('xT[{0}]'.format(i), positive=True, real=True) for i in r] xPj = Symbol('xPj', positive=True, real=True) xFj = xF[j] xTj = Symbol('xTj', positive=True, real=True) ppf = (xFj - xTj)/(xPj - xTj) tpf = (xFj - xPj)/(xTj - xPj) xP = [(((xF[i]/ppf)*(beta[i]**(NT+1) - 1))/(beta[i]**(NT+1) - beta[i]**(-NP))) \ for i in r] xT = [(((xF[i]/tpf)*(1 - beta[i]**(-NP)))/(beta[i]**(NT+1) - beta[i]**(-NP))) \ for i in r] rfeed = xFj / xF[k] rprod = xPj / xP[k] rtail = xTj / xT[k] # setup constraint equations numer = [ppf*xP[i]*log(rprod) + tpf*xT[i]*log(rtail) - xF[i]*log(rfeed) for i in r] denom = [log(beta[j]) * ((beta[i] - 1.0)/(beta[i] + 1.0)) for i in r] LoverF = sum([n/d for n, d in zip(numer, denom)]) SWUoverF = -1.0 * sum(numer) SWUoverP = SWUoverF / ppf prod_constraint = (xPj/xFj)*ppf - (beta[j]**(NT+1) - 1)/\ (beta[j]**(NT+1) - beta[j]**(-NP)) tail_constraint = (xTj/xFj)*(sum(xT)) - (1 - beta[j]**(-NP))/\ (beta[j]**(NT+1) - beta[j]**(-NP)) #xp_constraint = 1.0 - sum(xP) #xf_constraint = 1.0 - sum(xF) #xt_constraint = 1.0 - sum(xT) # This is NT(NP,...) and is correct! #nt_closed = solve(prod_constraint, NT)[0] # However, this is NT(NP,...) rewritten (by hand) to minimize the number of NP # and M* instances in the expression. Luckily this is only depends on the key # component and remains general no matter the number of components. nt_closed = (-MW[0]*log(alpha) + Mstar*log(alpha) + log(xTj) + log((-1.0 + xPj/\ xF[0])/(xPj - xTj)) - log(alpha**(NP*(MW[0] - Mstar))*(xF[0]*xPj - xPj*xTj)/\ (-xF[0]*xPj + xF[0]*xTj) + 1))/((MW[0] - Mstar)*log(alpha)) # new expression for normalized flow rate # NOTE: not needed, solved below #loverf = LoverF.xreplace({NT: nt_closed}) # Define the constraint equation with which to solve NP. This is chosen such to # minimize the number of ops in the derivatives (and thus np_closed). Other, # more verbose possibilities are commented out. #np_constraint = (xP[j]/sum(xP) - xPj).xreplace({NT: nt_closed}) #np_constraint = (xP[j]- sum(xP)*xPj).xreplace({NT: nt_closed}) #np_constraint = (xT[j]/sum(xT) - xTj).xreplace({NT: nt_closed}) np_constraint = (xT[j] - sum(xT)*xTj).xreplace({NT: nt_closed}) # get closed form approximation of NP via symbolic derivatives stat = _aggstatus(stat, " order-{0} NP approximation".format(nporder), aggstat) d0NP = np_constraint.xreplace({NP: NP0}) d1NP = diff(np_constraint, NP, 1).xreplace({NP: NP0}) if 1 == nporder: np_closed = NP0 - d1NP / d0NP elif 2 == nporder: d2NP = diff(np_constraint, NP, 2).xreplace({NP: NP0})/2.0 # taylor series polynomial coefficients, grouped by order # f(x) = ax**2 + bx + c a = d2NP b = d1NP - 2*NP0*d2NP c = d0NP - NP0*d1NP + NP0*NP0*d2NP # quadratic eq. (minus only) #np_closed = (-b - sqrt(b**2 - 4*a*c)) / (2*a) # However, we need to break up this expr as follows to prevent # a floating point arithmetic bug if b**2 - 4*a*c is very close # to zero but happens to be negative. LAME!!! np_2a = 2*a np_sqrt_base = b**2 - 4*a*c np_closed = (-NP_b - sqrt(NP_sqrt_base)) / (NP_2a) else: raise ValueError("nporder must be 1 or 2") # generate cse for writing out msg = " minimizing ops by eliminating common sub-expressions" stat = _aggstatus(stat, msg, aggstat) exprstages = [Eq(NP_b, b), Eq(NP_2a, np_2a), # fix for floating point sqrt() error Eq(NP_sqrt_base, np_sqrt_base), Eq(NP_sqrt_base, Abs(NP_sqrt_base)), Eq(NP1, np_closed), Eq(NT1, nt_closed).xreplace({NP: NP1})] cse_stages = cse(exprstages, numbered_symbols('n')) exprothers = [Eq(LpF, LoverF), Eq(PpF, ppf), Eq(TpF, tpf), Eq(SWUpF, SWUoverF), Eq(SWUpP, SWUoverP)] + \ [Eq(*z) for z in zip(xPi, xP)] + [Eq(*z) for z in zip(xTi, xT)] exprothers = [e.xreplace({NP: NP1, NT: NT1}) for e in exprothers] cse_others = cse(exprothers, numbered_symbols('g')) exprops = count_ops(exprstages + exprothers) cse_ops = count_ops(cse_stages + cse_others) msg = " reduced {0} ops to {1}".format(exprops, cse_ops) stat = _aggstatus(stat, msg, aggstat) # create function body ccode, repnames = cse_to_c(*cse_stages, indent=6, debug=debug) ccode_others, repnames_others = cse_to_c(*cse_others, indent=6, debug=debug) ccode += ccode_others repnames |= repnames_others msg = " completed in {0:.3G} s".format(time.time() - start_time) stat = _aggstatus(stat, msg, aggstat) if aggstat: print(stat) return ccode, repnames, stat
def intersect(self, other, **flags): if not isinstance(other, Circle): raise BaryException() if sympy.count_ops(self.eqn()) > sympy.count_ops(other.eqn()): raise BaryException() return [Point(*sol) for sol in hsolve([self.eqn(), ((self._eqn - other._eqn) / (x + y + z)).simplify()], x, y, z, **flags)]
def add_snap_expr_on_pareto_polyfit(pathdir, filename, math_expr, PA): def unsnap_recur(expr, param_dict, unsnapped_param_dict): """Recursively transform each numerical value into a learnable parameter.""" import sympy from sympy import Symbol if isinstance(expr, sympy.numbers.Float) or isinstance(expr, sympy.numbers.Integer) or isinstance(expr, sympy.numbers.Rational) or isinstance(expr, sympy.numbers.Pi): used_param_names = list(param_dict.keys()) + list(unsnapped_param_dict) unsnapped_param_name = get_next_available_key(used_param_names, "p", is_underscore=False) unsnapped_param_dict[unsnapped_param_name] = float(expr) unsnapped_expr = Symbol(unsnapped_param_name) return unsnapped_expr elif isinstance(expr, sympy.symbol.Symbol): return expr else: unsnapped_sub_expr_list = [] for sub_expr in expr.args: unsnapped_sub_expr = unsnap_recur(sub_expr, param_dict, unsnapped_param_dict) unsnapped_sub_expr_list.append(unsnapped_sub_expr) return expr.func(*unsnapped_sub_expr_list) def get_next_available_key(iterable, key, midfix="", suffix="", is_underscore=True): """Get the next available key that does not collide with the keys in the dictionary.""" if key + suffix not in iterable: return key + suffix else: i = 0 underscore = "_" if is_underscore else "" while "{}{}{}{}{}".format(key, underscore, midfix, i, suffix) in iterable: i += 1 new_key = "{}{}{}{}{}".format(key, underscore, midfix, i, suffix) return new_key eq = parse_expr(str(math_expr)) expr = eq # Get the numbers appearing in the expression is_atomic_number = lambda expr: expr.is_Atom and expr.is_number eq_numbers = [subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression)] # Do zero snap one parameter at a time zero_snapped_expr = [] for w in range(len(eq_numbers)): try: param_dict = {} unsnapped_param_dict = {'p':1} eq = unsnap_recur(expr,param_dict,unsnapped_param_dict) new_numbers = zeroSnap(eq_numbers,w+1) for kk in range(len(new_numbers)): eq_numbers[new_numbers[kk][0]] = new_numbers[kk][1] jj = 0 for parm in unsnapped_param_dict: if parm!="p": eq = eq.subs(parm, eq_numbers[jj]) jj = jj + 1 zero_snapped_expr = zero_snapped_expr + [eq] except: continue for i in range(len(zero_snapped_expr)): try: # Calculate the error of the new, snapped expression snapped_error = get_symbolic_expr_error(pathdir,filename,str(zero_snapped_expr[i])) # Calculate the complexity of the new, snapped expression expr = simplify(powsimp(zero_snapped_expr[i])) for s in (expr.free_symbols): s = symbols(str(s), real = True) expr = simplify(parse_expr(str(zero_snapped_expr[i]),locals())) expr = intify(expr) is_atomic_number = lambda expr: expr.is_Atom and expr.is_number numbers_expr = [subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression)] snapped_complexity = 0 for j in numbers_expr: snapped_complexity = snapped_complexity + get_number_DL_snapped(float(j)) # Add the complexity due to symbols n_variables = len(expr.free_symbols) n_operations = len(count_ops(expr,visual=True).free_symbols) if n_operations!=0 or n_variables!=0: snapped_complexity = snapped_complexity + (n_variables+n_operations)*np.log2((n_variables+n_operations)) PA.add(Point(x=snapped_complexity, y=snapped_error, data=str(expr))) except: print("error") print("") continue return(PA)
def cudaComputeRHSSourceUnStaged(fname,outs,varnames,headers=[],sharedMemSz=48*1024, max_alloc_threads=1024): with open(fname, 'w') as ofile: ofile.write("// generated by Dendro-GR SymPyGR code gernation framework\n") ofile.write("//date: "+str(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))+"\n") ofile.write("\n") ofile.write("\n") for header in headers: ofile.write("#include \""+header+"\"\n") mi = [0, 1, 2, 4, 5, 8] midx = ['00', '01', '02', '11', '12', '22'] ofile.write("\n") idx="[pp]" for var_id in range(0,len(varnames)): varOut=varnames[var_id] exp=outs[var_id] print("code generation for : "+varOut) num_e = 0 lexp = [] lname = [] if type(exp) == list: num_e = num_e + len(exp) for j, ev in enumerate(exp): lexp.append(ev) lname.append(varOut+repr(j)+idx) elif type(exp) == sympy.Matrix: num_e = num_e + len(exp) for j, k in enumerate(mi): lexp.append(exp[k]) lname.append(varOut+midx[j]+idx) else: num_e = num_e + 1 lexp.append(exp) lname.append(varOut+idx) # print("cse tree build begin") ee_name = 'DENDRO_' #''.join(random.choice(string.ascii_uppercase) for _ in range(5)) ee_syms = sympy.utilities.numbered_symbols(prefix=ee_name) _v = sympy.cse(lexp, symbols=ee_syms, optimizations='basic') # print("cse tree build completed") # bssn variables needed for rhs computation. bssnInputVars=[] # bssn variables output bssnOutputVars=[] # derivative variables needed for rhs computation derivVars=[] # staged bssn variables. bssnStagedVars=[] if type(exp) == list: for j, ev in enumerate(exp): regm=re.findall(re.compile(r"([A-Z,a-z,0-9,_]*\[pp\])"),dendro.change_deriv_names(str(ev))) for varDep in regm: if varDep[0:-4] in varEnumToInputSymbol.keys(): bssnInputVars.append(varDep[0:-4]) elif varDep[0:-4] in varEnumToOutputSymbol.keys(): bssnOutputVars.append(varDep[0:-4]) elif varDep[0:-4] in varEnumToStagedSymbol: bssnStagedVars.append(varDep[0:-4]) else: for key,value in custom_functions.items(): if value in varDep[0:-4]: derivVars.append(varDep[0:-4]) break elif type(exp)==sympy.Matrix: regm=re.findall(re.compile(r"([A-Z,a-z,0-9,_]*\[pp\])"),dendro.change_deriv_names(str(exp))) for varDep in regm: if varDep[0:-4] in varEnumToInputSymbol.keys(): bssnInputVars.append(varDep[0:-4]) elif varDep[0:-4] in varEnumToOutputSymbol.keys(): bssnOutputVars.append(varDep[0:-4]) elif varDep[0:-4] in varEnumToStagedSymbol: bssnStagedVars.append(varDep[0:-4]) else: for key,value in custom_functions.items(): if value in varDep[0:-4]: derivVars.append(varDep[0:-4]) break else: regm=re.findall(re.compile(r"([A-Z,a-z,0-9,_]*\[pp\])"),dendro.change_deriv_names(str(exp))) for varDep in regm: if varDep[0:-4] in varEnumToInputSymbol.keys(): bssnInputVars.append(varDep[0:-4]) elif varDep[0:-4] in varEnumToOutputSymbol.keys(): bssnOutputVars.append(varDep[0:-4]) elif varDep[0:-4] in varEnumToStagedSymbol: bssnStagedVars.append(varDep[0:-4]) else: for key,value in custom_functions.items(): if value in varDep[0:-4]: derivVars.append(varDep[0:-4]) break for lvar in lname: if lvar[0:-4] in varEnumToOutputSymbol.keys(): bssnOutputVars.append(lvar[0:-4]) else: bssnStagedVars.append(lvar[0:-4]) varEnumToStagedSymbol.append(lvar[0:-4]) bssnInputVars=list(set(bssnInputVars)) bssnOutputVars=list(set(bssnOutputVars)) bssnStagedVars=list(set(bssnStagedVars)) derivVars=list(set(derivVars)) total_dep=len(bssnInputVars)+len(bssnStagedVars)+len(derivVars)+len(bssnOutputVars); total_shared_mem_vars=len(bssnInputVars)+len(bssnStagedVars)+len(derivVars); print("\n total_shared_mem_vars ="+str(total_shared_mem_vars)+"\n") # print("dependenacy computation completed\n") #calculating max possible no of shared memory var allocations for each bssn var allocated_threads=max_alloc_threads if(total_shared_mem_vars>(sharedMemSz/(8*max_alloc_threads))): x=sharedMemSz /(8*total_shared_mem_vars) allocated_threads= (int)(math.pow(2,math.floor(math.log(x,2)))) print("allocated_threads = "+str(allocated_threads)+"\n\n") ofile.write("/** computes rhs "+varOut+"*/\n") ofile.write("\n__device__ void "+varOut+"(int pp, double eta, double *"+ dev_var_in+",\n") ofile.write("\tdouble * "+dev_var_out+",\n") ofile.write("\t#include \"para_derivs_offsets.h\"\n,\n") ofile.write("\t#include \"para_staged.h\"\n){\n") # allocate memory for shared deriv variables. ofile.write("\t//allocate memory for shared deriv variables. \n") ofile.write("\n\n") ofile.write("\t //input vars shared alloc begin\n") for var in bssnInputVars: ofile.write("\t__shared__ double "+var+"_shared[" + str(allocated_threads) +"];\n") ofile.write("\t //input vars shared alloc end\n") ofile.write("\t // staged vars shared alloc begin\n") for var in varEnumToStagedSymbol: if(var not in bssnStagedVars): ofile.write("\t__shared__ double "+var+"_shared[" + str(allocated_threads) +"];\n") ofile.write("\t // staged vars shared alloc end\n") ofile.write("\t // deriv vars shared alloc begin\n") for var in derivVars: ofile.write("\t__shared__ double "+var+"_shared[" + str(allocated_threads) +"];\n") ofile.write("\t // deriv vars shared alloc end\n") #ofile.write("\n\n\tint thread_id = blockIdx.x*"+str(allocated_threads)+" + threadIdx.x;\n") ofile.write("\tint t=threadIdx.x;\n") ofile.write("\t //input vars begin\n") for var in bssnInputVars: ofile.write("\t "+var+"_shared[t] = " +dev_var_in+"["+var+"Int+"+idx[1:-1]+"]"+";\n") ofile.write("\t //input vars end\n") ofile.write("\t // staged vars begin\n") for var in varEnumToStagedSymbol: if(var not in bssnStagedVars): ofile.write("\t "+var+"_shared[t] = "+var+idx+";\n") ofile.write("\t // staged vars end\n") ofile.write("\t // deriv vars begin\n") for var in derivVars: ofile.write("\t "+var+"_shared[t] = " + var +idx+";\n") ofile.write("\t // deriv vars end\n") ofile.write("\t__syncthreads();\n") ofile.write("\t\t//load data from global to shared memory ends\n") ofile.write("\t\t // Dendro: {{{ \n") ofile.write("\t\t // Dendro: original ops: "+str(sympy.count_ops(lexp))+"\n") rops=0 ofile.write("\t\t // Dendro: printing temp variables\n") for (v1, v2) in _v[0]: ofile.write('\t\t double ') ofile.write("\t\t"+change_to_shared_names(dendro.change_deriv_names(sympy.ccode(v2, assign_to=v1, user_functions=custom_functions)))+"\n") rops = rops + sympy.count_ops(v2) ofile.write("\t\t // Dendro: printing variables\n\n") for i, e in enumerate(_v[1]): ofile.write("\t\t "+change_to_shared_names(dendro.change_deriv_names(sympy.ccode(e, assign_to=lname[i], user_functions=custom_functions)))+"\n") rops = rops + sympy.count_ops(e) ofile.write("\t\t // Dendro: reduced ops: "+str(rops)+"\n") ofile.write("\t\t // Dendro: }}} \n") ofile.write("\t// store computed variables\n\n") ofile.write("} ")
def _mellin_transform(f, x, s_, integrator=_default_integrator, simplify=True): """ Backend function to compute mellin transforms. """ from sympy import re, Max, Min, count_ops # We use a fresh dummy, because assumptions on s might drop conditions on # convergence of the integral. s = _dummy('s', 'mellin-transform', f) F = integrator(x**(s-1) * f, x) if not F.has(Integral): return _simplify(F.subs(s, s_), simplify), (-oo, oo), True if not F.is_Piecewise: raise IntegralTransformError('Mellin', f, 'could not compute integral') F, cond = F.args[0] if F.has(Integral): raise IntegralTransformError('Mellin', f, 'integral in unexpected form') def process_conds(cond): """ Turn ``cond`` into a strip (a, b), and auxiliary conditions. """ a = -oo b = oo aux = True conds = conjuncts(to_cnf(cond)) t = Dummy('t', real=True) for c in conds: a_ = oo b_ = -oo aux_ = [] for d in disjuncts(c): d_ = d.replace(re, lambda x: x.as_real_imag()[0]).subs(re(s), t) if not d.is_Relational or (d.rel_op != '<' and d.rel_op != '<=') \ or d_.has(s) or not d_.has(t): aux_ += [d] continue soln = _solve_inequality(d_, t) if not soln.is_Relational or \ (soln.rel_op != '<' and soln.rel_op != '<='): aux_ += [d] continue if soln.lhs == t: b_ = Max(soln.rhs, b_) else: a_ = Min(soln.lhs, a_) if a_ != oo and a_ != b: a = Max(a_, a) elif b_ != -oo and b_ != a: b = Min(b_, b) else: aux = And(aux, Or(*aux_)) return a, b, aux conds = [process_conds(c) for c in disjuncts(cond)] conds = filter(lambda x: x[2] is not False, conds) conds.sort(key=lambda x: (x[0]-x[1], count_ops(x[2]))) if not conds: raise IntegralTransformError('Mellin', f, 'no convergence found') a, b, aux = conds[0] return _simplify(F.subs(s, s_), simplify), (a, b), aux
def count_ops(self, visual=None): """wrapper for count_ops that returns the operation count.""" from sympy import count_ops return count_ops(self, visual)
def simplifyfunc(eqs, occurrences=2): import sympy variables = eqs.atoms(sympy.Symbol) # the number of time the variable occurs in the equations occurrences = 1 solvar = [] solsol = [] while True: # the variables in each eq of eqs eqs_vars = [] # the number of variables in each eq of eqs eqs_vars_freq = [] for i in range(0, len(eqs)): symbols = eqs[i, 0].atoms(sympy.Symbol) eqs_vars.append(symbols) eqs_vars_freq.append(len(symbols)) varstar = None for var in variables: # I compute the number of times the variable occurs in the equations. # I then find whether equations exist with only one occurrence of that form of variable i.e. K - L has only one form of K but K + K^2 - L has two forms numoc = 0 besteq = None for i in range(len(eqs)): # number of occurrences if var in eqs_vars[i]: numoc = numoc + 1 # check number of times occurs eq = eqs[i] # replacing K + K**2 - L with K + K**2 - 1. # ISSUE: what if constants cancel i.e. K + K**2 - L + T? Then get wrong number. for var2 in variables: if var2 != var: eq = eq.subs(var2, 1) # ignore integer numterms = sympy.count_ops(eq) - 1 # set the best equation for this term if besteq is None: besteq = i elif besteqnumterms > numterms: besteq = i elif besteqnumterms == numterms and eqs_vars_freq[ besteq] > eqs_vars_freq[i]: besteq = i if besteq == i: besteqnumterms = numterms if numoc <= occurrences: if varstar is None or numocstar < numoc or ( numocstar == numoc and besteqnumterms < numtermsstar): if besteqnumterms == 1 or numoc == 1: varstar = var numocstar = numoc besteqstar = besteq numtermsstar = besteqnumterms # make replacements for single eq immediately. # if numocstar == 1: # break # if no sol: if varstar is None: break sol = sympy.solve(eqs[besteqstar], varstar) # remove row eqs.row_del(besteqstar) # substitute out other instances of variable for i in range(len(eqs)): eqs[i, 0] = eqs[i, 0].subs(varstar, sol) variables.remove(varstar) solvar.append(str(varstar)) if len(sol) == 1: sol = sol[0] solsol.append(sol) return (eqs, solvar, solsol)
def count(val): return count_ops(val, visual=True)
def count(val): return count_ops(val, visual=False)
def run_bf_polyfit(pathdir,pathdir_transformed,filename,BF_try_time,BF_ops_file_type, PA, polyfit_deg=4, output_type=""): ############################################################################################################################# # run BF on the data (+) print("Checking for brute force + \n") brute_force(pathdir_transformed,filename,BF_try_time,BF_ops_file_type,"+") try: # load the BF output data bf_all_output = np.loadtxt("results.dat", dtype="str") express = bf_all_output[:,2] prefactors = bf_all_output[:,1] prefactors = [str(i) for i in prefactors] # Calculate the complexity of the bf expression the same way as for gradient descent case complexity = [] errors = [] eqns = [] for i in range(len(prefactors)): try: if output_type=="": eqn = prefactors[i] + "+" + RPN_to_eq(express[i]) elif output_type=="acos": eqn = "cos(" + prefactors[i] + "+" + RPN_to_eq(express[i]) + ")" elif output_type=="asin": eqn = "sin(" + prefactors[i] + "+" + RPN_to_eq(express[i]) + ")" elif output_type=="atan": eqn = "tan(" + prefactors[i] + "+" + RPN_to_eq(express[i]) + ")" elif output_type=="cos": eqn = "acos(" + prefactors[i] + "+" + RPN_to_eq(express[i]) + ")" elif output_type=="exp": eqn = "log(" + prefactors[i] + "+" + RPN_to_eq(express[i]) + ")" elif output_type=="inverse": eqn = "1/(" + prefactors[i] + "+" + RPN_to_eq(express[i]) + ")" elif output_type=="log": eqn = "exp(" + prefactors[i] + "+" + RPN_to_eq(express[i]) + ")" elif output_type=="sin": eqn = "acos(" + prefactors[i] + "+" + RPN_to_eq(express[i]) + ")" elif output_type=="sqrt": eqn = "(" + prefactors[i] + "+" + RPN_to_eq(express[i]) + ")**2" elif output_type=="squared": eqn = "sqrt(" + prefactors[i] + "+" + RPN_to_eq(express[i]) + ")" elif output_type=="tan": eqn = "atan(" + prefactors[i] + "+" + RPN_to_eq(express[i]) + ")" eqns = eqns + [eqn] errors = errors + [get_symbolic_expr_error(pathdir,filename,eqn)] expr = parse_expr(eqn) is_atomic_number = lambda expr: expr.is_Atom and expr.is_number numbers_expr = [subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression)] compl = 0 for j in numbers_expr: try: compl = compl + get_number_DL(float(j)) except: compl = compl + 1000000 # Add the complexity due to symbols n_variables = len(expr.free_symbols) n_operations = len(count_ops(expr,visual=True).free_symbols) if n_operations!=0 or n_variables!=0: compl = compl + (n_variables+n_operations)*np.log2((n_variables+n_operations)) complexity = complexity + [compl] except: continue for i in range(len(complexity)): PA.add(Point(x=complexity[i], y=errors[i], data=eqns[i])) # run gradient descent of BF output parameters and add the results to the Pareto plot for i in range(len(express)): try: bf_gd_update = RPN_to_pytorch(pathdir+filename,eqns[i]) PA.add(Point(x=bf_gd_update[1],y=bf_gd_update[0],data=bf_gd_update[2])) except: continue except: pass ############################################################################################################################# # run BF on the data (*) print("Checking for brute force * \n") brute_force(pathdir_transformed,filename,BF_try_time,BF_ops_file_type,"*") try: # load the BF output data bf_all_output = np.loadtxt("results.dat", dtype="str") express = bf_all_output[:,2] prefactors = bf_all_output[:,1] prefactors = [str(i) for i in prefactors] # Calculate the complexity of the bf expression the same way as for gradient descent case complexity = [] errors = [] eqns = [] for i in range(len(prefactors)): try: if output_type=="": eqn = prefactors[i] + "*" + RPN_to_eq(express[i]) elif output_type=="acos": eqn = "cos(" + prefactors[i] + "*" + RPN_to_eq(express[i]) + ")" elif output_type=="asin": eqn = "sin(" + prefactors[i] + "*" + RPN_to_eq(express[i]) + ")" elif output_type=="atan": eqn = "tan(" + prefactors[i] + "*" + RPN_to_eq(express[i]) + ")" elif output_type=="cos": eqn = "acos(" + prefactors[i] + "*" + RPN_to_eq(express[i]) + ")" elif output_type=="exp": eqn = "log(" + prefactors[i] + "*" + RPN_to_eq(express[i]) + ")" elif output_type=="inverse": eqn = "1/(" + prefactors[i] + "*" + RPN_to_eq(express[i]) + ")" elif output_type=="log": eqn = "exp(" + prefactors[i] + "*" + RPN_to_eq(express[i]) + ")" elif output_type=="sin": eqn = "acos(" + prefactors[i] + "*" + RPN_to_eq(express[i]) + ")" elif output_type=="sqrt": eqn = "(" + prefactors[i] + "*" + RPN_to_eq(express[i]) + ")**2" elif output_type=="squared": eqn = "sqrt(" + prefactors[i] + "*" + RPN_to_eq(express[i]) + ")" elif output_type=="tan": eqn = "atan(" + prefactors[i] + "*" + RPN_to_eq(express[i]) + ")" eqns = eqns + [eqn] errors = errors + [get_symbolic_expr_error(pathdir,filename,eqn)] expr = parse_expr(eqn) is_atomic_number = lambda expr: expr.is_Atom and expr.is_number numbers_expr = [subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression)] compl = 0 for j in numbers_expr: try: compl = compl + get_number_DL(float(j)) except: compl = compl + 1000000 # Add the complexity due to symbols n_variables = len(expr.free_symbols) n_operations = len(count_ops(expr,visual=True).free_symbols) if n_operations!=0 or n_variables!=0: compl = compl + (n_variables+n_operations)*np.log2((n_variables+n_operations)) complexity = complexity + [compl] except: continue # add the BF output to the Pareto plot for i in range(len(complexity)): PA.add(Point(x=complexity[i], y=errors[i], data=eqns[i])) # run gradient descent of BF output parameters and add the results to the Pareto plot for i in range(len(express)): try: bf_gd_update = RPN_to_pytorch(pathdir+filename,eqns[i]) PA.add(Point(x=bf_gd_update[1],y=bf_gd_update[0],data=bf_gd_update[2])) except: continue except: pass ############################################################################################################################# # run polyfit on the data print("Checking polyfit \n") polyfit_result = polyfit(polyfit_deg, pathdir_transformed+filename) eqn = str(polyfit_result[0]) # Calculate the complexity of the polyfit expression the same way as for gradient descent case if output_type=="": eqn = eqn elif output_type=="acos": eqn = "cos(" + eqn + ")" elif output_type=="asin": eqn = "sin(" + eqn + ")" elif output_type=="atan": eqn = "tan(" + eqn + ")" elif output_type=="cos": eqn = "acos(" + eqn + ")" elif output_type=="exp": eqn = "log(" + eqn + ")" elif output_type=="inverse": eqn = "1/(" + eqn + ")" elif output_type=="log": eqn = "exp(" + eqn + ")" elif output_type=="sin": eqn = "acos(" + eqn + ")" elif output_type=="sqrt": eqn = "(" + eqn + ")**2" elif output_type=="squared": eqn = "sqrt(" + eqn + ")" elif output_type=="tan": eqn = "atan(" + eqn + ")" polyfit_err = get_symbolic_expr_error(pathdir,filename,eqn) expr = parse_expr(eqn) is_atomic_number = lambda expr: expr.is_Atom and expr.is_number numbers_expr = [subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression)] complexity = 0 for j in numbers_expr: complexity = complexity + get_number_DL(float(j)) try: # Add the complexity due to symbols n_variables = len(polyfit_result[0].free_symbols) n_operations = len(count_ops(polyfit_result[0],visual=True).free_symbols) if n_operations!=0 or n_variables!=0: complexity = complexity + (n_variables+n_operations)*np.log2((n_variables+n_operations)) except: pass #run zero snap on polyfit output PA_poly = ParetoSet() PA_poly.add(Point(x=complexity, y=polyfit_err, data=str(eqn))) PA_poly = add_snap_expr_on_pareto_polyfit(pathdir, filename, str(eqn), PA_poly) for l in range(len(PA_poly.get_pareto_points())): PA.add(Point(PA_poly.get_pareto_points()[l][0],PA_poly.get_pareto_points()[l][1],PA_poly.get_pareto_points()[l][2])) print("Complexity RMSE Expression") for pareto_i in range(len(PA.get_pareto_points())): print(PA.get_pareto_points()[pareto_i]) return PA
def add_bf_on_numbers_on_pareto(pathdir, filename, PA, math_expr): def unsnap_recur(expr, param_dict, unsnapped_param_dict): """Recursively transform each numerical value into a learnable parameter.""" import sympy from sympy import Symbol if isinstance(expr, sympy.numbers.Float) or isinstance( expr, sympy.numbers.Integer) or isinstance( expr, sympy.numbers.Rational) or isinstance( expr, sympy.numbers.Pi): used_param_names = list( param_dict.keys()) + list(unsnapped_param_dict) unsnapped_param_name = get_next_available_key(used_param_names, "p", is_underscore=False) unsnapped_param_dict[unsnapped_param_name] = float(expr) unsnapped_expr = Symbol(unsnapped_param_name) return unsnapped_expr elif isinstance(expr, sympy.symbol.Symbol): return expr else: unsnapped_sub_expr_list = [] for sub_expr in expr.args: unsnapped_sub_expr = unsnap_recur(sub_expr, param_dict, unsnapped_param_dict) unsnapped_sub_expr_list.append(unsnapped_sub_expr) return expr.func(*unsnapped_sub_expr_list) def get_next_available_key(iterable, key, midfix="", suffix="", is_underscore=True): """Get the next available key that does not collide with the keys in the dictionary.""" if key + suffix not in iterable: return key + suffix else: i = 0 underscore = "_" if is_underscore else "" while "{}{}{}{}{}".format(key, underscore, midfix, i, suffix) in iterable: i += 1 new_key = "{}{}{}{}{}".format(key, underscore, midfix, i, suffix) return new_key eq = parse_expr(str(math_expr)) expr = eq # Get the numbers appearing in the expression is_atomic_number = lambda expr: expr.is_Atom and expr.is_number eq_numbers = [ subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression) ] # Do bf on one parameter at a time bf_on_numbers_expr = [] for w in range(len(eq_numbers)): try: param_dict = {} unsnapped_param_dict = {'p': 1} eq_ = unsnap_recur(expr, param_dict, unsnapped_param_dict) eq = eq_ np.savetxt(pathdir + "number_for_bf_%s.txt" % w, [eq_numbers[w]]) brute_force_number(pathdir, "number_for_bf_%s.txt" % w) # Load the predictions made by the bf code bf_numbers = np.loadtxt("results.dat", usecols=(1, ), dtype="str") new_numbers = copy.deepcopy(eq_numbers) # replace the number under consideration by all the proposed bf numbers for kk in range(len(bf_numbers)): eq = eq_ new_numbers[w] = parse_expr(RPN_to_eq(bf_numbers[kk])) jj = 0 for parm in unsnapped_param_dict: if parm != "p": eq = eq.subs(parm, new_numbers[jj]) jj = jj + 1 bf_on_numbers_expr = bf_on_numbers_expr + [eq] except: continue for i in range(len(bf_on_numbers_expr)): try: # Calculate the error of the new, snapped expression snapped_error = get_symbolic_expr_error(pathdir, filename, str(bf_on_numbers_expr[i])) # Calculate the complexity of the new, snapped expression expr = simplify(powsimp(bf_on_numbers_expr[i])) is_atomic_number = lambda expr: expr.is_Atom and expr.is_number numbers_expr = [ subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression) ] snapped_complexity = 0 for j in numbers_expr: snapped_complexity = snapped_complexity + get_number_DL_snapped( float(j)) # Add the complexity due to symbols n_variables = len(expr.free_symbols) n_operations = len(count_ops(expr, visual=True).free_symbols) if n_operations != 0 or n_variables != 0: snapped_complexity = snapped_complexity + ( n_variables + n_operations) * np.log2( (n_variables + n_operations)) PA.add(Point(x=snapped_complexity, y=snapped_error, data=str(expr))) except: continue return (PA)
def add_snap_expr_on_pareto_polyfit(pathdir, filename, math_expr, PA): input_data = np.loadtxt(pathdir + filename) def unsnap_recur(expr, param_dict, unsnapped_param_dict): """Recursively transform each numerical value into a learnable parameter.""" import sympy from sympy import Symbol if isinstance(expr, sympy.numbers.Float) or isinstance( expr, sympy.numbers.Integer) or isinstance( expr, sympy.numbers.Rational) or isinstance( expr, sympy.numbers.Pi): used_param_names = list( param_dict.keys()) + list(unsnapped_param_dict) unsnapped_param_name = get_next_available_key(used_param_names, "p", is_underscore=False) unsnapped_param_dict[unsnapped_param_name] = float(expr) unsnapped_expr = Symbol(unsnapped_param_name) return unsnapped_expr elif isinstance(expr, sympy.symbol.Symbol): return expr else: unsnapped_sub_expr_list = [] for sub_expr in expr.args: unsnapped_sub_expr = unsnap_recur(sub_expr, param_dict, unsnapped_param_dict) unsnapped_sub_expr_list.append(unsnapped_sub_expr) return expr.func(*unsnapped_sub_expr_list) def get_next_available_key(iterable, key, midfix="", suffix="", is_underscore=True): """Get the next available key that does not collide with the keys in the dictionary.""" if key + suffix not in iterable: return key + suffix else: i = 0 underscore = "_" if is_underscore else "" while "{}{}{}{}{}".format(key, underscore, midfix, i, suffix) in iterable: i += 1 new_key = "{}{}{}{}{}".format(key, underscore, midfix, i, suffix) return new_key eq = parse_expr(str(math_expr)) expr = eq # # Get the numbers appearing in the expression # is_atomic_number = lambda expr: expr.is_Atom and expr.is_number # eq_numbers = [subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression)] # # # Do zero snap one parameter at a time # zero_snapped_expr = [] # for w in range(len(eq_numbers)): # try: # param_dict = {} # unsnapped_param_dict = {'p':1} # eq = unsnap_recur(expr,param_dict,unsnapped_param_dict) # new_numbers = zeroSnap(eq_numbers,w+1) # for kk in range(len(new_numbers)): # eq_numbers[new_numbers[kk][0]] = new_numbers[kk][1] # jj = 0 # for parm in unsnapped_param_dict: # if parm!="p": # eq = eq.subs(parm, eq_numbers[jj]) # jj = jj + 1 # zero_snapped_expr = zero_snapped_expr + [eq] # except: # continue # Get the numbers appearing in the expression is_atomic_number = lambda expr: expr.is_Atom and expr.is_number eq_numbers = [ subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression) ] # Do integer snap one parameter at a time integer_snapped_expr = [] for w in range(len(eq_numbers)): try: param_dict = {} unsnapped_param_dict = {'p': 1} eq = unsnap_recur(expr, param_dict, unsnapped_param_dict) del unsnapped_param_dict["p"] temp_unsnapped_param_dict = copy.deepcopy(unsnapped_param_dict) new_numbers = integerSnap(eq_numbers, w + 1) new_numbers = {"p" + str(k): v for k, v in new_numbers.items()} temp_unsnapped_param_dict.update(new_numbers) #for kk in range(len(new_numbers)): # eq_numbers[new_numbers[kk][0]] = new_numbers[kk][1] new_eq = re.sub(r"(p\d*)", r"{\1}", str(eq)) new_eq = new_eq.format_map(temp_unsnapped_param_dict) integer_snapped_expr = integer_snapped_expr + [parse_expr(new_eq)] except: continue # Get the numbers appearing in the expression is_atomic_number = lambda expr: expr.is_Atom and expr.is_number eq_numbers = [ subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression) ] # Do rational snap one parameter at a time rational_snapped_expr = [] for w in range(len(eq_numbers)): try: param_dict = {} unsnapped_param_dict = {'p': 1} eq = unsnap_recur(expr, param_dict, unsnapped_param_dict) del unsnapped_param_dict["p"] temp_unsnapped_param_dict = copy.deepcopy(unsnapped_param_dict) new_numbers = rationalSnap(eq_numbers, w + 1) new_numbers = {"p" + str(k): v for k, v in new_numbers.items()} temp_unsnapped_param_dict.update(new_numbers) #for kk in range(len(new_numbers)): # eq_numbers_snap[new_numbers[kk][0]] = new_numbers[kk][1][1:3] new_eq = re.sub(r"(p\d*)", r"{\1}", str(eq)) new_eq = new_eq.format_map(temp_unsnapped_param_dict) rational_snapped_expr = rational_snapped_expr + [ parse_expr(new_eq) ] except: continue snapped_expr = np.append(integer_snapped_expr, rational_snapped_expr) # snapped_expr = np.append(snapped_expr,rational_snapped_expr) integer_snapped_expr = snapped_expr for i in range(len(snapped_expr)): try: # Calculate the error of the new, snapped expression snapped_error = get_symbolic_expr_error(input_data, str(snapped_expr[i])) # Calculate the complexity of the new, snapped expression expr = snapped_expr[i] for s in (expr.free_symbols): s = symbols(str(s), real=True) expr = parse_expr(str(snapped_expr[i]), locals()) expr = intify(expr) is_atomic_number = lambda expr: expr.is_Atom and expr.is_number numbers_expr = [ subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression) ] snapped_complexity = 0 for j in numbers_expr: snapped_complexity = snapped_complexity + get_number_DL_snapped( float(j)) # Add the complexity due to symbols n_variables = len(expr.free_symbols) n_operations = len(count_ops(expr, visual=True).free_symbols) if n_operations != 0 or n_variables != 0: snapped_complexity = snapped_complexity + ( n_variables + n_operations) * np.log2( (n_variables + n_operations)) PA.add(Point(x=snapped_complexity, y=snapped_error, data=str(expr))) except: continue return (PA)
from sympy import simplify, cos, sin, trigsimp, cancel from sympy import sqrt, count_ops, oo, symbols, log from sympy.abc import x, y expr = (2 * x + 3 * x**2) / (4 * x * sin(y)**2 + 2 * x * cos(y)**2) expr simplify(expr) trigsimp(expr) cancel(_) root = 4 / (sqrt(2) + 3) simplify(root, ratio=1) == root count_ops(simplify(root, ratio=oo)) > count_ops(root) x, y = symbols('x y', positive=True) expr2 = log(x) + log(y) + log(x) * log(1 / y) expr3 = simplify(expr2) expr3 count_ops(expr2) count_ops(expr3) print(count_ops(expr2, visual=True)) print(count_ops(expr3, visual=True))
def add_snap_expr_on_pareto(pathdir, filename, math_expr, PA, DR_file=""): def unsnap_recur(expr, param_dict, unsnapped_param_dict): """Recursively transform each numerical value into a learnable parameter.""" import sympy from sympy import Symbol if isinstance(expr, sympy.numbers.Float) or isinstance( expr, sympy.numbers.Integer) or isinstance( expr, sympy.numbers.Rational) or isinstance( expr, sympy.numbers.Pi): used_param_names = list( param_dict.keys()) + list(unsnapped_param_dict) unsnapped_param_name = get_next_available_key(used_param_names, "p", is_underscore=False) unsnapped_param_dict[unsnapped_param_name] = float(expr) unsnapped_expr = Symbol(unsnapped_param_name) return unsnapped_expr elif isinstance(expr, sympy.symbol.Symbol): return expr else: unsnapped_sub_expr_list = [] for sub_expr in expr.args: unsnapped_sub_expr = unsnap_recur(sub_expr, param_dict, unsnapped_param_dict) unsnapped_sub_expr_list.append(unsnapped_sub_expr) return expr.func(*unsnapped_sub_expr_list) def get_next_available_key(iterable, key, midfix="", suffix="", is_underscore=True): """Get the next available key that does not collide with the keys in the dictionary.""" if key + suffix not in iterable: return key + suffix else: i = 0 underscore = "_" if is_underscore else "" while "{}{}{}{}{}".format(key, underscore, midfix, i, suffix) in iterable: i += 1 new_key = "{}{}{}{}{}".format(key, underscore, midfix, i, suffix) return new_key eq = parse_expr(str(math_expr)) expr = eq # Get the numbers appearing in the expression is_atomic_number = lambda expr: expr.is_Atom and expr.is_number eq_numbers = [ subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression) ] # Do integer snap one parameter at a time integer_snapped_expr = [] for w in range(len(eq_numbers)): try: param_dict = {} unsnapped_param_dict = {'p': 1} eq = unsnap_recur(expr, param_dict, unsnapped_param_dict) new_numbers = integerSnap(eq_numbers, w + 1) for kk in range(len(new_numbers)): eq_numbers[new_numbers[kk][0]] = new_numbers[kk][1] jj = 0 for parm in unsnapped_param_dict: if parm != "p": eq = eq.subs(parm, eq_numbers[jj]) jj = jj + 1 integer_snapped_expr = integer_snapped_expr + [eq] except: continue # # Get the numbers appearing in the expression # is_atomic_number = lambda expr: expr.is_Atom and expr.is_number # eq_numbers = [subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression)] # # # Do zero snap one parameter at a time # zero_snapped_expr = [] # for w in range(len(eq_numbers)): # try: # param_dict = {} # unsnapped_param_dict = {'p':1} # eq = unsnap_recur(expr,param_dict,unsnapped_param_dict) # new_numbers = zeroSnap(eq_numbers,w+1) # for kk in range(len(new_numbers)): # eq_numbers[new_numbers[kk][0]] = new_numbers[kk][1] # jj = 0 # for parm in unsnapped_param_dict: # if parm!="p": # eq = eq.subs(parm, eq_numbers[jj]) # jj = jj + 1 # zero_snapped_expr = zero_snapped_expr + [eq] # except: # continue # Get the numbers appearing in the expression is_atomic_number = lambda expr: expr.is_Atom and expr.is_number eq_numbers = [ subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression) ] # Do rational snap one parameter at a time rational_snapped_expr = [] for w in range(len(eq_numbers)): try: eq_numbers_snap = copy.deepcopy(eq_numbers) param_dict = {} unsnapped_param_dict = {'p': 1} eq = unsnap_recur(expr, param_dict, unsnapped_param_dict) new_numbers = rationalSnap(eq_numbers, w + 1) for kk in range(len(new_numbers)): eq_numbers_snap[new_numbers[kk][0]] = new_numbers[kk][1][1:3] jj = 0 for parm in unsnapped_param_dict: if parm != "p": try: eq = eq.subs( parm, Rational(eq_numbers_snap[jj][0], eq_numbers_snap[jj][1])) except: eq = eq.subs(parm, eq_numbers_snap[jj]) jj = jj + 1 rational_snapped_expr = rational_snapped_expr + [eq] except: continue snapped_expr = np.append(integer_snapped_expr, rational_snapped_expr) # snapped_expr = np.append(snapped_expr,rational_snapped_expr) for i in range(len(snapped_expr)): try: # Calculate the error of the new, snapped expression snapped_error = get_symbolic_expr_error(pathdir, filename, str(snapped_expr[i])) # Calculate the complexity of the new, snapped expression expr = simplify(powsimp(snapped_expr[i])) for s in (expr.free_symbols): s = symbols(str(s), real=True) expr = simplify(parse_expr(str(snapped_expr[i]), locals())) #print("expr 0", expr) expr = intify(expr) is_atomic_number = lambda expr: expr.is_Atom and expr.is_number numbers_expr = [ subexpression for subexpression in preorder_traversal(expr) if is_atomic_number(subexpression) ] if DR_file == "": snapped_complexity = 0 for j in numbers_expr: snapped_complexity = snapped_complexity + get_number_DL_snapped( float(j)) n_variables = len(expr.free_symbols) n_operations = len(count_ops(expr, visual=True).free_symbols) if n_operations != 0 or n_variables != 0: snapped_complexity = snapped_complexity + ( n_variables + n_operations) * np.log2( (n_variables + n_operations)) # If a bf file is provided, replace the variables with the actual ones before calculating the complexity else: dr_data = np.loadtxt(DR_file, dtype="str", delimiter=",") expr = str(expr) old_vars = ["x%s" % k for k in range(len(dr_data) - 3)] for i_dr in range(len(old_vars)): expr = expr.replace(old_vars[i_dr], "(" + dr_data[i_dr + 2] + ")") expr = "(" + dr_data[1] + ")*(" + expr + ")" expr = parse_expr(expr) for s in (expr.free_symbols): s = symbols(str(s), real=True) expr = simplify(parse_expr(str(expr), locals())) #print("expr 1", expr) #expr = intify(expr) #print("expr 2", expr) snapped_complexity = 0 for j in numbers_expr: snapped_complexity = snapped_complexity + get_number_DL_snapped( float(j)) n_variables = len(expr.free_symbols) n_operations = len(count_ops(expr, visual=True).free_symbols) if n_operations != 0 or n_variables != 0: snapped_complexity = snapped_complexity + ( n_variables + n_operations) * np.log2( (n_variables + n_operations)) PA.add(Point(x=snapped_complexity, y=snapped_error, data=str(expr))) except: continue return (PA)