Spaces:
Sleeping
Sleeping
""" | |
AST nodes specific to Fortran. | |
The functions defined in this module allows the user to express functions such as ``dsign`` | |
as a SymPy function for symbolic manipulation. | |
""" | |
from sympy.codegen.ast import ( | |
Attribute, CodeBlock, FunctionCall, Node, none, String, | |
Token, _mk_Tuple, Variable | |
) | |
from sympy.core.basic import Basic | |
from sympy.core.containers import Tuple | |
from sympy.core.expr import Expr | |
from sympy.core.function import Function | |
from sympy.core.numbers import Float, Integer | |
from sympy.core.symbol import Str | |
from sympy.core.sympify import sympify | |
from sympy.logic import true, false | |
from sympy.utilities.iterables import iterable | |
pure = Attribute('pure') | |
elemental = Attribute('elemental') # (all elemental procedures are also pure) | |
intent_in = Attribute('intent_in') | |
intent_out = Attribute('intent_out') | |
intent_inout = Attribute('intent_inout') | |
allocatable = Attribute('allocatable') | |
class Program(Token): | |
""" Represents a 'program' block in Fortran. | |
Examples | |
======== | |
>>> from sympy.codegen.ast import Print | |
>>> from sympy.codegen.fnodes import Program | |
>>> prog = Program('myprogram', [Print([42])]) | |
>>> from sympy import fcode | |
>>> print(fcode(prog, source_format='free')) | |
program myprogram | |
print *, 42 | |
end program | |
""" | |
__slots__ = _fields = ('name', 'body') | |
_construct_name = String | |
_construct_body = staticmethod(lambda body: CodeBlock(*body)) | |
class use_rename(Token): | |
""" Represents a renaming in a use statement in Fortran. | |
Examples | |
======== | |
>>> from sympy.codegen.fnodes import use_rename, use | |
>>> from sympy import fcode | |
>>> ren = use_rename("thingy", "convolution2d") | |
>>> print(fcode(ren, source_format='free')) | |
thingy => convolution2d | |
>>> full = use('signallib', only=['snr', ren]) | |
>>> print(fcode(full, source_format='free')) | |
use signallib, only: snr, thingy => convolution2d | |
""" | |
__slots__ = _fields = ('local', 'original') | |
_construct_local = String | |
_construct_original = String | |
def _name(arg): | |
if hasattr(arg, 'name'): | |
return arg.name | |
else: | |
return String(arg) | |
class use(Token): | |
""" Represents a use statement in Fortran. | |
Examples | |
======== | |
>>> from sympy.codegen.fnodes import use | |
>>> from sympy import fcode | |
>>> fcode(use('signallib'), source_format='free') | |
'use signallib' | |
>>> fcode(use('signallib', [('metric', 'snr')]), source_format='free') | |
'use signallib, metric => snr' | |
>>> fcode(use('signallib', only=['snr', 'convolution2d']), source_format='free') | |
'use signallib, only: snr, convolution2d' | |
""" | |
__slots__ = _fields = ('namespace', 'rename', 'only') | |
defaults = {'rename': none, 'only': none} | |
_construct_namespace = staticmethod(_name) | |
_construct_rename = staticmethod(lambda args: Tuple(*[arg if isinstance(arg, use_rename) else use_rename(*arg) for arg in args])) | |
_construct_only = staticmethod(lambda args: Tuple(*[arg if isinstance(arg, use_rename) else _name(arg) for arg in args])) | |
class Module(Token): | |
""" Represents a module in Fortran. | |
Examples | |
======== | |
>>> from sympy.codegen.fnodes import Module | |
>>> from sympy import fcode | |
>>> print(fcode(Module('signallib', ['implicit none'], []), source_format='free')) | |
module signallib | |
implicit none | |
<BLANKLINE> | |
contains | |
<BLANKLINE> | |
<BLANKLINE> | |
end module | |
""" | |
__slots__ = _fields = ('name', 'declarations', 'definitions') | |
defaults = {'declarations': Tuple()} | |
_construct_name = String | |
def _construct_declarations(cls, args): | |
args = [Str(arg) if isinstance(arg, str) else arg for arg in args] | |
return CodeBlock(*args) | |
_construct_definitions = staticmethod(lambda arg: CodeBlock(*arg)) | |
class Subroutine(Node): | |
""" Represents a subroutine in Fortran. | |
Examples | |
======== | |
>>> from sympy import fcode, symbols | |
>>> from sympy.codegen.ast import Print | |
>>> from sympy.codegen.fnodes import Subroutine | |
>>> x, y = symbols('x y', real=True) | |
>>> sub = Subroutine('mysub', [x, y], [Print([x**2 + y**2, x*y])]) | |
>>> print(fcode(sub, source_format='free', standard=2003)) | |
subroutine mysub(x, y) | |
real*8 :: x | |
real*8 :: y | |
print *, x**2 + y**2, x*y | |
end subroutine | |
""" | |
__slots__ = ('name', 'parameters', 'body') | |
_fields = __slots__ + Node._fields | |
_construct_name = String | |
_construct_parameters = staticmethod(lambda params: Tuple(*map(Variable.deduced, params))) | |
def _construct_body(cls, itr): | |
if isinstance(itr, CodeBlock): | |
return itr | |
else: | |
return CodeBlock(*itr) | |
class SubroutineCall(Token): | |
""" Represents a call to a subroutine in Fortran. | |
Examples | |
======== | |
>>> from sympy.codegen.fnodes import SubroutineCall | |
>>> from sympy import fcode | |
>>> fcode(SubroutineCall('mysub', 'x y'.split())) | |
' call mysub(x, y)' | |
""" | |
__slots__ = _fields = ('name', 'subroutine_args') | |
_construct_name = staticmethod(_name) | |
_construct_subroutine_args = staticmethod(_mk_Tuple) | |
class Do(Token): | |
""" Represents a Do loop in in Fortran. | |
Examples | |
======== | |
>>> from sympy import fcode, symbols | |
>>> from sympy.codegen.ast import aug_assign, Print | |
>>> from sympy.codegen.fnodes import Do | |
>>> i, n = symbols('i n', integer=True) | |
>>> r = symbols('r', real=True) | |
>>> body = [aug_assign(r, '+', 1/i), Print([i, r])] | |
>>> do1 = Do(body, i, 1, n) | |
>>> print(fcode(do1, source_format='free')) | |
do i = 1, n | |
r = r + 1d0/i | |
print *, i, r | |
end do | |
>>> do2 = Do(body, i, 1, n, 2) | |
>>> print(fcode(do2, source_format='free')) | |
do i = 1, n, 2 | |
r = r + 1d0/i | |
print *, i, r | |
end do | |
""" | |
__slots__ = _fields = ('body', 'counter', 'first', 'last', 'step', 'concurrent') | |
defaults = {'step': Integer(1), 'concurrent': false} | |
_construct_body = staticmethod(lambda body: CodeBlock(*body)) | |
_construct_counter = staticmethod(sympify) | |
_construct_first = staticmethod(sympify) | |
_construct_last = staticmethod(sympify) | |
_construct_step = staticmethod(sympify) | |
_construct_concurrent = staticmethod(lambda arg: true if arg else false) | |
class ArrayConstructor(Token): | |
""" Represents an array constructor. | |
Examples | |
======== | |
>>> from sympy import fcode | |
>>> from sympy.codegen.fnodes import ArrayConstructor | |
>>> ac = ArrayConstructor([1, 2, 3]) | |
>>> fcode(ac, standard=95, source_format='free') | |
'(/1, 2, 3/)' | |
>>> fcode(ac, standard=2003, source_format='free') | |
'[1, 2, 3]' | |
""" | |
__slots__ = _fields = ('elements',) | |
_construct_elements = staticmethod(_mk_Tuple) | |
class ImpliedDoLoop(Token): | |
""" Represents an implied do loop in Fortran. | |
Examples | |
======== | |
>>> from sympy import Symbol, fcode | |
>>> from sympy.codegen.fnodes import ImpliedDoLoop, ArrayConstructor | |
>>> i = Symbol('i', integer=True) | |
>>> idl = ImpliedDoLoop(i**3, i, -3, 3, 2) # -27, -1, 1, 27 | |
>>> ac = ArrayConstructor([-28, idl, 28]) # -28, -27, -1, 1, 27, 28 | |
>>> fcode(ac, standard=2003, source_format='free') | |
'[-28, (i**3, i = -3, 3, 2), 28]' | |
""" | |
__slots__ = _fields = ('expr', 'counter', 'first', 'last', 'step') | |
defaults = {'step': Integer(1)} | |
_construct_expr = staticmethod(sympify) | |
_construct_counter = staticmethod(sympify) | |
_construct_first = staticmethod(sympify) | |
_construct_last = staticmethod(sympify) | |
_construct_step = staticmethod(sympify) | |
class Extent(Basic): | |
""" Represents a dimension extent. | |
Examples | |
======== | |
>>> from sympy.codegen.fnodes import Extent | |
>>> e = Extent(-3, 3) # -3, -2, -1, 0, 1, 2, 3 | |
>>> from sympy import fcode | |
>>> fcode(e, source_format='free') | |
'-3:3' | |
>>> from sympy.codegen.ast import Variable, real | |
>>> from sympy.codegen.fnodes import dimension, intent_out | |
>>> dim = dimension(e, e) | |
>>> arr = Variable('x', real, attrs=[dim, intent_out]) | |
>>> fcode(arr.as_Declaration(), source_format='free', standard=2003) | |
'real*8, dimension(-3:3, -3:3), intent(out) :: x' | |
""" | |
def __new__(cls, *args): | |
if len(args) == 2: | |
low, high = args | |
return Basic.__new__(cls, sympify(low), sympify(high)) | |
elif len(args) == 0 or (len(args) == 1 and args[0] in (':', None)): | |
return Basic.__new__(cls) # assumed shape | |
else: | |
raise ValueError("Expected 0 or 2 args (or one argument == None or ':')") | |
def _sympystr(self, printer): | |
if len(self.args) == 0: | |
return ':' | |
return ":".join(str(arg) for arg in self.args) | |
assumed_extent = Extent() # or Extent(':'), Extent(None) | |
def dimension(*args): | |
""" Creates a 'dimension' Attribute with (up to 7) extents. | |
Examples | |
======== | |
>>> from sympy import fcode | |
>>> from sympy.codegen.fnodes import dimension, intent_in | |
>>> dim = dimension('2', ':') # 2 rows, runtime determined number of columns | |
>>> from sympy.codegen.ast import Variable, integer | |
>>> arr = Variable('a', integer, attrs=[dim, intent_in]) | |
>>> fcode(arr.as_Declaration(), source_format='free', standard=2003) | |
'integer*4, dimension(2, :), intent(in) :: a' | |
""" | |
if len(args) > 7: | |
raise ValueError("Fortran only supports up to 7 dimensional arrays") | |
parameters = [] | |
for arg in args: | |
if isinstance(arg, Extent): | |
parameters.append(arg) | |
elif isinstance(arg, str): | |
if arg == ':': | |
parameters.append(Extent()) | |
else: | |
parameters.append(String(arg)) | |
elif iterable(arg): | |
parameters.append(Extent(*arg)) | |
else: | |
parameters.append(sympify(arg)) | |
if len(args) == 0: | |
raise ValueError("Need at least one dimension") | |
return Attribute('dimension', parameters) | |
assumed_size = dimension('*') | |
def array(symbol, dim, intent=None, *, attrs=(), value=None, type=None): | |
""" Convenience function for creating a Variable instance for a Fortran array. | |
Parameters | |
========== | |
symbol : symbol | |
dim : Attribute or iterable | |
If dim is an ``Attribute`` it need to have the name 'dimension'. If it is | |
not an ``Attribute``, then it is passed to :func:`dimension` as ``*dim`` | |
intent : str | |
One of: 'in', 'out', 'inout' or None | |
\\*\\*kwargs: | |
Keyword arguments for ``Variable`` ('type' & 'value') | |
Examples | |
======== | |
>>> from sympy import fcode | |
>>> from sympy.codegen.ast import integer, real | |
>>> from sympy.codegen.fnodes import array | |
>>> arr = array('a', '*', 'in', type=integer) | |
>>> print(fcode(arr.as_Declaration(), source_format='free', standard=2003)) | |
integer*4, dimension(*), intent(in) :: a | |
>>> x = array('x', [3, ':', ':'], intent='out', type=real) | |
>>> print(fcode(x.as_Declaration(value=1), source_format='free', standard=2003)) | |
real*8, dimension(3, :, :), intent(out) :: x = 1 | |
""" | |
if isinstance(dim, Attribute): | |
if str(dim.name) != 'dimension': | |
raise ValueError("Got an unexpected Attribute argument as dim: %s" % str(dim)) | |
else: | |
dim = dimension(*dim) | |
attrs = list(attrs) + [dim] | |
if intent is not None: | |
if intent not in (intent_in, intent_out, intent_inout): | |
intent = {'in': intent_in, 'out': intent_out, 'inout': intent_inout}[intent] | |
attrs.append(intent) | |
if type is None: | |
return Variable.deduced(symbol, value=value, attrs=attrs) | |
else: | |
return Variable(symbol, type, value=value, attrs=attrs) | |
def _printable(arg): | |
return String(arg) if isinstance(arg, str) else sympify(arg) | |
def allocated(array): | |
""" Creates an AST node for a function call to Fortran's "allocated(...)" | |
Examples | |
======== | |
>>> from sympy import fcode | |
>>> from sympy.codegen.fnodes import allocated | |
>>> alloc = allocated('x') | |
>>> fcode(alloc, source_format='free') | |
'allocated(x)' | |
""" | |
return FunctionCall('allocated', [_printable(array)]) | |
def lbound(array, dim=None, kind=None): | |
""" Creates an AST node for a function call to Fortran's "lbound(...)" | |
Parameters | |
========== | |
array : Symbol or String | |
dim : expr | |
kind : expr | |
Examples | |
======== | |
>>> from sympy import fcode | |
>>> from sympy.codegen.fnodes import lbound | |
>>> lb = lbound('arr', dim=2) | |
>>> fcode(lb, source_format='free') | |
'lbound(arr, 2)' | |
""" | |
return FunctionCall( | |
'lbound', | |
[_printable(array)] + | |
([_printable(dim)] if dim else []) + | |
([_printable(kind)] if kind else []) | |
) | |
def ubound(array, dim=None, kind=None): | |
return FunctionCall( | |
'ubound', | |
[_printable(array)] + | |
([_printable(dim)] if dim else []) + | |
([_printable(kind)] if kind else []) | |
) | |
def shape(source, kind=None): | |
""" Creates an AST node for a function call to Fortran's "shape(...)" | |
Parameters | |
========== | |
source : Symbol or String | |
kind : expr | |
Examples | |
======== | |
>>> from sympy import fcode | |
>>> from sympy.codegen.fnodes import shape | |
>>> shp = shape('x') | |
>>> fcode(shp, source_format='free') | |
'shape(x)' | |
""" | |
return FunctionCall( | |
'shape', | |
[_printable(source)] + | |
([_printable(kind)] if kind else []) | |
) | |
def size(array, dim=None, kind=None): | |
""" Creates an AST node for a function call to Fortran's "size(...)" | |
Examples | |
======== | |
>>> from sympy import fcode, Symbol | |
>>> from sympy.codegen.ast import FunctionDefinition, real, Return | |
>>> from sympy.codegen.fnodes import array, sum_, size | |
>>> a = Symbol('a', real=True) | |
>>> body = [Return((sum_(a**2)/size(a))**.5)] | |
>>> arr = array(a, dim=[':'], intent='in') | |
>>> fd = FunctionDefinition(real, 'rms', [arr], body) | |
>>> print(fcode(fd, source_format='free', standard=2003)) | |
real*8 function rms(a) | |
real*8, dimension(:), intent(in) :: a | |
rms = sqrt(sum(a**2)*1d0/size(a)) | |
end function | |
""" | |
return FunctionCall( | |
'size', | |
[_printable(array)] + | |
([_printable(dim)] if dim else []) + | |
([_printable(kind)] if kind else []) | |
) | |
def reshape(source, shape, pad=None, order=None): | |
""" Creates an AST node for a function call to Fortran's "reshape(...)" | |
Parameters | |
========== | |
source : Symbol or String | |
shape : ArrayExpr | |
""" | |
return FunctionCall( | |
'reshape', | |
[_printable(source), _printable(shape)] + | |
([_printable(pad)] if pad else []) + | |
([_printable(order)] if pad else []) | |
) | |
def bind_C(name=None): | |
""" Creates an Attribute ``bind_C`` with a name. | |
Parameters | |
========== | |
name : str | |
Examples | |
======== | |
>>> from sympy import fcode, Symbol | |
>>> from sympy.codegen.ast import FunctionDefinition, real, Return | |
>>> from sympy.codegen.fnodes import array, sum_, bind_C | |
>>> a = Symbol('a', real=True) | |
>>> s = Symbol('s', integer=True) | |
>>> arr = array(a, dim=[s], intent='in') | |
>>> body = [Return((sum_(a**2)/s)**.5)] | |
>>> fd = FunctionDefinition(real, 'rms', [arr, s], body, attrs=[bind_C('rms')]) | |
>>> print(fcode(fd, source_format='free', standard=2003)) | |
real*8 function rms(a, s) bind(C, name="rms") | |
real*8, dimension(s), intent(in) :: a | |
integer*4 :: s | |
rms = sqrt(sum(a**2)/s) | |
end function | |
""" | |
return Attribute('bind_C', [String(name)] if name else []) | |
class GoTo(Token): | |
""" Represents a goto statement in Fortran | |
Examples | |
======== | |
>>> from sympy.codegen.fnodes import GoTo | |
>>> go = GoTo([10, 20, 30], 'i') | |
>>> from sympy import fcode | |
>>> fcode(go, source_format='free') | |
'go to (10, 20, 30), i' | |
""" | |
__slots__ = _fields = ('labels', 'expr') | |
defaults = {'expr': none} | |
_construct_labels = staticmethod(_mk_Tuple) | |
_construct_expr = staticmethod(sympify) | |
class FortranReturn(Token): | |
""" AST node explicitly mapped to a fortran "return". | |
Explanation | |
=========== | |
Because a return statement in fortran is different from C, and | |
in order to aid reuse of our codegen ASTs the ordinary | |
``.codegen.ast.Return`` is interpreted as assignment to | |
the result variable of the function. If one for some reason needs | |
to generate a fortran RETURN statement, this node should be used. | |
Examples | |
======== | |
>>> from sympy.codegen.fnodes import FortranReturn | |
>>> from sympy import fcode | |
>>> fcode(FortranReturn('x')) | |
' return x' | |
""" | |
__slots__ = _fields = ('return_value',) | |
defaults = {'return_value': none} | |
_construct_return_value = staticmethod(sympify) | |
class FFunction(Function): | |
_required_standard = 77 | |
def _fcode(self, printer): | |
name = self.__class__.__name__ | |
if printer._settings['standard'] < self._required_standard: | |
raise NotImplementedError("%s requires Fortran %d or newer" % | |
(name, self._required_standard)) | |
return '{}({})'.format(name, ', '.join(map(printer._print, self.args))) | |
class F95Function(FFunction): | |
_required_standard = 95 | |
class isign(FFunction): | |
""" Fortran sign intrinsic for integer arguments. """ | |
nargs = 2 | |
class dsign(FFunction): | |
""" Fortran sign intrinsic for double precision arguments. """ | |
nargs = 2 | |
class cmplx(FFunction): | |
""" Fortran complex conversion function. """ | |
nargs = 2 # may be extended to (2, 3) at a later point | |
class kind(FFunction): | |
""" Fortran kind function. """ | |
nargs = 1 | |
class merge(F95Function): | |
""" Fortran merge function """ | |
nargs = 3 | |
class _literal(Float): | |
_token = None # type: str | |
_decimals = None # type: int | |
def _fcode(self, printer, *args, **kwargs): | |
mantissa, sgnd_ex = ('%.{}e'.format(self._decimals) % self).split('e') | |
mantissa = mantissa.strip('0').rstrip('.') | |
ex_sgn, ex_num = sgnd_ex[0], sgnd_ex[1:].lstrip('0') | |
ex_sgn = '' if ex_sgn == '+' else ex_sgn | |
return (mantissa or '0') + self._token + ex_sgn + (ex_num or '0') | |
class literal_sp(_literal): | |
""" Fortran single precision real literal """ | |
_token = 'e' | |
_decimals = 9 | |
class literal_dp(_literal): | |
""" Fortran double precision real literal """ | |
_token = 'd' | |
_decimals = 17 | |
class sum_(Token, Expr): | |
__slots__ = _fields = ('array', 'dim', 'mask') | |
defaults = {'dim': none, 'mask': none} | |
_construct_array = staticmethod(sympify) | |
_construct_dim = staticmethod(sympify) | |
class product_(Token, Expr): | |
__slots__ = _fields = ('array', 'dim', 'mask') | |
defaults = {'dim': none, 'mask': none} | |
_construct_array = staticmethod(sympify) | |
_construct_dim = staticmethod(sympify) | |