diff --git a/pystencils/assignment.py b/pystencils/assignment.py index 9d570727fbd0d92b7fbb7f81472628ff21641e85..c3ae4b4367da32764224736ea55cbc2bd6acb219 100644 --- a/pystencils/assignment.py +++ b/pystencils/assignment.py @@ -20,6 +20,7 @@ def assignment_str(assignment): _old_new = sp.codegen.ast.Assignment.__new__ +# TODO Typing Part2 add default type, defult_float_type, default_int_type and use sane defaults def _Assignment__new__(cls, lhs, rhs, *args, **kwargs): if isinstance(lhs, (list, tuple, sp.Matrix)) and isinstance(rhs, (list, tuple, sp.Matrix)): assert len(lhs) == len(rhs), f'{lhs} and {rhs} must have same length when performing vector assignment!' diff --git a/pystencils/astnodes.py b/pystencils/astnodes.py index d2826fa850a6dbf06db192bf45509147633bfc93..ef0bcc6d758fdb67fc42b708038882360a9eee65 100644 --- a/pystencils/astnodes.py +++ b/pystencils/astnodes.py @@ -564,7 +564,6 @@ class LoopOverCoordinate(Node): class SympyAssignment(Node): def __init__(self, lhs_symbol, rhs_expr, is_const=True, use_auto=False): - # TODO add default type, float_type, int_type use sane defaults super(SympyAssignment, self).__init__(parent=None) self._lhs_symbol = sp.sympify(lhs_symbol) self.rhs = sp.sympify(rhs_expr) diff --git a/pystencils/backends/cbackend.py b/pystencils/backends/cbackend.py index 42503a8ef4100549a32bcc41fb36f94bb351b765..ddcfb02dc737551d8ce4c716b351889b22d2d034 100644 --- a/pystencils/backends/cbackend.py +++ b/pystencils/backends/cbackend.py @@ -64,6 +64,7 @@ def generate_c(ast_node: Node, printer = custom_backend elif dialect == Backend.C: try: + # TODO Vectorization Revamp: instruction_set should not be just slapped on ast instruction_set = ast_node.instruction_set except Exception: instruction_set = None @@ -126,7 +127,7 @@ def get_headers(ast_node: Node) -> Set[str]: # --------------------------------------- Backend Specific Nodes ------------------------------------------------------- -# TODO CustomCodeNode should not be backend specific +# TODO future CustomCodeNode should not be backend specific move it elsewhere class CustomCodeNode(Node): def __init__(self, code, symbols_read, symbols_defined, parent=None): super(CustomCodeNode, self).__init__(parent=parent) @@ -301,9 +302,10 @@ class CBackend: elif self._vector_instruction_set['float'] == '__m128': printed_mask = f"_mm_castps_si128({printed_mask})" - rhs_type = get_type_of_expression(node.rhs) # TOOD: vector only??? + rhs_type = get_type_of_expression(node.rhs) if type(rhs_type) is not VectorType: - rhs = CastFunc(node.rhs, VectorType(rhs_type)) + raise ValueError(f'Cannot vectorize inside of the pretty printer! This should have happen earlier!') + # rhs = CastFunc(node.rhs, VectorType(rhs_type)) # Unknown width else: rhs = node.rhs @@ -417,7 +419,7 @@ class CBackend: return self._print_Block(node.true_block) elif type(node.condition_expr) is BooleanFalse: return self._print_Block(node.false_block) - cond_type = get_type_of_expression(node.condition_expr) # TODO: Could be vector or bool? + cond_type = get_type_of_expression(node.condition_expr) if isinstance(cond_type, VectorType): raise ValueError("Problem with Conditional inside vectorized loop - use vec_any or vec_all") condition_expr = self.sympy_printer.doprint(node.condition_expr) @@ -468,7 +470,7 @@ class CustomSympyPrinter(CCodePrinter): return f'fabs({self._print(expr.args[0])})' def _print_AbstractType(self, node): - return str(node) + raise ValueError(f'Cannot print AbstractType: {node}') def _print_Function(self, expr): infix_functions = { @@ -600,7 +602,7 @@ class VectorizedCustomSympyPrinter(CustomSympyPrinter): instruction = 'makeVecConst' if basic_data_type.is_bool(): instruction = 'makeVecConstBool' - # TODO is int, or sint, or uint? + # TODO Vectorization Revamp: is int, or sint, or uint (my guess is sint) elif basic_data_type.is_int(): instruction = 'makeVecConstInt' return self.instruction_set[instruction].format(number, **self._kwargs) @@ -616,7 +618,7 @@ class VectorizedCustomSympyPrinter(CustomSympyPrinter): instruction = 'makeVecConst' if basic_data_type.is_bool(): instruction = 'makeVecConstBool' - # TODO is int, or sint, or uint? + # TODO Vectorization Revamp: is int, or sint, or uint (my guess is sint) elif basic_data_type.is_int(): instruction = 'makeVecConstInt' return self.instruction_set[instruction].format(symbol, **self._kwargs) @@ -625,7 +627,7 @@ class VectorizedCustomSympyPrinter(CustomSympyPrinter): arg, data_type = expr.args if type(data_type) is VectorType: # vector_memory_access is a cast_func itself so it should't be directly inside a cast_func - assert not isinstance(arg, VectorMemoryAccess) # TODO Is this true for our new type system? + assert not isinstance(arg, VectorMemoryAccess) if isinstance(arg, sp.Tuple): is_boolean = get_type_of_expression(arg[0]) == create_type("bool") is_integer = get_type_of_expression(arg[0]) == create_type("int") diff --git a/pystencils/backends/cuda_backend.py b/pystencils/backends/cuda_backend.py index ebcda56af81ceb11156172078300de817c3c22d9..f8fdb16dac8911811915d6d69e5af7af8f149f4f 100644 --- a/pystencils/backends/cuda_backend.py +++ b/pystencils/backends/cuda_backend.py @@ -37,26 +37,13 @@ class CudaBackend(CBackend): return code @staticmethod - def _print_ThreadBlockSynchronization(node): - code = "__synchtreads();" - return code + def _print_ThreadBlockSynchronization(_): + return "__synchtreads();" def _print_TextureDeclaration(self, node): - - # TODO: use fStrings here - if node.texture.field.dtype.numpy_dtype.itemsize > 4: - code = "texture<fp_tex_%s, cudaTextureType%iD, cudaReadModeElementType> %s;" % ( - str(node.texture.field.dtype), - node.texture.field.spatial_dimensions, - node.texture - ) - else: - code = "texture<%s, cudaTextureType%iD, cudaReadModeElementType> %s;" % ( - str(node.texture.field.dtype), - node.texture.field.spatial_dimensions, - node.texture - ) - return code + cond = node.texture.field.dtype.numpy_dtype.itemsize > 4 + return f'texture<{"fp_tex_" if cond else ""}{str(node.texture.field.dtype)}, ' \ + f'cudaTextureType{node.texture.field.spacial_dimensions}D, cudaReadModeElementType> {node.texture};' def _print_SkipIteration(self, _): return "return;" diff --git a/pystencils/bit_masks.py b/pystencils/bit_masks.py index ad4e967f0131dde5347c20de467afbab4892e149..f8b6b7ef0361cf3555ce67375a34328ef2e1157c 100644 --- a/pystencils/bit_masks.py +++ b/pystencils/bit_masks.py @@ -22,7 +22,7 @@ class flag_cond(sp.Function): def __new__(cls, flag_bit, mask_expression, *expressions): - # TODO reintroduce checking + # TODO Jan reintroduce checking # flag_dtype = get_type_of_expression(flag_bit) # if not flag_dtype.is_int(): # raise ValueError('Argument flag_bit must be of integer type.') diff --git a/pystencils/cache.py b/pystencils/cache.py index 15274ccb84e7609f7b80e44270993955d2e59db4..119e5ea6c6959c397e6c41351cfd077fd4c973c3 100644 --- a/pystencils/cache.py +++ b/pystencils/cache.py @@ -1,13 +1,8 @@ import os from collections.abc import Hashable -from functools import partial +from functools import partial, lru_cache from itertools import chain -try: - from functools import lru_cache as memorycache -except ImportError: # TODO what python version is this??? - from backports.functools_lru_cache import lru_cache as memorycache - from joblib import Memory from appdirs import user_cache_dir @@ -28,7 +23,7 @@ def _wrapper(wrapped_func, cached_func, *args, **kwargs): def memorycache_if_hashable(maxsize=128, typed=False): def wrapper(func): - return partial(_wrapper, func, memorycache(maxsize, typed)(func)) + return partial(_wrapper, func, lru_cache(maxsize, typed)(func)) return wrapper diff --git a/pystencils/config.py b/pystencils/config.py index 3fea3f37fd5b9a72f906456afc6ae0bbf63dabd3..e5f22392575de3d459967ed8ff3f535da42f0084 100644 --- a/pystencils/config.py +++ b/pystencils/config.py @@ -11,7 +11,8 @@ from pystencils.typing.typed_sympy import BasicType import numpy as np -# TODO: think of more classes better usage, factory whatever ... +# TODO: CreateKernelConfig is bloated think of more classes better usage, factory whatever ... +# Proposition: CreateKernelConfigs Classes for different targets? @dataclass class CreateKernelConfig: """ @@ -29,9 +30,8 @@ class CreateKernelConfig: """ Name of the generated function - only important if generated code is written out """ - # TODO: config should check that the datatype is a Numpy type - # TODO: check for the python types and issue warnings - # TODO: QoL default_number_float and default_number_int should be data_type if they are not specified by the user + # TODO Sane defaults: config should check that the datatype is a Numpy type + # TODO Sane defaults: QoL default_number_float and default_number_int should be data_type if they are not specified data_type: Union[str, Dict[str, BasicType]] = 'float64' """ Data type used for all untyped symbols (i.e. non-fields), can also be a dict from symbol name to type @@ -82,7 +82,7 @@ class CreateKernelConfig: Dict with indexing parameters (constructor parameters of indexing class) e.g. for 'block' one can specify '{'block_size': (20, 20, 10) }'. """ - # TODO rework this docstring + # TODO Markus rework this docstring default_assignment_simplifications: bool = False """ If `True` default simplifications are first performed on the Assignments. If problems occur during the @@ -127,7 +127,7 @@ class CreateKernelConfig: def __post_init__(self): # ---- Legacy parameters - # TODO adapt here the types for example "float", python float, everything ambiguous should not be allowed + # TODO Sane defaults: Check for abmigous types like "float", python float, which are dangerous for users if isinstance(self.target, str): new_target = Target[self.target.upper()] warnings.warn(f'Target "{self.target}" as str is deprecated. Use {new_target} instead', diff --git a/pystencils/cpu/kernelcreation.py b/pystencils/cpu/kernelcreation.py index 7e4216568c169cf0be1eb46a52c25df765952da6..4514c628c1e030bc7f5385991f9baf71475fd6bc 100644 --- a/pystencils/cpu/kernelcreation.py +++ b/pystencils/cpu/kernelcreation.py @@ -45,7 +45,7 @@ def create_kernel(assignments: Union[AssignmentCollection, NodeCollection], split_groups = assignments.simplification_hints['split_groups'] assignments = assignments.all_assignments - # TODO: try to delete + # TODO Jan: try to delete def type_symbol(term): if isinstance(term, Field.Access) or isinstance(term, TypedSymbol): return term @@ -57,7 +57,7 @@ def create_kernel(assignments: Union[AssignmentCollection, NodeCollection], else: raise ValueError("Term has to be field access or symbol") - # TODO move add_types to create_domain_kernel or create_kernel + # TODO Jan Cleanup: move add_types to create_domain_kernel or create_kernel? assignments = add_types(assignments, config) @@ -90,7 +90,6 @@ def create_kernel(assignments: Union[AssignmentCollection, NodeCollection], if any(FieldType.is_buffer(f) for f in all_fields): resolve_buffer_accesses(ast_node, get_base_buffer_index(ast_node), read_only_fields) - # TODO think about typing resolve_field_accesses(ast_node, read_only_fields, field_to_base_pointer_info=base_pointer_info) move_constants_before_loop(ast_node) return ast_node diff --git a/pystencils/cpu/vectorization.py b/pystencils/cpu/vectorization.py index 9f8e381c3f3f16627cdfecd6caa4e46e648f7efc..0b4a9a0b2a04f7cdff7114b3c89c6cbf47aaa4f4 100644 --- a/pystencils/cpu/vectorization.py +++ b/pystencils/cpu/vectorization.py @@ -77,8 +77,8 @@ class CachelineSize(ast.Node): def vectorize(kernel_ast: ast.KernelFunction, instruction_set: str = 'best', assume_aligned: bool = False, nontemporal: Union[bool, Container[Union[str, Field]]] = False, assume_inner_stride_one: bool = False, assume_sufficient_line_padding: bool = True): - # TODO we first introduce the remainder loop and then check if we can even vectorise. Maybe first copy the ast - # and return the copied version on failure + # TODO Vectorization Revamp we first introduce the remainder loop and then check if we can even vectorise. + # Maybe first copy the ast and return the copied version on failure """Explicit vectorization using SIMD vectorization via intrinsics. Args: @@ -124,7 +124,6 @@ def vectorize(kernel_ast: ast.KernelFunction, instruction_set: str = 'best', "to differently typed floating point fields") float_size = field_float_dtypes.pop().numpy_dtype.itemsize assert float_size in (8, 4) - # TODO: future work allow mixed precision fields default_float_type = 'double' if float_size == 8 else 'float' vector_is = get_vector_instruction_set(default_float_type, instruction_set=instruction_set) kernel_ast.instruction_set = vector_is @@ -164,7 +163,7 @@ def vectorize_inner_loops_and_adapt_load_stores(ast_node, assume_aligned, nontem if len(loop_nodes) == 0: continue loop_node = loop_nodes[0] - # TODO loop_node is the vectorized one + # loop_node is the vectorized one # Find all array accesses (indexed) that depend on the loop counter as offset loop_counter_symbol = ast.LoopOverCoordinate.get_loop_counter_symbol(loop_node.coordinate_to_loop_over) @@ -258,7 +257,7 @@ def insert_vector_casts(ast_node, instruction_set, default_float_type='double'): handled_functions = (sp.Add, sp.Mul, fast_division, fast_sqrt, fast_inv_sqrt, vec_any, vec_all, DivFunc, sp.UnevaluatedExpr, sp.Abs) - def visit_expr(expr, default_type='double'): # TODO get rid of default_type + def visit_expr(expr, default_type='double'): # TODO Vectorization Revamp: get rid of default_type if isinstance(expr, VectorMemoryAccess): return VectorMemoryAccess(*expr.args[0:4], visit_expr(expr.args[4], default_type), *expr.args[5:]) elif isinstance(expr, CastFunc): @@ -325,14 +324,13 @@ def insert_vector_casts(ast_node, instruction_set, default_float_type='double'): elif isinstance(expr, (sp.Number, TypedSymbol, BooleanAtom)): return expr else: - # TODO better error string - raise NotImplementedError(f'Should I raise or should I return now? {type(expr)} {expr}') + raise NotImplementedError(f'Due to defensive programming we handle only specific expressions.\n' + f'The expression {expr} of type {type(expr)} is not known yet.') def visit_node(node, substitution_dict, default_type='double'): substitution_dict = substitution_dict.copy() for arg in node.args: if isinstance(arg, ast.SympyAssignment): - # TODO only if not remainder loop (? if no VectorAccess then remainder loop) assignment = arg # If there is a remainder loop we do not vectorise it, thus lhs will indicate this # if isinstance(assignment.lhs, ast.ResolvedFieldAccess): diff --git a/pystencils/fast_approximation.py b/pystencils/fast_approximation.py index 65f85a71a25e2da0082a61a5418ce4c4eb656af0..ab0dc59740e9ec7fcd3e59eb826979cd5350aa3f 100644 --- a/pystencils/fast_approximation.py +++ b/pystencils/fast_approximation.py @@ -9,17 +9,25 @@ from pystencils.assignment import Assignment # noinspection PyPep8Naming class fast_division(sp.Function): - # TODO how is this fast? The printer prints a normal division??? + """ + Produces special float instructions for CUDA kernels + """ nargs = (2,) # noinspection PyPep8Naming class fast_sqrt(sp.Function): + """ + Produces special float instructions for CUDA kernels + """ nargs = (1, ) # noinspection PyPep8Naming class fast_inv_sqrt(sp.Function): + """ + Produces special float instructions for CUDA kernels + """ nargs = (1, ) diff --git a/pystencils/field.py b/pystencils/field.py index ceaeb82acc1a5eebfa6880fe5b8004ed832ee415..e92cac4046400061b19d851755af63db76dea110 100644 --- a/pystencils/field.py +++ b/pystencils/field.py @@ -319,7 +319,7 @@ class Field: assert isinstance(field_type, FieldType) assert len(shape) == len(strides) self.field_type = field_type - self._dtype = create_type(dtype) # TODO do we have AoS??? + self._dtype = create_type(dtype) self._layout = normalize_layout(layout) self.shape = shape self.strides = strides diff --git a/pystencils/functions.py b/pystencils/functions.py index c499d04ed6fcb1274639eb9791955db69c9619bf..722c2c5d410dd81968cc593871c6714bbbba1645 100644 --- a/pystencils/functions.py +++ b/pystencils/functions.py @@ -3,7 +3,9 @@ from pystencils.typing import PointerType class DivFunc(sp.Function): - # TODO: documentation + """ + DivFunc represents a division operation, since sympy represents divisions with ^-1 + """ is_Atom = True is_real = True @@ -27,8 +29,9 @@ class DivFunc(sp.Function): class AddressOf(sp.Function): - # TODO: docstring - # this is '&' in C + """ + AddressOf is the '&' operation in C. It gets the address of a lvalue. + """ is_Atom = True def __new__(cls, arg): diff --git a/pystencils/integer_functions.py b/pystencils/integer_functions.py index 1975a877e36787466d1d2ffb7f4aec8a5791d341..cd0e6f231edc754bcf4c5d7e991e6048c623a45c 100644 --- a/pystencils/integer_functions.py +++ b/pystencils/integer_functions.py @@ -1,4 +1,4 @@ -# TODO move to a module functions +# TODO #47 move to a module functions import numpy as np import sympy as sp diff --git a/pystencils/kernel_contrains_check.py b/pystencils/kernel_contrains_check.py index 7f7756d6f0318930c9e4d85656ce6314146da666..f79677656874dbe14c7bf7658e6b9534a6be18c1 100644 --- a/pystencils/kernel_contrains_check.py +++ b/pystencils/kernel_contrains_check.py @@ -13,8 +13,8 @@ from pystencils.transformations import NestedScopes accepted_functions = [ sp.Pow, - sp.sqrt, # TODO why not a class?? - # TODO trigonometric functions + sp.sqrt, + # TODO trigonometric functions (and whatever tests will fail) ] @@ -76,7 +76,7 @@ class KernelConstraintsCheck: self.process_lhs(assignment.lhs) def process_expression(self, rhs): - # TODO constraint for accepted functions + # TODO constraint for accepted functions, see TODO above self.update_accesses_rhs(rhs) if isinstance(rhs, Field.Access): self.fields_read.add(rhs.field) diff --git a/pystencils/kernelcreation.py b/pystencils/kernelcreation.py index bfb2c09a37323ff30943f19396e0f3d5ef65a519..cd744a0d9ad318792c7f6318bee3fbd41aa4746a 100644 --- a/pystencils/kernelcreation.py +++ b/pystencils/kernelcreation.py @@ -65,7 +65,7 @@ def create_kernel(assignments: Union[Assignment, List[Assignment], AssignmentCol if isinstance(assignments, list): assignments = NodeCollection(assignments) elif isinstance(assignments, AssignmentCollection): - # TODO check and doku + # TODO Markus check and doku # --- applying first default simplifications try: if config.default_assignment_simplifications: @@ -135,7 +135,6 @@ def create_domain_kernel(assignments: NodeCollection, *, config: CreateKernelCon if config.target == Target.CPU: if config.backend == Backend.C: from pystencils.cpu import add_openmp, create_kernel - # TODO: data type keyword should be unified to data_type ast = create_kernel(assignments, config=config) for optimization in config.cpu_prepend_optimizations: optimization(ast) diff --git a/pystencils/simp/simplifications.py b/pystencils/simp/simplifications.py index 086ff984115d5fcf03ed49dac45850cb17bd8c62..5ed8c4ea9ee62fca33933eead71be6fff5571704 100644 --- a/pystencils/simp/simplifications.py +++ b/pystencils/simp/simplifications.py @@ -225,9 +225,10 @@ def apply_on_all_subexpressions(operation: Callable[[sp.Expr], sp.Expr]): return f # TODO Markus -# TODO: make this really work for Assignmentcollections -# TODO: this function should ONLY evaluate -# TODO: do the optims_c99 elsewhere optionally +# make this really work for Assignmentcollections +# this function should ONLY evaluate +# do the optims_c99 elsewhere optionally + # def apply_sympy_optimisations(ac: AssignmentCollection): # """ Evaluates constant expressions (e.g. :math:`\\sqrt{3}` will be replaced by its floating point representation) # and applies the default sympy optimisations. See sympy.codegen.rewriting diff --git a/pystencils_tests/test_types.py b/pystencils_tests/test_types.py index 8ac96f84e732debd8e1ff50426c483b6b5523868..16466df5238dde45ae711e124db451c688182e17 100644 --- a/pystencils_tests/test_types.py +++ b/pystencils_tests/test_types.py @@ -84,7 +84,6 @@ def test_mixed_add(dtype1, dtype2): assert test_f[0] == constant+constant -# TODO vector def test_collation(): double_type = BasicType('float64') float_type = BasicType('float32') @@ -159,7 +158,6 @@ def test_sqrt_of_integer(dtype): assignments = [ps.Assignment(tmp, sp.sqrt(3)), ps.Assignment(f[0], tmp)] arr = np.array([1], dtype=dtype) - # TODO Jupyter add auto lhs float/double problem config = pystencils.config.CreateKernelConfig(data_type=dtype, default_number_float=dtype) ast = ps.create_kernel(assignments, config=config)