## 87 Package pytranslate ¶

### 87.1 Introduction to pytranslate ¶

`pytranslate` package provides Maxima to Python translation functionality. The package is experimental, and the specifications of the functions in this package might change. It was written as a Google Summer of Code project by Lakshya A Agrawal (Undergraduate Student, IIIT-Delhi) in 2019. A detailed project report is available as a GitHub Gist.

The package needs to be loaded in a Maxima instance for use, by executing `load("pytranslate");`
The statements are converted to python3 syntax. The file pytranslate.py must be imported for all translations to run, as shown in example.

Example:

    (%i1) load ("pytranslate")$

    /* Define an example function to calculate factorial */
    (%i2) pytranslate(my_factorial(x) := if (x = 1 or x = 0) then 1
                      else x * my_factorial(x - 1));
    (%o2)
    def my_factorial(x, v = v):
        v = Stack({}, v)
        v.ins({"x" : x})
        return((1 if ((v["x"] == 1) or (v["x"] == 0)) \
                  else (v["x"] * my_factorial((v["x"] + (-1))))))
    m["my_factorial"] = my_factorial

    (%i3) my_factorial(5);
    (%o3)                          120

    >>> from pytranslate import *
    >>> def my_factorial(x, v = v):
    ...     v = Stack({}, v)
    ...     v.ins({"x" : x})
    ...     return((1 if ((v["x"] == 1) or (v["x"] == 0)) \
    ...     else (v["x"] * my_factorial((v["x"] + (-1))))))
    ...
    >>> my_factorial(5)
    120

The Maxima to Python Translator works in two stages:
1. Conversion of the internal Maxima representation to a defined Intermediate Representation, henceforth referred as IR(mapping is present in `share/pytranslate/maxima-to-ir.html`)
2. The conversion of IR to Python.

Supported Maxima forms:
1. `Numbers`(including complex numbers)
2. `Assignment operators`
3. `Arithmetic operators`(+, -, *, ^, /, !)
4. `Logical operators`(and, or, not)
5. `Relational operators`(`>`, `<`, `>=`, `<=`, `!=`, `==`)
6. `Lists`
7. `Arrays`
8. `block`
9. `Function` and function calls
10. `if`-else converted to Python conditionals
11. `for` loops
12. `lambda` form

#### 87.1.1 Tests for pytranslate ¶

The tests for `pytranslate` are present at `share/pytranslate/rtest_pytranslate.mac` and can be run by executing `batch(rtest_pytranslate, test);`

### 87.2 Functions in pytranslate ¶

Function: pytranslate (expr, [print-ir]) ¶

  Translates the expression expr to equivalent python3 statements. Output is printed in the stdout.

  Example:

      (%i1) load ("pytranslate")$

      (%i2) pytranslate('(for i:8 step -1 unless i<3 do (print(i))));
      (%o2)
      v["i"] = 8
      while not((v["i"] < 3)):
          m["print"](v["i"])
          v["i"] = (v["i"] + -1)
      del v["i"]

  expr is evaluated, and the return value is used for translation. Hence, for statements like assignment, it might be useful to quote the statement:

      (%i1) load ("pytranslate")$

      (%i2) pytranslate(x:20);
      (%o2)
      20

      (%i3) pytranslate('(x:20));
      (%o3)
      v["x"] = 20

  Passing the optional parameter (print-ir) to `pytranslate` as t, will print the internal IR representation of `expr` and return the translated python3 code.

      (%i1) load("pytranslate");
      (%o1) pytranslate

      (%i2) pytranslate('(plot3d(lambda([x, y], x^2+y^(-1)), [x, 1, 10],
                         [y, 1, 10])), t);
      (body
       (funcall (element-array "m" (string "plot3d"))
                (lambda
                    ((symbol "x") (symbol "y")
                     (op-no-bracket
                      =
                      (symbol "v")
                      (funcall (symbol "stack") (dictionary) (symbol "v"))))
                  (op +
                      (funcall (element-array (symbol "m") (string "pow"))
                               (symbol "x") (num 2 0))
                      (funcall (element-array (symbol "m") (string "pow"))
                               (symbol "y") (unary-op - (num 1 0)))))
                (struct-list (string "x") (num 1 0) (num 10 0))
                (struct-list (string "y") (num 1 0) (num 10 0))))
      (%o2)
      m["plot3d"](lambda x, y, v = Stack({}, v): (m["pow"](x, 2) + m["\
      pow"](y, (-1))), ["x", 1, 10], ["y", 1, 10])

Function: show_form (expr) ¶

  Displays the internal maxima form of `expr`

      (%i4) show_form(a^b);
      ((mexpt) $a $b)
      (%o4) a^b

### 87.3 Extending pytranslate ¶

Working of pytranslate:

- The entry point for pytranslate is the function `$pytranslate` defined in `share/pytranslate/pytranslate.lisp`.
- `$pytranslate` calls the function `maxima-to-ir` with the Maxima expression as an argument(henceforth referred as `expr`).
- `maxima-to-ir` determines if `expr` is atomic or non-atomic(lisp cons form). If atomic, `atom-to-ir` is called with `expr` which returns the IR for the atomic expression.
  To define/modify translation for atomic expressions, make changes to the definition of `atom-to-ir` in accordance with the IR.
- If `expr` is non-atomic, the function `cons-to-ir` is called with `expr` as an argument.
  - `cons-to-ir` looks for `(caar expr)` which specifies the type of `expr`, in hash-table *maxima-direct-ir-map* and if the type is found, then appends the retrieved IR with the result of lisp call `(mapcar #'maxima-to-ir (cdr expr))`, which applies maxima-to-ir function to all the elements present in the list. Effectively, recursively generate IR for all the elements present in `expr` and append them to the IR map for the type.
    Example:

        (%i9) show_form(a+b);
        ((MPLUS) $B $A)

        (%i10) pytranslate(a+b, t);
        (body (op + (element-array (symbol "v") (string "b")) \
        (element-array (symbol "v") (string "a"))))
        (%o10)
        (v["b"] + v["a"])

    Here, operator + with internal maxima representation, `(mplus)` is present in *maxima-direct-ir-map* and mapped to `(op +)` to which the result of generating IR for all other elements of the list (a b), i.e. `(ELEMENT-ARRAY (SYMBOL "v") (STRING "b")) (ELEMENT-ARRAY (SYMBOL "v") (STRING "a"))` is appended.

  - If `(caar expr)` is not found in *maxima-direct-ir-map*, then `cons-to-ir` looks for the type in *maxima-special-ir-map* which returns the function to handle the translation of the type of `expr`. `cons-to-ir` then calls the returned function with argument `expr` as an argument.
    Example:

        (%i11) show_form(g(x) := x^2);
        ((mdefine simp) (($g) $x) ((mexpt) $x 2))

        (%i12) pytranslate(g(x):=x^2, t);
        (body
         (body
          (func-def (symbol "g")
                    ((symbol "x") (op-no-bracket = (symbol "v") (symbol "v")))
                    (body-indented
                        (op-no-bracket = (symbol "v") (funcall (symbol "stack") \
                        (dictionary) (symbol "v")))
                        (obj-funcall (symbol "v") (symbol "ins") (dictionary \
                        ((string "x") (symbol "x"))))
                        (funcall (symbol "return")
                            (funcall (element-array (symbol "f") (string "pow"))
                                 (element-array (symbol "v") (string "x"))
                                          (num 2 0)))))
          (op-no-bracket = (element-array (symbol "f") (string "g")) \
          (symbol "g"))))
        (%o12)
        def g(x, v = v):
            v = Stack({}, v)
            v.ins({"x" : x})
            return(f["pow"](v["x"], 2))
        f["g"] = g

    Here, `mdefine`, which is the type of `expr` is present in *maxima-special-ir-map* which returns `func-def-to-ir` as handler function, which is then called with `expr` to generate the IR.
    To define/modify translation for a type, add an entry to *maxima-direct-ir-map* if only a part of the IR needs to be generated and the rest can be appended, otherwise, for complete handling of `expr`, add an entry to *maxima-special-ir-map* and define a function with the name defined in *maxima-special-ir-map* which returns the IR for the form. The function naming convention for ir generators is (type)-to-ir, where type is the `(caar expr)` for expression(`mdefine -> func-def-to-ir`). The function must return a valid IR for the specific type.

- After the generation of IR, the function `ir-to-python` is called with the generated `ir` as an argument, which performs the codegen in a recursive manner.
  - `ir-to-python` looks for lisp `(car ir)` in the hash-table *ir-python-direct-templates*, which maps IR type to function handlers and calls the function returned with `ir` as an argument.
- To extend the IR of pytranslate, define a function with the naming convention (type)-to-python and add the name to *ir-python-direct-templates*.