def test_custom_codegen(): from sympy.printing.ccode import C99CodePrinter from sympy.functions.elementary.exponential import exp printer = C99CodePrinter(settings={'user_functions': {'exp': 'fastexp'}}) x, y = symbols('x y') expr = exp(x + y) # replace math.h with a different header gen = C99CodeGen(printer=printer, preprocessor_statements=['#include "fastexp.h"']) expected = ( '#include "expr.h"\n' '#include "fastexp.h"\n' 'double expr(double x, double y) {\n' ' double expr_result;\n' ' expr_result = fastexp(x + y);\n' ' return expr_result;\n' '}\n' ) result = codegen(('expr', expr), header=False, empty=False, code_gen=gen) source = result[0][1] assert source == expected # use both math.h and an external header gen = C99CodeGen(printer=printer) gen.preprocessor_statements.append('#include "fastexp.h"') expected = ( '#include "expr.h"\n' '#include <math.h>\n' '#include "fastexp.h"\n' 'double expr(double x, double y) {\n' ' double expr_result;\n' ' expr_result = fastexp(x + y);\n' ' return expr_result;\n' '}\n' ) result = codegen(('expr', expr), header=False, empty=False, code_gen=gen) source = result[0][1] assert source == expected
def test_multiple_results_c(): x, y, z = symbols('x,y,z') expr1 = (x + y)*z expr2 = (x - y)*z routine = make_routine( "test", [expr1, expr2] ) code_gen = C99CodeGen() raises(CodeGenError, lambda: get_string(code_gen.dump_h, [routine]))
def test_cython_wrapper_inoutarg(): from sympy import Equality x, y, z = symbols('x,y,z') code_gen = CythonCodeWrapper(C99CodeGen()) routine = make_routine("test", Equality(z, x + y + z)) source = get_string(code_gen.dump_pyx, [routine]) expected = ("cdef extern from 'file.h':\n" " void test(double x, double y, double *z)\n" "\n" "def test_c(double x, double y, double z):\n" "\n" " test(x, y, &z)\n" " return z") assert source == expected
def __init__(self, model, with_jacobian=False, cleanup=True, _logger=None): super(CythonRhsBuilder, self).__init__(model, with_jacobian, cleanup, _logger) routine_names = ["kinetics"] if with_jacobian: routine_names += ["kinetics_jacobian_y", "kinetics_jacobian_o"] # We want more control over various details of code generation and # wrapper module creation than sympy's autowrap provides, so we'll use # the lower-level building blocks directly. routines = {name: self._build_routine(name) for name in routine_names} code_gen = C99CodeGen() extra_compile_args = [ # The RHS evaluation is such a tiny part of overall integration # time, even for huge models, that compiler optimization actually # takes more time than it will ever yield back. Since Cython sets # -O2 by default we need to override it. "-O0", ] # Opt in to the newer numpy C API which is only supported in Cython 3+. import Cython if not Cython.__version__.startswith("0."): extra_compile_args.append( "-DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION") code_wrapper = CythonCodeWrapper( code_gen, filepath=self.work_path, extra_compile_args=extra_compile_args, ) # Build a module-name-safe string that identifies the model as uniquely # as possible to assist in debugging. escaped_name = re.sub( r"[^A-Za-z0-9]", "_", self.model_name.encode("unicode_escape").decode(), ) base_name = "pysb_" + escaped_name + "_kinetics" code_wrapper._filename = base_name code_wrapper._module_basename = base_name + "_wrapper" self._logger.debug("Running code generation and Cython compilation") functions = { name: code_wrapper.wrap_code(routine) for name, routine in routines.items() } # Grab specs for the Cython-compiled modules for reloading later. self.module_specs = { name: inspect.getmodule(function).__spec__ for name, function in functions.items() }
def test_c_code_reserved_words(): x, y, z = symbols('if, typedef, while') expr = (x + y) * z routine = make_routine("test", expr) code_gen = C99CodeGen() source = get_string(code_gen.dump_c, [routine]) expected = ( "#include \"file.h\"\n" "#include <math.h>\n" "double test(double if_, double typedef_, double while_) {\n" " double test_result;\n" " test_result = while_*(if_ + typedef_);\n" " return test_result;\n" "}\n" ) assert source == expected
def test_autowrap_custom_printer(): has_module('Cython') from sympy import pi from sympy.utilities.codegen import C99CodeGen from sympy.printing.ccode import C99CodePrinter from sympy.functions.elementary.exponential import exp class PiPrinter(C99CodePrinter): def _print_Pi(self, expr): return "S_PI" printer = PiPrinter() gen = C99CodeGen(printer=printer) gen.preprocessor_statements.append('#include "shortpi.h"') expr = pi * a expected = ( '#include "%s"\n' '#include <math.h>\n' '#include "shortpi.h"\n' '\n' 'double autofunc(double a) {\n' '\n' ' double autofunc_result;\n' ' autofunc_result = S_PI*a;\n' ' return autofunc_result;\n' '\n' '}\n' ) tmpdir = tempfile.mkdtemp() # write a trivial header file to use in the generated code open(os.path.join(tmpdir, 'shortpi.h'), 'w').write('#define S_PI 3.14') func = autowrap(expr, backend='cython', tempdir=tmpdir, code_gen=gen) assert func(4.2) == 3.14 * 4.2 # check that the generated code is correct for filename in os.listdir(tmpdir): if filename.startswith('wrapped_code') and filename.endswith('.c'): with open(os.path.join(tmpdir, filename)) as f: lines = f.readlines() expected = expected % filename.replace('.c', '.h') assert ''.join(lines[7:]) == expected
def test_autowrap_custom_printer(): has_module("Cython") from sympy import pi from sympy.utilities.codegen import C99CodeGen from sympy.printing.ccode import C99CodePrinter class PiPrinter(C99CodePrinter): def _print_Pi(self, expr): return "S_PI" printer = PiPrinter() gen = C99CodeGen(printer=printer) gen.preprocessor_statements.append('#include "shortpi.h"') expr = pi * a expected = ('#include "%s"\n' "#include <math.h>\n" '#include "shortpi.h"\n' "\n" "double autofunc(double a) {\n" "\n" " double autofunc_result;\n" " autofunc_result = S_PI*a;\n" " return autofunc_result;\n" "\n" "}\n") tmpdir = tempfile.mkdtemp() # write a trivial header file to use in the generated code open(os.path.join(tmpdir, "shortpi.h"), "w").write("#define S_PI 3.14") func = autowrap(expr, backend="cython", tempdir=tmpdir, code_gen=gen) assert func(4.2) == 3.14 * 4.2 # check that the generated code is correct for filename in os.listdir(tmpdir): if filename.startswith("wrapped_code") and filename.endswith(".c"): with open(os.path.join(tmpdir, filename)) as f: lines = f.readlines() expected = expected % filename.replace(".c", ".h") assert "".join(lines[7:]) == expected
def test_dummy_loops_c(): from sympy.tensor import IndexedBase, Idx i, m = symbols('i m', integer=True, cls=Dummy) x = IndexedBase('x') y = IndexedBase('y') i = Idx(i, m) expected = ( '#include "file.h"\n' '#include <math.h>\n' 'void test_dummies(int m_%(mno)i, double *x, double *y) {\n' ' for (int i_%(ino)i=0; i_%(ino)i<m_%(mno)i; i_%(ino)i++){\n' ' y[i_%(ino)i] = x[i_%(ino)i];\n' ' }\n' '}\n' ) % {'ino': i.label.dummy_index, 'mno': m.dummy_index} r = make_routine('test_dummies', Eq(y[i], x[i])) c89 = C89CodeGen() c99 = C99CodeGen() code = get_string(c99.dump_c, [r]) assert code == expected with raises(NotImplementedError): get_string(c89.dump_c, [r])
def ufuncify(args, expr, language=None, backend='numpy', tempdir=None, flags=None, verbose=False, helpers=None): """Generates a binary function that supports broadcasting on numpy arrays. Parameters ---------- args : iterable Either a Symbol or an iterable of symbols. Specifies the argument sequence for the function. expr A SymPy expression that defines the element wise operation. language : string, optional If supplied, (options: 'C' or 'F95'), specifies the language of the generated code. If ``None`` [default], the language is inferred based upon the specified backend. backend : string, optional Backend used to wrap the generated code. Either 'numpy' [default], 'cython', or 'f2py'. tempdir : string, optional Path to directory for temporary files. If this argument is supplied, the generated code and the wrapper input files are left intact in the specified path. flags : iterable, optional Additional option flags that will be passed to the backend verbose : bool, optional If True, autowrap will not mute the command line backends. This can be helpful for debugging. helpers : iterable, optional Used to define auxillary expressions needed for the main expr. If the main expression needs to call a specialized function it should be put in the ``helpers`` iterable. Autowrap will then make sure that the compiled main expression can link to the helper routine. Items should be tuples with (<funtion_name>, <sympy_expression>, <arguments>). It is mandatory to supply an argument sequence to helper routines. Note ---- The default backend ('numpy') will create actual instances of ``numpy.ufunc``. These support ndimensional broadcasting, and implicit type conversion. Use of the other backends will result in a "ufunc-like" function, which requires equal length 1-dimensional arrays for all arguments, and will not perform any type conversions. References ---------- [1] http://docs.scipy.org/doc/numpy/reference/ufuncs.html Examples ======== >>> from sympy.utilities.autowrap import ufuncify >>> from sympy.abc import x, y >>> import numpy as np >>> f = ufuncify((x, y), y + x**2) >>> type(f) <class 'numpy.ufunc'> >>> f([1, 2, 3], 2) array([ 3., 6., 11.]) >>> f(np.arange(5), 3) array([ 3., 4., 7., 12., 19.]) For the F2Py and Cython backends, inputs are required to be equal length 1-dimensional arrays. The F2Py backend will perform type conversion, but the Cython backend will error if the inputs are not of the expected type. >>> f_fortran = ufuncify((x, y), y + x**2, backend='F2Py') >>> f_fortran(1, 2) array([ 3.]) >>> f_fortran(np.array([1, 2, 3]), np.array([1.0, 2.0, 3.0])) array([ 2., 6., 12.]) >>> f_cython = ufuncify((x, y), y + x**2, backend='Cython') >>> f_cython(1, 2) # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: Argument '_x' has incorrect type (expected numpy.ndarray, got int) >>> f_cython(np.array([1.0]), np.array([2.0])) array([ 3.]) """ if isinstance(args, Symbol): args = (args, ) else: args = tuple(args) if language: _validate_backend_language(backend, language) else: language = _infer_language(backend) helpers = helpers if helpers else () flags = flags if flags else () if backend.upper() == 'NUMPY': # maxargs is set by numpy compile-time constant NPY_MAXARGS # If a future version of numpy modifies or removes this restriction # this variable should be changed or removed maxargs = 32 helps = [] for name, expr, args in helpers: helps.append(make_routine(name, expr, args)) code_wrapper = UfuncifyCodeWrapper(C99CodeGen("ufuncify"), tempdir, flags, verbose) if not isinstance(expr, (list, tuple)): expr = [expr] if len(expr) == 0: raise ValueError('Expression iterable has zero length') if (len(expr) + len(args)) > maxargs: raise ValueError( 'Cannot create ufunc with more than {0} total arguments: got {1} in, {2} out' .format(maxargs, len(args), len(expr))) routines = [ make_routine('autofunc{}'.format(idx), exprx, args) for idx, exprx in enumerate(expr) ] return code_wrapper.wrap_code(routines, helpers=helps) else: # Dummies are used for all added expressions to prevent name clashes # within the original expression. y = IndexedBase(Dummy()) m = Dummy(integer=True) i = Idx(Dummy(integer=True), m) f = implemented_function(Dummy().name, Lambda(args, expr)) # For each of the args create an indexed version. indexed_args = [IndexedBase(Dummy(str(a))) for a in args] # Order the arguments (out, args, dim) args = [y] + indexed_args + [m] args_with_indices = [a[i] for a in indexed_args] return autowrap(Eq(y[i], f(*args_with_indices)), language, backend, tempdir, args, flags, verbose, helpers)
def test_ufuncify_source_multioutput(): x, y, z = symbols('x,y,z') var_symbols = (x, y, z) expr = x + y**3 + 10 * z**2 code_wrapper = UfuncifyCodeWrapper(C99CodeGen("ufuncify")) CodeWrapper._module_counter = 0 routines = [ make_routine("func{}".format(i), expr.diff(var_symbols[i]), var_symbols) for i in range(len(var_symbols)) ] source = get_string(code_wrapper.dump_c, routines, funcname='multitest') expected = """\ #include "Python.h" #include "math.h" #include "numpy/ndarraytypes.h" #include "numpy/ufuncobject.h" #include "numpy/halffloat.h" #include "file.h" static PyMethodDef wrapper_module_0Methods[] = { {NULL, NULL, 0, NULL} }; static void multitest_ufunc(char **args, npy_intp *dimensions, npy_intp* steps, void* data) { npy_intp i; npy_intp n = dimensions[0]; char *in0 = args[0]; char *in1 = args[1]; char *in2 = args[2]; char *out0 = args[3]; char *out1 = args[4]; char *out2 = args[5]; npy_intp in0_step = steps[0]; npy_intp in1_step = steps[1]; npy_intp in2_step = steps[2]; npy_intp out0_step = steps[3]; npy_intp out1_step = steps[4]; npy_intp out2_step = steps[5]; for (i = 0; i < n; i++) { *((double *)out0) = func0(*(double *)in0, *(double *)in1, *(double *)in2); *((double *)out1) = func1(*(double *)in0, *(double *)in1, *(double *)in2); *((double *)out2) = func2(*(double *)in0, *(double *)in1, *(double *)in2); in0 += in0_step; in1 += in1_step; in2 += in2_step; out0 += out0_step; out1 += out1_step; out2 += out2_step; } } PyUFuncGenericFunction multitest_funcs[1] = {&multitest_ufunc}; static char multitest_types[6] = {NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE}; static void *multitest_data[1] = {NULL}; #if PY_VERSION_HEX >= 0x03000000 static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "wrapper_module_0", NULL, -1, wrapper_module_0Methods, NULL, NULL, NULL, NULL }; PyMODINIT_FUNC PyInit_wrapper_module_0(void) { PyObject *m, *d; PyObject *ufunc0; m = PyModule_Create(&moduledef); if (!m) { return NULL; } import_array(); import_umath(); d = PyModule_GetDict(m); ufunc0 = PyUFunc_FromFuncAndData(multitest_funcs, multitest_data, multitest_types, 1, 3, 3, PyUFunc_None, "wrapper_module_0", "Created in SymPy with Ufuncify", 0); PyDict_SetItemString(d, "multitest", ufunc0); Py_DECREF(ufunc0); return m; } #else PyMODINIT_FUNC initwrapper_module_0(void) { PyObject *m, *d; PyObject *ufunc0; m = Py_InitModule("wrapper_module_0", wrapper_module_0Methods); if (m == NULL) { return; } import_array(); import_umath(); d = PyModule_GetDict(m); ufunc0 = PyUFunc_FromFuncAndData(multitest_funcs, multitest_data, multitest_types, 1, 3, 3, PyUFunc_None, "wrapper_module_0", "Created in SymPy with Ufuncify", 0); PyDict_SetItemString(d, "multitest", ufunc0); Py_DECREF(ufunc0); } #endif""" assert source == expected
def test_ufuncify_source(): x, y, z = symbols('x,y,z') code_wrapper = UfuncifyCodeWrapper(C99CodeGen("ufuncify")) CodeWrapper._module_counter = 0 routine = make_routine("test", x + y + z) source = get_string(code_wrapper.dump_c, [routine]) expected = """\ #include "Python.h" #include "math.h" #include "numpy/ndarraytypes.h" #include "numpy/ufuncobject.h" #include "numpy/halffloat.h" #include "file.h" static PyMethodDef wrapper_module_0Methods[] = { {NULL, NULL, 0, NULL} }; static void test_ufunc(char **args, npy_intp *dimensions, npy_intp* steps, void* data) { npy_intp i; npy_intp n = dimensions[0]; char *in0 = args[0]; char *in1 = args[1]; char *in2 = args[2]; char *out0 = args[3]; npy_intp in0_step = steps[0]; npy_intp in1_step = steps[1]; npy_intp in2_step = steps[2]; npy_intp out0_step = steps[3]; for (i = 0; i < n; i++) { *((double *)out0) = test(*(double *)in0, *(double *)in1, *(double *)in2); in0 += in0_step; in1 += in1_step; in2 += in2_step; out0 += out0_step; } } PyUFuncGenericFunction test_funcs[1] = {&test_ufunc}; static char test_types[4] = {NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE}; static void *test_data[1] = {NULL}; #if PY_VERSION_HEX >= 0x03000000 static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "wrapper_module_0", NULL, -1, wrapper_module_0Methods, NULL, NULL, NULL, NULL }; PyMODINIT_FUNC PyInit_wrapper_module_0(void) { PyObject *m, *d; PyObject *ufunc0; m = PyModule_Create(&moduledef); if (!m) { return NULL; } import_array(); import_umath(); d = PyModule_GetDict(m); ufunc0 = PyUFunc_FromFuncAndData(test_funcs, test_data, test_types, 1, 3, 1, PyUFunc_None, "wrapper_module_0", "Created in SymPy with Ufuncify", 0); PyDict_SetItemString(d, "test", ufunc0); Py_DECREF(ufunc0); return m; } #else PyMODINIT_FUNC initwrapper_module_0(void) { PyObject *m, *d; PyObject *ufunc0; m = Py_InitModule("wrapper_module_0", wrapper_module_0Methods); if (m == NULL) { return; } import_array(); import_umath(); d = PyModule_GetDict(m); ufunc0 = PyUFunc_FromFuncAndData(test_funcs, test_data, test_types, 1, 3, 1, PyUFunc_None, "wrapper_module_0", "Created in SymPy with Ufuncify", 0); PyDict_SetItemString(d, "test", ufunc0); Py_DECREF(ufunc0); } #endif""" assert source == expected
def test_empty_c_header(): code_gen = C99CodeGen() source = get_string(code_gen.dump_h, []) assert source == "#ifndef PROJECT__FILE__H\n#define PROJECT__FILE__H\n#endif\n"
def ufuncify( args, expr, verbose=False, helpers=None, name=None ): """ Convenient wrapper to ufuncify an expression. Required arguments: args: list of the input arguments of the function to be built, as sympy symbols. expr: The expression to be evaluated. This may be either a single sympy expression, in which case a single-output ufunc will be built. You may also supply a list of sympy expressions, or any other iterator yielding sympy expressions. In this case the ufunc will have multiple outputs, in the order given in the list. The expressions can be strs, which will be sympified. Optional keyword arguments. verbose=False helpers=None See documentation for sympy.utilities.autowrap.ufuncify. """ args = list(args) # make sure it's a list if name is None: name = str(uuid.uuid4()) name = 'id_' + re.sub(r'\W', '_', name) if isinstance(expr, sy.Basic) or isinstance(expr, str): exprs = [ expr ] else: exprs = list(expr) for n,e in enumerate(exprs): if type(e) is str: exprs[n] = sy.sympify(e) for e in exprs: if not e.free_symbols.issubset(args): raise KSFDEXception( 'unknown symbols {syms} in expression {expr}'.format( syms = e.free_symbols.difference(set(args)), expr = e ) ) outs = [ sy.Symbol('_out{n}'.format(n=n)) for n in range(len(exprs)) ] eqs = [ sy.Eq(out, e) for out,e in zip(outs, exprs) ] alist = args + outs routine = make_routine( name=name, expr=eqs, argument_sequence=alist, language='C99' ) wrapper = UfuncifyCodeWrapperMultiple( comm=MPI.COMM_WORLD, generator=C99CodeGen('ufuncify'), flags=[], verbose=verbose ) ufunc = wrapper.wrap_code([routine]) return ufunc