Coverage for /usr/lib/python3/dist-packages/sympy/printing/rust.py: 25%

187 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1""" 

2Rust code printer 

3 

4The `RustCodePrinter` converts SymPy expressions into Rust expressions. 

5 

6A complete code generator, which uses `rust_code` extensively, can be found 

7in `sympy.utilities.codegen`. The `codegen` module can be used to generate 

8complete source code files. 

9 

10""" 

11 

12# Possible Improvement 

13# 

14# * make sure we follow Rust Style Guidelines_ 

15# * make use of pattern matching 

16# * better support for reference 

17# * generate generic code and use trait to make sure they have specific methods 

18# * use crates_ to get more math support 

19# - num_ 

20# + BigInt_, BigUint_ 

21# + Complex_ 

22# + Rational64_, Rational32_, BigRational_ 

23# 

24# .. _crates: https://crates.io/ 

25# .. _Guidelines: https://github.com/rust-lang/rust/tree/master/src/doc/style 

26# .. _num: http://rust-num.github.io/num/num/ 

27# .. _BigInt: http://rust-num.github.io/num/num/bigint/struct.BigInt.html 

28# .. _BigUint: http://rust-num.github.io/num/num/bigint/struct.BigUint.html 

29# .. _Complex: http://rust-num.github.io/num/num/complex/struct.Complex.html 

30# .. _Rational32: http://rust-num.github.io/num/num/rational/type.Rational32.html 

31# .. _Rational64: http://rust-num.github.io/num/num/rational/type.Rational64.html 

32# .. _BigRational: http://rust-num.github.io/num/num/rational/type.BigRational.html 

33 

34from __future__ import annotations 

35from typing import Any 

36 

37from sympy.core import S, Rational, Float, Lambda 

38from sympy.core.numbers import equal_valued 

39from sympy.printing.codeprinter import CodePrinter 

40 

41# Rust's methods for integer and float can be found at here : 

42# 

43# * `Rust - Primitive Type f64 <https://doc.rust-lang.org/std/primitive.f64.html>`_ 

44# * `Rust - Primitive Type i64 <https://doc.rust-lang.org/std/primitive.i64.html>`_ 

45# 

46# Function Style : 

47# 

48# 1. args[0].func(args[1:]), method with arguments 

49# 2. args[0].func(), method without arguments 

50# 3. args[1].func(), method without arguments (e.g. (e, x) => x.exp()) 

51# 4. func(args), function with arguments 

52 

53# dictionary mapping SymPy function to (argument_conditions, Rust_function). 

54# Used in RustCodePrinter._print_Function(self) 

55 

56# f64 method in Rust 

57known_functions = { 

58 # "": "is_nan", 

59 # "": "is_infinite", 

60 # "": "is_finite", 

61 # "": "is_normal", 

62 # "": "classify", 

63 "floor": "floor", 

64 "ceiling": "ceil", 

65 # "": "round", 

66 # "": "trunc", 

67 # "": "fract", 

68 "Abs": "abs", 

69 "sign": "signum", 

70 # "": "is_sign_positive", 

71 # "": "is_sign_negative", 

72 # "": "mul_add", 

73 "Pow": [(lambda base, exp: equal_valued(exp, -1), "recip", 2), # 1.0/x 

74 (lambda base, exp: equal_valued(exp, 0.5), "sqrt", 2), # x ** 0.5 

75 (lambda base, exp: equal_valued(exp, -0.5), "sqrt().recip", 2), # 1/(x ** 0.5) 

76 (lambda base, exp: exp == Rational(1, 3), "cbrt", 2), # x ** (1/3) 

77 (lambda base, exp: equal_valued(base, 2), "exp2", 3), # 2 ** x 

78 (lambda base, exp: exp.is_integer, "powi", 1), # x ** y, for i32 

79 (lambda base, exp: not exp.is_integer, "powf", 1)], # x ** y, for f64 

80 "exp": [(lambda exp: True, "exp", 2)], # e ** x 

81 "log": "ln", 

82 # "": "log", # number.log(base) 

83 # "": "log2", 

84 # "": "log10", 

85 # "": "to_degrees", 

86 # "": "to_radians", 

87 "Max": "max", 

88 "Min": "min", 

89 # "": "hypot", # (x**2 + y**2) ** 0.5 

90 "sin": "sin", 

91 "cos": "cos", 

92 "tan": "tan", 

93 "asin": "asin", 

94 "acos": "acos", 

95 "atan": "atan", 

96 "atan2": "atan2", 

97 # "": "sin_cos", 

98 # "": "exp_m1", # e ** x - 1 

99 # "": "ln_1p", # ln(1 + x) 

100 "sinh": "sinh", 

101 "cosh": "cosh", 

102 "tanh": "tanh", 

103 "asinh": "asinh", 

104 "acosh": "acosh", 

105 "atanh": "atanh", 

106 "sqrt": "sqrt", # To enable automatic rewrites 

107} 

108 

109# i64 method in Rust 

110# known_functions_i64 = { 

111# "": "min_value", 

112# "": "max_value", 

113# "": "from_str_radix", 

114# "": "count_ones", 

115# "": "count_zeros", 

116# "": "leading_zeros", 

117# "": "trainling_zeros", 

118# "": "rotate_left", 

119# "": "rotate_right", 

120# "": "swap_bytes", 

121# "": "from_be", 

122# "": "from_le", 

123# "": "to_be", # to big endian 

124# "": "to_le", # to little endian 

125# "": "checked_add", 

126# "": "checked_sub", 

127# "": "checked_mul", 

128# "": "checked_div", 

129# "": "checked_rem", 

130# "": "checked_neg", 

131# "": "checked_shl", 

132# "": "checked_shr", 

133# "": "checked_abs", 

134# "": "saturating_add", 

135# "": "saturating_sub", 

136# "": "saturating_mul", 

137# "": "wrapping_add", 

138# "": "wrapping_sub", 

139# "": "wrapping_mul", 

140# "": "wrapping_div", 

141# "": "wrapping_rem", 

142# "": "wrapping_neg", 

143# "": "wrapping_shl", 

144# "": "wrapping_shr", 

145# "": "wrapping_abs", 

146# "": "overflowing_add", 

147# "": "overflowing_sub", 

148# "": "overflowing_mul", 

149# "": "overflowing_div", 

150# "": "overflowing_rem", 

151# "": "overflowing_neg", 

152# "": "overflowing_shl", 

153# "": "overflowing_shr", 

154# "": "overflowing_abs", 

155# "Pow": "pow", 

156# "Abs": "abs", 

157# "sign": "signum", 

158# "": "is_positive", 

159# "": "is_negnative", 

160# } 

161 

162# These are the core reserved words in the Rust language. Taken from: 

163# http://doc.rust-lang.org/grammar.html#keywords 

164 

165reserved_words = ['abstract', 

166 'alignof', 

167 'as', 

168 'become', 

169 'box', 

170 'break', 

171 'const', 

172 'continue', 

173 'crate', 

174 'do', 

175 'else', 

176 'enum', 

177 'extern', 

178 'false', 

179 'final', 

180 'fn', 

181 'for', 

182 'if', 

183 'impl', 

184 'in', 

185 'let', 

186 'loop', 

187 'macro', 

188 'match', 

189 'mod', 

190 'move', 

191 'mut', 

192 'offsetof', 

193 'override', 

194 'priv', 

195 'proc', 

196 'pub', 

197 'pure', 

198 'ref', 

199 'return', 

200 'Self', 

201 'self', 

202 'sizeof', 

203 'static', 

204 'struct', 

205 'super', 

206 'trait', 

207 'true', 

208 'type', 

209 'typeof', 

210 'unsafe', 

211 'unsized', 

212 'use', 

213 'virtual', 

214 'where', 

215 'while', 

216 'yield'] 

217 

218 

219class RustCodePrinter(CodePrinter): 

220 """A printer to convert SymPy expressions to strings of Rust code""" 

221 printmethod = "_rust_code" 

222 language = "Rust" 

223 

224 _default_settings: dict[str, Any] = { 

225 'order': None, 

226 'full_prec': 'auto', 

227 'precision': 17, 

228 'user_functions': {}, 

229 'human': True, 

230 'contract': True, 

231 'dereference': set(), 

232 'error_on_reserved': False, 

233 'reserved_word_suffix': '_', 

234 'inline': False, 

235 } 

236 

237 def __init__(self, settings={}): 

238 CodePrinter.__init__(self, settings) 

239 self.known_functions = dict(known_functions) 

240 userfuncs = settings.get('user_functions', {}) 

241 self.known_functions.update(userfuncs) 

242 self._dereference = set(settings.get('dereference', [])) 

243 self.reserved_words = set(reserved_words) 

244 

245 def _rate_index_position(self, p): 

246 return p*5 

247 

248 def _get_statement(self, codestring): 

249 return "%s;" % codestring 

250 

251 def _get_comment(self, text): 

252 return "// %s" % text 

253 

254 def _declare_number_const(self, name, value): 

255 return "const %s: f64 = %s;" % (name, value) 

256 

257 def _format_code(self, lines): 

258 return self.indent_code(lines) 

259 

260 def _traverse_matrix_indices(self, mat): 

261 rows, cols = mat.shape 

262 return ((i, j) for i in range(rows) for j in range(cols)) 

263 

264 def _get_loop_opening_ending(self, indices): 

265 open_lines = [] 

266 close_lines = [] 

267 loopstart = "for %(var)s in %(start)s..%(end)s {" 

268 for i in indices: 

269 # Rust arrays start at 0 and end at dimension-1 

270 open_lines.append(loopstart % { 

271 'var': self._print(i), 

272 'start': self._print(i.lower), 

273 'end': self._print(i.upper + 1)}) 

274 close_lines.append("}") 

275 return open_lines, close_lines 

276 

277 def _print_caller_var(self, expr): 

278 if len(expr.args) > 1: 

279 # for something like `sin(x + y + z)`, 

280 # make sure we can get '(x + y + z).sin()' 

281 # instead of 'x + y + z.sin()' 

282 return '(' + self._print(expr) + ')' 

283 elif expr.is_number: 

284 return self._print(expr, _type=True) 

285 else: 

286 return self._print(expr) 

287 

288 def _print_Function(self, expr): 

289 """ 

290 basic function for printing `Function` 

291 

292 Function Style : 

293 

294 1. args[0].func(args[1:]), method with arguments 

295 2. args[0].func(), method without arguments 

296 3. args[1].func(), method without arguments (e.g. (e, x) => x.exp()) 

297 4. func(args), function with arguments 

298 """ 

299 

300 if expr.func.__name__ in self.known_functions: 

301 cond_func = self.known_functions[expr.func.__name__] 

302 func = None 

303 style = 1 

304 if isinstance(cond_func, str): 

305 func = cond_func 

306 else: 

307 for cond, func, style in cond_func: 

308 if cond(*expr.args): 

309 break 

310 if func is not None: 

311 if style == 1: 

312 ret = "%(var)s.%(method)s(%(args)s)" % { 

313 'var': self._print_caller_var(expr.args[0]), 

314 'method': func, 

315 'args': self.stringify(expr.args[1:], ", ") if len(expr.args) > 1 else '' 

316 } 

317 elif style == 2: 

318 ret = "%(var)s.%(method)s()" % { 

319 'var': self._print_caller_var(expr.args[0]), 

320 'method': func, 

321 } 

322 elif style == 3: 

323 ret = "%(var)s.%(method)s()" % { 

324 'var': self._print_caller_var(expr.args[1]), 

325 'method': func, 

326 } 

327 else: 

328 ret = "%(func)s(%(args)s)" % { 

329 'func': func, 

330 'args': self.stringify(expr.args, ", "), 

331 } 

332 return ret 

333 elif hasattr(expr, '_imp_') and isinstance(expr._imp_, Lambda): 

334 # inlined function 

335 return self._print(expr._imp_(*expr.args)) 

336 elif expr.func.__name__ in self._rewriteable_functions: 

337 # Simple rewrite to supported function possible 

338 target_f, required_fs = self._rewriteable_functions[expr.func.__name__] 

339 if self._can_print(target_f) and all(self._can_print(f) for f in required_fs): 

340 return self._print(expr.rewrite(target_f)) 

341 else: 

342 return self._print_not_supported(expr) 

343 

344 def _print_Pow(self, expr): 

345 if expr.base.is_integer and not expr.exp.is_integer: 

346 expr = type(expr)(Float(expr.base), expr.exp) 

347 return self._print(expr) 

348 return self._print_Function(expr) 

349 

350 def _print_Float(self, expr, _type=False): 

351 ret = super()._print_Float(expr) 

352 if _type: 

353 return ret + '_f64' 

354 else: 

355 return ret 

356 

357 def _print_Integer(self, expr, _type=False): 

358 ret = super()._print_Integer(expr) 

359 if _type: 

360 return ret + '_i32' 

361 else: 

362 return ret 

363 

364 def _print_Rational(self, expr): 

365 p, q = int(expr.p), int(expr.q) 

366 return '%d_f64/%d.0' % (p, q) 

367 

368 def _print_Relational(self, expr): 

369 lhs_code = self._print(expr.lhs) 

370 rhs_code = self._print(expr.rhs) 

371 op = expr.rel_op 

372 return "{} {} {}".format(lhs_code, op, rhs_code) 

373 

374 def _print_Indexed(self, expr): 

375 # calculate index for 1d array 

376 dims = expr.shape 

377 elem = S.Zero 

378 offset = S.One 

379 for i in reversed(range(expr.rank)): 

380 elem += expr.indices[i]*offset 

381 offset *= dims[i] 

382 return "%s[%s]" % (self._print(expr.base.label), self._print(elem)) 

383 

384 def _print_Idx(self, expr): 

385 return expr.label.name 

386 

387 def _print_Dummy(self, expr): 

388 return expr.name 

389 

390 def _print_Exp1(self, expr, _type=False): 

391 return "E" 

392 

393 def _print_Pi(self, expr, _type=False): 

394 return 'PI' 

395 

396 def _print_Infinity(self, expr, _type=False): 

397 return 'INFINITY' 

398 

399 def _print_NegativeInfinity(self, expr, _type=False): 

400 return 'NEG_INFINITY' 

401 

402 def _print_BooleanTrue(self, expr, _type=False): 

403 return "true" 

404 

405 def _print_BooleanFalse(self, expr, _type=False): 

406 return "false" 

407 

408 def _print_bool(self, expr, _type=False): 

409 return str(expr).lower() 

410 

411 def _print_NaN(self, expr, _type=False): 

412 return "NAN" 

413 

414 def _print_Piecewise(self, expr): 

415 if expr.args[-1].cond != True: 

416 # We need the last conditional to be a True, otherwise the resulting 

417 # function may not return a result. 

418 raise ValueError("All Piecewise expressions must contain an " 

419 "(expr, True) statement to be used as a default " 

420 "condition. Without one, the generated " 

421 "expression may not evaluate to anything under " 

422 "some condition.") 

423 lines = [] 

424 

425 for i, (e, c) in enumerate(expr.args): 

426 if i == 0: 

427 lines.append("if (%s) {" % self._print(c)) 

428 elif i == len(expr.args) - 1 and c == True: 

429 lines[-1] += " else {" 

430 else: 

431 lines[-1] += " else if (%s) {" % self._print(c) 

432 code0 = self._print(e) 

433 lines.append(code0) 

434 lines.append("}") 

435 

436 if self._settings['inline']: 

437 return " ".join(lines) 

438 else: 

439 return "\n".join(lines) 

440 

441 def _print_ITE(self, expr): 

442 from sympy.functions import Piecewise 

443 return self._print(expr.rewrite(Piecewise, deep=False)) 

444 

445 def _print_MatrixBase(self, A): 

446 if A.cols == 1: 

447 return "[%s]" % ", ".join(self._print(a) for a in A) 

448 else: 

449 raise ValueError("Full Matrix Support in Rust need Crates (https://crates.io/keywords/matrix).") 

450 

451 def _print_SparseRepMatrix(self, mat): 

452 # do not allow sparse matrices to be made dense 

453 return self._print_not_supported(mat) 

454 

455 def _print_MatrixElement(self, expr): 

456 return "%s[%s]" % (expr.parent, 

457 expr.j + expr.i*expr.parent.shape[1]) 

458 

459 def _print_Symbol(self, expr): 

460 

461 name = super()._print_Symbol(expr) 

462 

463 if expr in self._dereference: 

464 return '(*%s)' % name 

465 else: 

466 return name 

467 

468 def _print_Assignment(self, expr): 

469 from sympy.tensor.indexed import IndexedBase 

470 lhs = expr.lhs 

471 rhs = expr.rhs 

472 if self._settings["contract"] and (lhs.has(IndexedBase) or 

473 rhs.has(IndexedBase)): 

474 # Here we check if there is looping to be done, and if so 

475 # print the required loops. 

476 return self._doprint_loops(rhs, lhs) 

477 else: 

478 lhs_code = self._print(lhs) 

479 rhs_code = self._print(rhs) 

480 return self._get_statement("%s = %s" % (lhs_code, rhs_code)) 

481 

482 def indent_code(self, code): 

483 """Accepts a string of code or a list of code lines""" 

484 

485 if isinstance(code, str): 

486 code_lines = self.indent_code(code.splitlines(True)) 

487 return ''.join(code_lines) 

488 

489 tab = " " 

490 inc_token = ('{', '(', '{\n', '(\n') 

491 dec_token = ('}', ')') 

492 

493 code = [ line.lstrip(' \t') for line in code ] 

494 

495 increase = [ int(any(map(line.endswith, inc_token))) for line in code ] 

496 decrease = [ int(any(map(line.startswith, dec_token))) 

497 for line in code ] 

498 

499 pretty = [] 

500 level = 0 

501 for n, line in enumerate(code): 

502 if line in ('', '\n'): 

503 pretty.append(line) 

504 continue 

505 level -= decrease[n] 

506 pretty.append("%s%s" % (tab*level, line)) 

507 level += increase[n] 

508 return pretty 

509 

510 

511def rust_code(expr, assign_to=None, **settings): 

512 """Converts an expr to a string of Rust code 

513 

514 Parameters 

515 ========== 

516 

517 expr : Expr 

518 A SymPy expression to be converted. 

519 assign_to : optional 

520 When given, the argument is used as the name of the variable to which 

521 the expression is assigned. Can be a string, ``Symbol``, 

522 ``MatrixSymbol``, or ``Indexed`` type. This is helpful in case of 

523 line-wrapping, or for expressions that generate multi-line statements. 

524 precision : integer, optional 

525 The precision for numbers such as pi [default=15]. 

526 user_functions : dict, optional 

527 A dictionary where the keys are string representations of either 

528 ``FunctionClass`` or ``UndefinedFunction`` instances and the values 

529 are their desired C string representations. Alternatively, the 

530 dictionary value can be a list of tuples i.e. [(argument_test, 

531 cfunction_string)]. See below for examples. 

532 dereference : iterable, optional 

533 An iterable of symbols that should be dereferenced in the printed code 

534 expression. These would be values passed by address to the function. 

535 For example, if ``dereference=[a]``, the resulting code would print 

536 ``(*a)`` instead of ``a``. 

537 human : bool, optional 

538 If True, the result is a single string that may contain some constant 

539 declarations for the number symbols. If False, the same information is 

540 returned in a tuple of (symbols_to_declare, not_supported_functions, 

541 code_text). [default=True]. 

542 contract: bool, optional 

543 If True, ``Indexed`` instances are assumed to obey tensor contraction 

544 rules and the corresponding nested loops over indices are generated. 

545 Setting contract=False will not generate loops, instead the user is 

546 responsible to provide values for the indices in the code. 

547 [default=True]. 

548 

549 Examples 

550 ======== 

551 

552 >>> from sympy import rust_code, symbols, Rational, sin, ceiling, Abs, Function 

553 >>> x, tau = symbols("x, tau") 

554 >>> rust_code((2*tau)**Rational(7, 2)) 

555 '8*1.4142135623731*tau.powf(7_f64/2.0)' 

556 >>> rust_code(sin(x), assign_to="s") 

557 's = x.sin();' 

558 

559 Simple custom printing can be defined for certain types by passing a 

560 dictionary of {"type" : "function"} to the ``user_functions`` kwarg. 

561 Alternatively, the dictionary value can be a list of tuples i.e. 

562 [(argument_test, cfunction_string)]. 

563 

564 >>> custom_functions = { 

565 ... "ceiling": "CEIL", 

566 ... "Abs": [(lambda x: not x.is_integer, "fabs", 4), 

567 ... (lambda x: x.is_integer, "ABS", 4)], 

568 ... "func": "f" 

569 ... } 

570 >>> func = Function('func') 

571 >>> rust_code(func(Abs(x) + ceiling(x)), user_functions=custom_functions) 

572 '(fabs(x) + x.CEIL()).f()' 

573 

574 ``Piecewise`` expressions are converted into conditionals. If an 

575 ``assign_to`` variable is provided an if statement is created, otherwise 

576 the ternary operator is used. Note that if the ``Piecewise`` lacks a 

577 default term, represented by ``(expr, True)`` then an error will be thrown. 

578 This is to prevent generating an expression that may not evaluate to 

579 anything. 

580 

581 >>> from sympy import Piecewise 

582 >>> expr = Piecewise((x + 1, x > 0), (x, True)) 

583 >>> print(rust_code(expr, tau)) 

584 tau = if (x > 0) { 

585 x + 1 

586 } else { 

587 x 

588 }; 

589 

590 Support for loops is provided through ``Indexed`` types. With 

591 ``contract=True`` these expressions will be turned into loops, whereas 

592 ``contract=False`` will just print the assignment expression that should be 

593 looped over: 

594 

595 >>> from sympy import Eq, IndexedBase, Idx 

596 >>> len_y = 5 

597 >>> y = IndexedBase('y', shape=(len_y,)) 

598 >>> t = IndexedBase('t', shape=(len_y,)) 

599 >>> Dy = IndexedBase('Dy', shape=(len_y-1,)) 

600 >>> i = Idx('i', len_y-1) 

601 >>> e=Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i])) 

602 >>> rust_code(e.rhs, assign_to=e.lhs, contract=False) 

603 'Dy[i] = (y[i + 1] - y[i])/(t[i + 1] - t[i]);' 

604 

605 Matrices are also supported, but a ``MatrixSymbol`` of the same dimensions 

606 must be provided to ``assign_to``. Note that any expression that can be 

607 generated normally can also exist inside a Matrix: 

608 

609 >>> from sympy import Matrix, MatrixSymbol 

610 >>> mat = Matrix([x**2, Piecewise((x + 1, x > 0), (x, True)), sin(x)]) 

611 >>> A = MatrixSymbol('A', 3, 1) 

612 >>> print(rust_code(mat, A)) 

613 A = [x.powi(2), if (x > 0) { 

614 x + 1 

615 } else { 

616 x 

617 }, x.sin()]; 

618 """ 

619 

620 return RustCodePrinter(settings).doprint(expr, assign_to) 

621 

622 

623def print_rust_code(expr, **settings): 

624 """Prints Rust representation of the given expression.""" 

625 print(rust_code(expr, **settings))