kernelcreation.py 11 KB
Newer Older
Martin Bauer's avatar
Martin Bauer committed
1
from types import MappingProxyType
2
import sympy as sp
Martin Bauer's avatar
Martin Bauer committed
3
4
5
6
7
from pystencils.assignment import Assignment
from pystencils.astnodes import LoopOverCoordinate, Conditional, Block, SympyAssignment
from pystencils.simp.assignment_collection import AssignmentCollection
from pystencils.gpucuda.indexing import indexing_creator_from_params
from pystencils.transformations import remove_conditionals_in_staggered_kernel
Martin Bauer's avatar
Martin Bauer committed
8
9


Martin Bauer's avatar
Martin Bauer committed
10
def create_kernel(assignments, target='cpu', data_type="double", iteration_slice=None, ghost_layers=None,
Martin Bauer's avatar
Martin Bauer committed
11
                  cpu_openmp=False, cpu_vectorize_info=None,
Martin Bauer's avatar
Martin Bauer committed
12
                  gpu_indexing='block', gpu_indexing_params=MappingProxyType({})):
Martin Bauer's avatar
Martin Bauer committed
13
14
    """
    Creates abstract syntax tree (AST) of kernel, using a list of update equations.
15
16

    Args:
Martin Bauer's avatar
Martin Bauer committed
17
        assignments: can be a single assignment, sequence of assignments or an `AssignmentCollection`
18
19
20
21
22
23
24
        target: 'cpu', 'llvm' or 'gpu'
        data_type: data type used for all untyped symbols (i.e. non-fields), can also be a dict from symbol name
                  to type
        iteration_slice: rectangular subset to iterate over, if not specified the complete non-ghost layer \
                         part of the field is iterated over
        ghost_layers: if left to default, the number of necessary ghost layers is determined automatically
                     a single integer specifies the ghost layer count at all borders, can also be a sequence of
Martin Bauer's avatar
Martin Bauer committed
25
                     pairs ``[(x_lower_gl, x_upper_gl), .... ]``
26
27

        cpu_openmp: True or number of threads for OpenMP parallelization, False for no OpenMP
Martin Bauer's avatar
Martin Bauer committed
28
29
30
        cpu_vectorize_info: pair of instruction set name, i.e. one of 'sse, 'avx' or 'avx512'
                            and data type 'float' or 'double'. For example ``('avx', 'double')``
        gpu_indexing: either 'block' or 'line' , or custom indexing class, see `pystencils.gpucuda.AbstractIndexing`
31
        gpu_indexing_params: dict with indexing parameters (constructor parameters of indexing class)
Martin Bauer's avatar
Martin Bauer committed
32
                             e.g. for 'block' one can specify '{'block_size': (20, 20, 10) }'
33
34

    Returns:
Martin Bauer's avatar
Martin Bauer committed
35
        abstract syntax tree (AST) object, that can either be printed as source code with `show_code` or
36
        can be compiled with through its 'compile()' member
Martin Bauer's avatar
Martin Bauer committed
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

    Example:
        >>> import pystencils as ps
        >>> import numpy as np
        >>> s, d = ps.fields('s, d: [2D]')
        >>> assignment = ps.Assignment(d[0,0], s[0, 1] + s[0, -1] + s[1, 0] + s[-1, 0])
        >>> ast = ps.create_kernel(assignment, target='cpu', cpu_openmp=True)
        >>> kernel = ast.compile()
        >>> d_arr = np.zeros([5, 5])
        >>> kernel(d=d_arr, s=np.ones([5, 5]))
        >>> d_arr
        array([[0., 0., 0., 0., 0.],
               [0., 4., 4., 4., 0.],
               [0., 4., 4., 4., 0.],
               [0., 4., 4., 4., 0.],
               [0., 0., 0., 0., 0.]])
Martin Bauer's avatar
Martin Bauer committed
53
54
55
    """

    # ----  Normalizing parameters
Martin Bauer's avatar
Martin Bauer committed
56
    split_groups = ()
Martin Bauer's avatar
Martin Bauer committed
57
58
59
60
61
62
    if isinstance(assignments, AssignmentCollection):
        if 'split_groups' in assignments.simplification_hints:
            split_groups = assignments.simplification_hints['split_groups']
        assignments = assignments.all_assignments
    if isinstance(assignments, Assignment):
        assignments = [assignments]
Martin Bauer's avatar
Martin Bauer committed
63
64
65

    # ----  Creating ast
    if target == 'cpu':
Martin Bauer's avatar
Martin Bauer committed
66
67
        from pystencils.cpu import create_kernel
        from pystencils.cpu import add_openmp
Martin Bauer's avatar
Martin Bauer committed
68
        ast = create_kernel(assignments, type_info=data_type, split_groups=split_groups,
Martin Bauer's avatar
Martin Bauer committed
69
70
71
72
                            iteration_slice=iteration_slice, ghost_layers=ghost_layers)
        if cpu_openmp:
            add_openmp(ast, num_threads=cpu_openmp)
        if cpu_vectorize_info:
Martin Bauer's avatar
Martin Bauer committed
73
            import pystencils.backends.simd_instruction_sets as vec
74
            from pystencils.cpu.vectorization import vectorize
Martin Bauer's avatar
Martin Bauer committed
75
            vec_params = cpu_vectorize_info
Martin Bauer's avatar
Martin Bauer committed
76
77
            vec.selected_instruction_set = vec.x86_vector_instruction_set(instruction_set=vec_params[0],
                                                                          data_type=vec_params[1])
Martin Bauer's avatar
Martin Bauer committed
78
79
80
            vectorize(ast)
        return ast
    elif target == 'llvm':
Martin Bauer's avatar
Martin Bauer committed
81
        from pystencils.llvm import create_kernel
Martin Bauer's avatar
Martin Bauer committed
82
        ast = create_kernel(assignments, type_info=data_type, split_groups=split_groups,
Martin Bauer's avatar
Martin Bauer committed
83
                            iteration_slice=iteration_slice, ghost_layers=ghost_layers)
Martin Bauer's avatar
Martin Bauer committed
84
85
        return ast
    elif target == 'gpu':
Martin Bauer's avatar
Martin Bauer committed
86
        from pystencils.gpucuda import create_cuda_kernel
Martin Bauer's avatar
Martin Bauer committed
87
        ast = create_cuda_kernel(assignments, type_info=data_type,
Martin Bauer's avatar
Martin Bauer committed
88
89
                                 indexing_creator=indexing_creator_from_params(gpu_indexing, gpu_indexing_params),
                                 iteration_slice=iteration_slice, ghost_layers=ghost_layers)
Martin Bauer's avatar
Martin Bauer committed
90
91
92
93
94
        return ast
    else:
        raise ValueError("Unknown target %s. Has to be one of 'cpu', 'gpu' or 'llvm' " % (target,))


Martin Bauer's avatar
Martin Bauer committed
95
def create_indexed_kernel(assignments, index_fields, target='cpu', data_type="double", coordinate_names=('x', 'y', 'z'),
Martin Bauer's avatar
Martin Bauer committed
96
                          cpu_openmp=True, gpu_indexing='block', gpu_indexing_params=MappingProxyType({})):
Martin Bauer's avatar
Martin Bauer committed
97
    """
Martin Bauer's avatar
Martin Bauer committed
98
    Similar to :func:`create_kernel`, but here not all cells of a field are updated but only cells with
Martin Bauer's avatar
Martin Bauer committed
99
100
    coordinates which are stored in an index field. This traversal method can e.g. be used for boundary handling.

Martin Bauer's avatar
Martin Bauer committed
101
    The coordinates are stored in a separated index_field, which is a one dimensional array with struct data type.
Martin Bauer's avatar
Martin Bauer committed
102
    This struct has to contain fields named 'x', 'y' and for 3D fields ('z'). These names are configurable with the
Martin Bauer's avatar
Martin Bauer committed
103
    'coordinate_names' parameter. The struct can have also other fields that can be read and written in the kernel, for
Martin Bauer's avatar
Martin Bauer committed
104
105
    example boundary parameters.

Martin Bauer's avatar
Martin Bauer committed
106
107
    index_fields: list of index fields, i.e. 1D fields with struct data type
    coordinate_names: name of the coordinate fields in the struct data type
Martin Bauer's avatar
Martin Bauer committed
108

Martin Bauer's avatar
Martin Bauer committed
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
    Example:
        >>> import pystencils as ps
        >>> import numpy as np
        >>>
        >>> # Index field stores the indices of the cell to visit together with optional values
        >>> index_arr_dtype = np.dtype([('x', np.int32), ('y', np.int32), ('val', np.double)])
        >>> index_arr = np.array([(1, 1, 0.1), (2, 2, 0.2), (3, 3, 0.3)], dtype=index_arr_dtype)
        >>> idx_field = ps.fields(idx=index_arr)
        >>>
        >>> # Additional values  stored in index field can be accessed in the kernel as well
        >>> s, d = ps.fields('s, d: [2D]')
        >>> assignment = ps.Assignment(d[0,0], 2 * s[0, 1] + 2 * s[1, 0] + idx_field('val'))
        >>> ast = create_indexed_kernel(assignment, [idx_field], coordinate_names=('x', 'y'))
        >>> kernel = ast.compile()
        >>> d_arr = np.zeros([5, 5])
        >>> kernel(s=np.ones([5, 5]), d=d_arr, idx=index_arr)
        >>> d_arr
        array([[0. , 0. , 0. , 0. , 0. ],
               [0. , 4.1, 0. , 0. , 0. ],
               [0. , 0. , 4.2, 0. , 0. ],
               [0. , 0. , 0. , 4.3, 0. ],
               [0. , 0. , 0. , 0. , 0. ]])
    """
    if isinstance(assignments, Assignment):
        assignments = [assignments]
    elif isinstance(assignments, AssignmentCollection):
Martin Bauer's avatar
Martin Bauer committed
135
        assignments = assignments.all_assignments
Martin Bauer's avatar
Martin Bauer committed
136
    if target == 'cpu':
Martin Bauer's avatar
Martin Bauer committed
137
138
139
140
141
142
        from pystencils.cpu import create_indexed_kernel
        from pystencils.cpu import add_openmp
        ast = create_indexed_kernel(assignments, index_fields=index_fields, type_info=data_type,
                                    coordinate_names=coordinate_names)
        if cpu_openmp:
            add_openmp(ast, num_threads=cpu_openmp)
Martin Bauer's avatar
Martin Bauer committed
143
144
145
146
        return ast
    elif target == 'llvm':
        raise NotImplementedError("Indexed kernels are not yet supported in LLVM backend")
    elif target == 'gpu':
Martin Bauer's avatar
Martin Bauer committed
147
        from pystencils.gpucuda import created_indexed_cuda_kernel
Martin Bauer's avatar
Martin Bauer committed
148
149
150
        idx_creator = indexing_creator_from_params(gpu_indexing, gpu_indexing_params)
        ast = created_indexed_cuda_kernel(assignments, index_fields, type_info=data_type,
                                          coordinate_names=coordinate_names, indexing_creator=idx_creator)
Martin Bauer's avatar
Martin Bauer committed
151
152
153
        return ast
    else:
        raise ValueError("Unknown target %s. Has to be either 'cpu' or 'gpu'" % (target,))
154
155
156
157
158


def create_staggered_kernel(staggered_field, expressions, subexpressions=(), target='cpu', **kwargs):
    """Kernel that updates a staggered field.

Martin Bauer's avatar
Martin Bauer committed
159
160
    .. image:: /img/staggered_grid.svg

161
    Args:
162
163
164
        staggered_field: field where the first index coordinate defines the location of the staggered value
                can have 1 or 2 index coordinates, in case of of two index coordinates at every staggered location
                a vector is stored, expressions has to be a sequence of sequences then
Martin Bauer's avatar
Martin Bauer committed
165
166
                where e.g. ``f[0,0](0)`` is interpreted as value at the left cell boundary, ``f[1,0](0)`` the right cell
                boundary and ``f[0,0](1)`` the southern cell boundary etc.
167
        expressions: sequence of expressions of length dim, defining how the east, southern, (bottom) cell boundary
168
                     should be update.
169
170
171
        subexpressions: optional sequence of Assignments, that define subexpressions used in the main expressions
        target: 'cpu' or 'gpu'
        kwargs: passed directly to create_kernel, iteration slice and ghost_layers parameters are not allowed
172

173
    Returns:
174
        AST, see `create_kernel`
175
176
    """
    assert 'iteration_slice' not in kwargs and 'ghost_layers' not in kwargs
177
    assert staggered_field.index_dimensions in (1, 2), 'Staggered field must have one or two index dimensions'
178
179
180
181
182
    dim = staggered_field.spatial_dimensions

    counters = [LoopOverCoordinate.get_loop_counter_symbol(i) for i in range(dim)]
    conditions = [counters[i] < staggered_field.shape[i] - 1 for i in range(dim)]
    assert len(expressions) == dim
183
184
185
186
187
    if staggered_field.index_dimensions == 2:
        assert all(len(sublist) == len(expressions[0]) for sublist in expressions), \
            "If staggered field has two index dimensions expressions has to be a sequence of sequences of all the " \
            "same length."

188
189
190
    final_assignments = []
    for d in range(dim):
        cond = sp.And(*[conditions[i] for i in range(dim) if d != i])
191
192
193
194
195
196
197
198
        if staggered_field.index_dimensions == 1:
            assignments = [Assignment(staggered_field(d), expressions[d])]
            a_coll = AssignmentCollection(assignments, list(subexpressions)).new_filtered([staggered_field(d)])
        elif staggered_field.index_dimensions == 2:
            assert staggered_field.has_fixed_index_shape
            assignments = [Assignment(staggered_field(d, i), expr) for i, expr in enumerate(expressions[d])]
            a_coll = AssignmentCollection(assignments, list(subexpressions))
            a_coll = a_coll.new_filtered([staggered_field(d, i) for i in range(staggered_field.index_shape[1])])
199
200
        sp_assignments = [SympyAssignment(a.lhs, a.rhs) for a in a_coll.all_assignments]
        final_assignments.append(Conditional(cond, Block(sp_assignments)))
201

202
203
204
205
206
207
    ghost_layers = [(1, 0)] * dim

    ast = create_kernel(final_assignments, ghost_layers=ghost_layers, target=target, **kwargs)
    if target == 'cpu':
        remove_conditionals_in_staggered_kernel(ast)
    return ast