From d8be1eaedc7561ea4e84edebad6d7c74878246a4 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner <inform@tiker.net> Date: Wed, 10 Jun 2015 20:57:44 -0500 Subject: [PATCH] Revamp how Fortran source processing is done, expose passes in Python transforms --- bin/loopy | 29 ++- doc/reference.rst | 6 + examples/fortran/foo.floopy | 15 +- examples/fortran/matmul.floopy | 6 +- examples/fortran/outerprod.py | 8 - examples/fortran/sparse.floopy | 6 +- examples/fortran/tagging.floopy | 7 +- examples/fortran/volumeKernel.floopy | 6 +- examples/fortran/volumeKernelSimple.floopy | 6 +- examples/python/hello-loopy-lp.py | 11 ++ loopy/__init__.py | 3 + loopy/frontend/fortran/__init__.py | 211 +++++++++++++++------ loopy/frontend/fortran/translator.py | 51 +---- test/test_fortran.py | 48 ++--- 14 files changed, 240 insertions(+), 173 deletions(-) delete mode 100644 examples/fortran/outerprod.py create mode 100644 examples/python/hello-loopy-lp.py diff --git a/bin/loopy b/bin/loopy index 0f7b9a607..e607a6793 100644 --- a/bin/loopy +++ b/bin/loopy @@ -71,23 +71,23 @@ def main(): with open(args.infile, "r") as infile_fd: infile_content = infile_fd.read() - # {{{ path wrangling + if args.lang == "loopy": + # {{{ path wrangling - from os.path import dirname, abspath - from os import getcwd + from os.path import dirname, abspath + from os import getcwd - infile_dirname = dirname(args.infile) - if infile_dirname: - infile_dirname = abspath(infile_dirname) - else: - infile_dirname = getcwd() + infile_dirname = dirname(args.infile) + if infile_dirname: + infile_dirname = abspath(infile_dirname) + else: + infile_dirname = getcwd() - import sys - sys.path.append(infile_dirname) + import sys + sys.path.append(infile_dirname) - # }}} + # }}} - if args.lang == "loopy": data_dic = {} data_dic["lp"] = lp data_dic["np"] = np @@ -131,9 +131,8 @@ def main(): defines_to_python_code(defines_fd.read()) + pre_transform_code) - from loopy.frontend.fortran import f2loopy - kernels = f2loopy(infile_content, pre_transform_code=pre_transform_code, - use_c_preprocessor=(args.lang == "fpp")) + kernels = lp.parse_transformed_fortran( + infile_content, pre_transform_code=pre_transform_code) if args.name is not None: kernels = [kernel for kernel in kernels diff --git a/doc/reference.rst b/doc/reference.rst index af39de655..6ed83d430 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -334,10 +334,16 @@ function, which is responsible for creating kernels: .. autofunction:: make_kernel +.. autofunction:: parse_fortran + +.. autofunction:: parse_transformed_fortran + .. autofunction:: make_copy_kernel .. autofunction:: fuse_kernels +.. autofunction:: c_preprocess + Transforming Kernels -------------------- diff --git a/examples/fortran/foo.floopy b/examples/fortran/foo.floopy index 9b0575607..6b8741e11 100644 --- a/examples/fortran/foo.floopy +++ b/examples/fortran/foo.floopy @@ -1,8 +1,3 @@ -!$loopy begin define -! define("factor 4.0") -! define("real_type real*8") -!$loopy end define - subroutine fill(out, a, n) implicit none @@ -17,13 +12,19 @@ subroutine fill(out, a, n) end do end -!$loopy begin transform +!$loopy begin ! +! SOURCE = lp.c_preprocess(SOURCE, [ +! "factor 4.0", +! "real_type real*8", +! ]) +! fill, = lp.parse_fortran(SOURCE, FILENAME) ! fill = lp.split_iname(fill, "i", 128, ! outer_tag="g.0", inner_tag="l.0") ! fill = lp.split_iname(fill, "i_1", 128, ! outer_tag="g.0", inner_tag="l.0") +! RESULT = [fill] ! -!$loopy end transform +!$loopy end ! vim:filetype=floopy diff --git a/examples/fortran/matmul.floopy b/examples/fortran/matmul.floopy index ea85b0c6b..3352449d7 100644 --- a/examples/fortran/matmul.floopy +++ b/examples/fortran/matmul.floopy @@ -12,7 +12,8 @@ subroutine dgemm(m,n,l,alpha,a,b,c) end do end subroutine -!$loopy begin transform +!$loopy begin +! dgemm, = lp.parse_fortran(SOURCE, FILENAME) ! dgemm = lp.split_iname(dgemm, "i", 16, ! outer_tag="g.0", inner_tag="l.1") ! dgemm = lp.split_iname(dgemm, "j", 8, @@ -23,4 +24,5 @@ end subroutine ! dgemm = lp.extract_subst(dgemm, "b_acc", "b[i1,i2]", parameters="i1, i2") ! dgemm = lp.precompute(dgemm, "a_acc", "k_inner,i_inner") ! dgemm = lp.precompute(dgemm, "b_acc", "j_inner,k_inner") -!$loopy end transform +! RESULT = [dgemm] +!$loopy end diff --git a/examples/fortran/outerprod.py b/examples/fortran/outerprod.py deleted file mode 100644 index 4122c8437..000000000 --- a/examples/fortran/outerprod.py +++ /dev/null @@ -1,8 +0,0 @@ -lp_knl = lp.make_kernel( - "{[i,j]: 0<=i,j<n}", - "c[i,j] = a[i]*b[j]") - -lp_knl = lp.add_dtypes(lp_knl, {"a": np.float64, "b": np.float64}) -lp_knl = lp.split_iname(lp_knl, "i", 16, outer_tag="g.0", inner_tag="l.0") -lp_knl = lp.split_iname(lp_knl, "j", 16, outer_tag="g.1", inner_tag="l.1") - diff --git a/examples/fortran/sparse.floopy b/examples/fortran/sparse.floopy index f3cb50290..18542e6b0 100644 --- a/examples/fortran/sparse.floopy +++ b/examples/fortran/sparse.floopy @@ -22,10 +22,12 @@ subroutine sparse(rowstarts, colindices, values, m, n, nvals, x, y) end do end -!$loopy begin transform +!$loopy begin +! sparse, = lp.parse_fortran(SOURCE, FILENAME) ! sparse = lp.split_iname(sparse, "i", 128) ! sparse = lp.tag_inames(sparse, {"i_outer": "g.0"}) ! sparse = lp.tag_inames(sparse, {"i_inner": "l.0"}) ! sparse = lp.split_iname(sparse, "j", 4) ! sparse = lp.tag_inames(sparse, {"j_inner": "unr"}) -!$loopy end transform +! RESULT = [sparse] +!$loopy end diff --git a/examples/fortran/tagging.floopy b/examples/fortran/tagging.floopy index e7deb113a..40b487528 100644 --- a/examples/fortran/tagging.floopy +++ b/examples/fortran/tagging.floopy @@ -14,11 +14,12 @@ subroutine fill(out, a, n) end do end -!$loopy begin transform -! +!$loopy begin +! fill, = lp.parse_fortran(SOURCE, FILENAME) ! fill = lp.split_iname(fill, "i", 128, ! outer_tag="g.0", inner_tag="l.0") ! fill = lp.split_iname(fill, "i_1", 128, ! outer_tag="g.0", inner_tag="l.0") -!$loopy end transform +! RESULT = [fill] +!$loopy end ! vim:filetype=floopy diff --git a/examples/fortran/volumeKernel.floopy b/examples/fortran/volumeKernel.floopy index 953432d22..c5784b634 100644 --- a/examples/fortran/volumeKernel.floopy +++ b/examples/fortran/volumeKernel.floopy @@ -65,8 +65,9 @@ subroutine volumeKernel(elements, Nfields, Ngeo, Ndim, Dop, geo, Q, rhsQ ) end subroutine volumeKernel -!$loopy begin transform +!$loopy begin ! +! volumeKernel, = lp.parse_fortran(SOURCE, FILENAME) ! volumeKernel = lp.split_iname(volumeKernel, ! "e", 32, outer_tag="g.1", inner_tag="g.0") ! volumeKernel = lp.fix_parameters(volumeKernel, @@ -75,5 +76,6 @@ end subroutine volumeKernel ! i="l.0", j="l.1", k="l.2", ! i_1="l.0", j_1="l.1", k_1="l.2" ! )) +! RESULT = [volumeKernel] ! -!$loopy end transform +!$loopy end diff --git a/examples/fortran/volumeKernelSimple.floopy b/examples/fortran/volumeKernelSimple.floopy index 67948020d..afc3321b8 100644 --- a/examples/fortran/volumeKernelSimple.floopy +++ b/examples/fortran/volumeKernelSimple.floopy @@ -27,10 +27,12 @@ subroutine volumeKernel(elements, Nfields, Ngeo, Ndim, Dop, geo, Q, rhsQ ) end subroutine volumeKernel -!$loopy begin transform +!$loopy begin ! +! volumeKernel, = lp.parse_fortran(SOURCE, FILENAME) ! volumeKernel = lp.fix_parameters(volumeKernel, ! Nq=5, Ndim=3) ! volumeKernel = lp.tag_inames(volumeKernel, dict(i="l.0")) +! RESULT = [volumeKernel] ! -!$loopy end transform +!$loopy end diff --git a/examples/python/hello-loopy-lp.py b/examples/python/hello-loopy-lp.py new file mode 100644 index 000000000..382aadc09 --- /dev/null +++ b/examples/python/hello-loopy-lp.py @@ -0,0 +1,11 @@ +# This is a version of hello-loopy.py that can be run through +# a loopy binary using +# +# ./loopy --lang=loopy hello-loopy-lp.py - + +knl = lp.make_kernel( + "{ [i]: 0<=i<n }", + "out[i] = 2*a[i]") + +knl = lp.add_and_infer_dtypes(knl, dict(a=np.float32)) +lp_knl = lp.split_iname(knl, "i", 128, outer_tag="g.0", inner_tag="l.0") diff --git a/loopy/__init__.py b/loopy/__init__.py index 7bc45d3b1..f2b04d49f 100644 --- a/loopy/__init__.py +++ b/loopy/__init__.py @@ -67,6 +67,8 @@ from loopy.codegen import generate_code, generate_body from loopy.compiled import CompiledKernel from loopy.options import Options from loopy.auto_test import auto_test_vs_ref +from loopy.frontend.fortran import (c_preprocess, parse_transformed_fortran, + parse_fortran) __all__ = [ "TaggedVariable", "Reduction", "LinearSubscript", @@ -107,6 +109,7 @@ __all__ = [ "Options", "make_kernel", + "c_preprocess", "parse_transformed_fortran", "parse_fortran", # {{{ from this file diff --git a/loopy/frontend/fortran/__init__.py b/loopy/frontend/fortran/__init__.py index c6b6b3c71..bd798966d 100644 --- a/loopy/frontend/fortran/__init__.py +++ b/loopy/frontend/fortran/__init__.py @@ -25,22 +25,61 @@ THE SOFTWARE. from loopy.diagnostic import LoopyError -def _extract_define_lines(source): +def c_preprocess(source, defines=None, file_name="<floopy source>"): + """ + :arg source: a string, possibly containing C preprocessor constructs + :arg defines: a list of strings as they might occur after a + C-style ``#define`` directive, for example ``deg2rad(x) (x/180d0 * 3.14d0)``. + :return: a string + """ + try: + import ply.lex as lex + import ply.cpp as cpp + except ImportError: + raise LoopyError("Using the C preprocessor requires PLY to be installed") + + lexer = lex.lex(cpp) + + from ply.cpp import Preprocessor + p = Preprocessor(lexer) + + if defines: + for d in defines: + p.define(d) + + p.parse(source, file_name) + + tokens = [] + while True: + tok = p.token() + + if not tok: + break + + if tok.type == "CPP_COMMENT": + continue + + tokens.append(tok.value) + + return "".join(tokens) + + +def _extract_loopy_lines(source): lines = source.split("\n") import re comment_re = re.compile(r"^\s*\!(.*)$") remaining_lines = [] - define_lines = [] + loopy_lines = [] - in_define_code = False + in_loopy_code = False for l in lines: comment_match = comment_re.match(l) if comment_match is None: - if in_define_code: - raise LoopyError("non-comment source line in define block") + if in_loopy_code: + raise LoopyError("non-comment source line in loopy block") remaining_lines.append(l) continue @@ -48,91 +87,143 @@ def _extract_define_lines(source): cmt = comment_match.group(1) cmt_stripped = cmt.strip() - if cmt_stripped == "$loopy begin define": - if in_define_code: - raise LoopyError("can't enter transform code twice") - in_define_code = True + if cmt_stripped == "$loopy begin": + if in_loopy_code: + raise LoopyError("can't enter loopy block twice") + in_loopy_code = True - elif cmt_stripped == "$loopy end define": - if not in_define_code: - raise LoopyError("can't leave transform code twice") - in_define_code = False + elif cmt_stripped == "$loopy end": + if not in_loopy_code: + raise LoopyError("can't leave loopy block twice") + in_loopy_code = False - elif in_define_code: - define_lines.append(cmt) + elif in_loopy_code: + loopy_lines.append(cmt) else: remaining_lines.append(l) - return "\n".join(remaining_lines), "\n".join(define_lines) + return "\n".join(remaining_lines), "\n".join(loopy_lines) -def f2loopy(source, free_form=True, strict=True, +def parse_transformed_fortran(source, free_form=True, strict=True, pre_transform_code=None, transform_code_context=None, - use_c_preprocessor=False, preprocessor_defines=None, - file_name="<floopy code>"): + filename="<floopy code>"): """ - :arg preprocessor_defines: a list of strings as they might occur after a - C-style ``#define`` directive, for example ``deg2rad(x) (x/180d0 * 3.14d0)``. + :arg source: a string of Fortran source code which must include + a snippet of transform code as described below. + :arg pre_transform_code: code that is run in the same context + as the transform + + *source* may contain snippets of loopy transform code between markers:: + + !$loopy begin + ! ... + !$loopy end + + Within the transform code, the following symbols are predefined: + + * ``lp``: a reference to the :mod:`loopy` package + * ``np``: a reference to the :mod:`numpy` package + * ``SOURCE``: the source code surrounding the transform block. + This may be processed using :func:`c_preprocess` and + :func:`parse_fortran`. + * ``FILENAME``: the file name of the code being processed + + The transform code must define ``RESULT``, conventionally a list of + kernels, which is returned from this function unmodified. + + An example of *source* may look as follows:: + + subroutine fill(out, a, n) + implicit none + + real*8 a, out(n) + integer n, i + + do i = 1, n + out(i) = a + end do + end + + !$loopy begin + ! + ! fill, = lp.parse_fortran(SOURCE, FILENAME) + ! fill = lp.split_iname(fill, "i", split_amount, + ! outer_tag="g.0", inner_tag="l.0") + ! RESULT = [fill] + ! + !$loopy end """ - if use_c_preprocessor: - try: - import ply.lex as lex - import ply.cpp as cpp - except ImportError: - raise LoopyError("Using the C preprocessor requires PLY to be installed") - lexer = lex.lex(cpp) + source, transform_code = _extract_loopy_lines(source) + if not transform_code: + raise LoopyError("no transform code found") + + from loopy.tools import remove_common_indentation + transform_code = remove_common_indentation( + transform_code, + require_leading_newline=False) - from ply.cpp import Preprocessor - p = Preprocessor(lexer) + if transform_code_context is None: + proc_dict = {} + else: + proc_dict = transform_code_context.copy() - if preprocessor_defines: - for d in preprocessor_defines: - p.define(d) + import loopy as lp + import numpy as np - source, define_code = _extract_define_lines(source) - if define_code is not None: - from loopy.tools import remove_common_indentation - define_code = remove_common_indentation( - define_code, - require_leading_newline=False) - def_dict = {} - def_dict["define"] = p.define + proc_dict["lp"] = lp + proc_dict["np"] = np - if pre_transform_code is not None: - def_dict["_MODULE_SOURCE_CODE"] = pre_transform_code - exec(compile(pre_transform_code, - "<loopy pre-transform code>", "exec"), def_dict) + proc_dict["SOURCE"] = source + proc_dict["FILENAME"] = filename - def_dict["_MODULE_SOURCE_CODE"] = define_code - exec(compile(define_code, "<loopy defines>", "exec"), def_dict) + from os.path import dirname, abspath + from os import getcwd - p.parse(source, file_name) + infile_dirname = dirname(filename) + if infile_dirname: + infile_dirname = abspath(infile_dirname) + else: + infile_dirname = getcwd() - tokens = [] - while True: - tok = p.token() + import sys + prev_sys_path = sys.path + try: + if infile_dirname: + sys.path = prev_sys_path + [infile_dirname] - if not tok: - break + if pre_transform_code is not None: + proc_dict["_MODULE_SOURCE_CODE"] = pre_transform_code + exec(compile(pre_transform_code, + "<loopy pre-transform code>", "exec"), proc_dict) - if tok.type == "CPP_COMMENT": - continue + proc_dict["_MODULE_SOURCE_CODE"] = transform_code + exec(compile(transform_code, filename, "exec"), proc_dict) - tokens.append(tok.value) + finally: + sys.path = prev_sys_path - source = "".join(tokens) + if "RESULT" not in proc_dict: + raise LoopyError("transform code did not set RESULT") + return proc_dict["RESULT"] + + +def parse_fortran(source, filename="<floopy code>", free_form=True, strict=True): + """ + :returns: a list of :class:`loopy.LoopKernel` objects + """ from fparser import api tree = api.parse(source, isfree=free_form, isstrict=strict, analyze=False, ignore_comments=False) from loopy.frontend.fortran.translator import F2LoopyTranslator - f2loopy = F2LoopyTranslator(file_name) + f2loopy = F2LoopyTranslator(filename) f2loopy(tree) - return f2loopy.make_kernels(pre_transform_code=pre_transform_code, - transform_code_context=transform_code_context) + return f2loopy.make_kernels() + # vim: foldmethod=marker diff --git a/loopy/frontend/fortran/translator.py b/loopy/frontend/fortran/translator.py index eba76338d..9d6e3ea95 100644 --- a/loopy/frontend/fortran/translator.py +++ b/loopy/frontend/fortran/translator.py @@ -208,15 +208,9 @@ class F2LoopyTranslator(FTreeWalkerBase): self.kernels = [] - # Flag to record whether 'loopy begin transform' comment - # has been seen. - self.in_transform_code = False - self.instruction_tags = [] self.conditions = [] - self.transform_code_lines = [] - self.filename = filename self.index_dtype = None @@ -606,17 +600,7 @@ class F2LoopyTranslator(FTreeWalkerBase): faulty_loopy_pragma_match = self.faulty_loopy_pragma.match( stripped_comment_line) - if stripped_comment_line == "$loopy begin transform": - if self.in_transform_code: - raise TranslationError("can't enter transform code twice") - self.in_transform_code = True - - elif stripped_comment_line == "$loopy end transform": - if not self.in_transform_code: - raise TranslationError("can't leave transform code twice") - self.in_transform_code = False - - elif begin_tag_match: + if begin_tag_match: tag = begin_tag_match.group(1) if tag in self.instruction_tags: raise TranslationError("nested begin tag for tag '%s'" % tag) @@ -629,9 +613,6 @@ class F2LoopyTranslator(FTreeWalkerBase): "end tag without begin tag for tag '%s'" % tag) self.instruction_tags.remove(tag) - elif self.in_transform_code: - self.transform_code_lines.append(node.content) - elif faulty_loopy_pragma_match is not None: from warnings import warn warn("The comment line '%s' was not recognized as a loopy directive" @@ -641,18 +622,12 @@ class F2LoopyTranslator(FTreeWalkerBase): # }}} - def make_kernels(self, pre_transform_code=None, transform_code_context=None): + def make_kernels(self): kernel_names = [ sub.subprogram_name for sub in self.kernels] - if transform_code_context is None: - proc_dict = {} - else: - proc_dict = transform_code_context.copy() - - proc_dict["lp"] = lp - proc_dict["np"] = np + result = [] for sub in self.kernels: # {{{ figure out arguments @@ -704,25 +679,11 @@ class F2LoopyTranslator(FTreeWalkerBase): from loopy.loop import fuse_loop_domains knl = fuse_loop_domains(knl) + knl = lp.fold_constants(knl) - proc_dict[sub.subprogram_name] = lp.fold_constants(knl) - - from loopy.tools import remove_common_indentation - transform_code = remove_common_indentation( - "\n".join(self.transform_code_lines), - require_leading_newline=False) - - if pre_transform_code is not None: - proc_dict["_MODULE_SOURCE_CODE"] = pre_transform_code - exec(compile(pre_transform_code, - "<loopy pre-transform code>", "exec"), proc_dict) - - proc_dict["_MODULE_SOURCE_CODE"] = transform_code - exec(compile(transform_code, - "<loopy transforms>", "exec"), proc_dict) + result.append(knl) - return [proc_dict[knl_name] - for knl_name in kernel_names] + return result # }}} diff --git a/test/test_fortran.py b/test/test_fortran.py index ce27424d8..124035e52 100644 --- a/test/test_fortran.py +++ b/test/test_fortran.py @@ -58,16 +58,20 @@ def test_fill(ctx_factory): end do end - !$loopy begin transform + !$loopy begin ! - ! fill = lp.split_iname(fill, "i", 128, + ! fill, = lp.parse_fortran(SOURCE) + ! fill = lp.split_iname(fill, "i", split_amount, ! outer_tag="g.0", inner_tag="l.0") + ! RESULT = [fill] ! - !$loopy end transform + !$loopy end """ - from loopy.frontend.fortran import f2loopy - knl, = f2loopy(fortran_src) + knl, = lp.parse_transformed_fortran(fortran_src, + pre_transform_code="split_amount = 128") + + assert "i_inner" in knl.all_inames() ctx = ctx_factory() @@ -88,8 +92,7 @@ def test_fill_const(ctx_factory): end """ - from loopy.frontend.fortran import f2loopy - knl, = f2loopy(fortran_src) + knl, = lp.parse_fortran(fortran_src) ctx = ctx_factory() @@ -112,8 +115,7 @@ def test_asterisk_in_shape(ctx_factory): end """ - from loopy.frontend.fortran import f2loopy - knl, = f2loopy(fortran_src) + knl, = lp.parse_fortran(fortran_src) ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -137,8 +139,7 @@ def test_temporary_to_subst(ctx_factory): end """ - from loopy.frontend.fortran import f2loopy - knl, = f2loopy(fortran_src) + knl, = lp.parse_fortran(fortran_src) ref_knl = knl @@ -165,8 +166,7 @@ def test_temporary_to_subst_two_defs(ctx_factory): end """ - from loopy.frontend.fortran import f2loopy - knl, = f2loopy(fortran_src) + knl, = lp.parse_fortran(fortran_src) ref_knl = knl @@ -194,8 +194,7 @@ def test_temporary_to_subst_indices(ctx_factory): end """ - from loopy.frontend.fortran import f2loopy - knl, = f2loopy(fortran_src) + knl, = lp.parse_fortran(fortran_src) knl = lp.fix_parameters(knl, n=5) @@ -232,8 +231,7 @@ def test_if(ctx_factory): end """ - from loopy.frontend.fortran import f2loopy - knl, = f2loopy(fortran_src) + knl, = lp.parse_fortran(fortran_src) ref_knl = knl @@ -267,8 +265,7 @@ def test_tagged(ctx_factory): end """ - from loopy.frontend.fortran import f2loopy - knl, = f2loopy(fortran_src) + knl, = lp.parse_fortran(fortran_src) assert sum(1 for insn in lp.find_instructions(knl, "*$input")) == 2 @@ -294,8 +291,7 @@ def test_matmul(ctx_factory, buffer_inames): end subroutine """ - from loopy.frontend.fortran import f2loopy - knl, = f2loopy(fortran_src) + knl, = lp.parse_fortran(fortran_src) assert len(knl.domains) == 1 @@ -357,8 +353,7 @@ def test_batched_sparse(): """ - from loopy.frontend.fortran import f2loopy - knl, = f2loopy(fortran_src) + knl, = lp.parse_fortran(fortran_src) knl = lp.split_iname(knl, "i", 128) knl = lp.tag_inames(knl, {"i_outer": "g.0"}) @@ -393,12 +388,11 @@ def test_fuse_kernels(ctx_factory): xd_line = "result(e,i,j) = result(e,i,j) + d(i,k)*q(e,i,k)" yd_line = "result(e,i,j) = result(e,i,j) + d(i,k)*q(e,k,j)" - from loopy.frontend.fortran import f2loopy - xderiv, = f2loopy( + xderiv, = lp.parse_fortran( fortran_template.format(line=xd_line, name="xderiv")) - yderiv, = f2loopy( + yderiv, = lp.parse_fortran( fortran_template.format(line=yd_line, name="yderiv")) - xyderiv, = f2loopy( + xyderiv, = lp.parse_fortran( fortran_template.format( line=(xd_line + "\n" + yd_line), name="xyderiv")) -- GitLab