Spaces:
Sleeping
Sleeping
| """ | |
| Types used to represent a full function/module as an Abstract Syntax Tree. | |
| Most types are small, and are merely used as tokens in the AST. A tree diagram | |
| has been included below to illustrate the relationships between the AST types. | |
| AST Type Tree | |
| ------------- | |
| :: | |
| *Basic* | |
| | | |
| | | |
| CodegenAST | |
| | | |
| |--->AssignmentBase | |
| | |--->Assignment | |
| | |--->AugmentedAssignment | |
| | |--->AddAugmentedAssignment | |
| | |--->SubAugmentedAssignment | |
| | |--->MulAugmentedAssignment | |
| | |--->DivAugmentedAssignment | |
| | |--->ModAugmentedAssignment | |
| | | |
| |--->CodeBlock | |
| | | |
| | | |
| |--->Token | |
| |--->Attribute | |
| |--->For | |
| |--->String | |
| | |--->QuotedString | |
| | |--->Comment | |
| |--->Type | |
| | |--->IntBaseType | |
| | | |--->_SizedIntType | |
| | | |--->SignedIntType | |
| | | |--->UnsignedIntType | |
| | |--->FloatBaseType | |
| | |--->FloatType | |
| | |--->ComplexBaseType | |
| | |--->ComplexType | |
| |--->Node | |
| | |--->Variable | |
| | | |---> Pointer | |
| | |--->FunctionPrototype | |
| | |--->FunctionDefinition | |
| |--->Element | |
| |--->Declaration | |
| |--->While | |
| |--->Scope | |
| |--->Stream | |
| |--->FunctionCall | |
| |--->BreakToken | |
| |--->ContinueToken | |
| |--->NoneToken | |
| |--->Return | |
| Predefined types | |
| ---------------- | |
| A number of ``Type`` instances are provided in the ``sympy.codegen.ast`` module | |
| for convenience. Perhaps the two most common ones for code-generation (of numeric | |
| codes) are ``float32`` and ``float64`` (known as single and double precision respectively). | |
| There are also precision generic versions of Types (for which the codeprinters selects the | |
| underlying data type at time of printing): ``real``, ``integer``, ``complex_``, ``bool_``. | |
| The other ``Type`` instances defined are: | |
| - ``intc``: Integer type used by C's "int". | |
| - ``intp``: Integer type used by C's "unsigned". | |
| - ``int8``, ``int16``, ``int32``, ``int64``: n-bit integers. | |
| - ``uint8``, ``uint16``, ``uint32``, ``uint64``: n-bit unsigned integers. | |
| - ``float80``: known as "extended precision" on modern x86/amd64 hardware. | |
| - ``complex64``: Complex number represented by two ``float32`` numbers | |
| - ``complex128``: Complex number represented by two ``float64`` numbers | |
| Using the nodes | |
| --------------- | |
| It is possible to construct simple algorithms using the AST nodes. Let's construct a loop applying | |
| Newton's method:: | |
| >>> from sympy import symbols, cos | |
| >>> from sympy.codegen.ast import While, Assignment, aug_assign, Print, QuotedString | |
| >>> t, dx, x = symbols('tol delta val') | |
| >>> expr = cos(x) - x**3 | |
| >>> whl = While(abs(dx) > t, [ | |
| ... Assignment(dx, -expr/expr.diff(x)), | |
| ... aug_assign(x, '+', dx), | |
| ... Print([x]) | |
| ... ]) | |
| >>> from sympy import pycode | |
| >>> py_str = pycode(whl) | |
| >>> print(py_str) | |
| while (abs(delta) > tol): | |
| delta = (val**3 - math.cos(val))/(-3*val**2 - math.sin(val)) | |
| val += delta | |
| print(val) | |
| >>> import math | |
| >>> tol, val, delta = 1e-5, 0.5, float('inf') | |
| >>> exec(py_str) | |
| 1.1121416371 | |
| 0.909672693737 | |
| 0.867263818209 | |
| 0.865477135298 | |
| 0.865474033111 | |
| >>> print('%3.1g' % (math.cos(val) - val**3)) | |
| -3e-11 | |
| If we want to generate Fortran code for the same while loop we simple call ``fcode``:: | |
| >>> from sympy import fcode | |
| >>> print(fcode(whl, standard=2003, source_format='free')) | |
| do while (abs(delta) > tol) | |
| delta = (val**3 - cos(val))/(-3*val**2 - sin(val)) | |
| val = val + delta | |
| print *, val | |
| end do | |
| There is a function constructing a loop (or a complete function) like this in | |
| :mod:`sympy.codegen.algorithms`. | |
| """ | |
| from __future__ import annotations | |
| from typing import Any | |
| from collections import defaultdict | |
| from sympy.core.relational import (Ge, Gt, Le, Lt) | |
| from sympy.core import Symbol, Tuple, Dummy | |
| from sympy.core.basic import Basic | |
| from sympy.core.expr import Expr, Atom | |
| from sympy.core.numbers import Float, Integer, oo | |
| from sympy.core.sympify import _sympify, sympify, SympifyError | |
| from sympy.utilities.iterables import (iterable, topological_sort, | |
| numbered_symbols, filter_symbols) | |
| def _mk_Tuple(args): | |
| """ | |
| Create a SymPy Tuple object from an iterable, converting Python strings to | |
| AST strings. | |
| Parameters | |
| ========== | |
| args: iterable | |
| Arguments to :class:`sympy.Tuple`. | |
| Returns | |
| ======= | |
| sympy.Tuple | |
| """ | |
| args = [String(arg) if isinstance(arg, str) else arg for arg in args] | |
| return Tuple(*args) | |
| class CodegenAST(Basic): | |
| __slots__ = () | |
| class Token(CodegenAST): | |
| """ Base class for the AST types. | |
| Explanation | |
| =========== | |
| Defining fields are set in ``_fields``. Attributes (defined in _fields) | |
| are only allowed to contain instances of Basic (unless atomic, see | |
| ``String``). The arguments to ``__new__()`` correspond to the attributes in | |
| the order defined in ``_fields`. The ``defaults`` class attribute is a | |
| dictionary mapping attribute names to their default values. | |
| Subclasses should not need to override the ``__new__()`` method. They may | |
| define a class or static method named ``_construct_<attr>`` for each | |
| attribute to process the value passed to ``__new__()``. Attributes listed | |
| in the class attribute ``not_in_args`` are not passed to :class:`~.Basic`. | |
| """ | |
| __slots__: tuple[str, ...] = () | |
| _fields = __slots__ | |
| defaults: dict[str, Any] = {} | |
| not_in_args: list[str] = [] | |
| indented_args = ['body'] | |
| def is_Atom(self): | |
| return len(self._fields) == 0 | |
| def _get_constructor(cls, attr): | |
| """ Get the constructor function for an attribute by name. """ | |
| return getattr(cls, '_construct_%s' % attr, lambda x: x) | |
| def _construct(cls, attr, arg): | |
| """ Construct an attribute value from argument passed to ``__new__()``. """ | |
| # arg may be ``NoneToken()``, so comparison is done using == instead of ``is`` operator | |
| if arg == None: | |
| return cls.defaults.get(attr, none) | |
| else: | |
| if isinstance(arg, Dummy): # SymPy's replace uses Dummy instances | |
| return arg | |
| else: | |
| return cls._get_constructor(attr)(arg) | |
| def __new__(cls, *args, **kwargs): | |
| # Pass through existing instances when given as sole argument | |
| if len(args) == 1 and not kwargs and isinstance(args[0], cls): | |
| return args[0] | |
| if len(args) > len(cls._fields): | |
| raise ValueError("Too many arguments (%d), expected at most %d" % (len(args), len(cls._fields))) | |
| attrvals = [] | |
| # Process positional arguments | |
| for attrname, argval in zip(cls._fields, args): | |
| if attrname in kwargs: | |
| raise TypeError('Got multiple values for attribute %r' % attrname) | |
| attrvals.append(cls._construct(attrname, argval)) | |
| # Process keyword arguments | |
| for attrname in cls._fields[len(args):]: | |
| if attrname in kwargs: | |
| argval = kwargs.pop(attrname) | |
| elif attrname in cls.defaults: | |
| argval = cls.defaults[attrname] | |
| else: | |
| raise TypeError('No value for %r given and attribute has no default' % attrname) | |
| attrvals.append(cls._construct(attrname, argval)) | |
| if kwargs: | |
| raise ValueError("Unknown keyword arguments: %s" % ' '.join(kwargs)) | |
| # Parent constructor | |
| basic_args = [ | |
| val for attr, val in zip(cls._fields, attrvals) | |
| if attr not in cls.not_in_args | |
| ] | |
| obj = CodegenAST.__new__(cls, *basic_args) | |
| # Set attributes | |
| for attr, arg in zip(cls._fields, attrvals): | |
| setattr(obj, attr, arg) | |
| return obj | |
| def __eq__(self, other): | |
| if not isinstance(other, self.__class__): | |
| return False | |
| for attr in self._fields: | |
| if getattr(self, attr) != getattr(other, attr): | |
| return False | |
| return True | |
| def _hashable_content(self): | |
| return tuple([getattr(self, attr) for attr in self._fields]) | |
| def __hash__(self): | |
| return super().__hash__() | |
| def _joiner(self, k, indent_level): | |
| return (',\n' + ' '*indent_level) if k in self.indented_args else ', ' | |
| def _indented(self, printer, k, v, *args, **kwargs): | |
| il = printer._context['indent_level'] | |
| def _print(arg): | |
| if isinstance(arg, Token): | |
| return printer._print(arg, *args, joiner=self._joiner(k, il), **kwargs) | |
| else: | |
| return printer._print(arg, *args, **kwargs) | |
| if isinstance(v, Tuple): | |
| joined = self._joiner(k, il).join([_print(arg) for arg in v.args]) | |
| if k in self.indented_args: | |
| return '(\n' + ' '*il + joined + ',\n' + ' '*(il - 4) + ')' | |
| else: | |
| return ('({0},)' if len(v.args) == 1 else '({0})').format(joined) | |
| else: | |
| return _print(v) | |
| def _sympyrepr(self, printer, *args, joiner=', ', **kwargs): | |
| from sympy.printing.printer import printer_context | |
| exclude = kwargs.get('exclude', ()) | |
| values = [getattr(self, k) for k in self._fields] | |
| indent_level = printer._context.get('indent_level', 0) | |
| arg_reprs = [] | |
| for i, (attr, value) in enumerate(zip(self._fields, values)): | |
| if attr in exclude: | |
| continue | |
| # Skip attributes which have the default value | |
| if attr in self.defaults and value == self.defaults[attr]: | |
| continue | |
| ilvl = indent_level + 4 if attr in self.indented_args else 0 | |
| with printer_context(printer, indent_level=ilvl): | |
| indented = self._indented(printer, attr, value, *args, **kwargs) | |
| arg_reprs.append(('{1}' if i == 0 else '{0}={1}').format(attr, indented.lstrip())) | |
| return "{}({})".format(self.__class__.__name__, joiner.join(arg_reprs)) | |
| _sympystr = _sympyrepr | |
| def __repr__(self): # sympy.core.Basic.__repr__ uses sstr | |
| from sympy.printing import srepr | |
| return srepr(self) | |
| def kwargs(self, exclude=(), apply=None): | |
| """ Get instance's attributes as dict of keyword arguments. | |
| Parameters | |
| ========== | |
| exclude : collection of str | |
| Collection of keywords to exclude. | |
| apply : callable, optional | |
| Function to apply to all values. | |
| """ | |
| kwargs = {k: getattr(self, k) for k in self._fields if k not in exclude} | |
| if apply is not None: | |
| return {k: apply(v) for k, v in kwargs.items()} | |
| else: | |
| return kwargs | |
| class BreakToken(Token): | |
| """ Represents 'break' in C/Python ('exit' in Fortran). | |
| Use the premade instance ``break_`` or instantiate manually. | |
| Examples | |
| ======== | |
| >>> from sympy import ccode, fcode | |
| >>> from sympy.codegen.ast import break_ | |
| >>> ccode(break_) | |
| 'break' | |
| >>> fcode(break_, source_format='free') | |
| 'exit' | |
| """ | |
| break_ = BreakToken() | |
| class ContinueToken(Token): | |
| """ Represents 'continue' in C/Python ('cycle' in Fortran) | |
| Use the premade instance ``continue_`` or instantiate manually. | |
| Examples | |
| ======== | |
| >>> from sympy import ccode, fcode | |
| >>> from sympy.codegen.ast import continue_ | |
| >>> ccode(continue_) | |
| 'continue' | |
| >>> fcode(continue_, source_format='free') | |
| 'cycle' | |
| """ | |
| continue_ = ContinueToken() | |
| class NoneToken(Token): | |
| """ The AST equivalence of Python's NoneType | |
| The corresponding instance of Python's ``None`` is ``none``. | |
| Examples | |
| ======== | |
| >>> from sympy.codegen.ast import none, Variable | |
| >>> from sympy import pycode | |
| >>> print(pycode(Variable('x').as_Declaration(value=none))) | |
| x = None | |
| """ | |
| def __eq__(self, other): | |
| return other is None or isinstance(other, NoneToken) | |
| def _hashable_content(self): | |
| return () | |
| def __hash__(self): | |
| return super().__hash__() | |
| none = NoneToken() | |
| class AssignmentBase(CodegenAST): | |
| """ Abstract base class for Assignment and AugmentedAssignment. | |
| Attributes: | |
| =========== | |
| op : str | |
| Symbol for assignment operator, e.g. "=", "+=", etc. | |
| """ | |
| def __new__(cls, lhs, rhs): | |
| lhs = _sympify(lhs) | |
| rhs = _sympify(rhs) | |
| cls._check_args(lhs, rhs) | |
| return super().__new__(cls, lhs, rhs) | |
| def lhs(self): | |
| return self.args[0] | |
| def rhs(self): | |
| return self.args[1] | |
| def _check_args(cls, lhs, rhs): | |
| """ Check arguments to __new__ and raise exception if any problems found. | |
| Derived classes may wish to override this. | |
| """ | |
| from sympy.matrices.expressions.matexpr import ( | |
| MatrixElement, MatrixSymbol) | |
| from sympy.tensor.indexed import Indexed | |
| from sympy.tensor.array.expressions import ArrayElement | |
| # Tuple of things that can be on the lhs of an assignment | |
| assignable = (Symbol, MatrixSymbol, MatrixElement, Indexed, Element, Variable, | |
| ArrayElement) | |
| if not isinstance(lhs, assignable): | |
| raise TypeError("Cannot assign to lhs of type %s." % type(lhs)) | |
| # Indexed types implement shape, but don't define it until later. This | |
| # causes issues in assignment validation. For now, matrices are defined | |
| # as anything with a shape that is not an Indexed | |
| lhs_is_mat = hasattr(lhs, 'shape') and not isinstance(lhs, Indexed) | |
| rhs_is_mat = hasattr(rhs, 'shape') and not isinstance(rhs, Indexed) | |
| # If lhs and rhs have same structure, then this assignment is ok | |
| if lhs_is_mat: | |
| if not rhs_is_mat: | |
| raise ValueError("Cannot assign a scalar to a matrix.") | |
| elif lhs.shape != rhs.shape: | |
| raise ValueError("Dimensions of lhs and rhs do not align.") | |
| elif rhs_is_mat and not lhs_is_mat: | |
| raise ValueError("Cannot assign a matrix to a scalar.") | |
| class Assignment(AssignmentBase): | |
| """ | |
| Represents variable assignment for code generation. | |
| Parameters | |
| ========== | |
| lhs : Expr | |
| SymPy object representing the lhs of the expression. These should be | |
| singular objects, such as one would use in writing code. Notable types | |
| include Symbol, MatrixSymbol, MatrixElement, and Indexed. Types that | |
| subclass these types are also supported. | |
| rhs : Expr | |
| SymPy object representing the rhs of the expression. This can be any | |
| type, provided its shape corresponds to that of the lhs. For example, | |
| a Matrix type can be assigned to MatrixSymbol, but not to Symbol, as | |
| the dimensions will not align. | |
| Examples | |
| ======== | |
| >>> from sympy import symbols, MatrixSymbol, Matrix | |
| >>> from sympy.codegen.ast import Assignment | |
| >>> x, y, z = symbols('x, y, z') | |
| >>> Assignment(x, y) | |
| Assignment(x, y) | |
| >>> Assignment(x, 0) | |
| Assignment(x, 0) | |
| >>> A = MatrixSymbol('A', 1, 3) | |
| >>> mat = Matrix([x, y, z]).T | |
| >>> Assignment(A, mat) | |
| Assignment(A, Matrix([[x, y, z]])) | |
| >>> Assignment(A[0, 1], x) | |
| Assignment(A[0, 1], x) | |
| """ | |
| op = ':=' | |
| class AugmentedAssignment(AssignmentBase): | |
| """ | |
| Base class for augmented assignments. | |
| Attributes: | |
| =========== | |
| binop : str | |
| Symbol for binary operation being applied in the assignment, such as "+", | |
| "*", etc. | |
| """ | |
| binop = None # type: str | |
| def op(self): | |
| return self.binop + '=' | |
| class AddAugmentedAssignment(AugmentedAssignment): | |
| binop = '+' | |
| class SubAugmentedAssignment(AugmentedAssignment): | |
| binop = '-' | |
| class MulAugmentedAssignment(AugmentedAssignment): | |
| binop = '*' | |
| class DivAugmentedAssignment(AugmentedAssignment): | |
| binop = '/' | |
| class ModAugmentedAssignment(AugmentedAssignment): | |
| binop = '%' | |
| # Mapping from binary op strings to AugmentedAssignment subclasses | |
| augassign_classes = { | |
| cls.binop: cls for cls in [ | |
| AddAugmentedAssignment, SubAugmentedAssignment, MulAugmentedAssignment, | |
| DivAugmentedAssignment, ModAugmentedAssignment | |
| ] | |
| } | |
| def aug_assign(lhs, op, rhs): | |
| """ | |
| Create 'lhs op= rhs'. | |
| Explanation | |
| =========== | |
| Represents augmented variable assignment for code generation. This is a | |
| convenience function. You can also use the AugmentedAssignment classes | |
| directly, like AddAugmentedAssignment(x, y). | |
| Parameters | |
| ========== | |
| lhs : Expr | |
| SymPy object representing the lhs of the expression. These should be | |
| singular objects, such as one would use in writing code. Notable types | |
| include Symbol, MatrixSymbol, MatrixElement, and Indexed. Types that | |
| subclass these types are also supported. | |
| op : str | |
| Operator (+, -, /, \\*, %). | |
| rhs : Expr | |
| SymPy object representing the rhs of the expression. This can be any | |
| type, provided its shape corresponds to that of the lhs. For example, | |
| a Matrix type can be assigned to MatrixSymbol, but not to Symbol, as | |
| the dimensions will not align. | |
| Examples | |
| ======== | |
| >>> from sympy import symbols | |
| >>> from sympy.codegen.ast import aug_assign | |
| >>> x, y = symbols('x, y') | |
| >>> aug_assign(x, '+', y) | |
| AddAugmentedAssignment(x, y) | |
| """ | |
| if op not in augassign_classes: | |
| raise ValueError("Unrecognized operator %s" % op) | |
| return augassign_classes[op](lhs, rhs) | |
| class CodeBlock(CodegenAST): | |
| """ | |
| Represents a block of code. | |
| Explanation | |
| =========== | |
| For now only assignments are supported. This restriction will be lifted in | |
| the future. | |
| Useful attributes on this object are: | |
| ``left_hand_sides``: | |
| Tuple of left-hand sides of assignments, in order. | |
| ``left_hand_sides``: | |
| Tuple of right-hand sides of assignments, in order. | |
| ``free_symbols``: Free symbols of the expressions in the right-hand sides | |
| which do not appear in the left-hand side of an assignment. | |
| Useful methods on this object are: | |
| ``topological_sort``: | |
| Class method. Return a CodeBlock with assignments | |
| sorted so that variables are assigned before they | |
| are used. | |
| ``cse``: | |
| Return a new CodeBlock with common subexpressions eliminated and | |
| pulled out as assignments. | |
| Examples | |
| ======== | |
| >>> from sympy import symbols, ccode | |
| >>> from sympy.codegen.ast import CodeBlock, Assignment | |
| >>> x, y = symbols('x y') | |
| >>> c = CodeBlock(Assignment(x, 1), Assignment(y, x + 1)) | |
| >>> print(ccode(c)) | |
| x = 1; | |
| y = x + 1; | |
| """ | |
| def __new__(cls, *args): | |
| left_hand_sides = [] | |
| right_hand_sides = [] | |
| for i in args: | |
| if isinstance(i, Assignment): | |
| lhs, rhs = i.args | |
| left_hand_sides.append(lhs) | |
| right_hand_sides.append(rhs) | |
| obj = CodegenAST.__new__(cls, *args) | |
| obj.left_hand_sides = Tuple(*left_hand_sides) | |
| obj.right_hand_sides = Tuple(*right_hand_sides) | |
| return obj | |
| def __iter__(self): | |
| return iter(self.args) | |
| def _sympyrepr(self, printer, *args, **kwargs): | |
| il = printer._context.get('indent_level', 0) | |
| joiner = ',\n' + ' '*il | |
| joined = joiner.join(map(printer._print, self.args)) | |
| return ('{}(\n'.format(' '*(il-4) + self.__class__.__name__,) + | |
| ' '*il + joined + '\n' + ' '*(il - 4) + ')') | |
| _sympystr = _sympyrepr | |
| def free_symbols(self): | |
| return super().free_symbols - set(self.left_hand_sides) | |
| def topological_sort(cls, assignments): | |
| """ | |
| Return a CodeBlock with topologically sorted assignments so that | |
| variables are assigned before they are used. | |
| Examples | |
| ======== | |
| The existing order of assignments is preserved as much as possible. | |
| This function assumes that variables are assigned to only once. | |
| This is a class constructor so that the default constructor for | |
| CodeBlock can error when variables are used before they are assigned. | |
| >>> from sympy import symbols | |
| >>> from sympy.codegen.ast import CodeBlock, Assignment | |
| >>> x, y, z = symbols('x y z') | |
| >>> assignments = [ | |
| ... Assignment(x, y + z), | |
| ... Assignment(y, z + 1), | |
| ... Assignment(z, 2), | |
| ... ] | |
| >>> CodeBlock.topological_sort(assignments) | |
| CodeBlock( | |
| Assignment(z, 2), | |
| Assignment(y, z + 1), | |
| Assignment(x, y + z) | |
| ) | |
| """ | |
| if not all(isinstance(i, Assignment) for i in assignments): | |
| # Will support more things later | |
| raise NotImplementedError("CodeBlock.topological_sort only supports Assignments") | |
| if any(isinstance(i, AugmentedAssignment) for i in assignments): | |
| raise NotImplementedError("CodeBlock.topological_sort does not yet work with AugmentedAssignments") | |
| # Create a graph where the nodes are assignments and there is a directed edge | |
| # between nodes that use a variable and nodes that assign that | |
| # variable, like | |
| # [(x := 1, y := x + 1), (x := 1, z := y + z), (y := x + 1, z := y + z)] | |
| # If we then topologically sort these nodes, they will be in | |
| # assignment order, like | |
| # x := 1 | |
| # y := x + 1 | |
| # z := y + z | |
| # A = The nodes | |
| # | |
| # enumerate keeps nodes in the same order they are already in if | |
| # possible. It will also allow us to handle duplicate assignments to | |
| # the same variable when those are implemented. | |
| A = list(enumerate(assignments)) | |
| # var_map = {variable: [nodes for which this variable is assigned to]} | |
| # like {x: [(1, x := y + z), (4, x := 2 * w)], ...} | |
| var_map = defaultdict(list) | |
| for node in A: | |
| i, a = node | |
| var_map[a.lhs].append(node) | |
| # E = Edges in the graph | |
| E = [] | |
| for dst_node in A: | |
| i, a = dst_node | |
| for s in a.rhs.free_symbols: | |
| for src_node in var_map[s]: | |
| E.append((src_node, dst_node)) | |
| ordered_assignments = topological_sort([A, E]) | |
| # De-enumerate the result | |
| return cls(*[a for i, a in ordered_assignments]) | |
| def cse(self, symbols=None, optimizations=None, postprocess=None, | |
| order='canonical'): | |
| """ | |
| Return a new code block with common subexpressions eliminated. | |
| Explanation | |
| =========== | |
| See the docstring of :func:`sympy.simplify.cse_main.cse` for more | |
| information. | |
| Examples | |
| ======== | |
| >>> from sympy import symbols, sin | |
| >>> from sympy.codegen.ast import CodeBlock, Assignment | |
| >>> x, y, z = symbols('x y z') | |
| >>> c = CodeBlock( | |
| ... Assignment(x, 1), | |
| ... Assignment(y, sin(x) + 1), | |
| ... Assignment(z, sin(x) - 1), | |
| ... ) | |
| ... | |
| >>> c.cse() | |
| CodeBlock( | |
| Assignment(x, 1), | |
| Assignment(x0, sin(x)), | |
| Assignment(y, x0 + 1), | |
| Assignment(z, x0 - 1) | |
| ) | |
| """ | |
| from sympy.simplify.cse_main import cse | |
| # Check that the CodeBlock only contains assignments to unique variables | |
| if not all(isinstance(i, Assignment) for i in self.args): | |
| # Will support more things later | |
| raise NotImplementedError("CodeBlock.cse only supports Assignments") | |
| if any(isinstance(i, AugmentedAssignment) for i in self.args): | |
| raise NotImplementedError("CodeBlock.cse does not yet work with AugmentedAssignments") | |
| for i, lhs in enumerate(self.left_hand_sides): | |
| if lhs in self.left_hand_sides[:i]: | |
| raise NotImplementedError("Duplicate assignments to the same " | |
| "variable are not yet supported (%s)" % lhs) | |
| # Ensure new symbols for subexpressions do not conflict with existing | |
| existing_symbols = self.atoms(Symbol) | |
| if symbols is None: | |
| symbols = numbered_symbols() | |
| symbols = filter_symbols(symbols, existing_symbols) | |
| replacements, reduced_exprs = cse(list(self.right_hand_sides), | |
| symbols=symbols, optimizations=optimizations, postprocess=postprocess, | |
| order=order) | |
| new_block = [Assignment(var, expr) for var, expr in | |
| zip(self.left_hand_sides, reduced_exprs)] | |
| new_assignments = [Assignment(var, expr) for var, expr in replacements] | |
| return self.topological_sort(new_assignments + new_block) | |
| class For(Token): | |
| """Represents a 'for-loop' in the code. | |
| Expressions are of the form: | |
| "for target in iter: | |
| body..." | |
| Parameters | |
| ========== | |
| target : symbol | |
| iter : iterable | |
| body : CodeBlock or iterable | |
| ! When passed an iterable it is used to instantiate a CodeBlock. | |
| Examples | |
| ======== | |
| >>> from sympy import symbols, Range | |
| >>> from sympy.codegen.ast import aug_assign, For | |
| >>> x, i, j, k = symbols('x i j k') | |
| >>> for_i = For(i, Range(10), [aug_assign(x, '+', i*j*k)]) | |
| >>> for_i # doctest: -NORMALIZE_WHITESPACE | |
| For(i, iterable=Range(0, 10, 1), body=CodeBlock( | |
| AddAugmentedAssignment(x, i*j*k) | |
| )) | |
| >>> for_ji = For(j, Range(7), [for_i]) | |
| >>> for_ji # doctest: -NORMALIZE_WHITESPACE | |
| For(j, iterable=Range(0, 7, 1), body=CodeBlock( | |
| For(i, iterable=Range(0, 10, 1), body=CodeBlock( | |
| AddAugmentedAssignment(x, i*j*k) | |
| )) | |
| )) | |
| >>> for_kji =For(k, Range(5), [for_ji]) | |
| >>> for_kji # doctest: -NORMALIZE_WHITESPACE | |
| For(k, iterable=Range(0, 5, 1), body=CodeBlock( | |
| For(j, iterable=Range(0, 7, 1), body=CodeBlock( | |
| For(i, iterable=Range(0, 10, 1), body=CodeBlock( | |
| AddAugmentedAssignment(x, i*j*k) | |
| )) | |
| )) | |
| )) | |
| """ | |
| __slots__ = _fields = ('target', 'iterable', 'body') | |
| _construct_target = staticmethod(_sympify) | |
| def _construct_body(cls, itr): | |
| if isinstance(itr, CodeBlock): | |
| return itr | |
| else: | |
| return CodeBlock(*itr) | |
| def _construct_iterable(cls, itr): | |
| if not iterable(itr): | |
| raise TypeError("iterable must be an iterable") | |
| if isinstance(itr, list): # _sympify errors on lists because they are mutable | |
| itr = tuple(itr) | |
| return _sympify(itr) | |
| class String(Atom, Token): | |
| """ SymPy object representing a string. | |
| Atomic object which is not an expression (as opposed to Symbol). | |
| Parameters | |
| ========== | |
| text : str | |
| Examples | |
| ======== | |
| >>> from sympy.codegen.ast import String | |
| >>> f = String('foo') | |
| >>> f | |
| foo | |
| >>> str(f) | |
| 'foo' | |
| >>> f.text | |
| 'foo' | |
| >>> print(repr(f)) | |
| String('foo') | |
| """ | |
| __slots__ = _fields = ('text',) | |
| not_in_args = ['text'] | |
| is_Atom = True | |
| def _construct_text(cls, text): | |
| if not isinstance(text, str): | |
| raise TypeError("Argument text is not a string type.") | |
| return text | |
| def _sympystr(self, printer, *args, **kwargs): | |
| return self.text | |
| def kwargs(self, exclude = (), apply = None): | |
| return {} | |
| #to be removed when Atom is given a suitable func | |
| def func(self): | |
| return lambda: self | |
| def _latex(self, printer): | |
| from sympy.printing.latex import latex_escape | |
| return r'\texttt{{"{}"}}'.format(latex_escape(self.text)) | |
| class QuotedString(String): | |
| """ Represents a string which should be printed with quotes. """ | |
| class Comment(String): | |
| """ Represents a comment. """ | |
| class Node(Token): | |
| """ Subclass of Token, carrying the attribute 'attrs' (Tuple) | |
| Examples | |
| ======== | |
| >>> from sympy.codegen.ast import Node, value_const, pointer_const | |
| >>> n1 = Node([value_const]) | |
| >>> n1.attr_params('value_const') # get the parameters of attribute (by name) | |
| () | |
| >>> from sympy.codegen.fnodes import dimension | |
| >>> n2 = Node([value_const, dimension(5, 3)]) | |
| >>> n2.attr_params(value_const) # get the parameters of attribute (by Attribute instance) | |
| () | |
| >>> n2.attr_params('dimension') # get the parameters of attribute (by name) | |
| (5, 3) | |
| >>> n2.attr_params(pointer_const) is None | |
| True | |
| """ | |
| __slots__: tuple[str, ...] = ('attrs',) | |
| _fields = __slots__ | |
| defaults: dict[str, Any] = {'attrs': Tuple()} | |
| _construct_attrs = staticmethod(_mk_Tuple) | |
| def attr_params(self, looking_for): | |
| """ Returns the parameters of the Attribute with name ``looking_for`` in self.attrs """ | |
| for attr in self.attrs: | |
| if str(attr.name) == str(looking_for): | |
| return attr.parameters | |
| class Type(Token): | |
| """ Represents a type. | |
| Explanation | |
| =========== | |
| The naming is a super-set of NumPy naming. Type has a classmethod | |
| ``from_expr`` which offer type deduction. It also has a method | |
| ``cast_check`` which casts the argument to its type, possibly raising an | |
| exception if rounding error is not within tolerances, or if the value is not | |
| representable by the underlying data type (e.g. unsigned integers). | |
| Parameters | |
| ========== | |
| name : str | |
| Name of the type, e.g. ``object``, ``int16``, ``float16`` (where the latter two | |
| would use the ``Type`` sub-classes ``IntType`` and ``FloatType`` respectively). | |
| If a ``Type`` instance is given, the said instance is returned. | |
| Examples | |
| ======== | |
| >>> from sympy.codegen.ast import Type | |
| >>> t = Type.from_expr(42) | |
| >>> t | |
| integer | |
| >>> print(repr(t)) | |
| IntBaseType(String('integer')) | |
| >>> from sympy.codegen.ast import uint8 | |
| >>> uint8.cast_check(-1) # doctest: +ELLIPSIS | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: Minimum value for data type bigger than new value. | |
| >>> from sympy.codegen.ast import float32 | |
| >>> v6 = 0.123456 | |
| >>> float32.cast_check(v6) | |
| 0.123456 | |
| >>> v10 = 12345.67894 | |
| >>> float32.cast_check(v10) # doctest: +ELLIPSIS | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: Casting gives a significantly different value. | |
| >>> boost_mp50 = Type('boost::multiprecision::cpp_dec_float_50') | |
| >>> from sympy import cxxcode | |
| >>> from sympy.codegen.ast import Declaration, Variable | |
| >>> cxxcode(Declaration(Variable('x', type=boost_mp50))) | |
| 'boost::multiprecision::cpp_dec_float_50 x' | |
| References | |
| ========== | |
| .. [1] https://numpy.org/doc/stable/user/basics.types.html | |
| """ | |
| __slots__: tuple[str, ...] = ('name',) | |
| _fields = __slots__ | |
| _construct_name = String | |
| def _sympystr(self, printer, *args, **kwargs): | |
| return str(self.name) | |
| def from_expr(cls, expr): | |
| """ Deduces type from an expression or a ``Symbol``. | |
| Parameters | |
| ========== | |
| expr : number or SymPy object | |
| The type will be deduced from type or properties. | |
| Examples | |
| ======== | |
| >>> from sympy.codegen.ast import Type, integer, complex_ | |
| >>> Type.from_expr(2) == integer | |
| True | |
| >>> from sympy import Symbol | |
| >>> Type.from_expr(Symbol('z', complex=True)) == complex_ | |
| True | |
| >>> Type.from_expr(sum) # doctest: +ELLIPSIS | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: Could not deduce type from expr. | |
| Raises | |
| ====== | |
| ValueError when type deduction fails. | |
| """ | |
| if isinstance(expr, (float, Float)): | |
| return real | |
| if isinstance(expr, (int, Integer)) or getattr(expr, 'is_integer', False): | |
| return integer | |
| if getattr(expr, 'is_real', False): | |
| return real | |
| if isinstance(expr, complex) or getattr(expr, 'is_complex', False): | |
| return complex_ | |
| if isinstance(expr, bool) or getattr(expr, 'is_Relational', False): | |
| return bool_ | |
| else: | |
| raise ValueError("Could not deduce type from expr.") | |
| def _check(self, value): | |
| pass | |
| def cast_check(self, value, rtol=None, atol=0, precision_targets=None): | |
| """ Casts a value to the data type of the instance. | |
| Parameters | |
| ========== | |
| value : number | |
| rtol : floating point number | |
| Relative tolerance. (will be deduced if not given). | |
| atol : floating point number | |
| Absolute tolerance (in addition to ``rtol``). | |
| type_aliases : dict | |
| Maps substitutions for Type, e.g. {integer: int64, real: float32} | |
| Examples | |
| ======== | |
| >>> from sympy.codegen.ast import integer, float32, int8 | |
| >>> integer.cast_check(3.0) == 3 | |
| True | |
| >>> float32.cast_check(1e-40) # doctest: +ELLIPSIS | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: Minimum value for data type bigger than new value. | |
| >>> int8.cast_check(256) # doctest: +ELLIPSIS | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: Maximum value for data type smaller than new value. | |
| >>> v10 = 12345.67894 | |
| >>> float32.cast_check(v10) # doctest: +ELLIPSIS | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: Casting gives a significantly different value. | |
| >>> from sympy.codegen.ast import float64 | |
| >>> float64.cast_check(v10) | |
| 12345.67894 | |
| >>> from sympy import Float | |
| >>> v18 = Float('0.123456789012345646') | |
| >>> float64.cast_check(v18) | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: Casting gives a significantly different value. | |
| >>> from sympy.codegen.ast import float80 | |
| >>> float80.cast_check(v18) | |
| 0.123456789012345649 | |
| """ | |
| val = sympify(value) | |
| ten = Integer(10) | |
| exp10 = getattr(self, 'decimal_dig', None) | |
| if rtol is None: | |
| rtol = 1e-15 if exp10 is None else 2.0*ten**(-exp10) | |
| def tol(num): | |
| return atol + rtol*abs(num) | |
| new_val = self.cast_nocheck(value) | |
| self._check(new_val) | |
| delta = new_val - val | |
| if abs(delta) > tol(val): # rounding, e.g. int(3.5) != 3.5 | |
| raise ValueError("Casting gives a significantly different value.") | |
| return new_val | |
| def _latex(self, printer): | |
| from sympy.printing.latex import latex_escape | |
| type_name = latex_escape(self.__class__.__name__) | |
| name = latex_escape(self.name.text) | |
| return r"\text{{{}}}\left(\texttt{{{}}}\right)".format(type_name, name) | |
| class IntBaseType(Type): | |
| """ Integer base type, contains no size information. """ | |
| __slots__ = () | |
| cast_nocheck = lambda self, i: Integer(int(i)) | |
| class _SizedIntType(IntBaseType): | |
| __slots__ = ('nbits',) | |
| _fields = Type._fields + __slots__ | |
| _construct_nbits = Integer | |
| def _check(self, value): | |
| if value < self.min: | |
| raise ValueError("Value is too small: %d < %d" % (value, self.min)) | |
| if value > self.max: | |
| raise ValueError("Value is too big: %d > %d" % (value, self.max)) | |
| class SignedIntType(_SizedIntType): | |
| """ Represents a signed integer type. """ | |
| __slots__ = () | |
| def min(self): | |
| return -2**(self.nbits-1) | |
| def max(self): | |
| return 2**(self.nbits-1) - 1 | |
| class UnsignedIntType(_SizedIntType): | |
| """ Represents an unsigned integer type. """ | |
| __slots__ = () | |
| def min(self): | |
| return 0 | |
| def max(self): | |
| return 2**self.nbits - 1 | |
| two = Integer(2) | |
| class FloatBaseType(Type): | |
| """ Represents a floating point number type. """ | |
| __slots__ = () | |
| cast_nocheck = Float | |
| class FloatType(FloatBaseType): | |
| """ Represents a floating point type with fixed bit width. | |
| Base 2 & one sign bit is assumed. | |
| Parameters | |
| ========== | |
| name : str | |
| Name of the type. | |
| nbits : integer | |
| Number of bits used (storage). | |
| nmant : integer | |
| Number of bits used to represent the mantissa. | |
| nexp : integer | |
| Number of bits used to represent the mantissa. | |
| Examples | |
| ======== | |
| >>> from sympy import S | |
| >>> from sympy.codegen.ast import FloatType | |
| >>> half_precision = FloatType('f16', nbits=16, nmant=10, nexp=5) | |
| >>> half_precision.max | |
| 65504 | |
| >>> half_precision.tiny == S(2)**-14 | |
| True | |
| >>> half_precision.eps == S(2)**-10 | |
| True | |
| >>> half_precision.dig == 3 | |
| True | |
| >>> half_precision.decimal_dig == 5 | |
| True | |
| >>> half_precision.cast_check(1.0) | |
| 1.0 | |
| >>> half_precision.cast_check(1e5) # doctest: +ELLIPSIS | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: Maximum value for data type smaller than new value. | |
| """ | |
| __slots__ = ('nbits', 'nmant', 'nexp',) | |
| _fields = Type._fields + __slots__ | |
| _construct_nbits = _construct_nmant = _construct_nexp = Integer | |
| def max_exponent(self): | |
| """ The largest positive number n, such that 2**(n - 1) is a representable finite value. """ | |
| # cf. C++'s ``std::numeric_limits::max_exponent`` | |
| return two**(self.nexp - 1) | |
| def min_exponent(self): | |
| """ The lowest negative number n, such that 2**(n - 1) is a valid normalized number. """ | |
| # cf. C++'s ``std::numeric_limits::min_exponent`` | |
| return 3 - self.max_exponent | |
| def max(self): | |
| """ Maximum value representable. """ | |
| return (1 - two**-(self.nmant+1))*two**self.max_exponent | |
| def tiny(self): | |
| """ The minimum positive normalized value. """ | |
| # See C macros: FLT_MIN, DBL_MIN, LDBL_MIN | |
| # or C++'s ``std::numeric_limits::min`` | |
| # or numpy.finfo(dtype).tiny | |
| return two**(self.min_exponent - 1) | |
| def eps(self): | |
| """ Difference between 1.0 and the next representable value. """ | |
| return two**(-self.nmant) | |
| def dig(self): | |
| """ Number of decimal digits that are guaranteed to be preserved in text. | |
| When converting text -> float -> text, you are guaranteed that at least ``dig`` | |
| number of digits are preserved with respect to rounding or overflow. | |
| """ | |
| from sympy.functions import floor, log | |
| return floor(self.nmant * log(2)/log(10)) | |
| def decimal_dig(self): | |
| """ Number of digits needed to store & load without loss. | |
| Explanation | |
| =========== | |
| Number of decimal digits needed to guarantee that two consecutive conversions | |
| (float -> text -> float) to be idempotent. This is useful when one do not want | |
| to loose precision due to rounding errors when storing a floating point value | |
| as text. | |
| """ | |
| from sympy.functions import ceiling, log | |
| return ceiling((self.nmant + 1) * log(2)/log(10) + 1) | |
| def cast_nocheck(self, value): | |
| """ Casts without checking if out of bounds or subnormal. """ | |
| if value == oo: # float(oo) or oo | |
| return float(oo) | |
| elif value == -oo: # float(-oo) or -oo | |
| return float(-oo) | |
| return Float(str(sympify(value).evalf(self.decimal_dig)), self.decimal_dig) | |
| def _check(self, value): | |
| if value < -self.max: | |
| raise ValueError("Value is too small: %d < %d" % (value, -self.max)) | |
| if value > self.max: | |
| raise ValueError("Value is too big: %d > %d" % (value, self.max)) | |
| if abs(value) < self.tiny: | |
| raise ValueError("Smallest (absolute) value for data type bigger than new value.") | |
| class ComplexBaseType(FloatBaseType): | |
| __slots__ = () | |
| def cast_nocheck(self, value): | |
| """ Casts without checking if out of bounds or subnormal. """ | |
| from sympy.functions import re, im | |
| return ( | |
| super().cast_nocheck(re(value)) + | |
| super().cast_nocheck(im(value))*1j | |
| ) | |
| def _check(self, value): | |
| from sympy.functions import re, im | |
| super()._check(re(value)) | |
| super()._check(im(value)) | |
| class ComplexType(ComplexBaseType, FloatType): | |
| """ Represents a complex floating point number. """ | |
| __slots__ = () | |
| # NumPy types: | |
| intc = IntBaseType('intc') | |
| intp = IntBaseType('intp') | |
| int8 = SignedIntType('int8', 8) | |
| int16 = SignedIntType('int16', 16) | |
| int32 = SignedIntType('int32', 32) | |
| int64 = SignedIntType('int64', 64) | |
| uint8 = UnsignedIntType('uint8', 8) | |
| uint16 = UnsignedIntType('uint16', 16) | |
| uint32 = UnsignedIntType('uint32', 32) | |
| uint64 = UnsignedIntType('uint64', 64) | |
| float16 = FloatType('float16', 16, nexp=5, nmant=10) # IEEE 754 binary16, Half precision | |
| float32 = FloatType('float32', 32, nexp=8, nmant=23) # IEEE 754 binary32, Single precision | |
| float64 = FloatType('float64', 64, nexp=11, nmant=52) # IEEE 754 binary64, Double precision | |
| float80 = FloatType('float80', 80, nexp=15, nmant=63) # x86 extended precision (1 integer part bit), "long double" | |
| float128 = FloatType('float128', 128, nexp=15, nmant=112) # IEEE 754 binary128, Quadruple precision | |
| float256 = FloatType('float256', 256, nexp=19, nmant=236) # IEEE 754 binary256, Octuple precision | |
| complex64 = ComplexType('complex64', nbits=64, **float32.kwargs(exclude=('name', 'nbits'))) | |
| complex128 = ComplexType('complex128', nbits=128, **float64.kwargs(exclude=('name', 'nbits'))) | |
| # Generic types (precision may be chosen by code printers): | |
| untyped = Type('untyped') | |
| real = FloatBaseType('real') | |
| integer = IntBaseType('integer') | |
| complex_ = ComplexBaseType('complex') | |
| bool_ = Type('bool') | |
| class Attribute(Token): | |
| """ Attribute (possibly parametrized) | |
| For use with :class:`sympy.codegen.ast.Node` (which takes instances of | |
| ``Attribute`` as ``attrs``). | |
| Parameters | |
| ========== | |
| name : str | |
| parameters : Tuple | |
| Examples | |
| ======== | |
| >>> from sympy.codegen.ast import Attribute | |
| >>> volatile = Attribute('volatile') | |
| >>> volatile | |
| volatile | |
| >>> print(repr(volatile)) | |
| Attribute(String('volatile')) | |
| >>> a = Attribute('foo', [1, 2, 3]) | |
| >>> a | |
| foo(1, 2, 3) | |
| >>> a.parameters == (1, 2, 3) | |
| True | |
| """ | |
| __slots__ = _fields = ('name', 'parameters') | |
| defaults = {'parameters': Tuple()} | |
| _construct_name = String | |
| _construct_parameters = staticmethod(_mk_Tuple) | |
| def _sympystr(self, printer, *args, **kwargs): | |
| result = str(self.name) | |
| if self.parameters: | |
| result += '(%s)' % ', '.join((printer._print( | |
| arg, *args, **kwargs) for arg in self.parameters)) | |
| return result | |
| value_const = Attribute('value_const') | |
| pointer_const = Attribute('pointer_const') | |
| class Variable(Node): | |
| """ Represents a variable. | |
| Parameters | |
| ========== | |
| symbol : Symbol | |
| type : Type (optional) | |
| Type of the variable. | |
| attrs : iterable of Attribute instances | |
| Will be stored as a Tuple. | |
| Examples | |
| ======== | |
| >>> from sympy import Symbol | |
| >>> from sympy.codegen.ast import Variable, float32, integer | |
| >>> x = Symbol('x') | |
| >>> v = Variable(x, type=float32) | |
| >>> v.attrs | |
| () | |
| >>> v == Variable('x') | |
| False | |
| >>> v == Variable('x', type=float32) | |
| True | |
| >>> v | |
| Variable(x, type=float32) | |
| One may also construct a ``Variable`` instance with the type deduced from | |
| assumptions about the symbol using the ``deduced`` classmethod: | |
| >>> i = Symbol('i', integer=True) | |
| >>> v = Variable.deduced(i) | |
| >>> v.type == integer | |
| True | |
| >>> v == Variable('i') | |
| False | |
| >>> from sympy.codegen.ast import value_const | |
| >>> value_const in v.attrs | |
| False | |
| >>> w = Variable('w', attrs=[value_const]) | |
| >>> w | |
| Variable(w, attrs=(value_const,)) | |
| >>> value_const in w.attrs | |
| True | |
| >>> w.as_Declaration(value=42) | |
| Declaration(Variable(w, value=42, attrs=(value_const,))) | |
| """ | |
| __slots__ = ('symbol', 'type', 'value') | |
| _fields = __slots__ + Node._fields | |
| defaults = Node.defaults.copy() | |
| defaults.update({'type': untyped, 'value': none}) | |
| _construct_symbol = staticmethod(sympify) | |
| _construct_value = staticmethod(sympify) | |
| def deduced(cls, symbol, value=None, attrs=Tuple(), cast_check=True): | |
| """ Alt. constructor with type deduction from ``Type.from_expr``. | |
| Deduces type primarily from ``symbol``, secondarily from ``value``. | |
| Parameters | |
| ========== | |
| symbol : Symbol | |
| value : expr | |
| (optional) value of the variable. | |
| attrs : iterable of Attribute instances | |
| cast_check : bool | |
| Whether to apply ``Type.cast_check`` on ``value``. | |
| Examples | |
| ======== | |
| >>> from sympy import Symbol | |
| >>> from sympy.codegen.ast import Variable, complex_ | |
| >>> n = Symbol('n', integer=True) | |
| >>> str(Variable.deduced(n).type) | |
| 'integer' | |
| >>> x = Symbol('x', real=True) | |
| >>> v = Variable.deduced(x) | |
| >>> v.type | |
| real | |
| >>> z = Symbol('z', complex=True) | |
| >>> Variable.deduced(z).type == complex_ | |
| True | |
| """ | |
| if isinstance(symbol, Variable): | |
| return symbol | |
| try: | |
| type_ = Type.from_expr(symbol) | |
| except ValueError: | |
| type_ = Type.from_expr(value) | |
| if value is not None and cast_check: | |
| value = type_.cast_check(value) | |
| return cls(symbol, type=type_, value=value, attrs=attrs) | |
| def as_Declaration(self, **kwargs): | |
| """ Convenience method for creating a Declaration instance. | |
| Explanation | |
| =========== | |
| If the variable of the Declaration need to wrap a modified | |
| variable keyword arguments may be passed (overriding e.g. | |
| the ``value`` of the Variable instance). | |
| Examples | |
| ======== | |
| >>> from sympy.codegen.ast import Variable, NoneToken | |
| >>> x = Variable('x') | |
| >>> decl1 = x.as_Declaration() | |
| >>> # value is special NoneToken() which must be tested with == operator | |
| >>> decl1.variable.value is None # won't work | |
| False | |
| >>> decl1.variable.value == None # not PEP-8 compliant | |
| True | |
| >>> decl1.variable.value == NoneToken() # OK | |
| True | |
| >>> decl2 = x.as_Declaration(value=42.0) | |
| >>> decl2.variable.value == 42.0 | |
| True | |
| """ | |
| kw = self.kwargs() | |
| kw.update(kwargs) | |
| return Declaration(self.func(**kw)) | |
| def _relation(self, rhs, op): | |
| try: | |
| rhs = _sympify(rhs) | |
| except SympifyError: | |
| raise TypeError("Invalid comparison %s < %s" % (self, rhs)) | |
| return op(self, rhs, evaluate=False) | |
| __lt__ = lambda self, other: self._relation(other, Lt) | |
| __le__ = lambda self, other: self._relation(other, Le) | |
| __ge__ = lambda self, other: self._relation(other, Ge) | |
| __gt__ = lambda self, other: self._relation(other, Gt) | |
| class Pointer(Variable): | |
| """ Represents a pointer. See ``Variable``. | |
| Examples | |
| ======== | |
| Can create instances of ``Element``: | |
| >>> from sympy import Symbol | |
| >>> from sympy.codegen.ast import Pointer | |
| >>> i = Symbol('i', integer=True) | |
| >>> p = Pointer('x') | |
| >>> p[i+1] | |
| Element(x, indices=(i + 1,)) | |
| """ | |
| __slots__ = () | |
| def __getitem__(self, key): | |
| try: | |
| return Element(self.symbol, key) | |
| except TypeError: | |
| return Element(self.symbol, (key,)) | |
| class Element(Token): | |
| """ Element in (a possibly N-dimensional) array. | |
| Examples | |
| ======== | |
| >>> from sympy.codegen.ast import Element | |
| >>> elem = Element('x', 'ijk') | |
| >>> elem.symbol.name == 'x' | |
| True | |
| >>> elem.indices | |
| (i, j, k) | |
| >>> from sympy import ccode | |
| >>> ccode(elem) | |
| 'x[i][j][k]' | |
| >>> ccode(Element('x', 'ijk', strides='lmn', offset='o')) | |
| 'x[i*l + j*m + k*n + o]' | |
| """ | |
| __slots__ = _fields = ('symbol', 'indices', 'strides', 'offset') | |
| defaults = {'strides': none, 'offset': none} | |
| _construct_symbol = staticmethod(sympify) | |
| _construct_indices = staticmethod(lambda arg: Tuple(*arg)) | |
| _construct_strides = staticmethod(lambda arg: Tuple(*arg)) | |
| _construct_offset = staticmethod(sympify) | |
| class Declaration(Token): | |
| """ Represents a variable declaration | |
| Parameters | |
| ========== | |
| variable : Variable | |
| Examples | |
| ======== | |
| >>> from sympy.codegen.ast import Declaration, NoneToken, untyped | |
| >>> z = Declaration('z') | |
| >>> z.variable.type == untyped | |
| True | |
| >>> # value is special NoneToken() which must be tested with == operator | |
| >>> z.variable.value is None # won't work | |
| False | |
| >>> z.variable.value == None # not PEP-8 compliant | |
| True | |
| >>> z.variable.value == NoneToken() # OK | |
| True | |
| """ | |
| __slots__ = _fields = ('variable',) | |
| _construct_variable = Variable | |
| class While(Token): | |
| """ Represents a 'for-loop' in the code. | |
| Expressions are of the form: | |
| "while condition: | |
| body..." | |
| Parameters | |
| ========== | |
| condition : expression convertible to Boolean | |
| body : CodeBlock or iterable | |
| When passed an iterable it is used to instantiate a CodeBlock. | |
| Examples | |
| ======== | |
| >>> from sympy import symbols, Gt, Abs | |
| >>> from sympy.codegen import aug_assign, Assignment, While | |
| >>> x, dx = symbols('x dx') | |
| >>> expr = 1 - x**2 | |
| >>> whl = While(Gt(Abs(dx), 1e-9), [ | |
| ... Assignment(dx, -expr/expr.diff(x)), | |
| ... aug_assign(x, '+', dx) | |
| ... ]) | |
| """ | |
| __slots__ = _fields = ('condition', 'body') | |
| _construct_condition = staticmethod(lambda cond: _sympify(cond)) | |
| def _construct_body(cls, itr): | |
| if isinstance(itr, CodeBlock): | |
| return itr | |
| else: | |
| return CodeBlock(*itr) | |
| class Scope(Token): | |
| """ Represents a scope in the code. | |
| Parameters | |
| ========== | |
| body : CodeBlock or iterable | |
| When passed an iterable it is used to instantiate a CodeBlock. | |
| """ | |
| __slots__ = _fields = ('body',) | |
| def _construct_body(cls, itr): | |
| if isinstance(itr, CodeBlock): | |
| return itr | |
| else: | |
| return CodeBlock(*itr) | |
| class Stream(Token): | |
| """ Represents a stream. | |
| There are two predefined Stream instances ``stdout`` & ``stderr``. | |
| Parameters | |
| ========== | |
| name : str | |
| Examples | |
| ======== | |
| >>> from sympy import pycode, Symbol | |
| >>> from sympy.codegen.ast import Print, stderr, QuotedString | |
| >>> print(pycode(Print(['x'], file=stderr))) | |
| print(x, file=sys.stderr) | |
| >>> x = Symbol('x') | |
| >>> print(pycode(Print([QuotedString('x')], file=stderr))) # print literally "x" | |
| print("x", file=sys.stderr) | |
| """ | |
| __slots__ = _fields = ('name',) | |
| _construct_name = String | |
| stdout = Stream('stdout') | |
| stderr = Stream('stderr') | |
| class Print(Token): | |
| r""" Represents print command in the code. | |
| Parameters | |
| ========== | |
| formatstring : str | |
| *args : Basic instances (or convertible to such through sympify) | |
| Examples | |
| ======== | |
| >>> from sympy.codegen.ast import Print | |
| >>> from sympy import pycode | |
| >>> print(pycode(Print('x y'.split(), "coordinate: %12.5g %12.5g\\n"))) | |
| print("coordinate: %12.5g %12.5g\n" % (x, y), end="") | |
| """ | |
| __slots__ = _fields = ('print_args', 'format_string', 'file') | |
| defaults = {'format_string': none, 'file': none} | |
| _construct_print_args = staticmethod(_mk_Tuple) | |
| _construct_format_string = QuotedString | |
| _construct_file = Stream | |
| class FunctionPrototype(Node): | |
| """ Represents a function prototype | |
| Allows the user to generate forward declaration in e.g. C/C++. | |
| Parameters | |
| ========== | |
| return_type : Type | |
| name : str | |
| parameters: iterable of Variable instances | |
| attrs : iterable of Attribute instances | |
| Examples | |
| ======== | |
| >>> from sympy import ccode, symbols | |
| >>> from sympy.codegen.ast import real, FunctionPrototype | |
| >>> x, y = symbols('x y', real=True) | |
| >>> fp = FunctionPrototype(real, 'foo', [x, y]) | |
| >>> ccode(fp) | |
| 'double foo(double x, double y)' | |
| """ | |
| __slots__ = ('return_type', 'name', 'parameters') | |
| _fields: tuple[str, ...] = __slots__ + Node._fields | |
| _construct_return_type = Type | |
| _construct_name = String | |
| def _construct_parameters(args): | |
| def _var(arg): | |
| if isinstance(arg, Declaration): | |
| return arg.variable | |
| elif isinstance(arg, Variable): | |
| return arg | |
| else: | |
| return Variable.deduced(arg) | |
| return Tuple(*map(_var, args)) | |
| def from_FunctionDefinition(cls, func_def): | |
| if not isinstance(func_def, FunctionDefinition): | |
| raise TypeError("func_def is not an instance of FunctionDefinition") | |
| return cls(**func_def.kwargs(exclude=('body',))) | |
| class FunctionDefinition(FunctionPrototype): | |
| """ Represents a function definition in the code. | |
| Parameters | |
| ========== | |
| return_type : Type | |
| name : str | |
| parameters: iterable of Variable instances | |
| body : CodeBlock or iterable | |
| attrs : iterable of Attribute instances | |
| Examples | |
| ======== | |
| >>> from sympy import ccode, symbols | |
| >>> from sympy.codegen.ast import real, FunctionPrototype | |
| >>> x, y = symbols('x y', real=True) | |
| >>> fp = FunctionPrototype(real, 'foo', [x, y]) | |
| >>> ccode(fp) | |
| 'double foo(double x, double y)' | |
| >>> from sympy.codegen.ast import FunctionDefinition, Return | |
| >>> body = [Return(x*y)] | |
| >>> fd = FunctionDefinition.from_FunctionPrototype(fp, body) | |
| >>> print(ccode(fd)) | |
| double foo(double x, double y){ | |
| return x*y; | |
| } | |
| """ | |
| __slots__ = ('body', ) | |
| _fields = FunctionPrototype._fields[:-1] + __slots__ + Node._fields | |
| def _construct_body(cls, itr): | |
| if isinstance(itr, CodeBlock): | |
| return itr | |
| else: | |
| return CodeBlock(*itr) | |
| def from_FunctionPrototype(cls, func_proto, body): | |
| if not isinstance(func_proto, FunctionPrototype): | |
| raise TypeError("func_proto is not an instance of FunctionPrototype") | |
| return cls(body=body, **func_proto.kwargs()) | |
| class Return(Token): | |
| """ Represents a return command in the code. | |
| Parameters | |
| ========== | |
| return : Basic | |
| Examples | |
| ======== | |
| >>> from sympy.codegen.ast import Return | |
| >>> from sympy.printing.pycode import pycode | |
| >>> from sympy import Symbol | |
| >>> x = Symbol('x') | |
| >>> print(pycode(Return(x))) | |
| return x | |
| """ | |
| __slots__ = _fields = ('return',) | |
| _construct_return=staticmethod(_sympify) | |
| class FunctionCall(Token, Expr): | |
| """ Represents a call to a function in the code. | |
| Parameters | |
| ========== | |
| name : str | |
| function_args : Tuple | |
| Examples | |
| ======== | |
| >>> from sympy.codegen.ast import FunctionCall | |
| >>> from sympy import pycode | |
| >>> fcall = FunctionCall('foo', 'bar baz'.split()) | |
| >>> print(pycode(fcall)) | |
| foo(bar, baz) | |
| """ | |
| __slots__ = _fields = ('name', 'function_args') | |
| _construct_name = String | |
| _construct_function_args = staticmethod(lambda args: Tuple(*args)) | |
| class Raise(Token): | |
| """ Prints as 'raise ...' in Python, 'throw ...' in C++""" | |
| __slots__ = _fields = ('exception',) | |
| class RuntimeError_(Token): | |
| """ Represents 'std::runtime_error' in C++ and 'RuntimeError' in Python. | |
| Note that the latter is uncommon, and you might want to use e.g. ValueError. | |
| """ | |
| __slots__ = _fields = ('message',) | |
| _construct_message = String | |