<commit_before>"""
Fortran code printer

The FCodePrinter converts single sympy expressions into single Fortran
expressions, using the functions defined in the Fortran 77 standard where
possible. Some useful pointers to Fortran can be found on wikipedia:

http://en.wikipedia.org/wiki/Fortran

Most of the code below is based on the "Professional Programmer\'s Guide to
Fortran77" by <NAME>:

http://www.star.le.ac.uk/~cgp/prof77.html

Fortran is a case-insensitive language. This might cause trouble because sympy
is case sensitive. The implementation below does not care and leaves the
responsibility for generating properly cased Fortran code to the user.
"""


from str import StrPrinter
from sympy.printing.precedence import precedence
from sympy.core import S, Add, I, Symbol
from sympy.core.numbers import NumberSymbol
from sympy.functions import sin, cos, tan, asin, acos, atan, atan2, sinh, \
    cosh, tanh, sqrt, log, exp, abs, sign, conjugate, Piecewise
from sympy.utilities.iterables import postorder_traversal
from sympy.tensor import Idx


implicit_functions = set([
    sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, sqrt, log, exp,
    abs, sign, conjugate
])


class FCodePrinter(StrPrinter):
    """A printer to convert sympy expressions to strings of Fortran code"""
    printmethod = "_fcode"

    _default_settings = {
        'order': None,
        'full_prec': 'auto',
        'assign_to': None,
        'precision': 15,
        'user_functions': {},
        'human': True,
        'source_format': 'fixed',
    }
    def __init__(self, settings=None):
        if settings and isinstance(settings.get('assign_to'), basestring):
            settings['assign_to'] = Symbol(settings['assign_to'])
        StrPrinter.__init__(self, settings)
        self._init_leading_padding()

    def _init_leading_padding(self):
        # leading columns depend on fixed or free format
        if self._settings['source_format'] == 'fixed':
            self._lead_code = "      "
            self._lead_cont = "     @ "
            self._lead_comment = "C     "
        elif self._settings['source_format'] == 'free':
            self._lead_code = ""
            self._lead_cont = "      "
            self._lead_comment = "! "
        else:
            raise ValueError(
                    "Unknown source format: %s" % self._settings['source_format']
                    )

    def _pad_leading_columns(self, lines):
        result = []
        for line in lines:
            if line.startswith('!'):
                result.append(self._lead_comment + line[1:].lstrip())
            else:
                result.append(self._lead_code + line)
        return result

    def _get_loop_opening_ending_ints(self, expr):
        """Returns a tuple (open_lines, close_lines) containing lists of codelines
        """

        indices = expr.atoms(Idx)
        # FIXME: sort indices in an optimized way
        open_lines = []
        close_lines = []
        local_ints = set([])
        for i in indices:
            # fortran arrays start at 1 and end at dimension
            open_lines.append("do %s = %s, %s" % (i.label, i.lower+1, i.upper+1))
            close_lines.append("end do")
            local_ints.add(self._print(i.label))
        return open_lines, close_lines, local_ints


    def doprint(self, expr):
        """Returns Fortran code for expr (as a string)"""
        # find all number symbols
        number_symbols = set([])
        for sub in postorder_traversal(expr):
            if isinstance(sub, NumberSymbol):
                number_symbols.add(sub)
        number_symbols = [(str(ns), ns.evalf(self._settings["precision"]))
                          for ns in sorted(number_symbols)]

        # keep a set of expressions that are not strictly translatable to
        # Fortran.
        self._not_fortran = set([])


        # Setup loops if expression contain Indexed objects
        openloop, closeloop, local_ints = self._get_loop_opening_ending_ints(expr)

        lines = []

        if isinstance(expr, Piecewise):
            # support for top-level Piecewise function
            for i, (e, c) in enumerate(expr.args):
                if i == 0:
                    lines.append("if (%s) then" % self._print(c))
                elif i == len(expr.args)-1 and c == True:
                    lines.append("else")
                else:
                    lines.append("else if (%s) then" % self._print(c))
                if self._settings["assign_to"] is None:
                    lines.extend(openloop)
                    lines.append("  %s" % self._print(e))
                    lines.extend(closeloop)
                else:
                    lines.extend(openloop)
                    lines.append("  %s = %s" % (self._settings["assign_to"], self._print(e)))
                    lines.extend(closeloop)
            lines.append("end if")
        else:
            lines.extend(openloop)
            line = StrPrinter.doprint(self, expr)
            if self._settings["assign_to"] is None:
                text = "%s" % line
            else:
                text = "%s = %s" % (self._settings["assign_to"], line)
            lines.append(text)
            lines.extend(closeloop)

        # format the output
        if self._settings["human"]:
            frontlines = []
            if len(self._not_fortran) > 0:
                frontlines.append("! Not Fortran:")
                for expr in sorted(self._not_fortran):
                    frontlines.append("! %s" % expr)
            for name, value in number_symbols:
                frontlines.append("parameter (%s = %s)" % (name, value))
            frontlines.extend(lines)
            lines = frontlines
            lines = self._pad_leading_columns(lines)
            lines = self._wrap_fortran(lines)
            lines = self.indent_code(lines)
            result = "\n".join(lines)
        else:
            lines = self._pad_leading_columns(lines)
            lines = self._wrap_fortran(lines)
            lines = self.indent_code(lines)
            result = number_symbols, self._not_fortran, "\n".join(lines)

        del self._not_fortran
        return result

    def _print_Add(self, expr):
        # purpose: print complex numbers nicely in Fortran.
        # collect the purely real and purely imaginary parts:
        pure_real = []
        pure_imaginary = []
        mixed = []
        for arg in expr.args:<commit_msg>FCodePrinter: use self._print as key to [].sort() this avoids Expr.__lt__()<commit_after>"""
Fortran code printer

The FCodePrinter converts single sympy expressions into single Fortran
expressions, using the functions defined in the Fortran 77 standard where
possible. Some useful pointers to Fortran can be found on wikipedia:

http://en.wikipedia.org/wiki/Fortran

Most of the code below is based on the "Professional Programmer\'s Guide to
Fortran77" by <NAME>. Page:

http://www.star.le.ac.uk/~cgp/prof77.html

Fortran is a case-insensitive language. This might cause trouble because sympy
is case sensitive. The implementation below does not care and leaves the
responsibility for generating properly cased Fortran code to the user.
"""


from str import StrPrinter
from sympy.printing.precedence import precedence
from sympy.core import S, Add, I, Symbol
from sympy.core.numbers import NumberSymbol
from sympy.functions import sin, cos, tan, asin, acos, atan, atan2, sinh, \
    cosh, tanh, sqrt, log, exp, abs, sign, conjugate, Piecewise
from sympy.utilities.iterables import postorder_traversal
from sympy.tensor import Idx


implicit_functions = set([
    sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, sqrt, log, exp,
    abs, sign, conjugate
])


class FCodePrinter(StrPrinter):
    """A printer to convert sympy expressions to strings of Fortran code"""
    printmethod = "_fcode"

    _default_settings = {
        'order': None,
        'full_prec': 'auto',
        'assign_to': None,
        'precision': 15,
        'user_functions': {},
        'human': True,
        'source_format': 'fixed',
    }
    def __init__(self, settings=None):
        if settings and isinstance(settings.get('assign_to'), basestring):
            settings['assign_to'] = Symbol(settings['assign_to'])
        StrPrinter.__init__(self, settings)
        self._init_leading_padding()

    def _init_leading_padding(self):
        # leading columns depend on fixed or free format
        if self._settings['source_format'] == 'fixed':
            self._lead_code = "      "
            self._lead_cont = "     @ "
            self._lead_comment = "C     "
        elif self._settings['source_format'] == 'free':
            self._lead_code = ""
            self._lead_cont = "      "
            self._lead_comment = "! "
        else:
            raise ValueError(
                    "Unknown source format: %s" % self._settings['source_format']
                    )

    def _pad_leading_columns(self, lines):
        result = []
        for line in lines:
            if line.startswith('!'):
                result.append(self._lead_comment + line[1:].lstrip())
            else:
                result.append(self._lead_code + line)
        return result

    def _get_loop_opening_ending_ints(self, expr):
        """Returns a tuple (open_lines, close_lines) containing lists of codelines
        """

        indices = expr.atoms(Idx)
        # FIXME: sort indices in an optimized way
        open_lines = []
        close_lines = []
        local_ints = set([])
        for i in indices:
            # fortran arrays start at 1 and end at dimension
            open_lines.append("do %s = %s, %s" % (i.label, i.lower+1, i.upper+1))
            close_lines.append("end do")
            local_ints.add(self._print(i.label))
        return open_lines, close_lines, local_ints


    def doprint(self, expr):
        """Returns Fortran code for expr (as a string)"""
        # find all number symbols
        number_symbols = set([])
        for sub in postorder_traversal(expr):
            if isinstance(sub, NumberSymbol):
                number_symbols.add(sub)
        number_symbols = [(str(ns), ns.evalf(self._settings["precision"]))
                          for ns in sorted(number_symbols)]

        # keep a set of expressions that are not strictly translatable to
        # Fortran.
        self._not_fortran = set([])


        # Setup loops if expression contain Indexed objects
        openloop, closeloop, local_ints = self._get_loop_opening_ending_ints(expr)

        lines = []

        if isinstance(expr, Piecewise):
            # support for top-level Piecewise function
            for i, (e, c) in enumerate(expr.args):
                if i == 0:
                    lines.append("if (%s) then" % self._print(c))
                elif i == len(expr.args)-1 and c == True:
                    lines.append("else")
                else:
                    lines.append("else if (%s) then" % self._print(c))
                if self._settings["assign_to"] is None:
                    lines.extend(openloop)
                    lines.append("  %s" % self._print(e))
                    lines.extend(closeloop)
                else:
                    lines.extend(openloop)
                    lines.append("  %s = %s" % (self._settings["assign_to"], self._print(e)))
                    lines.extend(closeloop)
            lines.append("end if")
        else:
            lines.extend(openloop)
            line = StrPrinter.doprint(self, expr)
            if self._settings["assign_to"] is None:
                text = "%s" % line
            else:
                text = "%s = %s" % (self._settings["assign_to"], line)
            lines.append(text)
            lines.extend(closeloop)

        # format the output
        if self._settings["human"]:
            frontlines = []
            if len(self._not_fortran) > 0:
                frontlines.append("! Not Fortran:")
                for expr in sorted(self._not_fortran, key=self._print):
                    frontlines.append("! %s" % expr)
            for name, value in number_symbols:
                frontlines.append("parameter (%s = %s)" % (name, value))
            frontlines.extend(lines)
            lines = frontlines
            lines = self._pad_leading_columns(lines)
            lines = self._wrap_fortran(lines)
            lines = self.indent_code(lines)
            result = "\n".join(lines)
        else:
            lines = self._pad_leading_columns(lines)
            lines = self._wrap_fortran(lines)
            lines = self.indent_code(lines)
            result = number_symbols, self._not_fortran, "\n".join(lines)

        del self._not_fortran
        return result

    def _print_Add(self, expr):
        # purpose: print complex numbers nicely in Fortran.
        # collect the purely real and purely imaginary parts:
        pure_real = []
        pure_imaginary = []
        mixed = []
        for arg in expr.args:
{'fraction_non_alphanumeric': 0.10362614519978443, 'fraction_numerical': 0.002617599507275387, 'mean_word_length': 2.0428671820098385, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4591047', 'n_tokens_mistral': 3771, 'n_tokens_neox': 3579, 'n_words': 1018}