def generate_symbolic_tfs(stages=4): import sympy as sp # construct quad pendulum equation of motion matrix ksyms = sp.numbered_symbols('k') msyms = sp.numbered_symbols('m') w = sp.symbols('w') k = [next(ksyms) for n in range(stages)] m = [next(msyms) for n in range(stages)] A = sp.zeros(stages) for n in range(stages-1): # mass and restoring forces (diagonal elements) A[n, n] = k[n] + k[n+1] - m[n] * w**2 # couplings to stages above and below A[n, n+1] = -k[n+1] A[n+1, n] = -k[n+1] # mass and restoring force of bottom stage A[-1, -1] = k[-1] - m[-1] * w**2 # want TM equations of motion, so index 4 b = sp.zeros(stages, 1) b[-1] = 1 # solve linear system xsyms = sp.numbered_symbols('x') x = [next(xsyms) for n in range(stages)] ans = sp.linsolve((A, b), x) return ans
def __init__(self, n=4, h=4, canonic=True): if canonic: x, y = sym.symbols('x, y') a = tuple(take(n, sym.numbered_symbols('a', start=1))) else: x, y = sym.symbols('z_2, z_1', real=True) a = tuple(-r for r in take( n, sym.numbered_symbols('r', start=1, real=True))) super().__init__(x, y, a, h)
def cse2func(funcname, exprs, module=math, auto_import=True, calc_number=True, symbols="_tmp"): import textwrap printer = CseExprPrinter(settings={ "func_module": module, "calc_number": calc_number }) seq, results = cse(exprs, symbols=numbered_symbols(symbols)) codes = ["def %s:" % funcname] for variable, value in seq: if calc_number: value = value.evalf() codes.append("%s = %s" % (variable, printer._print(value))) returns = "return (%s)" % ", ".join( [printer._print(value) for value in results]) codes.append('\n'.join(textwrap.wrap(returns, 80))) if auto_import and printer.functions: codes.insert( 1, "from {} import {}".format(module.__name__, ", ".join(printer.functions))) code = '\n '.join(codes) return code
def OutputVector_CollectingFactorsNonZero(A,name, mode, initial_tabs = 1, max_index=30, optimizations='basic'): """ This method collects the constants of the replacement for vectors (only non-zero terms) Keyword arguments: A -- The factors name -- The name of the constant mode -- The mode of replacement initial_tabs -- The number of initial tabulations max_index -- The max number of indexes optimizations -- The level of optimizations """ symbol_name = "c" + name A_factors, A_collected = sympy.cse(A, sympy.numbered_symbols(symbol_name), optimizations) A = A_collected[0] # Overwrite lhs with the one with the collected components aux_dict = {} Acoefficient_str = "" for factor in A_factors: varname = factor[0] value = factor[1] output_value = OutputSymbolicVariable(value, mode, varname, aux_dict) Acoefficient_str += " const double " + varname.__str__() + " = " + output_value A_out = Acoefficient_str + "\n" + OutputVectorNonZero(A, name, mode, initial_tabs, max_index, aux_dict) return A_out
def get_cse_code(self, exprs, basename=None, dummy_groups=(), arrayify_groups=()): """ Get arrayified code for common subexpression. Parameters ---------- exprs : list of sympy expressions basename : str Stem of variable names (default: cse). dummy_groups : tuples """ if basename is None: basename = 'cse' cse_defs, cse_exprs = sympy.cse( exprs, symbols=sympy.numbered_symbols(basename)) # Let's convert the new expressions into (arrayified) code cse_defs_code = [ (vname, self.as_arrayified_code( vexpr, dummy_groups, arrayify_groups)) for vname, vexpr in cse_defs ] cse_exprs_code = [self.as_arrayified_code( x, dummy_groups, arrayify_groups) for x in cse_exprs] return cse_defs_code, cse_exprs_code
def test_satisfiable_all_models(): from sympy.abc import A, B assert next(satisfiable(False, all_models=True)) is False assert list(satisfiable((A >> ~A) & A, all_models=True)) == [False] assert list(satisfiable(True, all_models=True)) == [{true: true}] models = [{A: True, B: False}, {A: False, B: True}] result = satisfiable(A ^ B, all_models=True) models.remove(next(result)) models.remove(next(result)) raises(StopIteration, lambda: next(result)) assert not models assert list(satisfiable(Equivalent(A, B), all_models=True)) == \ [{A: False, B: False}, {A: True, B: True}] models = [{A: False, B: False}, {A: False, B: True}, {A: True, B: True}] for model in satisfiable(A >> B, all_models=True): models.remove(model) assert not models # This is a santiy test to check that only the required number # of solutions are generated. The expr below has 2**100 - 1 models # which would time out the test if all are generated at once. from sympy import numbered_symbols from sympy.logic.boolalg import Or sym = numbered_symbols() X = [next(sym) for i in range(100)] result = satisfiable(Or(*X), all_models=True) for i in range(10): assert next(result)
def Derivatives_CollectingFactors(A,name, mode, initial_tabs = 1, max_index=30, optimizations='basic', postprocess=None, order='canonical'): """ This method collects the constants of the replacement for derivatives TODO Think about collecting factors also in the derivatives Keyword arguments: A -- The factors name -- The name of the constant mode -- The mode of replacement initial_tabs -- The number of initial tabulations max_index -- The max number of indexes optimizations -- The level of optimizations postprocess -- The type of postprocess order -- The type of order """ symbol_name = "c" + name A_factors, A_collected = sympy.cse(A, sympy.numbered_symbols(symbol_name), optimizations, postprocess, order) A = A_collected Acoefficient_str = "" for factor in A_factors: varname = factor[0] value = factor[1] Acoefficient_str += " const double " + varname.__str__() + " = " + value return [A, Acoefficient_str]
def commonsubelim(filename1, filename2): modifiedlines = [] with open(filename1, 'r') as file: for line in file: line = line.strip('\n') currentline = line + '\n' flag = False if ('=' in line): line = line.strip(';') lines = line.split('=') docse = False lines[1] = lines[1].replace(' ', '') docse = re.search("[0-9]", lines[1]) if (not docse): lines[1] = eval(lines[1]) compressedexpr = cse(lines[1], numbered_symbols("help")) currentline = lines[0] + '=' for helper in compressedexpr[0]: flag = True #currentline=currentline+str(ccode(helper[1],helper[0]))+"\n" modifiedlines.append("int " + str(ccode(helper[1], helper[0])) + "\n") for i, result in enumerate(compressedexpr[1]): modifiedlines.append("int " + (ccode(result, "result"))) modifiedlines.append("\n") if (flag == False): modifiedlines.append(currentline) with open(filename2, 'w') as file: for line in modifiedlines: file.write(line) file.write('\n') file.truncate()
def com_sub_elim(): X1, Y1, Z1, X2, Y2 = symbols('X1 Y1 Z1 X2 Y2') Q = [X1, Y1, Z1] P = [X2, Y2] Rx, Ry, Rz = mixadd_complete(Q, P) tree = tree_cse([Rx, Ry, Rz], numbered_symbols('e')) print(tree)
def test_satisfiable_all_models(): from sympy.abc import A, B assert next(satisfiable(False, all_models=True)) is False assert list(satisfiable((A >> ~A) & A , all_models=True)) == [False] assert list(satisfiable(True, all_models=True)) == [{true: true}] models = [{A: True, B: False}, {A: False, B: True}] result = satisfiable(A ^ B, all_models=True) models.remove(next(result)) models.remove(next(result)) raises(StopIteration, lambda: next(result)) assert not models assert list(satisfiable(Equivalent(A, B), all_models=True)) == \ [{A: False, B: False}, {A: True, B: True}] models = [{A: False, B: False}, {A: False, B: True}, {A: True, B: True}] for model in satisfiable(A >> B, all_models=True): models.remove(model) assert not models # This is a santiy test to check that only the required number # of solutions are generated. The expr below has 2**100 - 1 models # which would time out the test if all are generated at once. from sympy import numbered_symbols from sympy.logic.boolalg import Or sym = numbered_symbols() X = [next(sym) for i in range(100)] result = satisfiable(Or(*X), all_models=True) for i in range(10): assert next(result)
def generate_jac_C(self, do_cse=False, chunk_size=100, sparse=True): """ translates the symbolic Jacobian to C code using SymPy’s `C-code printer <http://docs.sympy.org/dev/modules/printing.html#module-sympy.printing.ccode>`_. If the symbolic Jacobian has not been generated, it generates it by calling `generate_jac_sym`. Parameters ---------- do_cse : boolean Whether SymPy’s `common-subexpression detection <http://docs.sympy.org/dev/modules/rewriting.html#module-sympy.simplify.cse_main>`_ should be applied before translating to C code. It is almost always better to let the compiler do this (unless you want to set the compiler optimisation to `-O2` or lower): For simple differential equations this should not make any difference to the compiler’s optimisations. For large ones, it may make a difference but also take long. As this requires the entire Jacobian at once, it may void advantages gained from using generator functions as an input. chunk_size : integer If the number of instructions in the final C code exceeds this number, it will be split into chunks of this size. After the generation of each chunk, SymPy’s cache is cleared. See `large_systems` on why this is useful. If there is an obvious grouping of your Jacobian, the respective group size suggests itself for `chunk_size`. For example, the derivative of each dynamical variable explicitly depends on 60 others and the Jacobian is sparse, a chunk size of 60 suggests itself. If smaller than 1, no chunking will happen. sparse : boolean Whether a sparse Jacobian should be assumed for optimisation. Note that this does not mean that the Jacobian is stored, parsed or handled as a sparse matrix. This kind of optimisation would require SciPy’s ODE to be able to handle sparse matrices. """ self._generate_jac_sym() jac_sym_wc = self.jac_sym self.sparse_jac = sparse if do_cse: jac_matrix = sympy.Matrix([ [entry for entry in line] for line in jac_sym_wc ]) _cse = sympy.cse( jac_matrix, symbols = sympy.numbered_symbols("dummy_jac_") ) more_helpers = _cse[0] jac_sym_wc = _cse[1][0].tolist() else: more_helpers = [] render_declarations( (helper[0] for helper in more_helpers), self._tmpfile("declare_jac_helpers.c") ) set_dfdy = sympy.Function("set_dfdy") render_and_write_code( ( set_dfdy(i,j,entry) for i,line in enumerate(jac_sym_wc) for j,entry in enumerate(line) if ( (entry != 0) or not self.sparse_jac ) ), more_helpers, self._tmpfile(), "jac", {"set_dfdy":"set_dfdy", "y":"y"}, chunk_size = chunk_size ) self._jac_C_source = True
def generate_predict(self, f, u, Q, dt): ''' Generate a method for doing an EKF prediction step. f(X, u) -> X is the system dynamics model, X is a symbolic vector of each state variable name u is the symbolic control input vector. ''' N = self.N F = f.jacobian(self.X) U = f.jacobian(u) vs, es = sp.cse([F - sp.eye(N), f - self.X, Q], optimizations='basic', symbols=sp.numbered_symbols("tmp")) self.generate_predict_cc(f, u, Q, dt, N, F, vs, es) self.generate_predict_py(f, u, Q, dt, N, F, vs, es) vs, es = sp.cse([f - self.X, F - sp.eye(N), U], optimizations='basic', symbols=sp.numbered_symbols("tmp")) self.generate_controlstep_py(f, u, dt, N, vs, es)
def _generate_cse(self, prefix='pydy_'): # NOTE : This assumes the first two items in self.matrices are A and b # of and Ax=b system. This also ignores cse=False. gen1 = sm.numbered_symbols(prefix) subexprs1, mats_simp = sm.cse(self.matrices, symbols=gen1) A_simp = mats_simp[0] b_simp = mats_simp[1] x = A_simp.LUsolve(b_simp) gen2 = sm.numbered_symbols(prefix, start=len(subexprs1)) subexprs2, x_simp = sm.cse(x, symbols=gen2) # swap the b matrix with the x result mats_simp[1] = x_simp[0] self.subexprs = subexprs1 + subexprs2 self.simplified_matrices = tuple(mats_simp)
def _optimize(expressions): from sympy import cse, numbered_symbols commons, outputs = cse( expressions, numbered_symbols('t'), optimizations='basic', ) for symbol, expr in commons: print(symbol, '=', expr) print() for expr in outputs: print(expr)
def cse_codegen(symbols): cse_results = sympy.cse(symbols, sympy.numbered_symbols("c")) output = io.StringIO() for helper in cse_results[0]: output.write("Scalar const ") output.write(sympy.printing.ccode(helper[1], helper[0])) output.write("\n") assert len(cse_results[1]) == 1 output.write(sympy.printing.ccode(cse_results[1][0], "result")) output.write("\n") output.seek(0) return output
def generate_measurement(self, name, h_x, h_z, z_k, R_k): H = h_x.jacobian(self.X) M = h_z.jacobian(z_k) + h_x.jacobian(z_k) y_k = h_z - h_x vs, es = sp.cse([y_k, H, M], optimizations='basic', symbols=sp.numbered_symbols("tmp")) self.generate_measurement_cc(name, h_x, h_z, z_k, R_k, H, M, y_k, vs, es) self.generate_measurement_py(name, h_x, h_z, z_k, R_k, H, M, y_k, vs, es)
def generate_f_C(self, simplify=True, do_cse=False, chunk_size=100): """ translates the derivative to C code using SymPy’s `C-code printer <http://docs.sympy.org/dev/modules/printing.html#module-sympy.printing.ccode>`_. Parameters ---------- simplify : boolean Whether the derivative should be `simplified <http://docs.sympy.org/dev/modules/simplify/simplify.html>`_ (with `ratio=1.0`) before translating to C code. The main reason why you could want to disable this is if your derivative is already optimised and so large that simplifying takes a considerable amount of time. do_cse : boolean Whether SymPy’s `common-subexpression detection <http://docs.sympy.org/dev/modules/rewriting.html#module-sympy.simplify.cse_main>`_ should be applied before translating to C code. It is almost always better to let the compiler do this (unless you want to set the compiler optimisation to `-O2` or lower): For simple differential equations this should not make any difference to the compiler’s optimisations. For large ones, it may make a difference but also take long. As this requires all entries of `f` at once, it may void advantages gained from using generator functions as an input. chunk_size : integer If the number of instructions in the final C code exceeds this number, it will be split into chunks of this size. After the generation of each chunk, SymPy’s cache is cleared. See `large_systems` on why this is useful. If there is an obvious grouping of your :math:`f`, the group size suggests itself for `chunk_size`. For example, if you want to simulate the dynamics of three-dimensional oscillators coupled onto a 40×40 lattice and if the differential equations are grouped first by oscillator and then by lattice row, a chunk size of 120 suggests itself. If smaller than 1, no chunking will happen. """ f_sym_wc = self.f_sym() if simplify: f_sym_wc = (sympy.simplify(entry,ratio=1) for entry in f_sym_wc) if do_cse: _cse = sympy.cse( sympy.Matrix(list(self.f_sym())), symbols = sympy.numbered_symbols("dummy_f_") ) more_helpers = _cse[0] f_sym_wc = _cse[1][0] else: more_helpers = [] render_declarations( (helper[0] for helper in more_helpers), self._tmpfile("declare_f_helpers.c") ) set_dy = sympy.Function("set_dy") render_and_write_code( (set_dy(i,entry) for i,entry in enumerate(f_sym_wc)), more_helpers, self._tmpfile(), "f", {"set_dy":"set_dy", "y":"y"}, chunk_size = chunk_size ) self._f_C_source = True
def _print_list(self, list_of_exprs): if all(isinstance(x, sp.Eq) for x in list_of_exprs): list_of_lhs = [x.lhs for x in list_of_exprs] list_of_rhs = [x.rhs for x in list_of_exprs] common_variables, list_of_rhs = sp.cse( list_of_rhs, symbols=sp.numbered_symbols(prefix='com')) lines = [] for variable, expression in common_variables: ass = Assignment(variable, expression) lines.append('const double ' + self._print(ass)) for i in range(len(list_of_rhs)): ass = Assignment(list_of_lhs[i], list_of_rhs[i]) lines.append(self._print(ass)) return '\n'.join(lines)
def cse2func(funcname, exprs, module=math, auto_import=True, calc_number=True, symbols="_tmp"): import textwrap printer = CseExprPrinter(settings={"func_module": module, "calc_number": calc_number}) seq, results = cse(exprs, symbols=numbered_symbols(symbols)) codes = ["def %s:" % funcname] for variable, value in seq: if calc_number: value = value.evalf() codes.append("%s = %s" % (variable, printer._print(value))) returns = "return (%s)" % ", ".join([printer._print(value) for value in results]) codes.append('\n'.join(textwrap.wrap(returns, 80))) if auto_import and printer.functions: codes.insert(1, "from {} import {}".format(module.__name__, ", ".join(printer.functions))) code = '\n '.join(codes) return code
def test_lsystem_to_matrix_and_ordinate(self): """Should make a corresponding matrix from linear equations""" A = sympy.Matrix([[1, 1, 1], [1, 0, 2], [1, 11, 3]]) a = [-2, 0, 0] vars_gen = sympy.numbered_symbols("V") vars_list = [next(vars_gen) for _ in range(0, 3)] equations = [ vars_list[0] + vars_list[1] + vars_list[2] + 2, vars_list[0] + 2 * vars_list[2], vars_list[0] + 11 * vars_list[1] + 3 * vars_list[2], ] B, b = Equationeer().lsystem_to_matrix_and_ordinate(equations, vars_list) # print(B) self.assertEqual(A, B) self.assertEqual(a, b)
def generate_grid(matrix_dimension, P, frequency=1.02, ed=1, forced_em=None): grid = Grid() def choose_conductivity(em, ed): """Uses given probability to simulate different concentrations of metallic particles""" return (em, True) if rnd.random() < P else (ed, False) def choose_emf(node_from, node_to): """Places a battery only in vertical wires""" return 0 if abs(node_from.id - node_to.id) < matrix_dimension else 1 if forced_em == None: em = calculate_em(frequency) else: em = forced_em print("Em =", em) currents_gen = sympy.numbered_symbols('I') currents = [next(currents_gen) for _ in range(0, 2 * matrix_dimension ** 2)] currents_iter = iter(currents) grid.nodes = [Node(number_id) for number_id in range(0, matrix_dimension ** 2)] i = 0 for node_from in grid.nodes: for node_to_id in grid.neighborhood_nodes2(node_from): conductivity, conductor = choose_conductivity(em, ed) wire = Wire(grid, conductivity, conductor, choose_emf(node_from, grid.nodes[node_to_id]), node_from, grid.nodes[node_to_id], next(currents_iter)) grid.wires.append(wire) if wire.horizontal: grid.nodes[i].wires_from[0] = wire grid.nodes[node_to_id].wires_to[0] = wire else: grid.nodes[i].wires_from[1] = wire grid.nodes[node_to_id].wires_to[1] = wire i += 1 grid.dimension = matrix_dimension return grid, currents
def generate_code(inputs, exprs, func_name='generated_function', printer=None, simplify=False): """Generate code for a Python function from symbolic expression(s).""" if printer is None: printer = TupleArrayPrinter() if not isinstance(exprs, list) and not isinstance(exprs, tuple): exprs = [exprs] elif isinstance(exprs, tuple): exprs = list(exprs) func_expr_args = {} ignore_set = set() for i, expr in enumerate(exprs): if isinstance(expr, FunctionExpression): func_expr_args[i] = expr.args for arg in expr.args: ignore_set.update(( arg, ) if isinstance(arg, sym.Symbol) else arg.free_symbols) exprs[i] = expr.return_val flat_exprs, unflatten = flatten_arrays(exprs) if simplify: flat_exprs = [sym.simplify(expr) for expr in flat_exprs] intermediates, flat_outputs = sym.cse(flat_exprs, symbols=sym.numbered_symbols('_i'), optimizations='basic', ignore=ignore_set) outputs = unflatten(flat_outputs) code = f'def {func_name}({generate_input_list_string(inputs)}):\n ' code += '\n '.join([ f'{printer.doprint(i[0])} = {printer.doprint(i[1])}' for i in intermediates ]) code += '\n return (\n ' code += ',\n '.join([ (f'lambda {generate_input_list_string(func_expr_args[i])}: ' if i in func_expr_args else '') + f'{printer.doprint(output)}' for i, output in enumerate(outputs) ]) code += '\n )' return code
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 get_cse(matrix): rows = len(matrix) cols = len(matrix[0]) expr_list = [] coord_list = {} for row in range(0, rows): for col in range(0, cols): scase = sympy.sympify(matrix[row][col]) coord_list[(row, col)] = len(expr_list) expr_list.append(scase) cse = sympy.cse(expr_list, sympy.numbered_symbols('tmp')) tmp_list = cse[0] expr_list = cse[1] expr_mat = [] for row in range(0, rows): row_list = [] for col in range(0, cols): i = coord_list[(row, col)] e = expr_list[i] row_list.append(expr_list[i]) expr_mat.append(row_list) return (tmp_list, expr_mat)
def _generate_sub_expressions(self, int_var='z_'): """Finds common subexpressions in the mass matrix and forcing vector expression lists and rewrites them.""" # TODO: make common sub expressions optional list_of_lists = self.symbols.values() if None in list_of_lists: list_of_lists.remove(None) all_symbols = list(chain.from_iterable(list_of_lists)) while symbols(int_var) in all_symbols: int_var = random.choice(all_letters) + '_' sub_expressions, expressions = \ cse([entry[0] for entry in self.expressions['mass_matrix'] + self.expressions['forcing_vector']], numbered_symbols(int_var)) self.expressions = \ {'common_sub': sub_expressions, 'mass_matrix': expressions[:self.rows * self.cols], 'forcing_vector': expressions[self.rows * self.cols:]}
def get_cse(matrix): rows = len(matrix) cols = len(matrix[0]) expr_list = [] coord_list = {} for row in range(0, rows): for col in range(0, cols): scase = sympy.sympify(matrix[row][col]) coord_list[(row,col)] = len(expr_list) expr_list.append(scase) cse = sympy.cse(expr_list, sympy.numbered_symbols('tmp')) tmp_list = cse[0] expr_list = cse[1] expr_mat = [] for row in range(0, rows): row_list = [] for col in range(0, cols): i = coord_list[(row,col)] e = expr_list[i] row_list.append(expr_list[i]) expr_mat.append(row_list) return (tmp_list, expr_mat)
def _generate_cse(self): # This makes a big long list of every expression in all of the # matrices. exprs = [] for matrix in self.matrices: exprs += matrix[:] # Compute the common subexpresions. gen = sm.numbered_symbols('pydy_') self.subexprs, simplified_exprs = sm.cse(exprs, symbols=gen) # Turn the expressions back into matrices of the same type. simplified_matrices = [] idx = 0 for matrix in self.matrices: num_rows, num_cols = matrix.shape length = num_rows * num_cols m = type(matrix)(simplified_exprs[idx:idx + length]) simplified_matrices.append(m.reshape(num_rows, num_cols)) idx += length self.simplified_matrices = tuple(simplified_matrices)
def _generate_cse(self, prefix='pydy_'): # This makes a big long list of every expression in all of the # matrices. exprs = [] for matrix in self.matrices: exprs += matrix[:] # Compute the common subexpresions. gen = sm.numbered_symbols(prefix) self.subexprs, simplified_exprs = sm.cse(exprs, symbols=gen) # Turn the expressions back into matrices of the same type. simplified_matrices = [] idx = 0 for matrix in self.matrices: num_rows, num_cols = matrix.shape length = num_rows * num_cols m = type(matrix)(simplified_exprs[idx:idx + length]) simplified_matrices.append(m.reshape(num_rows, num_cols)) idx += length self.simplified_matrices = tuple(simplified_matrices)
def clean_numbers(expr, eps=1e-10): """ trys to clean all numbers from numeric noise """ if isinstance(expr, (list, tuple)): return [clean_numbers(elt, eps) for elt in expr] expr = trunc_small_values(expr) maxden = int(1/eps) floats = list(atoms(expr, sp.Float)) rats = [] dummy_symbs = [] symb_gen = sp.numbered_symbols('cde', cls = sp.Dummy) for f in floats: rat = sp_fff(f, maxden) rats.append(rat) dummy_symbs.append(next(symb_gen)) res1 = expr.subs(list(zip(floats, dummy_symbs))) res2 = res1.subs(list(zip(dummy_symbs, rats))) return res2
def clean_numbers(expr, eps=1e-10): """ trys to clean all numbers from numeric noise """ if isinstance(expr, (list, tuple)): return [clean_numbers(elt, eps) for elt in expr] expr = trunc_small_values(expr) maxden = int(1/eps) floats = list(atoms(expr, sp.Float)) rats = [] dummy_symbs = [] symb_gen = sp.numbered_symbols('cde', cls = sp.Dummy) for f in floats: rat = sp_fff(f, maxden) rats.append(rat) dummy_symbs.append(symb_gen.next()) res1 = expr.subs(zip(floats, dummy_symbs)) res2 = res1.subs(zip(dummy_symbs, rats)) return res2
def get_alpha_dot(): """Use sympy to perform most of the math and use the resulting formulae to calculate: inv(I) (\tau - w x (I w)) """ import sympy as S ixx, iyy, izz, ixy, ixz, iyz = S.symbols("ixx, iyy, izz, ixy, ixz, iyz") tx, ty, tz = S.symbols("tx, ty, tz") wx, wy, wz = S.symbols('wx, wy, wz') tau = S.Matrix([tx, ty, tz]) I = S.Matrix([[ixx, ixy, ixz], [ixy, iyy, iyz], [ixz, iyz, izz]]) w = S.Matrix([wx, wy, wz]) Iinv = I.inv() Iinv.simplify() # inv(I) (\tau - w x (Iw)) res = Iinv * (tau - w.cross(I * w)) res.simplify() # Now do some awesome sympy magic. syms, result = S.cse(res, symbols=S.numbered_symbols('tmp')) for lhs, rhs in syms: print("%s = %s" % (lhs, rhs)) for i in range(3): print("omega_dot[%d] =" % i, result[0][i])
def balance_stoichiometry(reactants, products, substances=None, substance_factory=Substance.from_formula, parametric_symbols=None, underdetermined=True, allow_duplicates=False): """ Balances stoichiometric coefficients of a reaction Parameters ---------- reactants : iterable of reactant keys products : iterable of product keys substances : OrderedDict or string or None Mapping reactant/product keys to instances of :class:`Substance`. substance_factory : callback parametric_symbols : generator of symbols Used to generate symbols for parametric solution for under-determined system of equations. Default is numbered "x-symbols" starting from 1. underdetermined : bool Allows to find a non-unique solution (in addition to a constant factor across all terms). Set to ``False`` to disallow (raise ValueError) on e.g. "C + O2 -> CO + CO2". Set to ``None`` if you want the symbols replaced so that the coefficients are the smallest possible positive (non-zero) integers. allow_duplicates : bool If False: raises an excpetion if keys appear in both ``reactants`` and ``products``. Examples -------- >>> ref = {'C2H2': 2, 'O2': 3}, {'CO': 4, 'H2O': 2} >>> balance_stoichiometry({'C2H2', 'O2'}, {'CO', 'H2O'}) == ref True >>> ref2 = {'H2': 1, 'O2': 1}, {'H2O2': 1} >>> balance_stoichiometry('H2 O2'.split(), ['H2O2'], 'H2 O2 H2O2') == ref2 True >>> reac, prod = 'CuSCN KIO3 HCl'.split(), 'CuSO4 KCl HCN ICl H2O'.split() >>> Reaction(*balance_stoichiometry(reac, prod)).string() '4 CuSCN + 7 KIO3 + 14 HCl -> 4 CuSO4 + 7 KCl + 4 HCN + 7 ICl + 5 H2O' >>> balance_stoichiometry({'Fe', 'O2'}, {'FeO', 'Fe2O3'}, underdetermined=False) Traceback (most recent call last): ... ValueError: The system was under-determined >>> r, p = balance_stoichiometry({'Fe', 'O2'}, {'FeO', 'Fe2O3'}) >>> list(set.union(*[v.free_symbols for v in r.values()])) [x1] >>> b = balance_stoichiometry({'Fe', 'O2'}, {'FeO', 'Fe2O3'}, underdetermined=None) >>> b == ({'Fe': 3, 'O2': 2}, {'FeO': 1, 'Fe2O3': 1}) True >>> d = balance_stoichiometry({'C', 'CO'}, {'C', 'CO', 'CO2'}, underdetermined=None, allow_duplicates=True) >>> d == ({'CO': 2}, {'C': 1, 'CO2': 1}) True Returns ------- balanced reactants : dict balanced products : dict """ import sympy from sympy import ( MutableDenseMatrix, gcd, zeros, linsolve, numbered_symbols, Wild, Symbol, Integer, Tuple, preorder_traversal as pre ) _intersect = sorted(set.intersection(*map(set, (reactants, products)))) if _intersect: if allow_duplicates: if underdetermined is not None: raise NotImplementedError("allow_duplicates currently requires underdetermined=None") if set(reactants) == set(products): raise ValueError("cannot balance: reactants and products identical") # For each duplicate, try to drop it completely: for dupl in _intersect: try: result = balance_stoichiometry( [sp for sp in reactants if sp != dupl], [sp for sp in products if sp != dupl], substances=substances, substance_factory=substance_factory, underdetermined=underdetermined, allow_duplicates=True) except Exception: continue else: return result for perm in product(*[(False, True)]*len(_intersect)): # brute force (naive) r = set(reactants) p = set(products) for remove_reac, dupl in zip(perm, _intersect): if remove_reac: r.remove(dupl) else: p.remove(dupl) try: result = balance_stoichiometry( r, p, substances=substances, substance_factory=substance_factory, parametric_symbols=parametric_symbols, underdetermined=underdetermined, allow_duplicates=False) except ValueError: continue else: return result else: raise ValueError("Failed to remove duplicate keys: %s" % _intersect) else: raise ValueError("Substances on both sides: %s" % str(_intersect)) if substances is None: substances = OrderedDict([(k, substance_factory(k)) for k in chain(reactants, products)]) if isinstance(substances, str): substances = OrderedDict([(k, substance_factory(k)) for k in substances.split()]) if type(reactants) == set: # we don't want isinstance since it might be "OrderedSet" reactants = sorted(reactants) if type(products) == set: products = sorted(products) subst_keys = list(reactants) + list(products) cks = Substance.composition_keys(substances.values()) if parametric_symbols is None: parametric_symbols = numbered_symbols('x', start=1, integer=True, positive=True) # ?C2H2 + ?O2 -> ?CO + ?H2O # Ax = 0 # A: x: # # C2H2 O2 CO H2O # C -2 0 1 0 x0 = 0 # H -2 0 0 2 x1 0 # O 0 -2 1 1 x2 0 # x3 def _get(ck, sk): return substances[sk].composition.get(ck, 0) * (-1 if sk in reactants else 1) for ck in cks: # check that all components are present on reactant & product sides for rk in reactants: if substances[rk].composition.get(ck, 0) != 0: break else: any_pos = any(substances[pk].composition.get(ck, 0) > 0 for pk in products) any_neg = any(substances[pk].composition.get(ck, 0) < 0 for pk in products) if any_pos and any_neg: pass # negative and positive parts among products, no worries else: raise ValueError("Component '%s' not among reactants" % ck) for pk in products: if substances[pk].composition.get(ck, 0) != 0: break else: any_pos = any(substances[pk].composition.get(ck, 0) > 0 for pk in reactants) any_neg = any(substances[pk].composition.get(ck, 0) < 0 for pk in reactants) if any_pos and any_neg: pass # negative and positive parts among reactants, no worries else: raise ValueError("Component '%s' not among products" % ck) A = MutableDenseMatrix([[_get(ck, sk) for sk in subst_keys] for ck in cks]) symbs = list(reversed([next(parametric_symbols) for _ in range(len(subst_keys))])) sol, = linsolve((A, zeros(len(cks), 1)), symbs) wi = Wild('wi', properties=[lambda k: not k.has(Symbol)]) cd = reduce(gcd, [1] + [1/m[wi] for m in map( lambda n: n.match(symbs[-1]/wi), pre(sol)) if m is not None]) sol = sol.func(*[arg/cd for arg in sol.args]) def remove(cont, symb, remaining): subsd = dict(zip(remaining/symb, remaining)) cont = cont.func(*[(arg/symb).expand().subs(subsd) for arg in cont.args]) if cont.has(symb): raise ValueError("Bug, please report an issue at https://github.com/bjodah/chempy") return cont done = False for idx, symb in enumerate(symbs): for expr in sol: iterable = expr.args if expr.is_Add else [expr] for term in iterable: if term.is_number: done = True break if done: break if done: break for expr in sol: if (expr/symb).is_number: sol = remove(sol, symb, MutableDenseMatrix(symbs[idx+1:])) break for symb in symbs: cd = 1 for expr in sol: iterable = expr.args if expr.is_Add else [expr] for term in iterable: if term.is_Mul and term.args[0].is_number and term.args[1] == symb: cd = gcd(cd, term.args[0]) if cd != 1: sol = sol.func(*[arg.subs(symb, symb/cd) for arg in sol.args]) integer_one = 1 # need 'is' check, SyntaxWarning when checking against literal if underdetermined is integer_one: from ._release import __version__ if int(__version__.split('.')[1]) > 6: warnings.warn( # deprecated because comparison with ``1`` problematic (True==1) ("Pass underdetermined == None instead of ``1`` (depreacted since 0.7.0," " will_be_missing_in='0.9.0')"), ChemPyDeprecationWarning) underdetermined = None if underdetermined is None: sol = Tuple(*[Integer(x) for x in _solve_balancing_ilp_pulp(A)]) fact = gcd(sol) sol = MutableDenseMatrix([e/fact for e in sol]).reshape(len(sol), 1) sol /= reduce(gcd, sol) if 0 in sol: raise ValueError("Superfluous species given.") if underdetermined: if any(x == sympy.nan for x in sol): raise ValueError("Failed to balance reaction") else: for x in sol: if len(x.free_symbols) != 0: raise ValueError("The system was under-determined") if not all(residual == 0 for residual in A * sol): raise ValueError("Failed to balance reaction") def _x(k): coeff = sol[subst_keys.index(k)] return int(coeff) if underdetermined is None else coeff return ( OrderedDict([(k, _x(k)) for k in reactants]), OrderedDict([(k, _x(k)) for k in products]) )
def generate_mass_forcing_cython_code(filename_prefix, mass_matrix, forcing_vector, constants, coordinates, speeds, specified=None): """Generates a Cython shared object module with a function that evaluates the mass_matrix and the forcing vector. Parameters ---------- filename_prefix : string The desired name of the created module. mass_matrix : sympy.matrices.dense.MutableDenseMatrix, shape(n,n) The symbolic mass matrix of the system. forcing_vector : sympy.matrices.dense.MutableDenseMatrix, shape(n,1) The symbolic forcing vector of the system. constants : list of sympy.core.symbol.Symbol The constants in the equations of motion. coordinates : list of sympy.core.function.Function The generalized coordinates of the system. speeds : list of sympy.core.function.Function The generalized speeds of the system. specified : list of sympy.core.function.Function, optional, default=None The specifed quantities of the system. """ # TODO : Maybe, allow expressions to be passed in for the specified # quantities for computation inside the c file. Would need the value of # time in the mass_forcing function. c_filename = filename_prefix + '_c.c' header_filename = filename_prefix + '_c.h' pyx_filename = filename_prefix + '.pyx' setup_py_filename = filename_prefix + '_setup.py' # Comma separated lists of the input variables to the arrays, so that # you know which order you should supply them. These are used in the # comments in the C and header file. # TODO: Add these to the doc string of the imported cythonized function. comma_lists = {} for list_name, sym_list in zip(['constants', 'coordinates', 'speeds', 'specified'], [constants, coordinates, speeds, specified]): if sym_list is not None: comma_lists[list_name] = ', '.join([str(s).split('(')[0] for s in sym_list]) else: comma_lists[list_name] = ['None Supplied'] rows, cols = mass_matrix.shape mass_matrix_list = mass_matrix.reshape(rows * cols, 1).tolist() forcing_vector_list = forcing_vector.tolist() # TODO: make common sub expressions optional sub_expressions, expressions = cse([entry[0] for entry in mass_matrix_list + forcing_vector_list], numbered_symbols('z_')) expressions = {'common_sub': sub_expressions, 'mass_matrix': expressions[:rows * cols], 'forcing_vector': expressions[rows * cols:]} PyDyCCodePrinter = pydy_c_printer(constants, coordinates, speeds, specified) code_blocks = {} for exp_type, expression_list in expressions.items(): c_lines = [] for i, exp in enumerate(expression_list): if exp_type == 'common_sub': code_str = PyDyCCodePrinter().doprint(exp[1]) c_lines.append('double {} = {};'.format(str(exp[0]), code_str)) else: code_str = PyDyCCodePrinter().doprint(exp) c_lines.append('{} = {};'.format('{}[{}]'.format(exp_type, i), code_str)) code_blocks[exp_type] = '\n '.join(c_lines) template_values = { 'header_filename': header_filename, 'constants_len': len(constants), 'mass_matrix_len': rows * cols, 'forcing_vector_len': len(forcing_vector), 'coordinates_len': len(coordinates), 'speeds_len': len(speeds), 'specified_len': len(specified) if specified is not None else 0, 'constants_list': comma_lists['constants'], 'coordinates_list': comma_lists['coordinates'], 'speeds_list': comma_lists['speeds'], 'specified_list': comma_lists['specified'], 'sub_expression_block': code_blocks['common_sub'], 'mass_matrix_block': code_blocks['mass_matrix'], 'forcing_vector_block': code_blocks['forcing_vector'], 'prefix': filename_prefix, 'c_filename': c_filename, 'pyx_filename': pyx_filename, } if specified is not None: specified_template = { 'specified_double': " " * 18 + "double specified[{specified_len}], // specified = [{specified_list}]".format(**template_values) + '\n', 'specified_assert': "\n assert len(specified) == {specified_len}\n".format(**template_values), 'def_specified_arg': ",\n np.ndarray[np.double_t, ndim=1, mode='c'] specified", 'cdef_specified_arg': "\n" + ' ' * 22 + "double* specified,", 'call_specified_arg': "\n" + ' ' * 17 + "<double*> specified.data," } else: specified_template = { 'specified_double': "", 'specified_assert': "", 'def_specified_arg': "", 'cdef_specified_arg': "", 'call_specified_arg': "" } template_values.update(specified_template) files = {c_filename: c_template, header_filename: h_template, pyx_filename: pyx_template, setup_py_filename: setup_template} for filename, template in files.items(): code = template.format(**template_values) with open(filename, 'w') as f: f.write(code) # This prevents output to stdout and waits till it is done. p = subprocess.Popen(['python', setup_py_filename, 'build_ext', '--inplace'], stderr=subprocess.STDOUT, stdout=subprocess.PIPE) p.wait()
def balance_stoichiometry(reactants, products, substances=None, substance_factory=Substance.from_formula, parametric_symbols=None, underdetermined=True, allow_duplicates=False): """ Balances stoichiometric coefficients of a reaction Parameters ---------- reactants : iterable of reactant keys products : iterable of product keys substances : OrderedDict or string or None Mapping reactant/product keys to instances of :class:`Substance`. substance_factory : callback parametric_symbols : generator of symbols Used to generate symbols for parametric solution for under-determined system of equations. Default is numbered "x-symbols" starting from 1. underdetermined : bool Allows to find a non-unique solution (in addition to a constant factor across all terms). Set to ``False`` to disallow (raise ValueError) on e.g. "C + O2 -> CO + CO2". Set to ``None`` if you want the symbols replaced so that the coefficients are the smallest possible positive (non-zero) integers. allow_duplicates : bool If False: raises an excpetion if keys appear in both ``reactants`` and ``products``. Examples -------- >>> ref = {'C2H2': 2, 'O2': 3}, {'CO': 4, 'H2O': 2} >>> balance_stoichiometry({'C2H2', 'O2'}, {'CO', 'H2O'}) == ref True >>> ref2 = {'H2': 1, 'O2': 1}, {'H2O2': 1} >>> balance_stoichiometry('H2 O2'.split(), ['H2O2'], 'H2 O2 H2O2') == ref2 True >>> reac, prod = 'CuSCN KIO3 HCl'.split(), 'CuSO4 KCl HCN ICl H2O'.split() >>> Reaction(*balance_stoichiometry(reac, prod)).string() '4 CuSCN + 7 KIO3 + 14 HCl -> 4 CuSO4 + 7 KCl + 4 HCN + 7 ICl + 5 H2O' >>> balance_stoichiometry({'Fe', 'O2'}, {'FeO', 'Fe2O3'}, underdetermined=False) Traceback (most recent call last): ... ValueError: The system was under-determined >>> r, p = balance_stoichiometry({'Fe', 'O2'}, {'FeO', 'Fe2O3'}) >>> list(set.union(*[v.free_symbols for v in r.values()])) [x1] >>> b = balance_stoichiometry({'Fe', 'O2'}, {'FeO', 'Fe2O3'}, underdetermined=None) >>> b == ({'Fe': 3, 'O2': 2}, {'FeO': 1, 'Fe2O3': 1}) True >>> d = balance_stoichiometry({'C', 'CO'}, {'C', 'CO', 'CO2'}, underdetermined=None, allow_duplicates=True) >>> d == ({'CO': 2}, {'C': 1, 'CO2': 1}) True Returns ------- balanced reactants : dict balanced products : dict """ import sympy from sympy import ( MutableDenseMatrix, gcd, zeros, linsolve, numbered_symbols, Wild, Symbol, Integer, Tuple, preorder_traversal as pre ) _intersect = sorted(set.intersection(*map(set, (reactants, products)))) if _intersect: if allow_duplicates: if underdetermined is not None: raise NotImplementedError("allow_duplicates currently requires underdetermined=None") if set(reactants) == set(products): raise ValueError("cannot balance: reactants and products identical") # For each duplicate, try to drop it completely: for dupl in _intersect: try: result = balance_stoichiometry( [sp for sp in reactants if sp != dupl], [sp for sp in products if sp != dupl], substances=substances, substance_factory=substance_factory, underdetermined=underdetermined, allow_duplicates=True) except Exception: continue else: return result for perm in product(*[(False, True)]*len(_intersect)): # brute force (naive) r = set(reactants) p = set(products) for remove_reac, dupl in zip(perm, _intersect): if remove_reac: r.remove(dupl) else: p.remove(dupl) try: result = balance_stoichiometry( r, p, substances=substances, substance_factory=substance_factory, parametric_symbols=parametric_symbols, underdetermined=underdetermined, allow_duplicates=False) except Exception: continue else: return result else: raise ValueError("Failed to remove duplicate keys: %s" % _intersect) else: raise ValueError("Substances on both sides: %s" % str(_intersect)) if substances is None: substances = OrderedDict([(k, substance_factory(k)) for k in chain(reactants, products)]) if isinstance(substances, str): substances = OrderedDict([(k, substance_factory(k)) for k in substances.split()]) if type(reactants) == set: # we don't want isinstance since it might be "OrderedSet" reactants = sorted(reactants) if type(products) == set: products = sorted(products) subst_keys = list(reactants) + list(products) cks = Substance.composition_keys(substances.values()) if parametric_symbols is None: parametric_symbols = numbered_symbols('x', start=1, integer=True, positive=True) # ?C2H2 + ?O2 -> ?CO + ?H2O # Ax = 0 # A: x: # # C2H2 O2 CO H2O # C -2 0 1 0 x0 = 0 # H -2 0 0 2 x1 0 # O 0 -2 1 1 x2 0 # x3 def _get(ck, sk): return substances[sk].composition.get(ck, 0) * (-1 if sk in reactants else 1) for ck in cks: # check that all components are present on reactant & product sides for rk in reactants: if substances[rk].composition.get(ck, 0) != 0: break else: raise ValueError("Component '%s' not among reactants" % ck) for pk in products: if substances[pk].composition.get(ck, 0) != 0: break else: raise ValueError("Component '%s' not among products" % ck) A = MutableDenseMatrix([[_get(ck, sk) for sk in subst_keys] for ck in cks]) symbs = list(reversed([next(parametric_symbols) for _ in range(len(subst_keys))])) sol, = linsolve((A, zeros(len(cks), 1)), symbs) wi = Wild('wi', properties=[lambda k: not k.has(Symbol)]) cd = reduce(gcd, [1] + [1/m[wi] for m in map( lambda n: n.match(symbs[-1]/wi), pre(sol)) if m is not None]) sol = sol.func(*[arg/cd for arg in sol.args]) def remove(cont, symb, remaining): subsd = dict(zip(remaining/symb, remaining)) cont = cont.func(*[(arg/symb).expand().subs(subsd) for arg in cont.args]) if cont.has(symb): raise ValueError("Bug, please report an issue at https://github.com/bjodah/chempy") return cont done = False for idx, symb in enumerate(symbs): for expr in sol: iterable = expr.args if expr.is_Add else [expr] for term in iterable: if term.is_number: done = True break if done: break if done: break for expr in sol: if (expr/symb).is_number: sol = remove(sol, symb, MutableDenseMatrix(symbs[idx+1:])) break for symb in symbs: cd = 1 for expr in sol: iterable = expr.args if expr.is_Add else [expr] for term in iterable: if term.is_Mul and term.args[0].is_number and term.args[1] == symb: cd = gcd(cd, term.args[0]) if cd != 1: sol = sol.func(*[arg.subs(symb, symb/cd) for arg in sol.args]) if underdetermined is 1: from ._release import __version__ if int(__version__.split('.')[1]) > 6: warnings.warn( # deprecated because comparison with ``1`` problematic (True==1) ("Pass underdetermined == None instead of ``1`` (depreacted since 0.7.0," " will_be_missing_in='0.9.0')"), ChemPyDeprecationWarning) underdetermined = None if underdetermined is None: sol = Tuple(*[Integer(x) for x in _solve_balancing_ilp_pulp(A)]) fact = gcd(sol) sol = MutableDenseMatrix([e/fact for e in sol]).reshape(len(sol), 1) sol /= reduce(gcd, sol) if 0 in sol: raise ValueError("Superfluous species given.") if underdetermined: if any(x == sympy.nan for x in sol): raise ValueError("Failed to balance reaction") else: for x in sol: if len(x.free_symbols) != 0: raise ValueError("The system was under-determined") if not all(residual == 0 for residual in A * sol): raise ValueError("Failed to balance reaction") def _x(k): coeff = sol[subst_keys.index(k)] return int(coeff) if underdetermined is None else coeff return ( OrderedDict([(k, _x(k)) for k in reactants]), OrderedDict([(k, _x(k)) for k in products]) )
# Form expressions for derivatives of the g's w.r.t. lean and pitch dh = [0] * 8 for i in range(4): for j in range(2): dh[2 * i + j] = h[i].diff(q[j + 1]) # Subsitution dictionary to replace dynamic symbols with regular symbols symbol_dict = {q[1]: Symbol('q1'), q[2]: Symbol('q2'), qd[0]: Symbol('qd0')} for i in range(4): h[i] = h[i].subs(symbol_dict) for i in range(8): dh[i] = dh[i].subs(symbol_dict) # CSE on g's z, h_red = cse(h, numbered_symbols('z')) # Form output code for g's output_code = " // Intermediate variables for h's\n" for zi_lhs, zi_rhs in z: output_code += " {0} = {1};\n".format(zi_lhs, ccode(zi_rhs)) output_code += "\n // h's\n" for i in range(4): output_code += " h[{0}] = {1};\n".format(i, ccode(h_red[i])) # CSE on dh's z, dh_red = cse(dh, numbered_symbols('z')) # Form output code for dg's output_code += "\n // Intermediate variables for dh's\n"
qd[0]: Symbol('qd0'), qd[1]: Symbol('qd1'), qd[2]: Symbol('qd2'), u[0]: Symbol('u0'), u[1]: Symbol('u1'), u[2]: Symbol('u2'), ud[0]: Symbol('ud0'), ud[1]: Symbol('ud1'), ud[2]: Symbol('ud2')} for i in range(len(exp_ode)): exp_ode[i] = exp_ode[i].subs(subs_dict) for i in range(len(exp_output)): exp_output[i] = exp_output[i].subs(subs_dict) for i in range(len(exp_jac)): exp_jac[i] = exp_jac[i].subs(subs_dict) # CSE on all quantities needed for numerical integration of ordinary # differential equations: qd_rhs (5), M_dyn (9), f_dyn (3) z, exp_ode_red = cse(exp_ode, numbered_symbols('z')) output_code = " // Intermediate variables for ODE function\n" for zi_lhs, zi_rhs in z: output_code += " {0} = {1};\n".format(zi_lhs, ccode(zi_rhs)) output_code += "\n // Kinematic differential equations\n" for i in range(5): output_code += " dxdt[{0}] = {1};\n".format(i, ccode(exp_ode_red[i])) output_code += "\n // Mass matrix\n" for i in range(3): for j in range(3): output_code += " M_dyn({0}, {1}) = {2};\n".format(i, j, ccode(exp_ode_red[5 + 3*i + j]))
def sparse_solve_for_commuting_term(cvector, psi_lower, order, orders, matrixrows, subspace, norm = False, fvarname = 'A', iofvars = None, subs_rules = None, split_orders = None): fvar_gen = sympy.numbered_symbols('fvar') fvars = [next(fvar_gen) for i in range(len(subspace))] psi_order = [Ncproduct(-fvars[subspace[key]], list(key)) for i,key in enumerate(subspace)] if norm: psi_total = psi_lower + psi_order new_orders = orders.copy() new_orders.update(dict(zip(fvars, [order]*len(fvars)))) sparse_normalise(psi_total, order, new_orders, fvars, cvector, matrixrows, split_orders, start_ind = len(fvars)) sub_sub_spaces = find_sub_subspaces(matrixrows) print(sub_sub_spaces) solutions = {} if subs_rules is None: subs_rules = {} #deal with empty rows # for i, row in matrixrows.items(): # if not row: # if sympy.simplify(cvector[i]) != 0: # poss = False # for iofvar in iofvars: # if iofvar in cvector[i].atoms(sympy.Symbol): # sub_sub_spaces.append([i]) # print('Warning, new sspace: ' + str(i)) # poss = True # if not poss: # print(matrixrows) # print(cvector) # raise ValueError('Error term to cancel in null') length_ss = len(sub_sub_spaces) fvargen = sympy.numbered_symbols(fvarname) tempgen = sympy.numbered_symbols('temp') tempvars = [] newfvars = [] for i, ss_space in enumerate(sub_sub_spaces): #if i == 4: #ipdb.set_trace() solutions.update(solve_for_sub_subspace(matrixrows, ss_space, fvars, cvector, iofvars, subs_rules, fvargen, newfvars, tempgen, tempvars)) print_progress(i, length_ss) solvector = [] for fvar in fvars: try: solvector.append(solutions[fvar]) except KeyError: newfvars.append(next(fvargen)) solvector.append(newfvars[-1]) for tempvar in tempvars: if tempvar not in subs_rules: newfvars.append(next(fvargen)) subs_rules[tempvar] = newfvars[-1] if newfvars: if iofvars is not None: iofvars[:] = newfvars if not subs_rules: return simplify_group([Ncproduct(-solvector[i], list(key)) for i,key in enumerate(subspace)]) else: ret = simplify_group([Ncproduct(-solvector[i].xreplace(subs_rules), list(key)) for i,key in enumerate(subspace)]) for tempvar in tempvars: subs_rules.pop(tempvar, None) return ret
# Form expressions for derivatives of the g's w.r.t. lean and pitch dh = [0]*8 for i in range(4): for j in range(2): dh[2*i + j] = h[i].diff(q[j+1]) # Subsitution dictionary to replace dynamic symbols with regular symbols symbol_dict = {q[1]: Symbol('q1'), q[2]: Symbol('q2'), qd[0]: Symbol('qd0')} for i in range(4): h[i] = h[i].subs(symbol_dict) for i in range(8): dh[i] = dh[i].subs(symbol_dict) # CSE on g's z, h_red = cse(h, numbered_symbols('z')) # Form output code for g's output_code = " // Intermediate variables for h's\n" for zi_lhs, zi_rhs in z: output_code += " {0} = {1};\n".format(zi_lhs, ccode(zi_rhs)) output_code += "\n // h's\n" for i in range(4): output_code += " h[{0}] = {1};\n".format(i, ccode(h_red[i])) # CSE on dh's z, dh_red = cse(dh, numbered_symbols('z')) # Form output code for dg's output_code += "\n // Intermediate variables for dh's\n"
def cse_lambdify(args, expr, **kwargs): ''' Wrapper for sympy.lambdify which makes use of common subexpressions. ''' # Note: # This was expected to speed up the evaluation of the created functions. # However performance gain is only at ca. 5% # check input expression if type(expr) == str: raise TypeError('Not implemented for string input expression!') # check given expression try: check_expression(expr) except TypeError as err: raise NotImplementedError("Only sympy expressions are allowed, yet") # get sequence of symbols from input arguments if type(args) == str: args = sp.symbols(args, seq=True) elif hasattr(args, '__iter__'): # this may kill assumptions args = [sp.Symbol(str(a)) for a in args] if not hasattr(args, '__iter__'): args = (args, ) # get the common subexpressions cse_pairs, red_exprs = sp.cse(expr, symbols=sp.numbered_symbols('r')) if len(red_exprs) == 1: red_exprs = red_exprs[0] # check if sympy found any common subexpressions if not cse_pairs: # if not, use standard lambdify return sp.lambdify(args, expr, **kwargs) # now we are looking for those arguments that are part of the reduced expression(s) shortcuts = zip(*cse_pairs)[0] atoms = sp.Set(red_exprs).atoms() cse_args = [arg for arg in tuple(args) + tuple(shortcuts) if arg in atoms] # next, we create a function that evaluates the reduced expression cse_expr = red_exprs # if dummify is set to False then sympy.lambdify still returns a numpy.matrix # regardless of the possibly passed module dictionary {'ImmutableMatrix' : numpy.array} if kwargs.get('dummify') == False: kwargs['dummify'] = True reduced_exprs_fnc = sp.lambdify(args=cse_args, expr=cse_expr, **kwargs) # get the function that evaluates the replacement pairs modules = kwargs.get('modules') if modules is None: modules = ['math', 'numpy', 'sympy'] namespaces = [] if isinstance(modules, (dict, str)) or not hasattr(modules, '__iter__'): namespaces.append(modules) else: namespaces += list(modules) nspace = {} for m in namespaces[::-1]: nspace.update(_get_namespace(m)) eval_pairs_fnc = make_cse_eval_function(input_args=args, replacement_pairs=cse_pairs, ret_filter=cse_args, namespace=nspace) # now we can wrap things together def cse_fnc(*args): cse_args_evaluated = eval_pairs_fnc(args) return reduced_exprs_fnc(*cse_args_evaluated) return cse_fnc
def variables(self): dummy_groups = ( DummyGroup('depvdummies', self._fo_odesys.na_depv), DummyGroup('paramdummies', self._fo_odesys.param_and_sol_symbs), ) arrayify_groups = ( ArrayifyGroup('depvdummies', self.depv_tok, self.depv_offset), ArrayifyGroup('paramdummies', self.param_tok, self.param_offset) ) code_func_cse, code_func_exprs = self.get_cse_code( self._fo_odesys.na_f.values(), 'csefunc', dummy_groups, arrayify_groups) y0 = ', '.join(['1.0'] * self.NY) y0_names = ', '.join(map(str, self._fo_odesys.na_depv)) params = ', '.join(['1.0'] * len(self.prog_param_symbs)) param_names = ', '.join(map(str, self.prog_param_symbs)) indepv = self._fo_odesys.indepv # TODO: this is inefficient, implement linear scaling algo in # FirstOrderODESystem sparse_jac = OrderedDict(reduce(add, [ [((i, j), expr) for j, expr in enumerate(row) if expr != 0]\ for i, row in enumerate(self._fo_odesys.na_jac.tolist()) ])) dfdt = self._fo_odesys.na_dfdt.values() code_jac_cse, code_jac_exprs = self.get_cse_code( sparse_jac.values() + dfdt, 'csejac', dummy_groups, arrayify_groups) code_pure_dfdt_cse, code_pure_dfdt_exprs = self.get_cse_code( dfdt, 'csedfdt', dummy_groups, arrayify_groups) code_dfdt_exprs = code_jac_exprs[len(sparse_jac):] code_jac_exprs = zip(sparse_jac.keys(), code_jac_exprs[:len(sparse_jac)]) # Populate ia, ja (sparse index specifiers using fortran indexing) # see documentation of LSODES in ODEPACK for definition # (Yale sparse matrix) # ja contains row indices of nonzero elements # ia contains index in ja where row i starts ia, ja = [1], [] k = 1 # <--- index starts at 1 in fortran, not at 0 as in C/Python code_yale_jac_exprs = [] code_yale_jac_cse = [] for ci in range(self.NY): col_exprs = [] cur_ja = [] for ri in [sri for sri, sci in sparse_jac.keys() if sci == ci]: cur_ja.append(ri+1) # Fortran index k += 1 col_exprs.append(sparse_jac[(ri,ci)]) # Store indices in ja ja.extend(cur_ja) # Store indicies in ia ia.append(k) # Extract common subexpressions for this column cse_defs, cse_exprs = sympy.cse( col_exprs, symbols = sympy.numbered_symbols( 'csejaccol{}'.format(ci))) # Format code: expressions in cse terms code_exprs = zip(cur_ja, [ self.as_arrayified_code(x, dummy_groups, arrayify_groups) for x \ in cse_exprs ]) code_yale_jac_exprs.append(code_exprs) # Format code: CSE definitions code_cse_defs=[] for var_name, var_expr in cse_defs: code_var_expr = self.as_arrayified_code( var_expr, dummy_groups, arrayify_groups) code_cse_defs.append((var_name, code_var_expr)) code_yale_jac_cse.append(code_cse_defs) ia = ia [:-1] return {'NY': self.NY, 'NNZ': len(sparse_jac), 'IA': ia, 'JA': ja, 'NPARAM': len(self.prog_param_symbs), 'y0_comma_sep_str': y0, 'y0_names': y0_names, 'param_vals_comma_sep_str': params, 'param_names': param_names, 'cse_func': code_func_cse, 'f': code_func_exprs, 'cse_jac': code_jac_cse, 'jac': code_jac_exprs, 'yale_jac_exprs': code_yale_jac_exprs, 'yale_jac_cse': code_yale_jac_cse, 'dfdt': code_dfdt_exprs, 'pure_dfdt_exprs': code_pure_dfdt_exprs, 'pure_dfdt_cse': code_pure_dfdt_cse, }
# substitute the algabrain equations in the ode system to generate the complete ode_subs a1.gen_ode_subs() # finding all the parameters a1.eq_all_params(Zone = 'algebraic') a1.eq_all_params(Zone = 'ode') a1.eq_all_params(Zone = 'ode_subs') # eample how to use the method replace_singe_var() # a1.eq['algebraic']['delta_L1'] = a1.replace_singe_var(a1.eq['algebraic']['delta_L1'], 'x_1', 'x_11') x_vec = a1.eq['ode_subs'].keys() # fixme: the code below is not working as 'm_1' and 'm_2' are strings, but they should be sympy objects # todo: change eq_all_params() to store and the sympy objects par_vec = [a1.all_params_ode_subs['m1'], a1.all_params_ode_subs['m2']] sens_sys_list = a1.sys_dict_to_list(a1.eq['ode_subs']) sens_sys = a1.sens_ext_sys(sens_sys_list, x_vec, par_vec) print sens_sys sens_sys_list = a1.sys_dict_to_list(sens_sys) from sympy import cse, numbered_symbols a4=cse(sens_sys_list, symbols=numbered_symbols(prefix='t', start=0)) a1.print_Sys_optim(a4, func_name = 'r1')
def ufuncify_matrix(args, expr, const=None, tmp_dir=None, parallel=False): """Returns a function that evaluates a matrix of expressions in a tight loop. Parameters ---------- args : iterable of sympy.Symbol A list of all symbols in expr in the desired order for the output function. expr : sympy.Matrix A matrix of expressions. const : tuple, optional This should include any of the symbols in args that should be constant with respect to the loop. tmp_dir : string, optional The path to a directory in which to store the generated files. If None then the files will be not be retained after the function is compiled. parallel : boolean, optional If True and openmp is installed, the generated code will be parallelized across threads. This is only useful when expr are extremely large. """ # TODO : This is my first ever global variable in Python. It'd probably # be better if this was a class attribute of a Ufuncifier class. And I'm # not sure if this current version counts sequentially. global module_counter matrix_size = expr.shape[0] * expr.shape[1] file_prefix_base = 'ufuncify_matrix' file_prefix = '{}_{}'.format(file_prefix_base, module_counter) if tmp_dir is None: codedir = tempfile.mkdtemp(".ufuncify_compile") else: codedir = os.path.abspath(tmp_dir) if not os.path.exists(codedir): os.makedirs(codedir) taken = False while not taken: try: open(os.path.join(codedir, file_prefix + '.pyx'), 'r') except IOError: taken = True else: file_prefix = '{}_{}'.format(file_prefix_base, module_counter) module_counter += 1 d = { 'routine_name': 'eval_matrix', 'file_prefix': file_prefix, 'matrix_output_size': matrix_size, 'num_rows': expr.shape[0], 'num_cols': expr.shape[1] } if parallel: if openmp_installed(): openmp = True else: openmp = False msg = ('openmp is not installed or not working properly, request ' 'for parallel execution ignored.') warnings.warn(msg) if parallel and openmp: d['loop_sig'] = "prange(n, nogil=True)" d['head_gil'] = " nogil" d['compile_args'] = "'-fopenmp'" d['link_args'] = "'-fopenmp'" else: d['loop_sig'] = "range(n)" d['head_gil'] = "" d['compile_args'] = "" d['link_args'] = "" matrix_sym = sm.MatrixSymbol('matrix', expr.shape[0], expr.shape[1]) sub_exprs, simple_mat = sm.cse(expr, sm.numbered_symbols('z_')) sub_expr_code = '\n'.join([ 'double ' + sm.ccode(sub_expr[1], sub_expr[0]) for sub_expr in sub_exprs ]) matrix_code = sm.ccode(simple_mat[0], matrix_sym) d['eval_code'] = ' ' + '\n '.join( (sub_expr_code + '\n' + matrix_code).split('\n')) c_indent = len('void {routine_name}('.format(**d)) c_arg_spacer = ',\n' + ' ' * c_indent input_args = ['double {}'.format(sm.ccode(a)) for a in args] d['input_args'] = c_arg_spacer.join(input_args) cython_input_args = [] indexed_input_args = [] for a in args: if const is not None and a in const: typ = 'double' idexy = '{}' else: typ = 'np.ndarray[np.double_t, ndim=1]' idexy = '{}[i]' cython_input_args.append('{} {}'.format(typ, sm.ccode(a))) indexed_input_args.append(idexy.format(sm.ccode(a))) cython_indent = len('def {routine_name}_loop('.format(**d)) cython_arg_spacer = ',\n' + ' ' * cython_indent d['numpy_typed_input_args'] = cython_arg_spacer.join(cython_input_args) d['indexed_input_args'] = ',\n'.join(indexed_input_args) files = {} files[d['file_prefix'] + '_c.c'] = _c_template.format(**d) files[d['file_prefix'] + '_h.h'] = _h_template.format(**d) files[d['file_prefix'] + '.pyx'] = _cython_template.format(**d) files[d['file_prefix'] + '_setup.py'] = _setup_template.format(**d) workingdir = os.getcwd() os.chdir(codedir) try: sys.path.append(codedir) for filename, code in files.items(): with open(filename, 'w') as f: f.write(code) cmd = [ sys.executable, d['file_prefix'] + '_setup.py', 'build_ext', '--inplace' ] subprocess.call(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) cython_module = importlib.import_module(d['file_prefix']) finally: module_counter += 1 sys.path.remove(codedir) os.chdir(workingdir) if tmp_dir is None: shutil.rmtree(codedir) return getattr(cython_module, d['routine_name'] + '_loop')
def ufuncify_matrix(args, expr, const=None, tmp_dir=None): """Returns a function that evaluates a matrix of expressions in a tight loop. Parameters ---------- args : iterable of sympy.Symbol A list of all symbols in expr in the desired order for the output function. expr : sympy.Matrix A matrix of expressions. const : tuple, optional This should include any of the symbols in args that should be constant with respect to the loop. tmp_dir : string, optional The path to a directory in which to store the generated files. If None then the files will be not be retained after the function is compiled. """ # TODO : This is my first ever global variable in Python. It'd probably # be better if this was a class attribute of a Ufuncifier class. And I'm # not sure if this current version counts sequentially. global module_counter matrix_size = expr.shape[0] * expr.shape[1] file_prefix_base = 'ufuncify_matrix' file_prefix = '{}_{}'.format(file_prefix_base, module_counter) if tmp_dir is None: codedir = tempfile.mkdtemp(".ufuncify_compile") else: codedir = os.path.abspath(tmp_dir) if not os.path.exists(codedir): os.makedirs(codedir) taken = False while not taken: try: open(os.path.join(codedir, file_prefix + '.pyx'), 'r') except IOError: taken = True else: file_prefix = '{}_{}'.format(file_prefix_base, module_counter) module_counter += 1 d = {'routine_name': 'eval_matrix', 'file_prefix': file_prefix, 'matrix_output_size': matrix_size, 'num_rows': expr.shape[0], 'num_cols': expr.shape[1]} matrix_sym = sy.MatrixSymbol('matrix', expr.shape[0], expr.shape[1]) sub_exprs, simple_mat = sy.cse(expr, sy.numbered_symbols('z_')) sub_expr_code = '\n'.join(['double ' + sy.ccode(sub_expr[1], sub_expr[0]) for sub_expr in sub_exprs]) matrix_code = sy.ccode(simple_mat[0], matrix_sym) d['eval_code'] = ' ' + '\n '.join((sub_expr_code + '\n' + matrix_code).split('\n')) c_indent = len('void {routine_name}('.format(**d)) c_arg_spacer = ',\n' + ' ' * c_indent input_args = ['double {}'.format(sy.ccode(a)) for a in args] d['input_args'] = c_arg_spacer.join(input_args) cython_input_args = [] indexed_input_args = [] for a in args: if const is not None and a in const: typ = 'double' idexy = '{}' else: typ = 'np.ndarray[np.double_t, ndim=1]' idexy = '{}[i]' cython_input_args.append('{} {}'.format(typ, sy.ccode(a))) indexed_input_args.append(idexy.format(sy.ccode(a))) cython_indent = len('def {routine_name}_loop('.format(**d)) cython_arg_spacer = ',\n' + ' ' * cython_indent d['numpy_typed_input_args'] = cython_arg_spacer.join(cython_input_args) d['indexed_input_args'] = ',\n'.join(indexed_input_args) files = {} files[d['file_prefix'] + '_c.c'] = _c_template.format(**d) files[d['file_prefix'] + '_h.h'] = _h_template.format(**d) files[d['file_prefix'] + '.pyx'] = _cython_template.format(**d) files[d['file_prefix'] + '_setup.py'] = _setup_template.format(**d) workingdir = os.getcwd() os.chdir(codedir) try: sys.path.append(codedir) for filename, code in files.items(): with open(filename, 'w') as f: f.write(code) cmd = [sys.executable, d['file_prefix'] + '_setup.py', 'build_ext', '--inplace'] subprocess.call(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) cython_module = importlib.import_module(d['file_prefix']) finally: module_counter += 1 sys.path.remove(codedir) os.chdir(workingdir) if tmp_dir is None: shutil.rmtree(codedir) return getattr(cython_module, d['routine_name'] + '_loop')
def outputC(sympyexpr, output_varname_str, filename = "stdout", params = "", prestring = "", poststring = ""): outCparams = parse_outCparams_string(params) preindent = outCparams.preindent TYPE = par.parval_from_str("PRECISION") if outCparams.enable_TYPE == "False": TYPE = "" # Step 0: Initialize # commentblock: comment block containing the input SymPy string, # set only if outCverbose==True # outstring: the output C code string commentblock = "" outstring = "" # Step 1: If enable_SIMD==True, then check if TYPE=="double". If not, error out. # Otherwise set TYPE="REAL_SIMD_ARRAY", which should be #define'd # within the C code. For example for AVX-256, the C code should have # #define REAL_SIMD_ARRAY __m256d if outCparams.enable_SIMD == "True": if TYPE not in ('double', ''): print("SIMD output currently only supports double precision or typeless. Sorry!") sys.exit(1) if TYPE == "double": TYPE = "REAL_SIMD_ARRAY" # Step 2a: Apply sanity checks when either sympyexpr or # output_varname_str is a list. if isinstance(output_varname_str, list) and not isinstance(sympyexpr, list): print("Error: Provided a list of output variable names, but only one SymPy expression.") sys.exit(1) if isinstance(sympyexpr, list): if not isinstance(output_varname_str, list): print("Error: Provided a list of SymPy expressions, but no corresponding list of output variable names") sys.exit(1) elif len(output_varname_str) != len(sympyexpr): print("Error: Length of SymPy expressions list ("+str(len(sympyexpr))+ ") != Length of corresponding output variable name list ("+str(len(output_varname_str))+")") sys.exit(1) # Step 2b: If sympyexpr and output_varname_str are not lists, # convert them to lists of one element each, to # simplify proceeding code. if not isinstance(output_varname_str, list) and not isinstance(sympyexpr, list): output_varname_strtmp = [output_varname_str] output_varname_str = output_varname_strtmp sympyexprtmp = [sympyexpr] sympyexpr = sympyexprtmp sympyexpr = sympyexpr[:] # pass-by-value (copy list) # Step 3: If outCparams.verbose = True, then output the original SymPy # expression(s) in code comments prior to actual C code if outCparams.outCverbose == "True": commentblock += preindent+"/*\n"+preindent+" * Original SymPy expression" if len(output_varname_str)>1: commentblock += "s" commentblock += ":\n" for i, varname in enumerate(output_varname_str): if i == 0: if len(output_varname_str) != 1: commentblock += preindent+" * \"[" else: commentblock += preindent+" * \"" else: commentblock += preindent+" * " commentblock += varname + " = " + str(sympyexpr[i]) if i == len(output_varname_str)-1: if len(output_varname_str) != 1: commentblock += "]\"\n" else: commentblock += "\"\n" else: commentblock += ",\n" commentblock += preindent+" */\n" # Step 4: Add proper indentation of C code: if outCparams.includebraces == "True": indent = outCparams.preindent+" " else: indent = outCparams.preindent+"" # Step 5: Should the output variable, e.g., outvar, be declared? # If so, start output line with e.g., "double outvar " outtypestring = "" if outCparams.declareoutputvars == "True": outtypestring = indent+TYPE + " " else: outtypestring = indent # Step 6a: If common subexpression elimination (CSE) disabled, then # just output the SymPy string in the most boring way, # nearly consistent with SymPy's ccode() function, # though with support for float & long double types # as well. SIMD_RATIONAL_decls = RATIONAL_decls = "" if outCparams.CSE_enable == "False": # If CSE is disabled: for i in range(len(sympyexpr)): outstring += outtypestring + ccode_postproc(sp.ccode(sympyexpr[i], output_varname_str[i], user_functions=custom_functions_for_SymPy_ccode))+"\n" # Step 6b: If CSE enabled, then perform CSE using SymPy and then # resulting C code. else: # If CSE is enabled: SIMD_const_varnms = [] SIMD_const_values = [] varprefix = '' if outCparams.CSE_varprefix == 'tmp' else outCparams.CSE_varprefix if outCparams.CSE_preprocess == "True" or outCparams.enable_SIMD == "True": # If CSE_preprocess == True, then perform partial factorization # If enable_SIMD == True, then declare _NegativeOne_ in preprocessing factor_negative = eval(outCparams.enable_SIMD) and eval(outCparams.SIMD_find_more_subs) sympyexpr, map_sym_to_rat = cse_preprocess(sympyexpr, prefix=varprefix, declare=eval(outCparams.enable_SIMD), negative=factor_negative, factor=eval(outCparams.CSE_preprocess)) for v in map_sym_to_rat: p, q = float(map_sym_to_rat[v].p), float(map_sym_to_rat[v].q) if outCparams.enable_SIMD == "False": RATIONAL_decls += indent + 'const double ' + str(v) + ' = ' # Since Integer is a subclass of Rational in SymPy, we need only check whether # the denominator q = 1 to determine if a rational is an integer. if q != 1: RATIONAL_decls += str(p) + '/' + str(q) + ';\n' else: RATIONAL_decls += str(p) + ';\n' sympy_version = sp.__version__.replace('rc', '...').replace('b', '...') sympy_major_version = int(sympy_version.split(".")[0]) sympy_minor_version = int(sympy_version.split(".")[1]) if sympy_major_version < 1 or (sympy_major_version == 1 and sympy_minor_version < 3): print('Warning: SymPy version', sympy_version, 'does not support CSE postprocessing.') CSE_results = sp.cse(sympyexpr, sp.numbered_symbols(outCparams.CSE_varprefix + '_'), order=outCparams.CSE_sorting) else: CSE_results = cse_postprocess(sp.cse(sympyexpr, sp.numbered_symbols(outCparams.CSE_varprefix + '_'), order=outCparams.CSE_sorting)) for commonsubexpression in CSE_results[0]: FULLTYPESTRING = "const " + TYPE + " " if outCparams.enable_TYPE == "False": FULLTYPESTRING = "" if outCparams.enable_SIMD == "True": outstring += indent + FULLTYPESTRING + str(commonsubexpression[0]) + " = " + \ str(expr_convert_to_SIMD_intrins(commonsubexpression[1],map_sym_to_rat,varprefix,outCparams.SIMD_find_more_FMAsFMSs)) + ";\n" else: outstring += indent + FULLTYPESTRING + ccode_postproc(sp.ccode(commonsubexpression[1], commonsubexpression[0], user_functions=custom_functions_for_SymPy_ccode)) + "\n" for i, result in enumerate(CSE_results[1]): if outCparams.enable_SIMD == "True": outstring += outtypestring + output_varname_str[i] + " = " + \ str(expr_convert_to_SIMD_intrins(result,map_sym_to_rat,varprefix,outCparams.SIMD_find_more_FMAsFMSs)) + ";\n" else: outstring += outtypestring+ccode_postproc(sp.ccode(result,output_varname_str[i], user_functions=custom_functions_for_SymPy_ccode))+"\n" # Complication: SIMD functions require numerical constants to be stored in SIMD arrays # Resolution: This function extends lists "SIMD_const_varnms" and "SIMD_const_values", # which store the name of each constant SIMD array (e.g., _Integer_1) and # the value of each variable (e.g., 1.0). if outCparams.enable_SIMD == "True": for v in map_sym_to_rat: p, q = float(map_sym_to_rat[v].p), float(map_sym_to_rat[v].q) SIMD_const_varnms.extend([str(v)]) if q != 1: SIMD_const_values.extend([str(p) + '/' + str(q)]) else: SIMD_const_values.extend([str(p)]) # Step 6b.i: If enable_SIMD == True , and # there is at least one SIMD const variable, # then declare the SIMD_const_varnms and SIMD_const_values arrays if outCparams.enable_SIMD == "True" and len(SIMD_const_varnms) != 0: # Step 6a) Sort the list of definitions. Idea from: # https://stackoverflow.com/questions/9764298/is-it-possible-to-sort-two-listswhich-reference-each-other-in-the-exact-same-w SIMD_const_varnms, SIMD_const_values = \ (list(t) for t in zip(*sorted(zip(SIMD_const_varnms, SIMD_const_values)))) # Step 6b) Remove duplicates uniq_varnms = superfast_uniq(SIMD_const_varnms) uniq_values = superfast_uniq(SIMD_const_values) SIMD_const_varnms = uniq_varnms SIMD_const_values = uniq_values if len(SIMD_const_varnms) != len(SIMD_const_values): print("Error: SIMD constant declaration arrays SIMD_const_varnms[] and SIMD_const_values[] have inconsistent sizes!") sys.exit(1) for i in range(len(SIMD_const_varnms)): if outCparams.enable_TYPE == "False": SIMD_RATIONAL_decls += indent + SIMD_const_varnms[i] + " = " + SIMD_const_values[i]+";" else: SIMD_RATIONAL_decls += indent + "const double " + "tmp" + SIMD_const_varnms[i] + " = " + SIMD_const_values[i] + ";\n" SIMD_RATIONAL_decls += indent + "const REAL_SIMD_ARRAY " + SIMD_const_varnms[i] + " = ConstSIMD(" + "tmp" + SIMD_const_varnms[i] + ");\n" SIMD_RATIONAL_decls += "\n" # Step 7: Construct final output string final_Ccode_output_str = commentblock # Step 7a: Output C code in indented curly brackets if # outCparams.includebraces = True if outCparams.includebraces == "True": final_Ccode_output_str += outCparams.preindent+"{\n" final_Ccode_output_str += prestring + RATIONAL_decls + SIMD_RATIONAL_decls + outstring + poststring if outCparams.includebraces == "True": final_Ccode_output_str += outCparams.preindent+"}\n" # Step 8: If filename == "stdout", then output # C code to standard out (useful for copy-paste or interactive # mode). Otherwise output to file specified in variable name. if filename == "stdout": # Output to standard out (stdout; "the screen") print(final_Ccode_output_str) elif filename == "returnstring": return final_Ccode_output_str else: # Output to the file specified by the function input parameter string 'filename': with open(filename, outCparams.outCfileaccess) as file: file.write(final_Ccode_output_str) successstr = "" if outCparams.outCfileaccess == "a": successstr = "Appended " elif outCparams.outCfileaccess == "w": successstr = "Wrote " print(successstr + "to file \"" + filename + "\"")
def generate(self, expressions, functionname, const_function=True, callbacks=None): """Given a numpy nd-array of sympy expressions, return a function which will generate a C-style function that will compute the entries of the array. The array is flattened out into a contiguous array with the last index varying fastest (row-major for matrices). """ orig_shape = expressions.shape s = "\n/** Computes the n-d array of shape (" for d in expressions.shape[:-1]: s += "{0}, ".format(d) s += "{0})\n *\n".format(expressions.shape[-1]) s += " * @param[out] ar a C-array of with {0}".format(expressions.size) s += " elements\n */\n" expressions_flat = np.zeros((expressions.size,), dtype=object) for i, ai in enumerate(expressions.flat): if isinstance(ai, int): expressions_flat[i] = S(0) else: expressions_flat[i] = ai.subs(self.subs_dict) if self._class_name != "": member_function_name = self._class_name + "::" + functionname function_signature = "void {0}(double ar[{1}])".format(member_function_name, expressions_flat.size) else: function_signature = "void {0}(double ar[{1}])".format(functionname, expressions_flat.size) if const_function: function_signature += " const" s += "// " + function_signature + ";\n" s += function_signature + "\n{\n" repl, redu = cse(expressions_flat, symbols=numbered_symbols("z")) if len(repl): s += " double z[{0}];\n\n".format(len(repl)) if callbacks is not None: for c in callbacks: cname, symbolname = c s += " " + cname + "(" + symbolname + ");\n" for i, r in enumerate(repl): s += " " + re.sub(r'z(\d+)', r'z[\1]', str(r[0])) + " = " tmp = re.sub(r'z(\d+)', r'z[\1]', ccode(r[1])) + ";\n" if self.state_prefix: tmp = re.sub(self.state_prefix + r'(\d+)', self.state_prefix + r'[\1]', tmp) for p, r in self.regex_list: test = re.compile(p) tmp = test.sub(r, tmp) s += tmp s += "\n" for i, red_i in enumerate(redu): s += " ar[{0}] = ".format(i) tmp = re.sub(r'z(\d+)', r'z[\1]', ccode(red_i)) if self.state_prefix: tmp = re.sub(self.state_prefix + r'(\d+)', self.state_prefix + r'[\1]', tmp) for p, r in self.regex_list: test = re.compile(p) tmp = test.sub(r, tmp) s += tmp s += ";\n" self.s += s + "}\n" return s
def dse_cse(expr): """ Perform common subexpression elimination on sympy expressions. :param expr: sympy equation or list of equations on which CSE is performed. :return: A list of the resulting equations after performing CSE """ expr = expr if isinstance(expr, list) else [expr] temps, stencils = cse(expr, numbered_symbols("temp")) # Restores the LHS for i in range(len(expr)): stencils[i] = Eq(expr[i].lhs, stencils[i].rhs) to_revert = {} to_keep = [] # Restores IndexedBases if they are collected by CSE and # reverts changes to simple index operations (eg: t - 1) for temp, value in temps: if isinstance(value, IndexedBase): to_revert[temp] = value elif isinstance(value, Indexed): to_revert[temp] = value elif isinstance(value, Add) and not \ set([t, x, y, z]).isdisjoint(set(value.args)): to_revert[temp] = value else: to_keep.append((temp, value)) # Restores the IndexedBases and the Indexes in the assignments to revert for temp, value in to_revert.items(): s_dict = {} for arg in preorder_traversal(value): if isinstance(arg, Indexed): new_indices = [] for index in arg.indices: if index in to_revert: new_indices.append(to_revert[index]) else: new_indices.append(index) if arg.base.label in to_revert: s_dict[arg] = Indexed(to_revert[value.base.label], *new_indices) to_revert[temp] = value.xreplace(s_dict) subs_dict = {} # Builds a dictionary of the replacements for expr in stencils + [assign for temp, assign in to_keep]: for arg in preorder_traversal(expr): if isinstance(arg, Indexed): new_indices = [] for index in arg.indices: if index in to_revert: new_indices.append(to_revert[index]) else: new_indices.append(index) if arg.base.label in to_revert: subs_dict[arg] = Indexed(to_revert[arg.base.label], *new_indices) elif tuple(new_indices) != arg.indices: subs_dict[arg] = Indexed(arg.base, *new_indices) if arg in to_revert: subs_dict[arg] = to_revert[arg] stencils = [stencil.xreplace(subs_dict) for stencil in stencils] to_keep = [Eq(temp[0], temp[1].xreplace(subs_dict)) for temp in to_keep] # If the RHS of a temporary variable is the LHS of a stencil, # update the value of the temporary variable after the stencil new_stencils = [] for stencil in stencils: new_stencils.append(stencil) for temp in to_keep: if stencil.lhs in preorder_traversal(temp.rhs): new_stencils.append(temp) break return to_keep + new_stencils
def solve_motion_equations(M, B, state_vars=[], input_vars=[], parameters_values=dict()): ''' Solves the motion equations given by the mass matrix and right hand side to define a callable function for the vector field of the respective control system. Parameters ---------- M : sympy.Matrix A sympy.Matrix containing sympy expressions and symbols that represents the mass matrix of the control system. B : sympy.Matrix A sympy.Matrix containing sympy expressions and symbols that represents the right hand site of the motion equations. state_vars : list A list with sympy.Symbols's for each state variable. input_vars : list A list with sympy.Symbols's for each input variable. parameter_values : dict A dictionary with a key:value pair for each system parameter. Returns ------- callable ''' M_shape = M.shape B_shape = B.shape assert(M_shape[0] == B_shape[0]) # at first we create a buffer for the string that we complete and execute # to dynamically define a function and return it fnc_str_buffer =''' def f(x, u): # System variables %s # x_str %s # u_str # Parameters %s # par_str # Sympy Common Expressions %s # cse_str # Vectorfield %s # ff_str return ff ''' ################################# # handle system state variables # ################################# # --> leads to x_str which shows how to unpack the state variables x_str = '' for var in state_vars: x_str += '%s, '%str(var) # as a last we remove the trailing '; ' to avoid syntax erros x_str = x_str + '= x' ########################## # handle input variables # ########################## # --> leads to u_str which will show how to unpack the inputs of the control system u_str = '' for var in input_vars: u_str += '%s, '%str(var) # after we remove the trailing '; ' to avoid syntax errors x_str will look like: # 'u1, u2, ... , um = u' u_str = u_str + '= u' ############################ # handle system parameters # ############################ # --> leads to par_str par_str = '' for k, v in parameters_values.items(): # 'k' is the name of a system parameter such as mass or gravitational acceleration # 'v' is its value in SI units par_str += '%s = %s; '%(str(k), str(v)) # as a last we remove the trailing '; ' from par_str to avoid syntax errors par_str = par_str[:-2] # now solve the motion equations w.r.t. the accelerations sol = M.solve(B) # use SymPy's Common Subexpression Elimination cse_list, cse_res = sp.cse(sol, symbols=sp.numbered_symbols('q')) ################################ # handle common subexpressions # ################################ # --> leads to cse_str cse_str = '' #cse_list = [(str(l), str(r)) for l, r in cse_list] for cse_pair in cse_list: cse_str += '%s = %s; '%(str(cse_pair[0]), str(cse_pair[1])) # add result of cse for i in xrange(M_shape[0]): cse_str += 'q%d_dd = %s; '%(i, str(cse_res[0][i])) cse_str = cse_str[:-2] ###################### # create vectorfield # ###################### # --> leads to ff_str ff_str = 'ff = [' for i in xrange(M_shape[0]): ff_str += '%s, '%str(state_vars[2*i+1]) ff_str += 'q%s_dd, '%(i) # remove trailing ',' and add closing brackets ff_str = ff_str[:-2] + ']' ############################ # Create callable function # ############################ # now we can replace all placeholders in the function string buffer fnc_str = fnc_str_buffer%(x_str, u_str, par_str, cse_str, ff_str) # and finally execute it which will create a python function 'f' exec(fnc_str) # now we have defined a callable function that can be used within PyTrajectory return f
def outputC(sympyexpr, output_varname_str, filename="stdout", params="", prestring="", poststring=""): outCparams = parse_outCparams_string(params) preindent = outCparams.preindent TYPE = par.parval_from_str("PRECISION") if outCparams.enable_TYPE == "False": TYPE = "" # Step 0: Initialize # commentblock: comment block containing the input SymPy string, # set only if outCverbose==True # outstring: the output C code string commentblock = "" outstring = "" # Step 1: If SIMD_enable==True, then check if TYPE=="double". If not, error out. # Otherwise set TYPE="REAL_SIMD_ARRAY", which should be #define'd # within the C code. For example for AVX-256, the C code should have # #define REAL_SIMD_ARRAY __m256d if outCparams.SIMD_enable == "True": if not (TYPE == "double" or TYPE == ""): print( "SIMD output currently only supports double precision or typeless. Sorry!" ) exit(1) if TYPE == "double": TYPE = "REAL_SIMD_ARRAY" else: TYPE = "" # Step 2a: Apply sanity checks when either sympyexpr or # output_varname_str is a list. if type(output_varname_str) is list and type(sympyexpr) is not list: print( "Error: Provided a list of output variable names, but only one SymPy expression." ) exit(1) if type(sympyexpr) is list: if type(output_varname_str) is not list: print( "Error: Provided a list of SymPy expressions, but no corresponding list of output variable names" ) exit(1) elif len(output_varname_str) != len(sympyexpr): print("Error: Length of SymPy expressions list (" + str(len(sympyexpr)) + ") != Length of corresponding output variable name list (" + str(len(output_varname_str)) + ")") exit(1) # Step 2b: If sympyexpr and output_varname_str are not lists, # convert them to lists of one element each, to # simplify proceeding code. if type(output_varname_str) is not list and type(sympyexpr) is not list: output_varname_strtmp = [output_varname_str] output_varname_str = output_varname_strtmp sympyexprtmp = [sympyexpr] sympyexpr = sympyexprtmp # Step 3: If outCparams.verbose = True, then output the original SymPy # expression(s) in code comments prior to actual C code if outCparams.outCverbose == "True": commentblock += preindent + "/*\n" + preindent + " * Original SymPy expression" if len(output_varname_str) > 1: commentblock += "s" commentblock += ":\n" for i in range(len(output_varname_str)): if i == 0: if len(output_varname_str) != 1: commentblock += preindent + " * \"[" else: commentblock += preindent + " * \"" else: commentblock += preindent + " * " commentblock += output_varname_str[i] + " = " + str(sympyexpr[i]) if i == len(output_varname_str) - 1: if len(output_varname_str) != 1: commentblock += "]\"\n" else: commentblock += "\"\n" else: commentblock += ",\n" commentblock += preindent + " */\n" # Step 4: Add proper indentation of C code: if outCparams.includebraces == "True": indent = outCparams.preindent + " " else: indent = outCparams.preindent + "" # Step 5: Should the output variable, e.g., outvar, be declared? # If so, start output line with e.g., "double outvar " outtypestring = "" if outCparams.declareoutputvars == "True": outtypestring = outCparams.preindent + indent + TYPE + " " else: outtypestring = outCparams.preindent + indent # Step 6a: If common subexpression elimination (CSE) disabled, then # just output the SymPy string in the most boring way, # nearly consistent with SymPy's ccode() function, # though with support for float & long double types # as well. SIMD_decls = "" if outCparams.CSE_enable == "False": # If CSE is disabled: for i in range(len(sympyexpr)): outstring += outtypestring + ccode_postproc( sp.ccode(sympyexpr[i], output_varname_str[i])) + "\n" # Step 6b: If CSE enabled, then perform CSE using SymPy and then # resulting C code. else: # If CSE is enabled: SIMD_const_varnms = [] SIMD_const_values = [] CSE_results = sp.cse(sympyexpr, sp.numbered_symbols(outCparams.CSE_varprefix), order='canonical') for commonsubexpression in CSE_results[0]: FULLTYPESTRING = "const " + TYPE + " " if outCparams.enable_TYPE == "False": FULLTYPESTRING = "" if outCparams.SIMD_enable == "True": outstring += outCparams.preindent + indent + FULLTYPESTRING + str(commonsubexpression[0]) + " = " + \ str(expr_convert_to_SIMD_intrins(commonsubexpression[1],SIMD_const_varnms,SIMD_const_values,outCparams.SIMD_debug)) + ";\n" else: outstring += outCparams.preindent + indent + FULLTYPESTRING + ccode_postproc( sp.ccode(commonsubexpression[1], commonsubexpression[0])) + "\n" for i, result in enumerate(CSE_results[1]): if outCparams.SIMD_enable == "True": outstring += outtypestring + output_varname_str[i] + " = " + \ str(expr_convert_to_SIMD_intrins(result,SIMD_const_varnms,SIMD_const_values,outCparams.SIMD_debug)) + ";\n" else: outstring += outtypestring + ccode_postproc( sp.ccode(result, output_varname_str[i])) + "\n" # Step 6b.i: If SIMD_enable == True , and # there is at least one SIMD const variable, # then declare the SIMD_const_varnms and SIMD_const_values arrays if outCparams.SIMD_enable == "True" and len(SIMD_const_varnms) != 0: # Step 6a) Sort the list of definitions. Idea from: # https://stackoverflow.com/questions/9764298/is-it-possible-to-sort-two-listswhich-reference-each-other-in-the-exact-same-w SIMD_const_varnms, SIMD_const_values = \ (list(t) for t in zip(*sorted(zip(SIMD_const_varnms, SIMD_const_values)))) # Step 6b) Remove duplicates uniq_varnms = superfast_uniq(SIMD_const_varnms) uniq_values = superfast_uniq(SIMD_const_values) SIMD_const_varnms = uniq_varnms SIMD_const_values = uniq_values if len(SIMD_const_varnms) != len(SIMD_const_values): print( "Error: SIMD constant declaration arrays SIMD_const_varnms[] and SIMD_const_values[] have inconsistent sizes!" ) exit(1) for i in range(len(SIMD_const_varnms)): if outCparams.enable_TYPE == "False": SIMD_decls += outCparams.preindent + indent + SIMD_const_varnms[ i] + " = " + SIMD_const_values[i] + ";" else: SIMD_decls += outCparams.preindent + indent + "const double " + outCparams.CSE_varprefix + SIMD_const_varnms[ i] + " = " + SIMD_const_values[i] + ";\n" SIMD_decls += outCparams.preindent + indent + "const REAL_SIMD_ARRAY " + SIMD_const_varnms[ i] + " = ConstSIMD(" + outCparams.CSE_varprefix + SIMD_const_varnms[ i] + ");\n" SIMD_decls += "\n" # Step 7: Construct final output string final_Ccode_output_str = commentblock # Step 7a: Output C code in indented curly brackets if # outCparams.includebraces = True if outCparams.includebraces == "True": final_Ccode_output_str += outCparams.preindent + "{\n" final_Ccode_output_str += prestring + SIMD_decls + outstring + poststring if outCparams.includebraces == "True": final_Ccode_output_str += outCparams.preindent + "}\n" # Step 8: If filename == "stdout", then output # C code to standard out (useful for copy-paste or interactive # mode). Otherwise output to file specified in variable name. if filename == "stdout": # Output to standard out (stdout; "the screen") print(final_Ccode_output_str) elif filename == "returnstring": return final_Ccode_output_str else: # Output to the file specified by the function input parameter string 'filename': with open(filename, outCparams.outCfileaccess) as file: file.write(final_Ccode_output_str) successstr = "" if outCparams.outCfileaccess == "a": successstr = "Appended " elif outCparams.outCfileaccess == "w": successstr = "Wrote " print(successstr + "to file \"" + filename + "\"")
import commutator as comm from sympy import I, symbols, latex, numbered_symbols p = comm.print_group c = comm.calculate_commutator N = comm.Ncproduct X_gen = numbered_symbols('X') Y_gen = numbered_symbols('Y') L = 20 X = [next(X_gen) for i in range(L+1)] Y = [next(Y_gen) for i in range(L+1)] orders = dict(zip(X+Y,[1]*2*(L+1))) p.orders = orders Ypart = [N(I*Y[j+1], [2*j+1,2*j+4]) for j in range(L-2)] Xpart = [N(-X[j+1], [2*j+1,2*j+2,2*j+3,2*j+4]) for j in range(L-2)] small = Xpart+Ypart Zpart = [N(-I, [2*j+2,2*j+3]) for j in range(L-1)] Jpart = Zpart H = small +Jpart START_ORDER = 1 END_ORDER = 8 START_PSI = N(1, 'a1') START_IOFVARS = [] START_SPLIT_ORDERS = [0, 1] START_NORMDICT = {}
def cse_lambdify(args, expr, **kwargs): ''' Wrapper for sympy.lambdify which makes use of common subexpressions. ''' # Note: # This was expected to speed up the evaluation of the created functions. # However performance gain is only at ca. 5% # check input expression if type(expr) == str: raise TypeError('Not implemented for string input expression!') # check given expression try: check_expression(expr) except TypeError as err: raise NotImplementedError("Only sympy expressions are allowed, yet") # get sequence of symbols from input arguments if type(args) == str: args = sp.symbols(args, seq=True) elif hasattr(args, '__iter__'): # this may kill assumptions args = [sp.Symbol(str(a)) for a in args] if not hasattr(args, '__iter__'): args = (args,) # get the common subexpressions cse_pairs, red_exprs = sp.cse(expr, symbols=sp.numbered_symbols('r')) if len(red_exprs) == 1: red_exprs = red_exprs[0] # check if sympy found any common subexpressions if not cse_pairs: # if not, use standard lambdify return sp.lambdify(args, expr, **kwargs) # now we are looking for those arguments that are part of the reduced expression(s) shortcuts = zip(*cse_pairs)[0] atoms = sp.Set(red_exprs).atoms() cse_args = [arg for arg in tuple(args) + tuple(shortcuts) if arg in atoms] # next, we create a function that evaluates the reduced expression cse_expr = red_exprs # if dummify is set to False then sympy.lambdify still returns a numpy.matrix # regardless of the possibly passed module dictionary {'ImmutableMatrix' : numpy.array} if kwargs.get('dummify') == False: kwargs['dummify'] = True reduced_exprs_fnc = sp.lambdify(args=cse_args, expr=cse_expr, **kwargs) # get the function that evaluates the replacement pairs modules = kwargs.get('modules') if modules is None: modules = ['math', 'numpy', 'sympy'] namespaces = [] if isinstance(modules, (dict, str)) or not hasattr(modules, '__iter__'): namespaces.append(modules) else: namespaces += list(modules) nspace = {} for m in namespaces[::-1]: nspace.update(_get_namespace(m)) eval_pairs_fnc = make_cse_eval_function(input_args=args, replacement_pairs=cse_pairs, ret_filter=cse_args, namespace=nspace) # now we can wrap things together def cse_fnc(*args): cse_args_evaluated = eval_pairs_fnc(args) return reduced_exprs_fnc(*cse_args_evaluated) return cse_fnc
from sympy import I, symbols, numbered_symbols, S import commutator as comm p = comm.print_group N = comm.SigmaProduct L = 20 V, J, J2, f = symbols('V J J2 f', real=True) h_gen = numbered_symbols('h', real=True) hs = [next(h_gen) for i in range(L + 1)] vardict = {'J2': J2, 'f': f, 'V': V, 'J': J} vardict.update(zip([str(h) for h in hs], hs)) orders = {f: 1} Js = [J, J2] p.orders = orders fpart = [N(-f, ("x", [j])) for j in range(1, L + 1)] hpart = [N(-hs[j], ("z", [j])) for j in range(1, L + 1)] Jpart = [N(-J, ("zz", [j, j + 1])) for j in range(1, L)] small = fpart large = Jpart + hpart H = large + small zeroth_order = [N(1, "z10")] print("Hamiltonian:") p(H, breaks=False)
("rear_.c", ((w + c)*N.x + ((rR + tR)*N.z & R.x)*R.x) & R.x), ("front_.Ixx", R.x & IFront & R.x), ("front_.Iyy", R.y & IFront & R.y), ("front_.Izz", R.z & IFront & R.z), ("front_.Ixz", R.x & IFront & R.z), ("front_.J", IFyy), ("front_.m", mH + mF), ("front_.R", rF), ("front_.r", tF), ("front_.a", FO_HO_mc.pos_from(FO) & R.x), ("front_.b", FO_HO_mc.pos_from(FO) & R.z), ("front_.c", (c*N.x + (rF + tF)*N.z) & R.x), ("ls_", ((rR + tR)*N.z + w * N.x - (rF + tF)*N.z) & R.z)] eqns_to_cse = [rhs for lhs, rhs in expressions] repl, redu = cse(eqns_to_cse, symbols=numbered_symbols("z")) s = '#include "bicycle.h"\n' s += '#include "whipple.h"\n\n' s += 'namespace bicycle {\n\n' s += 'void Bicycle::set_parameters(const Whipple & w)\n{\n' s += ' double * z = new double[{0}];\n\n'.format(len(repl)) for i, r in enumerate(repl): s += " " + re.sub(r'z(\d+)', r'z[\1]', str(r[0])) + " = " s += re.sub(r'z(\d+)', r'z[\1]', ccode(r[1])) + ";\n" s += "\n" for i, (ex_i, red_i) in enumerate(zip(expressions, redu)): s += " " + ex_i[0] + " = " s += re.sub(r'z(\d+)', r'z[\1]', ccode(red_i)) s += ";\n"