diff --git a/pystencils/data_types.py b/pystencils/data_types.py index 5d553006bcca4c1cccbddb681d8710a92b75c437..37b4f9479635f7661528c15275c59a4b81e89310 100644 --- a/pystencils/data_types.py +++ b/pystencils/data_types.py @@ -7,6 +7,7 @@ import numpy as np import sympy as sp import sympy.codegen.ast from sympy.core.cache import cacheit +from sympy.core.logic import fuzzy_not, fuzzy_or from sympy.logic.boolalg import Boolean, BooleanFunction import pystencils @@ -20,6 +21,105 @@ except ImportError as e: _ir_importerror = e +class Type(sp.Basic): + is_Atom = True + + def __new__(cls, *args, **kwargs): + return sp.Basic.__new__(cls) + + def _sympystr(self, *args, **kwargs): + return str(self) + + +class BasicType(Type): + @staticmethod + def numpy_name_to_c(name): + if name == 'float64': + return 'double' + elif name == 'float32': + return 'float' + elif name == 'complex64': + return 'ComplexFloat' + elif name == 'complex128': + return 'ComplexDouble' + elif name.startswith('int'): + width = int(name[len("int"):]) + return "int%d_t" % (width,) + elif name.startswith('uint'): + width = int(name[len("uint"):]) + return "uint%d_t" % (width,) + elif name == 'bool': + return 'bool' + else: + raise NotImplementedError("Can map numpy to C name for %s" % (name,)) + + def __init__(self, dtype, const=False): + self.const = const + if isinstance(dtype, Type): + self._dtype = dtype.numpy_dtype + else: + self._dtype = np.dtype(dtype) + assert self._dtype.fields is None, "Tried to initialize NativeType with a structured type" + assert self._dtype.hasobject is False + assert self._dtype.subdtype is None + + def __getnewargs__(self): + return self.numpy_dtype, self.const + + @property + def base_type(self): + return None + + @property + def numpy_dtype(self): + return self._dtype + + @property + def sympy_dtype(self): + return getattr(sympy.codegen.ast, str(self.numpy_dtype)) + + @property + def item_size(self): + return 1 + + def is_int(self): + return self.numpy_dtype in np.sctypes['int'] or self.numpy_dtype in np.sctypes['uint'] + + def is_float(self): + return self.numpy_dtype in np.sctypes['float'] + + def is_uint(self): + return self.numpy_dtype in np.sctypes['uint'] + + def is_complex(self): + return self.numpy_dtype in np.sctypes['complex'] + + def is_other(self): + return self.numpy_dtype in np.sctypes['others'] + + @property + def base_name(self): + return BasicType.numpy_name_to_c(str(self._dtype)) + + def __str__(self): + result = BasicType.numpy_name_to_c(str(self._dtype)) + if self.const: + result += " const" + return result + + def __repr__(self): + return str(self) + + def __eq__(self, other): + if not isinstance(other, BasicType): + return False + else: + return (self.numpy_dtype, self.const) == (other.numpy_dtype, other.const) + + def __hash__(self): + return hash(str(self)) + + def typed_symbols(names, dtype, *args): symbols = sp.symbols(names, *args) if isinstance(symbols, Tuple): @@ -144,48 +244,48 @@ class cast_func(sp.Function): @property def is_integer(self): - """ - Uses Numpy type hierarchy to determine :func:`sympy.Expr.is_integer` predicate - - For reference: Numpy type hierarchy https://docs.scipy.org/doc/numpy-1.13.0/reference/arrays.scalars.html - """ - if hasattr(self.dtype, 'numpy_dtype'): - return np.issubdtype(self.dtype.numpy_dtype, np.integer) or super().is_integer - else: - return super().is_integer + try: + dtype_assumptions = assumptions_from_dtype(self.args[1].numpy_dtype) + except Exception: + dtype_assumptions = {} + # we do not want a float to be integer even if it numerically is + return dtype_assumptions.get('integer', None) @property def is_negative(self): - """ - See :func:`.TypedSymbol.is_integer` - """ - if hasattr(self.dtype, 'numpy_dtype'): - if np.issubdtype(self.dtype.numpy_dtype, np.unsignedinteger): - return False - - return super().is_negative + try: + dtype_assumptions = assumptions_from_dtype(self.args[1].numpy_dtype) + except Exception: + dtype_assumptions = {} + return fuzzy_or((self.args[0].is_negative, dtype_assumptions.get('negative', None))) @property def is_nonnegative(self): - """ - See :func:`.TypedSymbol.is_integer` - """ - if self.is_negative is False: - return True - else: - return super().is_nonnegative + try: + dtype_assumptions = assumptions_from_dtype(self.args[1].numpy_dtype) + except Exception: + dtype_assumptions = {} + return fuzzy_or((self.args[0].is_nonnegative, fuzzy_not(dtype_assumptions.get('negative', None)))) @property def is_real(self): - """ - See :func:`.TypedSymbol.is_integer` - """ - if hasattr(self.dtype, 'numpy_dtype'): - return np.issubdtype(self.dtype.numpy_dtype, np.integer) or \ - np.issubdtype(self.dtype.numpy_dtype, np.floating) or \ - super().is_real - else: - return super().is_real + try: + dtype_assumptions = assumptions_from_dtype(self.args[1].numpy_dtype) + except Exception: + dtype_assumptions = {} + return fuzzy_or((self.args[0].is_real, dtype_assumptions.get('real', None))) + + @property + def is_extended_real(self): + try: + dtype_assumptions = assumptions_from_dtype(self.args[1].numpy_dtype) + except Exception: + dtype_assumptions = {} + return fuzzy_or((self.args[0].is_extended_real, dtype_assumptions.get('real', None))) + + @property + def is_imaginary(self): + return self.args[0].is_imaginary # noinspection PyPep8Naming @@ -214,7 +314,27 @@ class pointer_arithmetic_func(sp.Function, Boolean): raise NotImplementedError() +def create_type(specification): + """Creates a subclass of Type according to a string or an object of subclass Type. + + Args: + specification: Type object, or a string + + Returns: + Type object, or a new Type object parsed from the string + """ + if isinstance(specification, Type): + return specification + else: + numpy_dtype = np.dtype(specification) + if numpy_dtype.fields is None: + return BasicType(numpy_dtype, const=False) + else: + return StructType(numpy_dtype, const=False) + + class TypedSymbol(sp.Symbol): + def __new__(cls, *args, **kwds): obj = TypedSymbol.__xnew_cached_(cls, *args, **kwds) return obj @@ -267,25 +387,6 @@ class TypedSymbol(sp.Symbol): return headers -def create_type(specification): - """Creates a subclass of Type according to a string or an object of subclass Type. - - Args: - specification: Type object, or a string - - Returns: - Type object, or a new Type object parsed from the string - """ - if isinstance(specification, Type): - return specification - else: - numpy_dtype = np.dtype(specification) - if numpy_dtype.fields is None: - return BasicType(numpy_dtype, const=False) - else: - return StructType(numpy_dtype, const=False) - - @memorycache(maxsize=64) def create_composite_type_from_string(specification): """Creates a new Type object from a c-like string specification. @@ -575,105 +676,6 @@ def get_type_of_expression(expr, raise NotImplementedError("Could not determine type for", expr, type(expr)) -class Type(sp.Basic): - is_Atom = True - - def __new__(cls, *args, **kwargs): - return sp.Basic.__new__(cls) - - def _sympystr(self, *args, **kwargs): - return str(self) - - -class BasicType(Type): - @staticmethod - def numpy_name_to_c(name): - if name == 'float64': - return 'double' - elif name == 'float32': - return 'float' - elif name == 'complex64': - return 'ComplexFloat' - elif name == 'complex128': - return 'ComplexDouble' - elif name.startswith('int'): - width = int(name[len("int"):]) - return "int%d_t" % (width,) - elif name.startswith('uint'): - width = int(name[len("uint"):]) - return "uint%d_t" % (width,) - elif name == 'bool': - return 'bool' - else: - raise NotImplementedError("Can map numpy to C name for %s" % (name,)) - - def __init__(self, dtype, const=False): - self.const = const - if isinstance(dtype, Type): - self._dtype = dtype.numpy_dtype - else: - self._dtype = np.dtype(dtype) - assert self._dtype.fields is None, "Tried to initialize NativeType with a structured type" - assert self._dtype.hasobject is False - assert self._dtype.subdtype is None - - def __getnewargs__(self): - return self.numpy_dtype, self.const - - @property - def base_type(self): - return None - - @property - def numpy_dtype(self): - return self._dtype - - @property - def sympy_dtype(self): - return getattr(sympy.codegen.ast, str(self.numpy_dtype)) - - @property - def item_size(self): - return 1 - - def is_int(self): - return self.numpy_dtype in np.sctypes['int'] or self.numpy_dtype in np.sctypes['uint'] - - def is_float(self): - return self.numpy_dtype in np.sctypes['float'] - - def is_uint(self): - return self.numpy_dtype in np.sctypes['uint'] - - def is_complex(self): - return self.numpy_dtype in np.sctypes['complex'] - - def is_other(self): - return self.numpy_dtype in np.sctypes['others'] - - @property - def base_name(self): - return BasicType.numpy_name_to_c(str(self._dtype)) - - def __str__(self): - result = BasicType.numpy_name_to_c(str(self._dtype)) - if self.const: - result += " const" - return result - - def __repr__(self): - return str(self) - - def __eq__(self, other): - if not isinstance(other, BasicType): - return False - else: - return (self.numpy_dtype, self.const) == (other.numpy_dtype, other.const) - - def __hash__(self): - return hash(str(self)) - - class VectorType(Type): instruction_set = None