Skip to content

Commit

Permalink
Fix boolean cast in Fortran code (#1789)
Browse files Browse the repository at this point in the history
Add missing cast when creating an array from an object with a different
datatype. Fixes #1785

In order to call the cast on the argument passed to `np.array` (which
may be a `InhomogeneousTupleType`) fix the type of the cast functions.

Modify `InhomogeneousTupleType.datatype` to return a `FixedSizeType` if
the datatypes of all elements are equivalent.

Also fix some minor bugs after #1756
  • Loading branch information
EmilyBourne committed Apr 9, 2024
1 parent d44bf3a commit f17d55b
Show file tree
Hide file tree
Showing 11 changed files with 64 additions and 193 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -24,6 +24,7 @@ All notable changes to this project will be documented in this file.
- #1792 : Fix array unpacking.
- #1795 : Fix bug when returning slices in C.
- #1732 : Fix multidimensional list indexing in Python.
- #1785 : Add missing cast when creating an array of booleans from non-boolean values.

### Changed
- #1720 : functions with the `@inline` decorator are no longer exposed to Python in the shared library.
Expand Down
5 changes: 0 additions & 5 deletions pyccel/ast/builtins.py
Expand Up @@ -513,11 +513,6 @@ def __init__(self, *args):

# Get possible datatypes
dtypes = [a.class_type.datatype for a in args]
# Extract all dtypes inside any inhomogeneous tuples
while any(isinstance(d, InhomogeneousTupleType) for d in dtypes):
dtypes = [di for d in dtypes for di in ((d_elem.datatype for d_elem in d)
if isinstance(d, InhomogeneousTupleType)
else [d])]
# Create a set of dtypes using the same key for compatible types
dtypes = set((d.primitive_type, d.precision) if isinstance(d, FixedSizeNumericType) else d for d in dtypes)

Expand Down
16 changes: 9 additions & 7 deletions pyccel/ast/datatypes.py
Expand Up @@ -714,10 +714,14 @@ class InhomogeneousTupleType(ContainerType, TupleType, metaclass = ArgumentSingl
*args : tuple of DataTypes
The datatypes stored in the inhomogeneous tuple.
"""
__slots__ = ('_element_types',)
__slots__ = ('_element_types', '_datatype')

def __init__(self, *args):
self._element_types = args

possible_types = set(t.datatype for t in self._element_types)
dtype = possible_types.pop()
self._datatype = dtype if all(d == dtype for d in possible_types) else self
super().__init__()

def __str__(self):
Expand Down Expand Up @@ -754,13 +758,11 @@ def datatype(self):
"""
The datatype of the object.
The datatype of the object.
The datatype of the object. For an inhomogeneous tuple the datatype is the type
of the tuple unless the tuple is comprised of containers which are all based on
compatible data types. In this case one of the underlying types is returned.
"""
possible_types = set(t.datatype for t in self._element_types)
if len(possible_types) == 1:
return possible_types.pop()
else:
return self
return self._datatype

class DictType(ContainerType, metaclass = ArgumentSingleton):
"""
Expand Down
19 changes: 14 additions & 5 deletions pyccel/ast/numpyext.py
Expand Up @@ -184,12 +184,14 @@ class NumpyFloat(PythonFloat):
The argument passed to the function.
"""
__slots__ = ('_rank','_shape','_order','_class_type')
_static_type = NumpyFloat64Type()
name = 'float'

def __init__(self, arg):
self._shape = arg.shape
self._rank = arg.rank
self._order = arg.order
self._class_type = arg.class_type.switch_basic_type(self.static_type())
self._class_type = NumpyNDArrayType(self.static_type()) if self._rank else self.static_type()
super().__init__(arg)

@property
Expand Down Expand Up @@ -250,7 +252,7 @@ def __init__(self, arg):
self._shape = arg.shape
self._rank = arg.rank
self._order = arg.order
self._class_type = arg.class_type.switch_basic_type(self.static_type())
self._class_type = NumpyNDArrayType(self.static_type()) if self._rank else self.static_type()
super().__init__(arg)

@property
Expand All @@ -277,12 +279,14 @@ class NumpyInt(PythonInt):
The argument passed to the function.
"""
__slots__ = ('_shape','_rank','_order','_class_type')
_static_type = numpy_precision_map[(PrimitiveIntegerType(), PythonInt._static_type.precision)]
name = 'int'

def __init__(self, arg=None, base=10):
self._shape = arg.shape
self._rank = arg.rank
self._order = arg.order
self._class_type = arg.class_type.switch_basic_type(self.static_type())
self._class_type = NumpyNDArrayType(self.static_type()) if self._rank else self.static_type()
super().__init__(arg)

@property
Expand Down Expand Up @@ -374,7 +378,10 @@ class NumpyReal(PythonReal):
name = 'real'
def __new__(cls, arg):
if isinstance(arg.dtype, PythonNativeBool):
return NumpyInt(arg)
if arg.rank:
return NumpyInt(arg)
else:
return PythonInt(arg)
else:
return super().__new__(cls, arg)

Expand Down Expand Up @@ -452,14 +459,16 @@ class NumpyComplex(PythonComplex):
_real_cast = NumpyReal
_imag_cast = NumpyImag
__slots__ = ('_rank','_shape','_order','_class_type')
_static_type = NumpyComplex128Type()
name = 'complex'

def __init__(self, arg0, arg1 = None):
if arg1 is not None:
raise NotImplementedError("Use builtin complex function not deprecated np.complex")
self._shape = arg0.shape
self._rank = arg0.rank
self._order = arg0.order
self._class_type = arg0.class_type.switch_basic_type(self.static_type())
self._class_type = NumpyNDArrayType(self.static_type()) if self._rank else self.static_type()
super().__init__(arg0)

@property
Expand Down
2 changes: 1 addition & 1 deletion pyccel/codegen/printing/ccode.py
Expand Up @@ -1242,7 +1242,7 @@ def get_declare_type(self, expr):
else:
errors.report(PYCCEL_RESTRICTION_TODO+' (rank>0)', symbol=expr, severity='fatal')
elif not isinstance(class_type, CustomDataType):
dtype = self.find_in_dtype_registry(class_type)
dtype = self.find_in_dtype_registry(expr.dtype)
else:
dtype = self._print(expr.class_type)

Expand Down
11 changes: 9 additions & 2 deletions pyccel/codegen/printing/fcode.py
Expand Up @@ -1190,12 +1190,17 @@ def _print_NumpyWhere(self, expr):
return stmt

def _print_NumpyArray(self, expr):
expr_args = (expr.arg,) if isinstance(expr.arg, Variable) else expr.arg
order = expr.order

try :
cast_func = DtypePrecisionToCastFunction[expr.dtype]
except KeyError:
errors.report(PYCCEL_RESTRICTION_TODO, severity='fatal')
arg = expr.arg if expr.arg.dtype == expr.dtype else cast_func(expr.arg)
# If Numpy array is stored with column-major ordering, transpose values
# use reshape with order for rank > 2
if expr.rank <= 2:
rhs_code = self._print(expr.arg)
rhs_code = self._print(arg)
if expr.arg.order and expr.arg.order != expr.order:
rhs_code = f'transpose({rhs_code})'
if expr.arg.rank < expr.rank:
Expand All @@ -1205,6 +1210,8 @@ def _print_NumpyArray(self, expr):
shape_code = ', '.join(self._print(i) for i in expr.shape[::-1])
rhs_code = f"reshape({rhs_code}, [{shape_code}])"
else:
expr_args = (expr.arg,) if isinstance(expr.arg, Variable) else expr.arg
expr_args = tuple(a if a.dtype == expr.dtype else cast_func(a) for a in expr_args)
new_args = []
inv_order = 'C' if order == 'F' else 'F'
for a in expr_args:
Expand Down
8 changes: 4 additions & 4 deletions tests/epyccel/modules/python_annotations.py
Expand Up @@ -12,21 +12,21 @@ def array_int32_2d_F_add( x:'int32[:,:](order=F)', y:'int32[:,:](order=F)' ):
def array_int_1d_scalar_add( x:'int[:]', a:'int' ):
x[:] += a

def array_real_1d_scalar_add( x:'real[:]', a:'real' ):
def array_float_1d_scalar_add( x:'float[:]', a:'float' ):
x[:] += a

def array_real_2d_F_scalar_add( x:'real[:,:](order=F)', a:'real' ):
def array_float_2d_F_scalar_add( x:'float[:,:](order=F)', a:'float' ):
x[:,:] += a

def array_real_2d_F_add( x:'real[:,:](order=F)', y:'real[:,:](order=F)' ):
def array_float_2d_F_add( x:'float[:,:](order=F)', y:'float[:,:](order=F)' ):
x[:,:] += y

def array_int32_2d_F_complex_3d_expr( x:'int32[:,:](order=F)', y:'int32[:,:](order=F)' ):
from numpy import full, int32
z = full((2,3),5,order='F', dtype=int32)
x[:] = (x // y) * x + z

def array_real_1d_complex_3d_expr( x:'real[:]', y:'real[:]' ):
def array_float_1d_complex_3d_expr( x:'float[:]', y:'float[:]' ):
from numpy import full
z = full(3,5)
x[:] = (x // y) * x + z
Expand Down
19 changes: 10 additions & 9 deletions tests/epyccel/recognised_functions/test_numpy_funcs.py
Expand Up @@ -2160,25 +2160,26 @@ def create_array_tuple_ref(a : 'int[:,:]'):
tmp_arr = np.ones((3,4), dtype=int)
assert np.allclose(array_tuple_ref(tmp_arr), create_array_tuple_ref(tmp_arr))

@pytest.mark.parametrize( 'language', (
pytest.param("fortran", marks = pytest.mark.fortran),
pytest.param("c", marks = [
pytest.mark.skip(reason="Changing dtype is broken in C. See #1641"),
pytest.mark.c]
),
pytest.param("python", marks = pytest.mark.python)
)
)
def test_array_new_dtype(language):
def create_float_array_tuple_ref(a : 'int[:,:]'):
from numpy import array
b = (a[0,:], a[1,:])
c = array(b, dtype=float)
return c
def create_bool_array_tuple_ref(a : 'int[:,:]'):
from numpy import array
b = (a[0,:], a[1,:])
c = array(b, dtype=bool)
return c

array_float_tuple_ref = epyccel(create_float_array_tuple_ref, language = language)
tmp_arr = np.ones((3,4), dtype=int)
assert np.allclose(array_float_tuple_ref(tmp_arr), create_float_array_tuple_ref(tmp_arr))

array_bool_tuple_ref = epyccel(create_float_array_tuple_ref, language = language)
tmp_arr = np.ones((3,4), dtype=int)
assert np.allclose(array_bool_tuple_ref(tmp_arr), create_bool_array_tuple_ref(tmp_arr))

@pytest.mark.parametrize( 'language', (
pytest.param("fortran", marks = pytest.mark.fortran),
pytest.param("c", marks = [
Expand Down
16 changes: 8 additions & 8 deletions tests/epyccel/test_array_as_func_args.py
Expand Up @@ -31,12 +31,12 @@ def array_int_1d_scalar_add(x : 'T[:]', a : 'T', x_len : int):

assert np.array_equal( x1, x2 )

def test_array_real_1d_scalar_add(language):
@template('T', ['float32', 'double'])
def array_real_1d_scalar_add(x : 'T[:]', a : 'T', x_len : int):
def test_array_float_1d_scalar_add(language):
@template('T', ['float32', 'float'])
def array_float_1d_scalar_add(x : 'T[:]', a : 'T', x_len : int):
for i in range(x_len):
x[i] += a
f1 = array_real_1d_scalar_add
f1 = array_float_1d_scalar_add
f2 = epyccel(f1, language=language)

for t in float_types:
Expand Down Expand Up @@ -92,13 +92,13 @@ def array_int_2d_scalar_add( x : 'T[:,:]', a : 'T', d1 : int, d2 : int):

assert np.array_equal( x1, x2 )

def test_array_real_2d_scalar_add(language):
@template('T', ['float32', 'double'])
def array_real_2d_scalar_add(x : 'T[:,:]', a : 'T', d1 : int, d2 : int):
def test_array_float_2d_scalar_add(language):
@template('T', ['float32', 'float'])
def array_float_2d_scalar_add(x : 'T[:,:]', a : 'T', d1 : int, d2 : int):
for i in range(d1):
for j in range(d2):
x[i, j] += a
f1 = array_real_2d_scalar_add
f1 = array_float_2d_scalar_add
f2 = epyccel(f1, language=language)

for t in float_types:
Expand Down

0 comments on commit f17d55b

Please sign in to comment.