jscode.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. """
  2. Javascript code printer
  3. The JavascriptCodePrinter converts single SymPy expressions into single
  4. Javascript expressions, using the functions defined in the Javascript
  5. Math object where possible.
  6. """
  7. from typing import Any, Dict as tDict
  8. from sympy.core import S
  9. from sympy.printing.codeprinter import CodePrinter
  10. from sympy.printing.precedence import precedence, PRECEDENCE
  11. # dictionary mapping SymPy function to (argument_conditions, Javascript_function).
  12. # Used in JavascriptCodePrinter._print_Function(self)
  13. known_functions = {
  14. 'Abs': 'Math.abs',
  15. 'acos': 'Math.acos',
  16. 'acosh': 'Math.acosh',
  17. 'asin': 'Math.asin',
  18. 'asinh': 'Math.asinh',
  19. 'atan': 'Math.atan',
  20. 'atan2': 'Math.atan2',
  21. 'atanh': 'Math.atanh',
  22. 'ceiling': 'Math.ceil',
  23. 'cos': 'Math.cos',
  24. 'cosh': 'Math.cosh',
  25. 'exp': 'Math.exp',
  26. 'floor': 'Math.floor',
  27. 'log': 'Math.log',
  28. 'Max': 'Math.max',
  29. 'Min': 'Math.min',
  30. 'sign': 'Math.sign',
  31. 'sin': 'Math.sin',
  32. 'sinh': 'Math.sinh',
  33. 'tan': 'Math.tan',
  34. 'tanh': 'Math.tanh',
  35. }
  36. class JavascriptCodePrinter(CodePrinter):
  37. """"A Printer to convert Python expressions to strings of JavaScript code
  38. """
  39. printmethod = '_javascript'
  40. language = 'JavaScript'
  41. _default_settings = {
  42. 'order': None,
  43. 'full_prec': 'auto',
  44. 'precision': 17,
  45. 'user_functions': {},
  46. 'human': True,
  47. 'allow_unknown_functions': False,
  48. 'contract': True,
  49. } # type: tDict[str, Any]
  50. def __init__(self, settings={}):
  51. CodePrinter.__init__(self, settings)
  52. self.known_functions = dict(known_functions)
  53. userfuncs = settings.get('user_functions', {})
  54. self.known_functions.update(userfuncs)
  55. def _rate_index_position(self, p):
  56. return p*5
  57. def _get_statement(self, codestring):
  58. return "%s;" % codestring
  59. def _get_comment(self, text):
  60. return "// {}".format(text)
  61. def _declare_number_const(self, name, value):
  62. return "var {} = {};".format(name, value.evalf(self._settings['precision']))
  63. def _format_code(self, lines):
  64. return self.indent_code(lines)
  65. def _traverse_matrix_indices(self, mat):
  66. rows, cols = mat.shape
  67. return ((i, j) for i in range(rows) for j in range(cols))
  68. def _get_loop_opening_ending(self, indices):
  69. open_lines = []
  70. close_lines = []
  71. loopstart = "for (var %(varble)s=%(start)s; %(varble)s<%(end)s; %(varble)s++){"
  72. for i in indices:
  73. # Javascript arrays start at 0 and end at dimension-1
  74. open_lines.append(loopstart % {
  75. 'varble': self._print(i.label),
  76. 'start': self._print(i.lower),
  77. 'end': self._print(i.upper + 1)})
  78. close_lines.append("}")
  79. return open_lines, close_lines
  80. def _print_Pow(self, expr):
  81. PREC = precedence(expr)
  82. if expr.exp == -1:
  83. return '1/%s' % (self.parenthesize(expr.base, PREC))
  84. elif expr.exp == 0.5:
  85. return 'Math.sqrt(%s)' % self._print(expr.base)
  86. elif expr.exp == S.One/3:
  87. return 'Math.cbrt(%s)' % self._print(expr.base)
  88. else:
  89. return 'Math.pow(%s, %s)' % (self._print(expr.base),
  90. self._print(expr.exp))
  91. def _print_Rational(self, expr):
  92. p, q = int(expr.p), int(expr.q)
  93. return '%d/%d' % (p, q)
  94. def _print_Mod(self, expr):
  95. num, den = expr.args
  96. PREC = precedence(expr)
  97. snum, sden = [self.parenthesize(arg, PREC) for arg in expr.args]
  98. # % is remainder (same sign as numerator), not modulo (same sign as
  99. # denominator), in js. Hence, % only works as modulo if both numbers
  100. # have the same sign
  101. if (num.is_nonnegative and den.is_nonnegative or
  102. num.is_nonpositive and den.is_nonpositive):
  103. return f"{snum} % {sden}"
  104. return f"(({snum} % {sden}) + {sden}) % {sden}"
  105. def _print_Relational(self, expr):
  106. lhs_code = self._print(expr.lhs)
  107. rhs_code = self._print(expr.rhs)
  108. op = expr.rel_op
  109. return "{} {} {}".format(lhs_code, op, rhs_code)
  110. def _print_Indexed(self, expr):
  111. # calculate index for 1d array
  112. dims = expr.shape
  113. elem = S.Zero
  114. offset = S.One
  115. for i in reversed(range(expr.rank)):
  116. elem += expr.indices[i]*offset
  117. offset *= dims[i]
  118. return "%s[%s]" % (self._print(expr.base.label), self._print(elem))
  119. def _print_Idx(self, expr):
  120. return self._print(expr.label)
  121. def _print_Exp1(self, expr):
  122. return "Math.E"
  123. def _print_Pi(self, expr):
  124. return 'Math.PI'
  125. def _print_Infinity(self, expr):
  126. return 'Number.POSITIVE_INFINITY'
  127. def _print_NegativeInfinity(self, expr):
  128. return 'Number.NEGATIVE_INFINITY'
  129. def _print_Piecewise(self, expr):
  130. from sympy.codegen.ast import Assignment
  131. if expr.args[-1].cond != True:
  132. # We need the last conditional to be a True, otherwise the resulting
  133. # function may not return a result.
  134. raise ValueError("All Piecewise expressions must contain an "
  135. "(expr, True) statement to be used as a default "
  136. "condition. Without one, the generated "
  137. "expression may not evaluate to anything under "
  138. "some condition.")
  139. lines = []
  140. if expr.has(Assignment):
  141. for i, (e, c) in enumerate(expr.args):
  142. if i == 0:
  143. lines.append("if (%s) {" % self._print(c))
  144. elif i == len(expr.args) - 1 and c == True:
  145. lines.append("else {")
  146. else:
  147. lines.append("else if (%s) {" % self._print(c))
  148. code0 = self._print(e)
  149. lines.append(code0)
  150. lines.append("}")
  151. return "\n".join(lines)
  152. else:
  153. # The piecewise was used in an expression, need to do inline
  154. # operators. This has the downside that inline operators will
  155. # not work for statements that span multiple lines (Matrix or
  156. # Indexed expressions).
  157. ecpairs = ["((%s) ? (\n%s\n)\n" % (self._print(c), self._print(e))
  158. for e, c in expr.args[:-1]]
  159. last_line = ": (\n%s\n)" % self._print(expr.args[-1].expr)
  160. return ": ".join(ecpairs) + last_line + " ".join([")"*len(ecpairs)])
  161. def _print_MatrixElement(self, expr):
  162. return "{}[{}]".format(self.parenthesize(expr.parent,
  163. PRECEDENCE["Atom"], strict=True),
  164. expr.j + expr.i*expr.parent.shape[1])
  165. def indent_code(self, code):
  166. """Accepts a string of code or a list of code lines"""
  167. if isinstance(code, str):
  168. code_lines = self.indent_code(code.splitlines(True))
  169. return ''.join(code_lines)
  170. tab = " "
  171. inc_token = ('{', '(', '{\n', '(\n')
  172. dec_token = ('}', ')')
  173. code = [ line.lstrip(' \t') for line in code ]
  174. increase = [ int(any(map(line.endswith, inc_token))) for line in code ]
  175. decrease = [ int(any(map(line.startswith, dec_token)))
  176. for line in code ]
  177. pretty = []
  178. level = 0
  179. for n, line in enumerate(code):
  180. if line in ('', '\n'):
  181. pretty.append(line)
  182. continue
  183. level -= decrease[n]
  184. pretty.append("%s%s" % (tab*level, line))
  185. level += increase[n]
  186. return pretty
  187. def jscode(expr, assign_to=None, **settings):
  188. """Converts an expr to a string of javascript code
  189. Parameters
  190. ==========
  191. expr : Expr
  192. A SymPy expression to be converted.
  193. assign_to : optional
  194. When given, the argument is used as the name of the variable to which
  195. the expression is assigned. Can be a string, ``Symbol``,
  196. ``MatrixSymbol``, or ``Indexed`` type. This is helpful in case of
  197. line-wrapping, or for expressions that generate multi-line statements.
  198. precision : integer, optional
  199. The precision for numbers such as pi [default=15].
  200. user_functions : dict, optional
  201. A dictionary where keys are ``FunctionClass`` instances and values are
  202. their string representations. Alternatively, the dictionary value can
  203. be a list of tuples i.e. [(argument_test, js_function_string)]. See
  204. below for examples.
  205. human : bool, optional
  206. If True, the result is a single string that may contain some constant
  207. declarations for the number symbols. If False, the same information is
  208. returned in a tuple of (symbols_to_declare, not_supported_functions,
  209. code_text). [default=True].
  210. contract: bool, optional
  211. If True, ``Indexed`` instances are assumed to obey tensor contraction
  212. rules and the corresponding nested loops over indices are generated.
  213. Setting contract=False will not generate loops, instead the user is
  214. responsible to provide values for the indices in the code.
  215. [default=True].
  216. Examples
  217. ========
  218. >>> from sympy import jscode, symbols, Rational, sin, ceiling, Abs
  219. >>> x, tau = symbols("x, tau")
  220. >>> jscode((2*tau)**Rational(7, 2))
  221. '8*Math.sqrt(2)*Math.pow(tau, 7/2)'
  222. >>> jscode(sin(x), assign_to="s")
  223. 's = Math.sin(x);'
  224. Custom printing can be defined for certain types by passing a dictionary of
  225. "type" : "function" to the ``user_functions`` kwarg. Alternatively, the
  226. dictionary value can be a list of tuples i.e. [(argument_test,
  227. js_function_string)].
  228. >>> custom_functions = {
  229. ... "ceiling": "CEIL",
  230. ... "Abs": [(lambda x: not x.is_integer, "fabs"),
  231. ... (lambda x: x.is_integer, "ABS")]
  232. ... }
  233. >>> jscode(Abs(x) + ceiling(x), user_functions=custom_functions)
  234. 'fabs(x) + CEIL(x)'
  235. ``Piecewise`` expressions are converted into conditionals. If an
  236. ``assign_to`` variable is provided an if statement is created, otherwise
  237. the ternary operator is used. Note that if the ``Piecewise`` lacks a
  238. default term, represented by ``(expr, True)`` then an error will be thrown.
  239. This is to prevent generating an expression that may not evaluate to
  240. anything.
  241. >>> from sympy import Piecewise
  242. >>> expr = Piecewise((x + 1, x > 0), (x, True))
  243. >>> print(jscode(expr, tau))
  244. if (x > 0) {
  245. tau = x + 1;
  246. }
  247. else {
  248. tau = x;
  249. }
  250. Support for loops is provided through ``Indexed`` types. With
  251. ``contract=True`` these expressions will be turned into loops, whereas
  252. ``contract=False`` will just print the assignment expression that should be
  253. looped over:
  254. >>> from sympy import Eq, IndexedBase, Idx
  255. >>> len_y = 5
  256. >>> y = IndexedBase('y', shape=(len_y,))
  257. >>> t = IndexedBase('t', shape=(len_y,))
  258. >>> Dy = IndexedBase('Dy', shape=(len_y-1,))
  259. >>> i = Idx('i', len_y-1)
  260. >>> e=Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i]))
  261. >>> jscode(e.rhs, assign_to=e.lhs, contract=False)
  262. 'Dy[i] = (y[i + 1] - y[i])/(t[i + 1] - t[i]);'
  263. Matrices are also supported, but a ``MatrixSymbol`` of the same dimensions
  264. must be provided to ``assign_to``. Note that any expression that can be
  265. generated normally can also exist inside a Matrix:
  266. >>> from sympy import Matrix, MatrixSymbol
  267. >>> mat = Matrix([x**2, Piecewise((x + 1, x > 0), (x, True)), sin(x)])
  268. >>> A = MatrixSymbol('A', 3, 1)
  269. >>> print(jscode(mat, A))
  270. A[0] = Math.pow(x, 2);
  271. if (x > 0) {
  272. A[1] = x + 1;
  273. }
  274. else {
  275. A[1] = x;
  276. }
  277. A[2] = Math.sin(x);
  278. """
  279. return JavascriptCodePrinter(settings).doprint(expr, assign_to)
  280. def print_jscode(expr, **settings):
  281. """Prints the Javascript representation of the given expression.
  282. See jscode for the meaning of the optional arguments.
  283. """
  284. print(jscode(expr, **settings))