Implemented tests of the generated strings, and a script in test_external/ to test resulting binaries.
The existing CCodeGen and related tests was used as templates for the new functionality. --- sympy/test_external/test_codegen_f95_ifort.py | 240 ++++++++++++++++ sympy/utilities/codegen.py | 164 +++++++++++- sympy/utilities/tests/test_codegen.py | 370 ++++++++++++++++++++++++- 3 files changed, 770 insertions(+), 4 deletions(-) create mode 100644 sympy/test_external/test_codegen_f95_ifort.py diff --git a/sympy/test_external/test_codegen_f95_ifort.py b/sympy/test_external/test_codegen_f95_ifort.py new file mode 100644 index 0000000..0030cc0 --- /dev/null +++ b/sympy/test_external/test_codegen_f95_ifort.py @@ -0,0 +1,240 @@ +# This tests the compilation and execution of the source code generated with +# utilities.codegen. The compilation takes place in a temporary directory that +# is removed after the test. By default the test directory is always removed, +# but this behavior can be changed by setting the environment variable +# SYMPY_TEST_CLEAN_TEMP to: +# export SYMPY_TEST_CLEAN_TEMP=always : the default behavior. +# export SYMPY_TEST_CLEAN_TEMP=success : only remove the directories of working tests. +# export SYMPY_TEST_CLEAN_TEMP=never : never remove the directories with the test code. +# When a directory is not removed, the necessary information is printed on +# screen to find the files that belong to the (failed) tests. If a test does +# not fail, py.test captures all the output and you will not see the directories +# corresponding to the successful tests. Use the --nocapture option to see all +# the output. + +# All tests below have a counterpart in utilities/test/test_codegen.py. In the +# latter file, the resulting code is compared with predefined strings, without +# compilation or execution. + +# All the generated Fortran code should conform with the Fortran 95 standard, +# which facilitates the incorporation in various projects. The tests below +# assume that the binary ifort is somewhere in the path and that it can compile +# standard Fortran 95 code. + + +from sympy import symbols +from sympy.utilities.codegen import codegen, FCodeGen, Routine, InputArgument, \ + Result +import sys, os, tempfile + + +# templates for the main program that will test the generated code. + +main_template = """ +program main + include "codegen.h" + integer :: result; + result = 0 + + %s + + call exit(result) +end program +""" + +numerical_test_template = """ + if (abs(%(call)s)>%(threshold)s) then + write(6,"('Numerical validation failed:')") + write(6,"('%(call)s=',e15.5,'threshold=',e15.5)") %(call)s, %(threshold)s + result = -1; + end if +""" + + +def try_run(commands): + """Run a series of commands and only return True if all ran fine.""" + for command in commands: + retcode = os.system(command) + if retcode != 0: + return False + return True + + +def run_f95_test(label, routines, numerical_tests, friendly=True): + """A driver for the codegen tests. + + This driver assumes that a compiler ifort is present in the PATH and that + ifort is (at least) a Fortran 90 compiler. The generated code is written in + a temporary directory, together with a main program that validates the + generated code. The test passes when the compilation and the validation + run correctly. + """ + # Do all the magic to compile, run and validate the test code + # 1) prepare the temporary working directory, switch to that dir + work = tempfile.mkdtemp("_sympy_f95_test", "%s_" % label) + oldwork = os.getcwd() + os.chdir(work) + # 2) write the generated code + if friendly: + # interpret the routines as a name_expr list and call the friendly + # function codegen + codegen(routines, 'F95', "codegen", to_files=True) + else: + code_gen = FCodeGen() + code_gen.write(routines, "codegen", to_files=True) + # 3) write a simple main program that links to the generated code, and that + # includes the numerical tests + test_strings = [] + for fn_name, args, expected, threshold in numerical_tests: + call_string = "%s(%s)-(%s)" % (fn_name, ",".join(str(arg) for arg in args), expected) + call_string = fortranize_double_constants(call_string) + threshold = fortranize_double_constants(str(threshold)) + test_strings.append(numerical_test_template % { + "call": call_string, + "threshold": threshold, + }) + f = file("main.f90", "w") + f.write(main_template % "".join(test_strings)) + f.close() + # 4) Compile and link + compiled = try_run([ + "ifort -c codegen.f90 -o codegen.o", + "ifort -c main.f90 -o main.o", + "ifort main.o codegen.o -o test.exe" + ]) + # 5) Run if compiled + if compiled: + executed = try_run(["./test.exe"]) + else: + executed = False + # 6) Clean up stuff + clean = os.getenv('SYMPY_TEST_CLEAN_TEMP', 'always').lower() + if clean not in ('always', 'success', 'never'): + raise ValueError("SYMPY_TEST_CLEAN_TEMP must be one of the following: 'always', 'success' or 'never'.") + if clean == 'always' or (clean == 'success' and compiled and executed): + def safe_remove(filename): + if os.path.isfile(filename): + os.remove(filename) + safe_remove("codegen.f90") + safe_remove("codegen.h") + safe_remove("codegen.o") + safe_remove("main.f90") + safe_remove("main.o") + safe_remove("test.exe") + os.chdir(oldwork) + os.rmdir(work) + else: + print >> sys.stderr, "TEST NOT REMOVED: %s" % work + os.chdir(oldwork) + # 7) Do the assertions in the end + assert compiled + assert executed + +def fortranize_double_constants(code_string): + """ + Replaces every literal float with literal doubles + """ + import re + pattern_exp = re.compile('\d+(\.)?\d*[eE]-?\d+') + pattern_float = re.compile('\d+\.\d*(?!\d*d)') + + def subs_exp(matchobj): + return re.sub('[eE]','d',matchobj.group(0)) + + def subs_float(matchobj): + return "%sd0" % matchobj.group(0) + + code_string = pattern_exp.sub(subs_exp, code_string) + code_string = pattern_float.sub(subs_float, code_string) + + return code_string + + + +def is_feasible(): + # This test should always work, otherwise the f95 compiler is not present. + x,y,z = symbols('xyz') + expr = (x+y)*z + routine = Routine("test", [InputArgument(symbol) for symbol in x,y,z], [Result(expr)]) + numerical_tests = [ + ("test", (1.0, 6.0, 3.0), 21.0, 1e-15), + ("test", (-1.0, 2.0, -2.5), -2.5, 1e-15), + ] + run_f95_test("is_feasible", [routine], numerical_tests, friendly=False) + + +try: + is_feasible() +except AssertionError: + disabled = True + + +def test_basic(): + is_feasible() + + +def test_basic_codegen(): + x,y,z = symbols('xyz') + numerical_tests = [ + ("test", (1.0, 6.0, 3.0), 21.0, 1e-15), + ("test", (-1.0, 2.0, -2.5), -2.5, 1e-15), + ] + run_f95_test("basic_codegen", [("test", (x+y)*z)], numerical_tests) + +def test_intrinsic_math1_codegen(): + # not included: log10 + from sympy import acos, asin, atan, ceiling, cos, cosh, floor, log, ln, \ + sin, sinh, sqrt, tan, tanh, N + x = symbols('x') + name_expr = [ + ("test_fabs", abs(x)), + ("test_acos", acos(x)), + ("test_asin", asin(x)), + ("test_atan", atan(x)), + # ("test_ceil", ceiling(x)), + ("test_cos", cos(x)), + ("test_cosh", cosh(x)), + # ("test_floor", floor(x)), + ("test_log", log(x)), + ("test_ln", ln(x)), + ("test_sin", sin(x)), + ("test_sinh", sinh(x)), + ("test_sqrt", sqrt(x)), + ("test_tan", tan(x)), + ("test_tanh", tanh(x)), + ] + numerical_tests = [] + for name, expr in name_expr: + for xval in 0.2, 0.5, 0.8: + expected = N(expr.subs(x, xval)) + numerical_tests.append((name, (xval,), expected, 1e-14)) + run_f95_test("intrinsic_math1", name_expr, numerical_tests) + +def test_instrinsic_math2_codegen(): + # not included: frexp, ldexp, modf, fmod + from sympy import atan2, N + x, y = symbols('xy') + name_expr = [ + ("test_atan2", atan2(x,y)), + ("test_pow", x**y), + ] + numerical_tests = [] + for name, expr in name_expr: + for xval,yval in (0.2, 1.3), (0.5, -0.2), (0.8, 0.8): + expected = N(expr.subs(x, xval).subs(y, yval)) + numerical_tests.append((name, (xval,yval), expected, 1e-14)) + run_f95_test("intrinsic_math2", name_expr, numerical_tests) + +def test_complicated_codegen(): + from sympy import sin, cos, tan, N + x,y,z = symbols('xyz') + name_expr = [ + ("test1", ((sin(x)+cos(y)+tan(z))**7).expand()), + ("test2", cos(cos(cos(cos(cos(cos(cos(cos(x+y+z))))))))), + ] + numerical_tests = [] + for name, expr in name_expr: + for xval,yval,zval in (0.2, 1.3, -0.3), (0.5, -0.2, 0.0), (0.8, 2.1, 0.8): + expected = N(expr.subs(x, xval).subs(y, yval).subs(z, zval)) + numerical_tests.append((name, (xval,yval,zval), expected, 1e-12)) + run_f95_test("complicated_codegen", name_expr, numerical_tests) diff --git a/sympy/utilities/codegen.py b/sympy/utilities/codegen.py index 23b0e0b..8e701c4 100644 --- a/sympy/utilities/codegen.py +++ b/sympy/utilities/codegen.py @@ -76,6 +76,7 @@ from sympy.core.basic import Basic from sympy.utilities.iterables import postorder_traversal from sympy.printing.ccode import ccode +from sympy.printing.fcode import fcode from StringIO import StringIO import sympy, os @@ -86,7 +87,7 @@ "Routine", "DataType", "default_datatypes", "get_default_datatype", "Argument", "InputArgument", "Result", # routines -> code - "CodeGen", "CCodeGen", + "CodeGen", "CCodeGen", "FCodeGen" # friendly functions "codegen", ] @@ -263,8 +264,9 @@ class CodeGenError(Exception): class CCodeGen(CodeGen): + def _dump_header(self, f): - """Writes a common header for the .c and the .h file.""" + """Writes a common header for the generated files.""" print >> f, "/****************************************************************************** " tmp = header_comment % {"version": sympy.__version__, "project": self.project} for line in tmp.splitlines(): @@ -372,6 +374,162 @@ def dump_h(self, routines, f, prefix, header=True, empty=True): # functions it has to call. dump_fns = [dump_c, dump_h] +class FCodeGen(CodeGen): + """ + Generator for Fortran 95 code + """ + + def _dump_header(self, f): + """Writes a common header for the generated files.""" + print >> f, "!****************************************************************************** " + tmp = header_comment % {"version": sympy.__version__, "project": self.project} + for line in tmp.splitlines(): + print >> f, "!*%s* " % line.center(76) + print >> f, "!******************************************************************************/" + + def _get_routine_opening(self, routine): + """ + Returns the opening statements of the fortran routine + """ + code_list = [] + if len(routine.results) > 1: + raise CodeGenError("Fortran only supports a single or no return value.") + elif len(routine.results) == 1: + result = routine.results[0] + code_list.append(result.datatype.fname) + code_list.append("function") + else: + code_list.append("subroutine") + + # name of the routine + arguments + code_list.append("%s(%s)\n" % (routine.name, + ", ".join("%s" % arg.name for arg in routine.arguments) + )) + + code_list.append('implicit none\n') + + # argument type declarations + code_list.append("\n ".join( + ["%s :: %s" % (arg.datatype.fname, arg.name) + for arg in routine.arguments]) + + '\n') + + return code_list + + def _get_routine_ending(self, routine): + """ + Returns the closing statements of the fortran routine + """ + if len(routine.results) == 1: + return ["end function\n"] + else: + return ["end subroutine\n"] + + def get_interface(self, routine): + """Returns a string for the function interface for the given routine and + a single result object, which can be None. + + If the routine has multiple result objects, a CodeGenError is + raised. + + See: http://en.wikipedia.org/wiki/Function_prototype + + """ + prototype = [ "interface\n" ] + prototype.extend(self._get_routine_opening(routine)) + prototype.extend(self._get_routine_ending(routine)) + prototype.append("end interface\n") + + return " ".join(prototype) + + def _get_result(self, routine): + """Returns a single result object, which can be None. + + If the routine has multiple result objects, an CodeGenError is + raised. + + See: http://en.wikipedia.org/wiki/Function_prototype + """ + + if len(routine.results) > 1: + raise CodeGenError("Fortran only supports a single or no return value.") + elif len(routine.results) == 1: + result = routine.results[0] + else: + result = None + + return result + + def dump_f95(self, routines, f, prefix, header=True, empty=True): + """Write the F95 code file. + + This file contains all the definitions of the routines in f95 code and + refers to the header file. + + Arguments: + routines -- a list of Routine instances + f -- a file-like object to write the file to + prefix -- the filename prefix, used to refer to the proper header + file. Only the basename of the prefix is used. + + Optional arguments: + header -- When True, a header comment is included on top of each + source file. [DEFAULT=True] + empty -- When True, empty lines are included to structure the + source files. [DEFAULT=True] + """ + if header: + self._dump_header(f) + if empty: print >> f + + for routine in routines: + code_lines = self._get_routine_opening(routine) + + result = self._get_result(routine) + if result is not None: + code_lines.append("%s = %s\n" %(routine.name, + fcode(result.expr, source_format='free'))) + print >> f, ' '.join(code_lines), + + if empty: print >> f + code_lines = self._get_routine_ending(routine) + print >> f, ' '.join(code_lines), + + if empty: print >> f + if empty: print >> f + dump_f95.extension = "f90" + + def dump_h(self, routines, f, prefix, header=True, empty=True): + """Writes the interface header file. + + This file contains all the function declarations. + + Arguments: + routines -- a list of Routine instances + f -- a file-like object to write the file to + prefix -- the filename prefix, used to construct the include + guards. + + Optional arguments: + header -- When True, a header comment is included on top of each + source file. [DEFAULT=True] + empty -- When True, empty lines are included to structure the + source files. [DEFAULT=True] + """ + if header: + self._dump_header(f) + if empty: print >> f + # declaration of the function prototypes + for routine in routines: + prototype = self.get_interface(routine) + print >> f, prototype, + if empty: print >> f + dump_h.extension = "h" + + # This list of dump functions is used by CodeGen.write to know which dump + # functions it has to call. + dump_fns = [dump_f95, dump_h] + # # Friendly functions @@ -427,7 +585,7 @@ def codegen(name_expr, language, prefix, project="project", to_files=False, head """ # Initialize the code generator. - CodeGenClass = {"C": CCodeGen}.get(language.upper()) + CodeGenClass = {"C": CCodeGen, "F95": FCodeGen}.get(language.upper()) if CodeGenClass is None: raise ValueError("Language '%s' is not supported." % language) code_gen = CodeGenClass(project) diff --git a/sympy/utilities/tests/test_codegen.py b/sympy/utilities/tests/test_codegen.py index 771d023..0a102a3 100644 --- a/sympy/utilities/tests/test_codegen.py +++ b/sympy/utilities/tests/test_codegen.py @@ -1,6 +1,6 @@ from sympy import symbols, raises from sympy.utilities.codegen import CCodeGen, Routine, InputArgument, Result, \ - codegen, CodeGenError + codegen, CodeGenError, FCodeGen from StringIO import StringIO def get_string(dump_fn, routines, prefix="file"): @@ -229,3 +229,371 @@ def test_complicated_codegen(): 'double test2(double x, double y, double z);\n' '#endif\n' ) + +def test_empty_f_code(): + code_gen = FCodeGen() + source = get_string(code_gen.dump_f95, []) + assert source == "" + +def test_empty_f_header(): + code_gen = FCodeGen() + source = get_string(code_gen.dump_h, []) + assert source == "" + +def test_simple_f_code(): + x,y,z = symbols('xyz') + expr = (x+y)*z + routine = Routine("test", [InputArgument(symbol) for symbol in x,y,z], [Result(expr)]) + code_gen = FCodeGen() + source = get_string(code_gen.dump_f95, [routine]) + expected = ( + "REAL*8 function test(x, y, z)\n" + " implicit none\n" + " REAL*8 :: x\n" + " REAL*8 :: y\n" + " REAL*8 :: z\n" + " test = z*(x + y)\n" + "end function\n" + ) + assert source == expected + +def test_simple_f_header(): + x,y,z = symbols('xyz') + expr = (x+y)*z + routine = Routine("test", [InputArgument(symbol) for symbol in x,y,z], [Result(expr)]) + code_gen = FCodeGen() + source = get_string(code_gen.dump_h, [routine]) + expected = ( + "interface\n" + " REAL*8 function test(x, y, z)\n" + " implicit none\n" + " REAL*8 :: x\n" + " REAL*8 :: y\n" + " REAL*8 :: z\n" + " end function\n" + " end interface\n" + ) + assert source == expected + +def test_simple_f_codegen(): + x,y,z = symbols('xyz') + expr = (x+y)*z + result = codegen(("test", (x+y)*z), "F95", "file", header=False, empty=False) + expected = [ + ("file.f90", + "REAL*8 function test(x, y, z)\n" + " implicit none\n" + " REAL*8 :: x\n" + " REAL*8 :: y\n" + " REAL*8 :: z\n" + " test = z*(x + y)\n" + "end function\n"), + ("file.h", + "interface\n" + " REAL*8 function test(x, y, z)\n" + " implicit none\n" + " REAL*8 :: x\n" + " REAL*8 :: y\n" + " REAL*8 :: z\n" + " end function\n" + " end interface\n") + ] + assert result == expected + +def test_multiple_results_f(): + x,y,z = symbols('xyz') + expr1 = (x+y)*z + expr2 = (x-y)*z + routine = Routine( + "test", + [InputArgument(symbol) for symbol in x,y,z], + [Result(expr1),Result(expr2)] + ) + code_gen = FCodeGen() + raises(CodeGenError, 'get_string(code_gen.dump_h, [routine])') + +def test_no_results_f(): + x = symbols('x') + raises(ValueError, 'Routine("test", [InputArgument(x)], [])') + +def test_intrinsic_math_codegen(): + # not included: log10 + from sympy import acos, asin, atan, ceiling, cos, cosh, floor, log, ln, \ + sin, sinh, sqrt, tan, tanh, N + x = symbols('x') + name_expr = [ + ("test_abs", abs(x)), + ("test_acos", acos(x)), + ("test_asin", asin(x)), + ("test_atan", atan(x)), + # ("test_ceil", ceiling(x)), + ("test_cos", cos(x)), + ("test_cosh", cosh(x)), + # ("test_floor", floor(x)), + ("test_log", log(x)), + ("test_ln", ln(x)), + ("test_sin", sin(x)), + ("test_sinh", sinh(x)), + ("test_sqrt", sqrt(x)), + ("test_tan", tan(x)), + ("test_tanh", tanh(x)), + ] + result = codegen(name_expr, "F95", "file", header=False, empty=False) + assert result[0][0] == "file.f90" + expected = ( + 'REAL*8 function test_abs(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' test_abs = abs(x)\n' + 'end function\n' + 'REAL*8 function test_acos(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' test_acos = acos(x)\n' + 'end function\n' + 'REAL*8 function test_asin(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' test_asin = asin(x)\n' + 'end function\n' + 'REAL*8 function test_atan(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' test_atan = atan(x)\n' + 'end function\n' + 'REAL*8 function test_cos(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' test_cos = cos(x)\n' + 'end function\n' + 'REAL*8 function test_cosh(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' test_cosh = cosh(x)\n' + 'end function\n' + 'REAL*8 function test_log(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' test_log = log(x)\n' + 'end function\n' + 'REAL*8 function test_ln(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' test_ln = log(x)\n' + 'end function\n' + 'REAL*8 function test_sin(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' test_sin = sin(x)\n' + 'end function\n' + 'REAL*8 function test_sinh(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' test_sinh = sinh(x)\n' + 'end function\n' + 'REAL*8 function test_sqrt(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' test_sqrt = sqrt(x)\n' + 'end function\n' + 'REAL*8 function test_tan(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' test_tan = tan(x)\n' + 'end function\n' + 'REAL*8 function test_tanh(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' test_tanh = tanh(x)\n' + 'end function\n' + ) + assert result[0][1] == expected + + assert result[1][0] == "file.h" + expected = ( + 'interface\n' + ' REAL*8 function test_abs(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' end function\n' + ' end interface\n' + 'interface\n' + ' REAL*8 function test_acos(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' end function\n' + ' end interface\n' + 'interface\n' + ' REAL*8 function test_asin(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' end function\n' + ' end interface\n' + 'interface\n' + ' REAL*8 function test_atan(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' end function\n' + ' end interface\n' + 'interface\n' + ' REAL*8 function test_cos(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' end function\n' + ' end interface\n' + 'interface\n' + ' REAL*8 function test_cosh(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' end function\n' + ' end interface\n' + 'interface\n' + ' REAL*8 function test_log(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' end function\n' + ' end interface\n' + 'interface\n' + ' REAL*8 function test_ln(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' end function\n' + ' end interface\n' + 'interface\n' + ' REAL*8 function test_sin(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' end function\n' + ' end interface\n' + 'interface\n' + ' REAL*8 function test_sinh(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' end function\n' + ' end interface\n' + 'interface\n' + ' REAL*8 function test_sqrt(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' end function\n' + ' end interface\n' + 'interface\n' + ' REAL*8 function test_tan(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' end function\n' + ' end interface\n' + 'interface\n' + ' REAL*8 function test_tanh(x)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' end function\n' + ' end interface\n' + ) + assert result[1][1] == expected + +def test_intrinsic_math2_codegen(): + # not included: frexp, ldexp, modf, fmod + from sympy import atan2, N + x, y = symbols('xy') + name_expr = [ + ("test_atan2", atan2(x,y)), + ("test_pow", x**y), + ] + result = codegen(name_expr, "F95", "file", header=False, empty=False) + assert result[0][0] == "file.f90" + expected = ( + 'REAL*8 function test_atan2(x, y)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' REAL*8 :: y\n' + ' test_atan2 = atan2(x, y)\n' + 'end function\n' + 'REAL*8 function test_pow(x, y)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' REAL*8 :: y\n' + ' test_pow = x**y\n' + 'end function\n' + ) + assert result[0][1] == expected + + assert result[1][0] == "file.h" + expected = ( + 'interface\n' + ' REAL*8 function test_atan2(x, y)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' REAL*8 :: y\n' + ' end function\n' + ' end interface\n' + 'interface\n' + ' REAL*8 function test_pow(x, y)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' REAL*8 :: y\n' + ' end function\n' + ' end interface\n' + ) + assert result[1][1] == expected + +def test_complicated_codegen_f95(): + from sympy import sin, cos, tan, N + x,y,z = symbols('xyz') + name_expr = [ + ("test1", ((sin(x)+cos(y)+tan(z))**7).expand()), + ("test2", cos(cos(cos(cos(cos(cos(cos(cos(x+y+z))))))))), + ] + result = codegen(name_expr, "F95", "file", header=False, empty=False) + assert result[0][0] == "file.f90" + expected = ( + 'REAL*8 function test1(x, y, z)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' REAL*8 :: y\n' + ' REAL*8 :: z\n' + ' test1 = 7*cos(y)**6*sin(x) + 7*cos(y)**6*tan(z) + 7*sin(x)**6*cos(y) + 7*sin(x) &\n' + ' **6*tan(z) + 7*tan(z)**6*cos(y) + 7*tan(z)**6*sin(x) + 42*cos(y) &\n' + ' **5*sin(x)*tan(z) + 42*sin(x)**5*cos(y)*tan(z) + 42*tan(z)**5*cos &\n' + ' (y)*sin(x) + 105*cos(y)**2*sin(x)**4*tan(z) + 105*cos(y)**2*tan(z &\n' + ' )**4*sin(x) + 105*cos(y)**4*sin(x)**2*tan(z) + 105*cos(y)**4*tan( &\n' + ' z)**2*sin(x) + 105*sin(x)**2*tan(z)**4*cos(y) + 105*sin(x)**4*tan &\n' + ' (z)**2*cos(y) + 140*cos(y)**3*sin(x)**3*tan(z) + 140*cos(y)**3* &\n' + ' tan(z)**3*sin(x) + 140*sin(x)**3*tan(z)**3*cos(y) + 21*cos(y)**5* &\n' + ' sin(x)**2 + 21*cos(y)**5*tan(z)**2 + 21*sin(x)**5*tan(z)**2 + &\n' + ' 210*cos(y)**2*sin(x)**3*tan(z)**2 + 210*cos(y)**3*sin(x)**2*tan(z &\n' + ' )**2 + 35*cos(y)**4*sin(x)**3 + 35*cos(y)**4*tan(z)**3 + 35*sin(x &\n' + ' )**4*tan(z)**3 + 210*cos(y)**2*sin(x)**2*tan(z)**3 + 35*cos(y) &\n' + ' **3*sin(x)**4 + 35*cos(y)**3*tan(z)**4 + 35*sin(x)**3*tan(z)**4 + &\n' + ' 21*cos(y)**2*sin(x)**5 + 21*cos(y)**2*tan(z)**5 + 21*sin(x)**2* &\n' + ' tan(z)**5 + cos(y)**7 + sin(x)**7 + tan(z)**7\n' + 'end function\n' + 'REAL*8 function test2(x, y, z)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' REAL*8 :: y\n' + ' REAL*8 :: z\n' + ' test2 = cos(cos(cos(cos(cos(cos(cos(cos(x + y + z))))))))\n' + 'end function\n' + ) + assert result[0][1] == expected + assert result[1][0] == "file.h" + expected = ( + 'interface\n' + ' REAL*8 function test1(x, y, z)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' REAL*8 :: y\n' + ' REAL*8 :: z\n' + ' end function\n' + ' end interface\n' + 'interface\n' + ' REAL*8 function test2(x, y, z)\n' + ' implicit none\n' + ' REAL*8 :: x\n' + ' REAL*8 :: y\n' + ' REAL*8 :: z\n' + ' end function\n' + ' end interface\n' + ) + assert result[1][1] == expected -- 1.6.5 -- You received this message because you are subscribed to the Google Groups "sympy-patches" group. To post to this group, send email to sympy-patc...@googlegroups.com. To unsubscribe from this group, send email to sympy-patches+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/sympy-patches?hl=en.