Coverage for /usr/lib/python3/dist-packages/sympy/codegen/ast.py: 54%

548 statements  

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

1""" 

2Types used to represent a full function/module as an Abstract Syntax Tree. 

3 

4Most types are small, and are merely used as tokens in the AST. A tree diagram 

5has been included below to illustrate the relationships between the AST types. 

6 

7 

8AST Type Tree 

9------------- 

10:: 

11 

12 *Basic* 

13 | 

14 | 

15 CodegenAST 

16 | 

17 |--->AssignmentBase 

18 | |--->Assignment 

19 | |--->AugmentedAssignment 

20 | |--->AddAugmentedAssignment 

21 | |--->SubAugmentedAssignment 

22 | |--->MulAugmentedAssignment 

23 | |--->DivAugmentedAssignment 

24 | |--->ModAugmentedAssignment 

25 | 

26 |--->CodeBlock 

27 | 

28 | 

29 |--->Token 

30 |--->Attribute 

31 |--->For 

32 |--->String 

33 | |--->QuotedString 

34 | |--->Comment 

35 |--->Type 

36 | |--->IntBaseType 

37 | | |--->_SizedIntType 

38 | | |--->SignedIntType 

39 | | |--->UnsignedIntType 

40 | |--->FloatBaseType 

41 | |--->FloatType 

42 | |--->ComplexBaseType 

43 | |--->ComplexType 

44 |--->Node 

45 | |--->Variable 

46 | | |---> Pointer 

47 | |--->FunctionPrototype 

48 | |--->FunctionDefinition 

49 |--->Element 

50 |--->Declaration 

51 |--->While 

52 |--->Scope 

53 |--->Stream 

54 |--->Print 

55 |--->FunctionCall 

56 |--->BreakToken 

57 |--->ContinueToken 

58 |--->NoneToken 

59 |--->Return 

60 

61 

62Predefined types 

63---------------- 

64 

65A number of ``Type`` instances are provided in the ``sympy.codegen.ast`` module 

66for convenience. Perhaps the two most common ones for code-generation (of numeric 

67codes) are ``float32`` and ``float64`` (known as single and double precision respectively). 

68There are also precision generic versions of Types (for which the codeprinters selects the 

69underlying data type at time of printing): ``real``, ``integer``, ``complex_``, ``bool_``. 

70 

71The other ``Type`` instances defined are: 

72 

73- ``intc``: Integer type used by C's "int". 

74- ``intp``: Integer type used by C's "unsigned". 

75- ``int8``, ``int16``, ``int32``, ``int64``: n-bit integers. 

76- ``uint8``, ``uint16``, ``uint32``, ``uint64``: n-bit unsigned integers. 

77- ``float80``: known as "extended precision" on modern x86/amd64 hardware. 

78- ``complex64``: Complex number represented by two ``float32`` numbers 

79- ``complex128``: Complex number represented by two ``float64`` numbers 

80 

81Using the nodes 

82--------------- 

83 

84It is possible to construct simple algorithms using the AST nodes. Let's construct a loop applying 

85Newton's method:: 

86 

87 >>> from sympy import symbols, cos 

88 >>> from sympy.codegen.ast import While, Assignment, aug_assign, Print 

89 >>> t, dx, x = symbols('tol delta val') 

90 >>> expr = cos(x) - x**3 

91 >>> whl = While(abs(dx) > t, [ 

92 ... Assignment(dx, -expr/expr.diff(x)), 

93 ... aug_assign(x, '+', dx), 

94 ... Print([x]) 

95 ... ]) 

96 >>> from sympy import pycode 

97 >>> py_str = pycode(whl) 

98 >>> print(py_str) 

99 while (abs(delta) > tol): 

100 delta = (val**3 - math.cos(val))/(-3*val**2 - math.sin(val)) 

101 val += delta 

102 print(val) 

103 >>> import math 

104 >>> tol, val, delta = 1e-5, 0.5, float('inf') 

105 >>> exec(py_str) 

106 1.1121416371 

107 0.909672693737 

108 0.867263818209 

109 0.865477135298 

110 0.865474033111 

111 >>> print('%3.1g' % (math.cos(val) - val**3)) 

112 -3e-11 

113 

114If we want to generate Fortran code for the same while loop we simple call ``fcode``:: 

115 

116 >>> from sympy import fcode 

117 >>> print(fcode(whl, standard=2003, source_format='free')) 

118 do while (abs(delta) > tol) 

119 delta = (val**3 - cos(val))/(-3*val**2 - sin(val)) 

120 val = val + delta 

121 print *, val 

122 end do 

123 

124There is a function constructing a loop (or a complete function) like this in 

125:mod:`sympy.codegen.algorithms`. 

126 

127""" 

128 

129from __future__ import annotations 

130from typing import Any 

131 

132from collections import defaultdict 

133 

134from sympy.core.relational import (Ge, Gt, Le, Lt) 

135from sympy.core import Symbol, Tuple, Dummy 

136from sympy.core.basic import Basic 

137from sympy.core.expr import Expr, Atom 

138from sympy.core.numbers import Float, Integer, oo 

139from sympy.core.sympify import _sympify, sympify, SympifyError 

140from sympy.utilities.iterables import (iterable, topological_sort, 

141 numbered_symbols, filter_symbols) 

142 

143 

144def _mk_Tuple(args): 

145 """ 

146 Create a SymPy Tuple object from an iterable, converting Python strings to 

147 AST strings. 

148 

149 Parameters 

150 ========== 

151 

152 args: iterable 

153 Arguments to :class:`sympy.Tuple`. 

154 

155 Returns 

156 ======= 

157 

158 sympy.Tuple 

159 """ 

160 args = [String(arg) if isinstance(arg, str) else arg for arg in args] 

161 return Tuple(*args) 

162 

163 

164class CodegenAST(Basic): 

165 __slots__ = () 

166 

167 

168class Token(CodegenAST): 

169 """ Base class for the AST types. 

170 

171 Explanation 

172 =========== 

173 

174 Defining fields are set in ``_fields``. Attributes (defined in _fields) 

175 are only allowed to contain instances of Basic (unless atomic, see 

176 ``String``). The arguments to ``__new__()`` correspond to the attributes in 

177 the order defined in ``_fields`. The ``defaults`` class attribute is a 

178 dictionary mapping attribute names to their default values. 

179 

180 Subclasses should not need to override the ``__new__()`` method. They may 

181 define a class or static method named ``_construct_<attr>`` for each 

182 attribute to process the value passed to ``__new__()``. Attributes listed 

183 in the class attribute ``not_in_args`` are not passed to :class:`~.Basic`. 

184 """ 

185 

186 __slots__: tuple[str, ...] = () 

187 _fields = __slots__ 

188 defaults: dict[str, Any] = {} 

189 not_in_args: list[str] = [] 

190 indented_args = ['body'] 

191 

192 @property 

193 def is_Atom(self): 

194 return len(self._fields) == 0 

195 

196 @classmethod 

197 def _get_constructor(cls, attr): 

198 """ Get the constructor function for an attribute by name. """ 

199 return getattr(cls, '_construct_%s' % attr, lambda x: x) 

200 

201 @classmethod 

202 def _construct(cls, attr, arg): 

203 """ Construct an attribute value from argument passed to ``__new__()``. """ 

204 # arg may be ``NoneToken()``, so comparison is done using == instead of ``is`` operator 

205 if arg == None: 

206 return cls.defaults.get(attr, none) 

207 else: 

208 if isinstance(arg, Dummy): # SymPy's replace uses Dummy instances 

209 return arg 

210 else: 

211 return cls._get_constructor(attr)(arg) 

212 

213 def __new__(cls, *args, **kwargs): 

214 # Pass through existing instances when given as sole argument 

215 if len(args) == 1 and not kwargs and isinstance(args[0], cls): 

216 return args[0] 

217 

218 if len(args) > len(cls._fields): 

219 raise ValueError("Too many arguments (%d), expected at most %d" % (len(args), len(cls._fields))) 

220 

221 attrvals = [] 

222 

223 # Process positional arguments 

224 for attrname, argval in zip(cls._fields, args): 

225 if attrname in kwargs: 

226 raise TypeError('Got multiple values for attribute %r' % attrname) 

227 

228 attrvals.append(cls._construct(attrname, argval)) 

229 

230 # Process keyword arguments 

231 for attrname in cls._fields[len(args):]: 

232 if attrname in kwargs: 

233 argval = kwargs.pop(attrname) 

234 

235 elif attrname in cls.defaults: 

236 argval = cls.defaults[attrname] 

237 

238 else: 

239 raise TypeError('No value for %r given and attribute has no default' % attrname) 

240 

241 attrvals.append(cls._construct(attrname, argval)) 

242 

243 if kwargs: 

244 raise ValueError("Unknown keyword arguments: %s" % ' '.join(kwargs)) 

245 

246 # Parent constructor 

247 basic_args = [ 

248 val for attr, val in zip(cls._fields, attrvals) 

249 if attr not in cls.not_in_args 

250 ] 

251 obj = CodegenAST.__new__(cls, *basic_args) 

252 

253 # Set attributes 

254 for attr, arg in zip(cls._fields, attrvals): 

255 setattr(obj, attr, arg) 

256 

257 return obj 

258 

259 def __eq__(self, other): 

260 if not isinstance(other, self.__class__): 

261 return False 

262 for attr in self._fields: 

263 if getattr(self, attr) != getattr(other, attr): 

264 return False 

265 return True 

266 

267 def _hashable_content(self): 

268 return tuple([getattr(self, attr) for attr in self._fields]) 

269 

270 def __hash__(self): 

271 return super().__hash__() 

272 

273 def _joiner(self, k, indent_level): 

274 return (',\n' + ' '*indent_level) if k in self.indented_args else ', ' 

275 

276 def _indented(self, printer, k, v, *args, **kwargs): 

277 il = printer._context['indent_level'] 

278 def _print(arg): 

279 if isinstance(arg, Token): 

280 return printer._print(arg, *args, joiner=self._joiner(k, il), **kwargs) 

281 else: 

282 return printer._print(arg, *args, **kwargs) 

283 

284 if isinstance(v, Tuple): 

285 joined = self._joiner(k, il).join([_print(arg) for arg in v.args]) 

286 if k in self.indented_args: 

287 return '(\n' + ' '*il + joined + ',\n' + ' '*(il - 4) + ')' 

288 else: 

289 return ('({0},)' if len(v.args) == 1 else '({0})').format(joined) 

290 else: 

291 return _print(v) 

292 

293 def _sympyrepr(self, printer, *args, joiner=', ', **kwargs): 

294 from sympy.printing.printer import printer_context 

295 exclude = kwargs.get('exclude', ()) 

296 values = [getattr(self, k) for k in self._fields] 

297 indent_level = printer._context.get('indent_level', 0) 

298 

299 arg_reprs = [] 

300 

301 for i, (attr, value) in enumerate(zip(self._fields, values)): 

302 if attr in exclude: 

303 continue 

304 

305 # Skip attributes which have the default value 

306 if attr in self.defaults and value == self.defaults[attr]: 

307 continue 

308 

309 ilvl = indent_level + 4 if attr in self.indented_args else 0 

310 with printer_context(printer, indent_level=ilvl): 

311 indented = self._indented(printer, attr, value, *args, **kwargs) 

312 arg_reprs.append(('{1}' if i == 0 else '{0}={1}').format(attr, indented.lstrip())) 

313 

314 return "{}({})".format(self.__class__.__name__, joiner.join(arg_reprs)) 

315 

316 _sympystr = _sympyrepr 

317 

318 def __repr__(self): # sympy.core.Basic.__repr__ uses sstr 

319 from sympy.printing import srepr 

320 return srepr(self) 

321 

322 def kwargs(self, exclude=(), apply=None): 

323 """ Get instance's attributes as dict of keyword arguments. 

324 

325 Parameters 

326 ========== 

327 

328 exclude : collection of str 

329 Collection of keywords to exclude. 

330 

331 apply : callable, optional 

332 Function to apply to all values. 

333 """ 

334 kwargs = {k: getattr(self, k) for k in self._fields if k not in exclude} 

335 if apply is not None: 

336 return {k: apply(v) for k, v in kwargs.items()} 

337 else: 

338 return kwargs 

339 

340class BreakToken(Token): 

341 """ Represents 'break' in C/Python ('exit' in Fortran). 

342 

343 Use the premade instance ``break_`` or instantiate manually. 

344 

345 Examples 

346 ======== 

347 

348 >>> from sympy import ccode, fcode 

349 >>> from sympy.codegen.ast import break_ 

350 >>> ccode(break_) 

351 'break' 

352 >>> fcode(break_, source_format='free') 

353 'exit' 

354 """ 

355 

356break_ = BreakToken() 

357 

358 

359class ContinueToken(Token): 

360 """ Represents 'continue' in C/Python ('cycle' in Fortran) 

361 

362 Use the premade instance ``continue_`` or instantiate manually. 

363 

364 Examples 

365 ======== 

366 

367 >>> from sympy import ccode, fcode 

368 >>> from sympy.codegen.ast import continue_ 

369 >>> ccode(continue_) 

370 'continue' 

371 >>> fcode(continue_, source_format='free') 

372 'cycle' 

373 """ 

374 

375continue_ = ContinueToken() 

376 

377class NoneToken(Token): 

378 """ The AST equivalence of Python's NoneType 

379 

380 The corresponding instance of Python's ``None`` is ``none``. 

381 

382 Examples 

383 ======== 

384 

385 >>> from sympy.codegen.ast import none, Variable 

386 >>> from sympy import pycode 

387 >>> print(pycode(Variable('x').as_Declaration(value=none))) 

388 x = None 

389 

390 """ 

391 def __eq__(self, other): 

392 return other is None or isinstance(other, NoneToken) 

393 

394 def _hashable_content(self): 

395 return () 

396 

397 def __hash__(self): 

398 return super().__hash__() 

399 

400 

401none = NoneToken() 

402 

403 

404class AssignmentBase(CodegenAST): 

405 """ Abstract base class for Assignment and AugmentedAssignment. 

406 

407 Attributes: 

408 =========== 

409 

410 op : str 

411 Symbol for assignment operator, e.g. "=", "+=", etc. 

412 """ 

413 

414 def __new__(cls, lhs, rhs): 

415 lhs = _sympify(lhs) 

416 rhs = _sympify(rhs) 

417 

418 cls._check_args(lhs, rhs) 

419 

420 return super().__new__(cls, lhs, rhs) 

421 

422 @property 

423 def lhs(self): 

424 return self.args[0] 

425 

426 @property 

427 def rhs(self): 

428 return self.args[1] 

429 

430 @classmethod 

431 def _check_args(cls, lhs, rhs): 

432 """ Check arguments to __new__ and raise exception if any problems found. 

433 

434 Derived classes may wish to override this. 

435 """ 

436 from sympy.matrices.expressions.matexpr import ( 

437 MatrixElement, MatrixSymbol) 

438 from sympy.tensor.indexed import Indexed 

439 from sympy.tensor.array.expressions import ArrayElement 

440 

441 # Tuple of things that can be on the lhs of an assignment 

442 assignable = (Symbol, MatrixSymbol, MatrixElement, Indexed, Element, Variable, 

443 ArrayElement) 

444 if not isinstance(lhs, assignable): 

445 raise TypeError("Cannot assign to lhs of type %s." % type(lhs)) 

446 

447 # Indexed types implement shape, but don't define it until later. This 

448 # causes issues in assignment validation. For now, matrices are defined 

449 # as anything with a shape that is not an Indexed 

450 lhs_is_mat = hasattr(lhs, 'shape') and not isinstance(lhs, Indexed) 

451 rhs_is_mat = hasattr(rhs, 'shape') and not isinstance(rhs, Indexed) 

452 

453 # If lhs and rhs have same structure, then this assignment is ok 

454 if lhs_is_mat: 

455 if not rhs_is_mat: 

456 raise ValueError("Cannot assign a scalar to a matrix.") 

457 elif lhs.shape != rhs.shape: 

458 raise ValueError("Dimensions of lhs and rhs do not align.") 

459 elif rhs_is_mat and not lhs_is_mat: 

460 raise ValueError("Cannot assign a matrix to a scalar.") 

461 

462 

463class Assignment(AssignmentBase): 

464 """ 

465 Represents variable assignment for code generation. 

466 

467 Parameters 

468 ========== 

469 

470 lhs : Expr 

471 SymPy object representing the lhs of the expression. These should be 

472 singular objects, such as one would use in writing code. Notable types 

473 include Symbol, MatrixSymbol, MatrixElement, and Indexed. Types that 

474 subclass these types are also supported. 

475 

476 rhs : Expr 

477 SymPy object representing the rhs of the expression. This can be any 

478 type, provided its shape corresponds to that of the lhs. For example, 

479 a Matrix type can be assigned to MatrixSymbol, but not to Symbol, as 

480 the dimensions will not align. 

481 

482 Examples 

483 ======== 

484 

485 >>> from sympy import symbols, MatrixSymbol, Matrix 

486 >>> from sympy.codegen.ast import Assignment 

487 >>> x, y, z = symbols('x, y, z') 

488 >>> Assignment(x, y) 

489 Assignment(x, y) 

490 >>> Assignment(x, 0) 

491 Assignment(x, 0) 

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

493 >>> mat = Matrix([x, y, z]).T 

494 >>> Assignment(A, mat) 

495 Assignment(A, Matrix([[x, y, z]])) 

496 >>> Assignment(A[0, 1], x) 

497 Assignment(A[0, 1], x) 

498 """ 

499 

500 op = ':=' 

501 

502 

503class AugmentedAssignment(AssignmentBase): 

504 """ 

505 Base class for augmented assignments. 

506 

507 Attributes: 

508 =========== 

509 

510 binop : str 

511 Symbol for binary operation being applied in the assignment, such as "+", 

512 "*", etc. 

513 """ 

514 binop = None # type: str 

515 

516 @property 

517 def op(self): 

518 return self.binop + '=' 

519 

520 

521class AddAugmentedAssignment(AugmentedAssignment): 

522 binop = '+' 

523 

524 

525class SubAugmentedAssignment(AugmentedAssignment): 

526 binop = '-' 

527 

528 

529class MulAugmentedAssignment(AugmentedAssignment): 

530 binop = '*' 

531 

532 

533class DivAugmentedAssignment(AugmentedAssignment): 

534 binop = '/' 

535 

536 

537class ModAugmentedAssignment(AugmentedAssignment): 

538 binop = '%' 

539 

540 

541# Mapping from binary op strings to AugmentedAssignment subclasses 

542augassign_classes = { 

543 cls.binop: cls for cls in [ 

544 AddAugmentedAssignment, SubAugmentedAssignment, MulAugmentedAssignment, 

545 DivAugmentedAssignment, ModAugmentedAssignment 

546 ] 

547} 

548 

549 

550def aug_assign(lhs, op, rhs): 

551 """ 

552 Create 'lhs op= rhs'. 

553 

554 Explanation 

555 =========== 

556 

557 Represents augmented variable assignment for code generation. This is a 

558 convenience function. You can also use the AugmentedAssignment classes 

559 directly, like AddAugmentedAssignment(x, y). 

560 

561 Parameters 

562 ========== 

563 

564 lhs : Expr 

565 SymPy object representing the lhs of the expression. These should be 

566 singular objects, such as one would use in writing code. Notable types 

567 include Symbol, MatrixSymbol, MatrixElement, and Indexed. Types that 

568 subclass these types are also supported. 

569 

570 op : str 

571 Operator (+, -, /, \\*, %). 

572 

573 rhs : Expr 

574 SymPy object representing the rhs of the expression. This can be any 

575 type, provided its shape corresponds to that of the lhs. For example, 

576 a Matrix type can be assigned to MatrixSymbol, but not to Symbol, as 

577 the dimensions will not align. 

578 

579 Examples 

580 ======== 

581 

582 >>> from sympy import symbols 

583 >>> from sympy.codegen.ast import aug_assign 

584 >>> x, y = symbols('x, y') 

585 >>> aug_assign(x, '+', y) 

586 AddAugmentedAssignment(x, y) 

587 """ 

588 if op not in augassign_classes: 

589 raise ValueError("Unrecognized operator %s" % op) 

590 return augassign_classes[op](lhs, rhs) 

591 

592 

593class CodeBlock(CodegenAST): 

594 """ 

595 Represents a block of code. 

596 

597 Explanation 

598 =========== 

599 

600 For now only assignments are supported. This restriction will be lifted in 

601 the future. 

602 

603 Useful attributes on this object are: 

604 

605 ``left_hand_sides``: 

606 Tuple of left-hand sides of assignments, in order. 

607 ``left_hand_sides``: 

608 Tuple of right-hand sides of assignments, in order. 

609 ``free_symbols``: Free symbols of the expressions in the right-hand sides 

610 which do not appear in the left-hand side of an assignment. 

611 

612 Useful methods on this object are: 

613 

614 ``topological_sort``: 

615 Class method. Return a CodeBlock with assignments 

616 sorted so that variables are assigned before they 

617 are used. 

618 ``cse``: 

619 Return a new CodeBlock with common subexpressions eliminated and 

620 pulled out as assignments. 

621 

622 Examples 

623 ======== 

624 

625 >>> from sympy import symbols, ccode 

626 >>> from sympy.codegen.ast import CodeBlock, Assignment 

627 >>> x, y = symbols('x y') 

628 >>> c = CodeBlock(Assignment(x, 1), Assignment(y, x + 1)) 

629 >>> print(ccode(c)) 

630 x = 1; 

631 y = x + 1; 

632 

633 """ 

634 def __new__(cls, *args): 

635 left_hand_sides = [] 

636 right_hand_sides = [] 

637 for i in args: 

638 if isinstance(i, Assignment): 

639 lhs, rhs = i.args 

640 left_hand_sides.append(lhs) 

641 right_hand_sides.append(rhs) 

642 

643 obj = CodegenAST.__new__(cls, *args) 

644 

645 obj.left_hand_sides = Tuple(*left_hand_sides) 

646 obj.right_hand_sides = Tuple(*right_hand_sides) 

647 return obj 

648 

649 def __iter__(self): 

650 return iter(self.args) 

651 

652 def _sympyrepr(self, printer, *args, **kwargs): 

653 il = printer._context.get('indent_level', 0) 

654 joiner = ',\n' + ' '*il 

655 joined = joiner.join(map(printer._print, self.args)) 

656 return ('{}(\n'.format(' '*(il-4) + self.__class__.__name__,) + 

657 ' '*il + joined + '\n' + ' '*(il - 4) + ')') 

658 

659 _sympystr = _sympyrepr 

660 

661 @property 

662 def free_symbols(self): 

663 return super().free_symbols - set(self.left_hand_sides) 

664 

665 @classmethod 

666 def topological_sort(cls, assignments): 

667 """ 

668 Return a CodeBlock with topologically sorted assignments so that 

669 variables are assigned before they are used. 

670 

671 Examples 

672 ======== 

673 

674 The existing order of assignments is preserved as much as possible. 

675 

676 This function assumes that variables are assigned to only once. 

677 

678 This is a class constructor so that the default constructor for 

679 CodeBlock can error when variables are used before they are assigned. 

680 

681 >>> from sympy import symbols 

682 >>> from sympy.codegen.ast import CodeBlock, Assignment 

683 >>> x, y, z = symbols('x y z') 

684 

685 >>> assignments = [ 

686 ... Assignment(x, y + z), 

687 ... Assignment(y, z + 1), 

688 ... Assignment(z, 2), 

689 ... ] 

690 >>> CodeBlock.topological_sort(assignments) 

691 CodeBlock( 

692 Assignment(z, 2), 

693 Assignment(y, z + 1), 

694 Assignment(x, y + z) 

695 ) 

696 

697 """ 

698 

699 if not all(isinstance(i, Assignment) for i in assignments): 

700 # Will support more things later 

701 raise NotImplementedError("CodeBlock.topological_sort only supports Assignments") 

702 

703 if any(isinstance(i, AugmentedAssignment) for i in assignments): 

704 raise NotImplementedError("CodeBlock.topological_sort does not yet work with AugmentedAssignments") 

705 

706 # Create a graph where the nodes are assignments and there is a directed edge 

707 # between nodes that use a variable and nodes that assign that 

708 # variable, like 

709 

710 # [(x := 1, y := x + 1), (x := 1, z := y + z), (y := x + 1, z := y + z)] 

711 

712 # If we then topologically sort these nodes, they will be in 

713 # assignment order, like 

714 

715 # x := 1 

716 # y := x + 1 

717 # z := y + z 

718 

719 # A = The nodes 

720 # 

721 # enumerate keeps nodes in the same order they are already in if 

722 # possible. It will also allow us to handle duplicate assignments to 

723 # the same variable when those are implemented. 

724 A = list(enumerate(assignments)) 

725 

726 # var_map = {variable: [nodes for which this variable is assigned to]} 

727 # like {x: [(1, x := y + z), (4, x := 2 * w)], ...} 

728 var_map = defaultdict(list) 

729 for node in A: 

730 i, a = node 

731 var_map[a.lhs].append(node) 

732 

733 # E = Edges in the graph 

734 E = [] 

735 for dst_node in A: 

736 i, a = dst_node 

737 for s in a.rhs.free_symbols: 

738 for src_node in var_map[s]: 

739 E.append((src_node, dst_node)) 

740 

741 ordered_assignments = topological_sort([A, E]) 

742 

743 # De-enumerate the result 

744 return cls(*[a for i, a in ordered_assignments]) 

745 

746 def cse(self, symbols=None, optimizations=None, postprocess=None, 

747 order='canonical'): 

748 """ 

749 Return a new code block with common subexpressions eliminated. 

750 

751 Explanation 

752 =========== 

753 

754 See the docstring of :func:`sympy.simplify.cse_main.cse` for more 

755 information. 

756 

757 Examples 

758 ======== 

759 

760 >>> from sympy import symbols, sin 

761 >>> from sympy.codegen.ast import CodeBlock, Assignment 

762 >>> x, y, z = symbols('x y z') 

763 

764 >>> c = CodeBlock( 

765 ... Assignment(x, 1), 

766 ... Assignment(y, sin(x) + 1), 

767 ... Assignment(z, sin(x) - 1), 

768 ... ) 

769 ... 

770 >>> c.cse() 

771 CodeBlock( 

772 Assignment(x, 1), 

773 Assignment(x0, sin(x)), 

774 Assignment(y, x0 + 1), 

775 Assignment(z, x0 - 1) 

776 ) 

777 

778 """ 

779 from sympy.simplify.cse_main import cse 

780 

781 # Check that the CodeBlock only contains assignments to unique variables 

782 if not all(isinstance(i, Assignment) for i in self.args): 

783 # Will support more things later 

784 raise NotImplementedError("CodeBlock.cse only supports Assignments") 

785 

786 if any(isinstance(i, AugmentedAssignment) for i in self.args): 

787 raise NotImplementedError("CodeBlock.cse does not yet work with AugmentedAssignments") 

788 

789 for i, lhs in enumerate(self.left_hand_sides): 

790 if lhs in self.left_hand_sides[:i]: 

791 raise NotImplementedError("Duplicate assignments to the same " 

792 "variable are not yet supported (%s)" % lhs) 

793 

794 # Ensure new symbols for subexpressions do not conflict with existing 

795 existing_symbols = self.atoms(Symbol) 

796 if symbols is None: 

797 symbols = numbered_symbols() 

798 symbols = filter_symbols(symbols, existing_symbols) 

799 

800 replacements, reduced_exprs = cse(list(self.right_hand_sides), 

801 symbols=symbols, optimizations=optimizations, postprocess=postprocess, 

802 order=order) 

803 

804 new_block = [Assignment(var, expr) for var, expr in 

805 zip(self.left_hand_sides, reduced_exprs)] 

806 new_assignments = [Assignment(var, expr) for var, expr in replacements] 

807 return self.topological_sort(new_assignments + new_block) 

808 

809 

810class For(Token): 

811 """Represents a 'for-loop' in the code. 

812 

813 Expressions are of the form: 

814 "for target in iter: 

815 body..." 

816 

817 Parameters 

818 ========== 

819 

820 target : symbol 

821 iter : iterable 

822 body : CodeBlock or iterable 

823! When passed an iterable it is used to instantiate a CodeBlock. 

824 

825 Examples 

826 ======== 

827 

828 >>> from sympy import symbols, Range 

829 >>> from sympy.codegen.ast import aug_assign, For 

830 >>> x, i, j, k = symbols('x i j k') 

831 >>> for_i = For(i, Range(10), [aug_assign(x, '+', i*j*k)]) 

832 >>> for_i # doctest: -NORMALIZE_WHITESPACE 

833 For(i, iterable=Range(0, 10, 1), body=CodeBlock( 

834 AddAugmentedAssignment(x, i*j*k) 

835 )) 

836 >>> for_ji = For(j, Range(7), [for_i]) 

837 >>> for_ji # doctest: -NORMALIZE_WHITESPACE 

838 For(j, iterable=Range(0, 7, 1), body=CodeBlock( 

839 For(i, iterable=Range(0, 10, 1), body=CodeBlock( 

840 AddAugmentedAssignment(x, i*j*k) 

841 )) 

842 )) 

843 >>> for_kji =For(k, Range(5), [for_ji]) 

844 >>> for_kji # doctest: -NORMALIZE_WHITESPACE 

845 For(k, iterable=Range(0, 5, 1), body=CodeBlock( 

846 For(j, iterable=Range(0, 7, 1), body=CodeBlock( 

847 For(i, iterable=Range(0, 10, 1), body=CodeBlock( 

848 AddAugmentedAssignment(x, i*j*k) 

849 )) 

850 )) 

851 )) 

852 """ 

853 __slots__ = _fields = ('target', 'iterable', 'body') 

854 _construct_target = staticmethod(_sympify) 

855 

856 @classmethod 

857 def _construct_body(cls, itr): 

858 if isinstance(itr, CodeBlock): 

859 return itr 

860 else: 

861 return CodeBlock(*itr) 

862 

863 @classmethod 

864 def _construct_iterable(cls, itr): 

865 if not iterable(itr): 

866 raise TypeError("iterable must be an iterable") 

867 if isinstance(itr, list): # _sympify errors on lists because they are mutable 

868 itr = tuple(itr) 

869 return _sympify(itr) 

870 

871 

872class String(Atom, Token): 

873 """ SymPy object representing a string. 

874 

875 Atomic object which is not an expression (as opposed to Symbol). 

876 

877 Parameters 

878 ========== 

879 

880 text : str 

881 

882 Examples 

883 ======== 

884 

885 >>> from sympy.codegen.ast import String 

886 >>> f = String('foo') 

887 >>> f 

888 foo 

889 >>> str(f) 

890 'foo' 

891 >>> f.text 

892 'foo' 

893 >>> print(repr(f)) 

894 String('foo') 

895 

896 """ 

897 __slots__ = _fields = ('text',) 

898 not_in_args = ['text'] 

899 is_Atom = True 

900 

901 @classmethod 

902 def _construct_text(cls, text): 

903 if not isinstance(text, str): 

904 raise TypeError("Argument text is not a string type.") 

905 return text 

906 

907 def _sympystr(self, printer, *args, **kwargs): 

908 return self.text 

909 

910 def kwargs(self, exclude = (), apply = None): 

911 return {} 

912 

913 #to be removed when Atom is given a suitable func 

914 @property 

915 def func(self): 

916 return lambda: self 

917 

918 def _latex(self, printer): 

919 from sympy.printing.latex import latex_escape 

920 return r'\texttt{{"{}"}}'.format(latex_escape(self.text)) 

921 

922class QuotedString(String): 

923 """ Represents a string which should be printed with quotes. """ 

924 

925class Comment(String): 

926 """ Represents a comment. """ 

927 

928class Node(Token): 

929 """ Subclass of Token, carrying the attribute 'attrs' (Tuple) 

930 

931 Examples 

932 ======== 

933 

934 >>> from sympy.codegen.ast import Node, value_const, pointer_const 

935 >>> n1 = Node([value_const]) 

936 >>> n1.attr_params('value_const') # get the parameters of attribute (by name) 

937 () 

938 >>> from sympy.codegen.fnodes import dimension 

939 >>> n2 = Node([value_const, dimension(5, 3)]) 

940 >>> n2.attr_params(value_const) # get the parameters of attribute (by Attribute instance) 

941 () 

942 >>> n2.attr_params('dimension') # get the parameters of attribute (by name) 

943 (5, 3) 

944 >>> n2.attr_params(pointer_const) is None 

945 True 

946 

947 """ 

948 

949 __slots__: tuple[str, ...] = ('attrs',) 

950 _fields = __slots__ 

951 

952 defaults: dict[str, Any] = {'attrs': Tuple()} 

953 

954 _construct_attrs = staticmethod(_mk_Tuple) 

955 

956 def attr_params(self, looking_for): 

957 """ Returns the parameters of the Attribute with name ``looking_for`` in self.attrs """ 

958 for attr in self.attrs: 

959 if str(attr.name) == str(looking_for): 

960 return attr.parameters 

961 

962 

963class Type(Token): 

964 """ Represents a type. 

965 

966 Explanation 

967 =========== 

968 

969 The naming is a super-set of NumPy naming. Type has a classmethod 

970 ``from_expr`` which offer type deduction. It also has a method 

971 ``cast_check`` which casts the argument to its type, possibly raising an 

972 exception if rounding error is not within tolerances, or if the value is not 

973 representable by the underlying data type (e.g. unsigned integers). 

974 

975 Parameters 

976 ========== 

977 

978 name : str 

979 Name of the type, e.g. ``object``, ``int16``, ``float16`` (where the latter two 

980 would use the ``Type`` sub-classes ``IntType`` and ``FloatType`` respectively). 

981 If a ``Type`` instance is given, the said instance is returned. 

982 

983 Examples 

984 ======== 

985 

986 >>> from sympy.codegen.ast import Type 

987 >>> t = Type.from_expr(42) 

988 >>> t 

989 integer 

990 >>> print(repr(t)) 

991 IntBaseType(String('integer')) 

992 >>> from sympy.codegen.ast import uint8 

993 >>> uint8.cast_check(-1) # doctest: +ELLIPSIS 

994 Traceback (most recent call last): 

995 ... 

996 ValueError: Minimum value for data type bigger than new value. 

997 >>> from sympy.codegen.ast import float32 

998 >>> v6 = 0.123456 

999 >>> float32.cast_check(v6) 

1000 0.123456 

1001 >>> v10 = 12345.67894 

1002 >>> float32.cast_check(v10) # doctest: +ELLIPSIS 

1003 Traceback (most recent call last): 

1004 ... 

1005 ValueError: Casting gives a significantly different value. 

1006 >>> boost_mp50 = Type('boost::multiprecision::cpp_dec_float_50') 

1007 >>> from sympy import cxxcode 

1008 >>> from sympy.codegen.ast import Declaration, Variable 

1009 >>> cxxcode(Declaration(Variable('x', type=boost_mp50))) 

1010 'boost::multiprecision::cpp_dec_float_50 x' 

1011 

1012 References 

1013 ========== 

1014 

1015 .. [1] https://numpy.org/doc/stable/user/basics.types.html 

1016 

1017 """ 

1018 __slots__: tuple[str, ...] = ('name',) 

1019 _fields = __slots__ 

1020 

1021 _construct_name = String 

1022 

1023 def _sympystr(self, printer, *args, **kwargs): 

1024 return str(self.name) 

1025 

1026 @classmethod 

1027 def from_expr(cls, expr): 

1028 """ Deduces type from an expression or a ``Symbol``. 

1029 

1030 Parameters 

1031 ========== 

1032 

1033 expr : number or SymPy object 

1034 The type will be deduced from type or properties. 

1035 

1036 Examples 

1037 ======== 

1038 

1039 >>> from sympy.codegen.ast import Type, integer, complex_ 

1040 >>> Type.from_expr(2) == integer 

1041 True 

1042 >>> from sympy import Symbol 

1043 >>> Type.from_expr(Symbol('z', complex=True)) == complex_ 

1044 True 

1045 >>> Type.from_expr(sum) # doctest: +ELLIPSIS 

1046 Traceback (most recent call last): 

1047 ... 

1048 ValueError: Could not deduce type from expr. 

1049 

1050 Raises 

1051 ====== 

1052 

1053 ValueError when type deduction fails. 

1054 

1055 """ 

1056 if isinstance(expr, (float, Float)): 

1057 return real 

1058 if isinstance(expr, (int, Integer)) or getattr(expr, 'is_integer', False): 

1059 return integer 

1060 if getattr(expr, 'is_real', False): 

1061 return real 

1062 if isinstance(expr, complex) or getattr(expr, 'is_complex', False): 

1063 return complex_ 

1064 if isinstance(expr, bool) or getattr(expr, 'is_Relational', False): 

1065 return bool_ 

1066 else: 

1067 raise ValueError("Could not deduce type from expr.") 

1068 

1069 def _check(self, value): 

1070 pass 

1071 

1072 def cast_check(self, value, rtol=None, atol=0, precision_targets=None): 

1073 """ Casts a value to the data type of the instance. 

1074 

1075 Parameters 

1076 ========== 

1077 

1078 value : number 

1079 rtol : floating point number 

1080 Relative tolerance. (will be deduced if not given). 

1081 atol : floating point number 

1082 Absolute tolerance (in addition to ``rtol``). 

1083 type_aliases : dict 

1084 Maps substitutions for Type, e.g. {integer: int64, real: float32} 

1085 

1086 Examples 

1087 ======== 

1088 

1089 >>> from sympy.codegen.ast import integer, float32, int8 

1090 >>> integer.cast_check(3.0) == 3 

1091 True 

1092 >>> float32.cast_check(1e-40) # doctest: +ELLIPSIS 

1093 Traceback (most recent call last): 

1094 ... 

1095 ValueError: Minimum value for data type bigger than new value. 

1096 >>> int8.cast_check(256) # doctest: +ELLIPSIS 

1097 Traceback (most recent call last): 

1098 ... 

1099 ValueError: Maximum value for data type smaller than new value. 

1100 >>> v10 = 12345.67894 

1101 >>> float32.cast_check(v10) # doctest: +ELLIPSIS 

1102 Traceback (most recent call last): 

1103 ... 

1104 ValueError: Casting gives a significantly different value. 

1105 >>> from sympy.codegen.ast import float64 

1106 >>> float64.cast_check(v10) 

1107 12345.67894 

1108 >>> from sympy import Float 

1109 >>> v18 = Float('0.123456789012345646') 

1110 >>> float64.cast_check(v18) 

1111 Traceback (most recent call last): 

1112 ... 

1113 ValueError: Casting gives a significantly different value. 

1114 >>> from sympy.codegen.ast import float80 

1115 >>> float80.cast_check(v18) 

1116 0.123456789012345649 

1117 

1118 """ 

1119 val = sympify(value) 

1120 

1121 ten = Integer(10) 

1122 exp10 = getattr(self, 'decimal_dig', None) 

1123 

1124 if rtol is None: 

1125 rtol = 1e-15 if exp10 is None else 2.0*ten**(-exp10) 

1126 

1127 def tol(num): 

1128 return atol + rtol*abs(num) 

1129 

1130 new_val = self.cast_nocheck(value) 

1131 self._check(new_val) 

1132 

1133 delta = new_val - val 

1134 if abs(delta) > tol(val): # rounding, e.g. int(3.5) != 3.5 

1135 raise ValueError("Casting gives a significantly different value.") 

1136 

1137 return new_val 

1138 

1139 def _latex(self, printer): 

1140 from sympy.printing.latex import latex_escape 

1141 type_name = latex_escape(self.__class__.__name__) 

1142 name = latex_escape(self.name.text) 

1143 return r"\text{{{}}}\left(\texttt{{{}}}\right)".format(type_name, name) 

1144 

1145 

1146class IntBaseType(Type): 

1147 """ Integer base type, contains no size information. """ 

1148 __slots__ = () 

1149 cast_nocheck = lambda self, i: Integer(int(i)) 

1150 

1151 

1152class _SizedIntType(IntBaseType): 

1153 __slots__ = ('nbits',) 

1154 _fields = Type._fields + __slots__ 

1155 

1156 _construct_nbits = Integer 

1157 

1158 def _check(self, value): 

1159 if value < self.min: 

1160 raise ValueError("Value is too small: %d < %d" % (value, self.min)) 

1161 if value > self.max: 

1162 raise ValueError("Value is too big: %d > %d" % (value, self.max)) 

1163 

1164 

1165class SignedIntType(_SizedIntType): 

1166 """ Represents a signed integer type. """ 

1167 __slots__ = () 

1168 @property 

1169 def min(self): 

1170 return -2**(self.nbits-1) 

1171 

1172 @property 

1173 def max(self): 

1174 return 2**(self.nbits-1) - 1 

1175 

1176 

1177class UnsignedIntType(_SizedIntType): 

1178 """ Represents an unsigned integer type. """ 

1179 __slots__ = () 

1180 @property 

1181 def min(self): 

1182 return 0 

1183 

1184 @property 

1185 def max(self): 

1186 return 2**self.nbits - 1 

1187 

1188two = Integer(2) 

1189 

1190class FloatBaseType(Type): 

1191 """ Represents a floating point number type. """ 

1192 __slots__ = () 

1193 cast_nocheck = Float 

1194 

1195class FloatType(FloatBaseType): 

1196 """ Represents a floating point type with fixed bit width. 

1197 

1198 Base 2 & one sign bit is assumed. 

1199 

1200 Parameters 

1201 ========== 

1202 

1203 name : str 

1204 Name of the type. 

1205 nbits : integer 

1206 Number of bits used (storage). 

1207 nmant : integer 

1208 Number of bits used to represent the mantissa. 

1209 nexp : integer 

1210 Number of bits used to represent the mantissa. 

1211 

1212 Examples 

1213 ======== 

1214 

1215 >>> from sympy import S 

1216 >>> from sympy.codegen.ast import FloatType 

1217 >>> half_precision = FloatType('f16', nbits=16, nmant=10, nexp=5) 

1218 >>> half_precision.max 

1219 65504 

1220 >>> half_precision.tiny == S(2)**-14 

1221 True 

1222 >>> half_precision.eps == S(2)**-10 

1223 True 

1224 >>> half_precision.dig == 3 

1225 True 

1226 >>> half_precision.decimal_dig == 5 

1227 True 

1228 >>> half_precision.cast_check(1.0) 

1229 1.0 

1230 >>> half_precision.cast_check(1e5) # doctest: +ELLIPSIS 

1231 Traceback (most recent call last): 

1232 ... 

1233 ValueError: Maximum value for data type smaller than new value. 

1234 """ 

1235 

1236 __slots__ = ('nbits', 'nmant', 'nexp',) 

1237 _fields = Type._fields + __slots__ 

1238 

1239 _construct_nbits = _construct_nmant = _construct_nexp = Integer 

1240 

1241 

1242 @property 

1243 def max_exponent(self): 

1244 """ The largest positive number n, such that 2**(n - 1) is a representable finite value. """ 

1245 # cf. C++'s ``std::numeric_limits::max_exponent`` 

1246 return two**(self.nexp - 1) 

1247 

1248 @property 

1249 def min_exponent(self): 

1250 """ The lowest negative number n, such that 2**(n - 1) is a valid normalized number. """ 

1251 # cf. C++'s ``std::numeric_limits::min_exponent`` 

1252 return 3 - self.max_exponent 

1253 

1254 @property 

1255 def max(self): 

1256 """ Maximum value representable. """ 

1257 return (1 - two**-(self.nmant+1))*two**self.max_exponent 

1258 

1259 @property 

1260 def tiny(self): 

1261 """ The minimum positive normalized value. """ 

1262 # See C macros: FLT_MIN, DBL_MIN, LDBL_MIN 

1263 # or C++'s ``std::numeric_limits::min`` 

1264 # or numpy.finfo(dtype).tiny 

1265 return two**(self.min_exponent - 1) 

1266 

1267 

1268 @property 

1269 def eps(self): 

1270 """ Difference between 1.0 and the next representable value. """ 

1271 return two**(-self.nmant) 

1272 

1273 @property 

1274 def dig(self): 

1275 """ Number of decimal digits that are guaranteed to be preserved in text. 

1276 

1277 When converting text -> float -> text, you are guaranteed that at least ``dig`` 

1278 number of digits are preserved with respect to rounding or overflow. 

1279 """ 

1280 from sympy.functions import floor, log 

1281 return floor(self.nmant * log(2)/log(10)) 

1282 

1283 @property 

1284 def decimal_dig(self): 

1285 """ Number of digits needed to store & load without loss. 

1286 

1287 Explanation 

1288 =========== 

1289 

1290 Number of decimal digits needed to guarantee that two consecutive conversions 

1291 (float -> text -> float) to be idempotent. This is useful when one do not want 

1292 to loose precision due to rounding errors when storing a floating point value 

1293 as text. 

1294 """ 

1295 from sympy.functions import ceiling, log 

1296 return ceiling((self.nmant + 1) * log(2)/log(10) + 1) 

1297 

1298 def cast_nocheck(self, value): 

1299 """ Casts without checking if out of bounds or subnormal. """ 

1300 if value == oo: # float(oo) or oo 

1301 return float(oo) 

1302 elif value == -oo: # float(-oo) or -oo 

1303 return float(-oo) 

1304 return Float(str(sympify(value).evalf(self.decimal_dig)), self.decimal_dig) 

1305 

1306 def _check(self, value): 

1307 if value < -self.max: 

1308 raise ValueError("Value is too small: %d < %d" % (value, -self.max)) 

1309 if value > self.max: 

1310 raise ValueError("Value is too big: %d > %d" % (value, self.max)) 

1311 if abs(value) < self.tiny: 

1312 raise ValueError("Smallest (absolute) value for data type bigger than new value.") 

1313 

1314class ComplexBaseType(FloatBaseType): 

1315 

1316 __slots__ = () 

1317 

1318 def cast_nocheck(self, value): 

1319 """ Casts without checking if out of bounds or subnormal. """ 

1320 from sympy.functions import re, im 

1321 return ( 

1322 super().cast_nocheck(re(value)) + 

1323 super().cast_nocheck(im(value))*1j 

1324 ) 

1325 

1326 def _check(self, value): 

1327 from sympy.functions import re, im 

1328 super()._check(re(value)) 

1329 super()._check(im(value)) 

1330 

1331 

1332class ComplexType(ComplexBaseType, FloatType): 

1333 """ Represents a complex floating point number. """ 

1334 __slots__ = () 

1335 

1336 

1337# NumPy types: 

1338intc = IntBaseType('intc') 

1339intp = IntBaseType('intp') 

1340int8 = SignedIntType('int8', 8) 

1341int16 = SignedIntType('int16', 16) 

1342int32 = SignedIntType('int32', 32) 

1343int64 = SignedIntType('int64', 64) 

1344uint8 = UnsignedIntType('uint8', 8) 

1345uint16 = UnsignedIntType('uint16', 16) 

1346uint32 = UnsignedIntType('uint32', 32) 

1347uint64 = UnsignedIntType('uint64', 64) 

1348float16 = FloatType('float16', 16, nexp=5, nmant=10) # IEEE 754 binary16, Half precision 

1349float32 = FloatType('float32', 32, nexp=8, nmant=23) # IEEE 754 binary32, Single precision 

1350float64 = FloatType('float64', 64, nexp=11, nmant=52) # IEEE 754 binary64, Double precision 

1351float80 = FloatType('float80', 80, nexp=15, nmant=63) # x86 extended precision (1 integer part bit), "long double" 

1352float128 = FloatType('float128', 128, nexp=15, nmant=112) # IEEE 754 binary128, Quadruple precision 

1353float256 = FloatType('float256', 256, nexp=19, nmant=236) # IEEE 754 binary256, Octuple precision 

1354 

1355complex64 = ComplexType('complex64', nbits=64, **float32.kwargs(exclude=('name', 'nbits'))) 

1356complex128 = ComplexType('complex128', nbits=128, **float64.kwargs(exclude=('name', 'nbits'))) 

1357 

1358# Generic types (precision may be chosen by code printers): 

1359untyped = Type('untyped') 

1360real = FloatBaseType('real') 

1361integer = IntBaseType('integer') 

1362complex_ = ComplexBaseType('complex') 

1363bool_ = Type('bool') 

1364 

1365 

1366class Attribute(Token): 

1367 """ Attribute (possibly parametrized) 

1368 

1369 For use with :class:`sympy.codegen.ast.Node` (which takes instances of 

1370 ``Attribute`` as ``attrs``). 

1371 

1372 Parameters 

1373 ========== 

1374 

1375 name : str 

1376 parameters : Tuple 

1377 

1378 Examples 

1379 ======== 

1380 

1381 >>> from sympy.codegen.ast import Attribute 

1382 >>> volatile = Attribute('volatile') 

1383 >>> volatile 

1384 volatile 

1385 >>> print(repr(volatile)) 

1386 Attribute(String('volatile')) 

1387 >>> a = Attribute('foo', [1, 2, 3]) 

1388 >>> a 

1389 foo(1, 2, 3) 

1390 >>> a.parameters == (1, 2, 3) 

1391 True 

1392 """ 

1393 __slots__ = _fields = ('name', 'parameters') 

1394 defaults = {'parameters': Tuple()} 

1395 

1396 _construct_name = String 

1397 _construct_parameters = staticmethod(_mk_Tuple) 

1398 

1399 def _sympystr(self, printer, *args, **kwargs): 

1400 result = str(self.name) 

1401 if self.parameters: 

1402 result += '(%s)' % ', '.join((printer._print( 

1403 arg, *args, **kwargs) for arg in self.parameters)) 

1404 return result 

1405 

1406value_const = Attribute('value_const') 

1407pointer_const = Attribute('pointer_const') 

1408 

1409 

1410class Variable(Node): 

1411 """ Represents a variable. 

1412 

1413 Parameters 

1414 ========== 

1415 

1416 symbol : Symbol 

1417 type : Type (optional) 

1418 Type of the variable. 

1419 attrs : iterable of Attribute instances 

1420 Will be stored as a Tuple. 

1421 

1422 Examples 

1423 ======== 

1424 

1425 >>> from sympy import Symbol 

1426 >>> from sympy.codegen.ast import Variable, float32, integer 

1427 >>> x = Symbol('x') 

1428 >>> v = Variable(x, type=float32) 

1429 >>> v.attrs 

1430 () 

1431 >>> v == Variable('x') 

1432 False 

1433 >>> v == Variable('x', type=float32) 

1434 True 

1435 >>> v 

1436 Variable(x, type=float32) 

1437 

1438 One may also construct a ``Variable`` instance with the type deduced from 

1439 assumptions about the symbol using the ``deduced`` classmethod: 

1440 

1441 >>> i = Symbol('i', integer=True) 

1442 >>> v = Variable.deduced(i) 

1443 >>> v.type == integer 

1444 True 

1445 >>> v == Variable('i') 

1446 False 

1447 >>> from sympy.codegen.ast import value_const 

1448 >>> value_const in v.attrs 

1449 False 

1450 >>> w = Variable('w', attrs=[value_const]) 

1451 >>> w 

1452 Variable(w, attrs=(value_const,)) 

1453 >>> value_const in w.attrs 

1454 True 

1455 >>> w.as_Declaration(value=42) 

1456 Declaration(Variable(w, value=42, attrs=(value_const,))) 

1457 

1458 """ 

1459 

1460 __slots__ = ('symbol', 'type', 'value') 

1461 _fields = __slots__ + Node._fields 

1462 

1463 defaults = Node.defaults.copy() 

1464 defaults.update({'type': untyped, 'value': none}) 

1465 

1466 _construct_symbol = staticmethod(sympify) 

1467 _construct_value = staticmethod(sympify) 

1468 

1469 @classmethod 

1470 def deduced(cls, symbol, value=None, attrs=Tuple(), cast_check=True): 

1471 """ Alt. constructor with type deduction from ``Type.from_expr``. 

1472 

1473 Deduces type primarily from ``symbol``, secondarily from ``value``. 

1474 

1475 Parameters 

1476 ========== 

1477 

1478 symbol : Symbol 

1479 value : expr 

1480 (optional) value of the variable. 

1481 attrs : iterable of Attribute instances 

1482 cast_check : bool 

1483 Whether to apply ``Type.cast_check`` on ``value``. 

1484 

1485 Examples 

1486 ======== 

1487 

1488 >>> from sympy import Symbol 

1489 >>> from sympy.codegen.ast import Variable, complex_ 

1490 >>> n = Symbol('n', integer=True) 

1491 >>> str(Variable.deduced(n).type) 

1492 'integer' 

1493 >>> x = Symbol('x', real=True) 

1494 >>> v = Variable.deduced(x) 

1495 >>> v.type 

1496 real 

1497 >>> z = Symbol('z', complex=True) 

1498 >>> Variable.deduced(z).type == complex_ 

1499 True 

1500 

1501 """ 

1502 if isinstance(symbol, Variable): 

1503 return symbol 

1504 

1505 try: 

1506 type_ = Type.from_expr(symbol) 

1507 except ValueError: 

1508 type_ = Type.from_expr(value) 

1509 

1510 if value is not None and cast_check: 

1511 value = type_.cast_check(value) 

1512 return cls(symbol, type=type_, value=value, attrs=attrs) 

1513 

1514 def as_Declaration(self, **kwargs): 

1515 """ Convenience method for creating a Declaration instance. 

1516 

1517 Explanation 

1518 =========== 

1519 

1520 If the variable of the Declaration need to wrap a modified 

1521 variable keyword arguments may be passed (overriding e.g. 

1522 the ``value`` of the Variable instance). 

1523 

1524 Examples 

1525 ======== 

1526 

1527 >>> from sympy.codegen.ast import Variable, NoneToken 

1528 >>> x = Variable('x') 

1529 >>> decl1 = x.as_Declaration() 

1530 >>> # value is special NoneToken() which must be tested with == operator 

1531 >>> decl1.variable.value is None # won't work 

1532 False 

1533 >>> decl1.variable.value == None # not PEP-8 compliant 

1534 True 

1535 >>> decl1.variable.value == NoneToken() # OK 

1536 True 

1537 >>> decl2 = x.as_Declaration(value=42.0) 

1538 >>> decl2.variable.value == 42.0 

1539 True 

1540 

1541 """ 

1542 kw = self.kwargs() 

1543 kw.update(kwargs) 

1544 return Declaration(self.func(**kw)) 

1545 

1546 def _relation(self, rhs, op): 

1547 try: 

1548 rhs = _sympify(rhs) 

1549 except SympifyError: 

1550 raise TypeError("Invalid comparison %s < %s" % (self, rhs)) 

1551 return op(self, rhs, evaluate=False) 

1552 

1553 __lt__ = lambda self, other: self._relation(other, Lt) 

1554 __le__ = lambda self, other: self._relation(other, Le) 

1555 __ge__ = lambda self, other: self._relation(other, Ge) 

1556 __gt__ = lambda self, other: self._relation(other, Gt) 

1557 

1558class Pointer(Variable): 

1559 """ Represents a pointer. See ``Variable``. 

1560 

1561 Examples 

1562 ======== 

1563 

1564 Can create instances of ``Element``: 

1565 

1566 >>> from sympy import Symbol 

1567 >>> from sympy.codegen.ast import Pointer 

1568 >>> i = Symbol('i', integer=True) 

1569 >>> p = Pointer('x') 

1570 >>> p[i+1] 

1571 Element(x, indices=(i + 1,)) 

1572 

1573 """ 

1574 __slots__ = () 

1575 

1576 def __getitem__(self, key): 

1577 try: 

1578 return Element(self.symbol, key) 

1579 except TypeError: 

1580 return Element(self.symbol, (key,)) 

1581 

1582 

1583class Element(Token): 

1584 """ Element in (a possibly N-dimensional) array. 

1585 

1586 Examples 

1587 ======== 

1588 

1589 >>> from sympy.codegen.ast import Element 

1590 >>> elem = Element('x', 'ijk') 

1591 >>> elem.symbol.name == 'x' 

1592 True 

1593 >>> elem.indices 

1594 (i, j, k) 

1595 >>> from sympy import ccode 

1596 >>> ccode(elem) 

1597 'x[i][j][k]' 

1598 >>> ccode(Element('x', 'ijk', strides='lmn', offset='o')) 

1599 'x[i*l + j*m + k*n + o]' 

1600 

1601 """ 

1602 __slots__ = _fields = ('symbol', 'indices', 'strides', 'offset') 

1603 defaults = {'strides': none, 'offset': none} 

1604 _construct_symbol = staticmethod(sympify) 

1605 _construct_indices = staticmethod(lambda arg: Tuple(*arg)) 

1606 _construct_strides = staticmethod(lambda arg: Tuple(*arg)) 

1607 _construct_offset = staticmethod(sympify) 

1608 

1609 

1610class Declaration(Token): 

1611 """ Represents a variable declaration 

1612 

1613 Parameters 

1614 ========== 

1615 

1616 variable : Variable 

1617 

1618 Examples 

1619 ======== 

1620 

1621 >>> from sympy.codegen.ast import Declaration, NoneToken, untyped 

1622 >>> z = Declaration('z') 

1623 >>> z.variable.type == untyped 

1624 True 

1625 >>> # value is special NoneToken() which must be tested with == operator 

1626 >>> z.variable.value is None # won't work 

1627 False 

1628 >>> z.variable.value == None # not PEP-8 compliant 

1629 True 

1630 >>> z.variable.value == NoneToken() # OK 

1631 True 

1632 """ 

1633 __slots__ = _fields = ('variable',) 

1634 _construct_variable = Variable 

1635 

1636 

1637class While(Token): 

1638 """ Represents a 'for-loop' in the code. 

1639 

1640 Expressions are of the form: 

1641 "while condition: 

1642 body..." 

1643 

1644 Parameters 

1645 ========== 

1646 

1647 condition : expression convertible to Boolean 

1648 body : CodeBlock or iterable 

1649 When passed an iterable it is used to instantiate a CodeBlock. 

1650 

1651 Examples 

1652 ======== 

1653 

1654 >>> from sympy import symbols, Gt, Abs 

1655 >>> from sympy.codegen import aug_assign, Assignment, While 

1656 >>> x, dx = symbols('x dx') 

1657 >>> expr = 1 - x**2 

1658 >>> whl = While(Gt(Abs(dx), 1e-9), [ 

1659 ... Assignment(dx, -expr/expr.diff(x)), 

1660 ... aug_assign(x, '+', dx) 

1661 ... ]) 

1662 

1663 """ 

1664 __slots__ = _fields = ('condition', 'body') 

1665 _construct_condition = staticmethod(lambda cond: _sympify(cond)) 

1666 

1667 @classmethod 

1668 def _construct_body(cls, itr): 

1669 if isinstance(itr, CodeBlock): 

1670 return itr 

1671 else: 

1672 return CodeBlock(*itr) 

1673 

1674 

1675class Scope(Token): 

1676 """ Represents a scope in the code. 

1677 

1678 Parameters 

1679 ========== 

1680 

1681 body : CodeBlock or iterable 

1682 When passed an iterable it is used to instantiate a CodeBlock. 

1683 

1684 """ 

1685 __slots__ = _fields = ('body',) 

1686 

1687 @classmethod 

1688 def _construct_body(cls, itr): 

1689 if isinstance(itr, CodeBlock): 

1690 return itr 

1691 else: 

1692 return CodeBlock(*itr) 

1693 

1694 

1695class Stream(Token): 

1696 """ Represents a stream. 

1697 

1698 There are two predefined Stream instances ``stdout`` & ``stderr``. 

1699 

1700 Parameters 

1701 ========== 

1702 

1703 name : str 

1704 

1705 Examples 

1706 ======== 

1707 

1708 >>> from sympy import pycode, Symbol 

1709 >>> from sympy.codegen.ast import Print, stderr, QuotedString 

1710 >>> print(pycode(Print(['x'], file=stderr))) 

1711 print(x, file=sys.stderr) 

1712 >>> x = Symbol('x') 

1713 >>> print(pycode(Print([QuotedString('x')], file=stderr))) # print literally "x" 

1714 print("x", file=sys.stderr) 

1715 

1716 """ 

1717 __slots__ = _fields = ('name',) 

1718 _construct_name = String 

1719 

1720stdout = Stream('stdout') 

1721stderr = Stream('stderr') 

1722 

1723 

1724class Print(Token): 

1725 """ Represents print command in the code. 

1726 

1727 Parameters 

1728 ========== 

1729 

1730 formatstring : str 

1731 *args : Basic instances (or convertible to such through sympify) 

1732 

1733 Examples 

1734 ======== 

1735 

1736 >>> from sympy.codegen.ast import Print 

1737 >>> from sympy import pycode 

1738 >>> print(pycode(Print('x y'.split(), "coordinate: %12.5g %12.5g"))) 

1739 print("coordinate: %12.5g %12.5g" % (x, y)) 

1740 

1741 """ 

1742 

1743 __slots__ = _fields = ('print_args', 'format_string', 'file') 

1744 defaults = {'format_string': none, 'file': none} 

1745 

1746 _construct_print_args = staticmethod(_mk_Tuple) 

1747 _construct_format_string = QuotedString 

1748 _construct_file = Stream 

1749 

1750 

1751class FunctionPrototype(Node): 

1752 """ Represents a function prototype 

1753 

1754 Allows the user to generate forward declaration in e.g. C/C++. 

1755 

1756 Parameters 

1757 ========== 

1758 

1759 return_type : Type 

1760 name : str 

1761 parameters: iterable of Variable instances 

1762 attrs : iterable of Attribute instances 

1763 

1764 Examples 

1765 ======== 

1766 

1767 >>> from sympy import ccode, symbols 

1768 >>> from sympy.codegen.ast import real, FunctionPrototype 

1769 >>> x, y = symbols('x y', real=True) 

1770 >>> fp = FunctionPrototype(real, 'foo', [x, y]) 

1771 >>> ccode(fp) 

1772 'double foo(double x, double y)' 

1773 

1774 """ 

1775 

1776 __slots__ = ('return_type', 'name', 'parameters') 

1777 _fields: tuple[str, ...] = __slots__ + Node._fields 

1778 

1779 _construct_return_type = Type 

1780 _construct_name = String 

1781 

1782 @staticmethod 

1783 def _construct_parameters(args): 

1784 def _var(arg): 

1785 if isinstance(arg, Declaration): 

1786 return arg.variable 

1787 elif isinstance(arg, Variable): 

1788 return arg 

1789 else: 

1790 return Variable.deduced(arg) 

1791 return Tuple(*map(_var, args)) 

1792 

1793 @classmethod 

1794 def from_FunctionDefinition(cls, func_def): 

1795 if not isinstance(func_def, FunctionDefinition): 

1796 raise TypeError("func_def is not an instance of FunctionDefinition") 

1797 return cls(**func_def.kwargs(exclude=('body',))) 

1798 

1799 

1800class FunctionDefinition(FunctionPrototype): 

1801 """ Represents a function definition in the code. 

1802 

1803 Parameters 

1804 ========== 

1805 

1806 return_type : Type 

1807 name : str 

1808 parameters: iterable of Variable instances 

1809 body : CodeBlock or iterable 

1810 attrs : iterable of Attribute instances 

1811 

1812 Examples 

1813 ======== 

1814 

1815 >>> from sympy import ccode, symbols 

1816 >>> from sympy.codegen.ast import real, FunctionPrototype 

1817 >>> x, y = symbols('x y', real=True) 

1818 >>> fp = FunctionPrototype(real, 'foo', [x, y]) 

1819 >>> ccode(fp) 

1820 'double foo(double x, double y)' 

1821 >>> from sympy.codegen.ast import FunctionDefinition, Return 

1822 >>> body = [Return(x*y)] 

1823 >>> fd = FunctionDefinition.from_FunctionPrototype(fp, body) 

1824 >>> print(ccode(fd)) 

1825 double foo(double x, double y){ 

1826 return x*y; 

1827 } 

1828 """ 

1829 

1830 __slots__ = ('body', ) 

1831 _fields = FunctionPrototype._fields[:-1] + __slots__ + Node._fields 

1832 

1833 @classmethod 

1834 def _construct_body(cls, itr): 

1835 if isinstance(itr, CodeBlock): 

1836 return itr 

1837 else: 

1838 return CodeBlock(*itr) 

1839 

1840 @classmethod 

1841 def from_FunctionPrototype(cls, func_proto, body): 

1842 if not isinstance(func_proto, FunctionPrototype): 

1843 raise TypeError("func_proto is not an instance of FunctionPrototype") 

1844 return cls(body=body, **func_proto.kwargs()) 

1845 

1846 

1847class Return(Token): 

1848 """ Represents a return command in the code. 

1849 

1850 Parameters 

1851 ========== 

1852 

1853 return : Basic 

1854 

1855 Examples 

1856 ======== 

1857 

1858 >>> from sympy.codegen.ast import Return 

1859 >>> from sympy.printing.pycode import pycode 

1860 >>> from sympy import Symbol 

1861 >>> x = Symbol('x') 

1862 >>> print(pycode(Return(x))) 

1863 return x 

1864 

1865 """ 

1866 __slots__ = _fields = ('return',) 

1867 _construct_return=staticmethod(_sympify) 

1868 

1869 

1870class FunctionCall(Token, Expr): 

1871 """ Represents a call to a function in the code. 

1872 

1873 Parameters 

1874 ========== 

1875 

1876 name : str 

1877 function_args : Tuple 

1878 

1879 Examples 

1880 ======== 

1881 

1882 >>> from sympy.codegen.ast import FunctionCall 

1883 >>> from sympy import pycode 

1884 >>> fcall = FunctionCall('foo', 'bar baz'.split()) 

1885 >>> print(pycode(fcall)) 

1886 foo(bar, baz) 

1887 

1888 """ 

1889 __slots__ = _fields = ('name', 'function_args') 

1890 

1891 _construct_name = String 

1892 _construct_function_args = staticmethod(lambda args: Tuple(*args))