Skip to content

Commit e36726f

Browse files
authored
Improve non-trivial datatypes (pyccel#1808)
Move the storage of rank and order from `ast.basic.TypedAstNode` to an internal property of `ast.datatypes.PyccelType` objects (the properties are still exposed via `ast.basic.TypedAstNode`). This allows mixed ordered objects to be described and therefore created (e.g. `list[float[:,:](order=F)]`). A test is added for this case. This change should make pyccel#1583 simple to implement. Making the rank and order part of the type fixes pyccel#1821 . This provides enough information to allow lists of arrays. Fixes pyccel#1721. In order to correctly index types such as `list[float[:,:](order=F)]` the creation of an `IndexedElement` in the syntactic stage is modified. Instead of collapsing all the indices into one `IndexedElement` an `IndexedElement` is created for each `ast.Subscript`. In the semantic stage this is collapsed into one `IndexedElement` for each container. E.g. for the following code: ```python import numpy as np a = (np.ones((3,4)), np.zeros((3,4))) a[0][1][2] ``` On the devel branch after the syntactic stage the last line is described as `IndexedElement('a', (0, 1, 2))`. This persists to the codegen stage. On this branch after the syntactic stage the last line is described as `IndexedElement(IndexedElement(IndexedElement('a', (0,)), (1,)), (2,))`. After the semantic stage it becomes `IndexedElement(IndexedElement('a', (0,)), (1,2))`. The printers are modified to handle this. The generated code is not changed (as support for lists has not yet been added) so the indices are still used together to index a 3D array. **Commit Summary** - Update documentation - Add missing `if __name__ == '__main__'` on old documentation examples. - Remove `static_rank` and `static_order` properties from `pyccel.ast.basic`. These properties are now contained in `static_type`. - Remove `_rank` and `_order` properties from `TypedAstNode`s. - Change `PyccelOperator.set_shape_rank` functions to `PyccelOperator.set_shape` functions (the rank must now be determined at the same time as the type). - Remove `PyccelOperator._set_order` functions. - Simplify homogeneity checks for tuples, lists and sets - Ensure the shape of an empty list/tuple literal is set correctly. - Correct the class type of `CmathPolar` - Remove the `order` argument from the `Allocate` constructor (the information is now retrieved from the order of the allocated variable). - Add the properties `rank` and `order` to `pyccel.ast.datatypes.PyccelType`. - Add a `switch_rank` function to `pyccel.ast.datatypes.HomogeneousContainerType`. - Add a `container_rank` property to `pyccel.ast.datatypes.HomogeneousContainerType`. - Parameterise `NumpyNDArrayType` by the rank and the order - Change the argument of `NumpyNewArray` from `dtype` to `class_type` (to set the rank/order in the subclass). - Allow `NumpyArray` to handle mixed rank objects (e.g. list of F-ordered arrays). - Rename `NumpyUfuncBase._set_shape_rank` to `NumpyUfuncBase._get_shape_rank` and return the shape and rank to avoid saving the rank unnecessarily. - Rename `NumpyUfuncBase._set_order` to `NumpyUfuncBase._get_order` and pass the rank and return the order to avoid saving unnecessarily. - Add docstrings. - Add a `NumpyNDArrayType.__new__` function which redirects rank 0 arrays to scalar types. - Augment `NumpyNDArrayType.__add__` to handle rank and ordering. - Add a `NumpyNDArrayType.swap_order` function to change between C and F ordered equivalent types. - Add `__str__` or `__repr__` functions to `PyccelType`s such that printing them gives a valid type annotation. - Add a `NumpyInt` object to easily find the NumPy integer which has the same precision as Python integers. - Remove `rank` and `order` arguments from `pyccel.ast.type_annotations.VariableTypeAnnotation` constructor (information now available in `class_type`). - Remove `order` property from `pyccel.ast.type_annotations.VariableTypeAnnotation` (`rank` is retained for now). - Update vector expression unravelling functions to handle multiple levels of `IndexedElement`s. - Improve docstrings in `pyccel.ast.utilities`. - Remove `rank` and `order` arguments from `pyccel.ast.variable.Variable` constructor (the information is now retrieved from the `class_type`). - Remove unused property `is_stack_scalar`. - Simplify `is_ndarray` method. - Add `_is_slice` attribute to `IndexedElement` to indicate if an element or a slice of the base is represented. - Add `allows_negative_indexes` property to `IndexedElement`. - Update `_print_IndexedElement` to handle multi-level `IndexedElement`s. - Correct `abs` call in `_print_NumpyNorm` - Improve error message for wrong arguments. - Allocate strings on the stack to avoid calling `Allocate` (to be improved with pyccel#459 ). - Remove `get_type_description` (this is now handled by `PyccelType.__str__`). - Provide a traceback to `errors.report` to allow the location of a `TypeError` raised during a `FunctionCall` to be more easily located. - Stop collapsing `ast.Subscript` into one `IndexedElement` object. - Add some mixed ordered tests. - Disable tests with ambiguous interfaces (see pyccel#1821).
1 parent 1390622 commit e36726f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1176
-875
lines changed

.dict_custom.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,5 @@ setter
113113
bitwise
114114
datatyping
115115
datatypes
116+
indexable
117+
traceback

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ All notable changes to this project will be documented in this file.
1414
- #1750 : Add Python support for set method `remove()`.
1515
- #1787 : Ensure `STC` is installed with Pyccel.
1616
- #1743 : Add Python support for set method `discard()`.
17+
- \[INTERNALS\] Added `container_rank` property to `ast.datatypes.PyccelType` objects.
18+
- \[DEVELOPER\] Added an improved traceback to the developer-mode errors for errors in function calls.
1719

1820
### Fixed
1921

@@ -29,6 +31,7 @@ All notable changes to this project will be documented in this file.
2931
- #1785 : Add missing cast when creating an array of booleans from non-boolean values.
3032
- #1218 : Fix bug when assigning an array to a slice in Fortran.
3133
- #1830 : Fix missing allocation when returning an annotated array expression.
34+
- #1821 : Ensure an error is raised when creating an ambiguous interface.
3235

3336
### Changed
3437
- #1720 : functions with the `@inline` decorator are no longer exposed to Python in the shared library.
@@ -39,11 +42,15 @@ All notable changes to this project will be documented in this file.
3942
- \[INTERNALS\] Build `utilities.metaclasses.ArgumentSingleton` on the fly to ensure correct docstrings.
4043
- \[INTERNALS\] Rewrite datatyping system. See #1722.
4144
- \[INTERNALS\] Moved precision from `ast.basic.TypedAstNode` to an internal property of `ast.datatypes.FixedSizeNumericType` objects.
45+
- \[INTERNALS\] Moved rank from `ast.basic.TypedAstNode` to an internal property of `ast.datatypes.PyccelType` objects.
46+
- \[INTERNALS\] Moved order from `ast.basic.TypedAstNode` to an internal property of `ast.datatypes.PyccelType` objects.
4247
- \[INTERNALS\] Use cached `__add__` method to determine result type of arithmetic operations.
4348
- \[INTERNALS\] Use cached `__and__` method to determine result type of bitwise comparison operations.
4449
- \[INTERNALS\] Removed unused `fcode`, `ccode`, `cwrappercode`, `luacode`, and `pycode` functions from printers.
4550
- \[INTERNALS\] Removed unused arguments from methods in `pyccel.codegen.codegen.Codegen`.
4651
- \[INTERNALS\] Stop storing `FunctionDef`, `ClassDef`, and `Import` objects inside `CodeBlock`s.
52+
- \[INTERNALS\] Remove the `order` argument from the `pyccel.ast.core.Allocate` constructor.
53+
- \[INTERNALS\] Remove `rank` and `order` arguments from `pyccel.ast.variable.Variable` constructor.
4754

4855
### Deprecated
4956

developer_docs/ast_nodes.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,6 @@ The order indicates how an array is laid out in memory. This can either be row-m
5454

5555
The static type is the class type that would be assigned to an object created using an instance of this class as a type annotation.
5656

57-
### Static rank
58-
59-
The static rank is the rank that would be assigned to an object created using an instance of this class as a type annotation.
60-
61-
### Static order
62-
63-
The static order is the order that would be assigned to an object created using an instance of this class as a type annotation.
64-
6557
## Pyccel Internal Function
6658

6759
The class `pyccel.ast.internals.PyccelInternalFunction` is a super class. This class should never be used directly but provides functionalities which are common to certain AST objects. These AST nodes are those which describe functions which are supported by Pyccel. For example it is used for functions from the `math` library, the `cmath` library, the `numpy` library, etc. `PyccelInternalFunction` inherits from `TypedAstNode`. The type information for the sub-class describes the type of the result of the function.

developer_docs/type_inference.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ The and operator describes what happens when two numeric types are combined in a
6868
When using these operators on an unknown number of arguments it can be useful to use `NativeGeneric()` as a starting point for the sum.
6969

7070
#### Container Type
71-
A `ContainerType` is an object which is comprised of `FixedSizeType` objects (e.g. `ndarray`,`list`,`tuple`, custom class). The sub-class `HomogeneousContainerType` describes containers which contain homogeneous data. These objects are characterised by an `element_type`. The elements of a `HomogeneousContainerType` are instances of `PyccelType`, but they can be either `FixedSizeType`s or `ContainerType`s.
71+
A `ContainerType` is an object which is comprised of `FixedSizeType` objects (e.g. `ndarray`,`list`,`tuple`, custom class). The sub-class `HomogeneousContainerType` describes containers which contain homogeneous data. These objects are characterised by an `element_type`, a `rank` (and associated `container_rank`) and an `order`. The elements of a `HomogeneousContainerType` are instances of `PyccelType`, but they can be either `FixedSizeType`s or `ContainerType`s. The `container_rank` is an integer equal to the number of indices necessary to index the container and get an element of type `element_type`. As these elements may also be indexable the `rank` property allows us to get the number of indices necessary to obtain a scalar element. It is the sum of all the `container_rank`s in the nested types. The `order` specifies the order in which the indices should be used to index the object. This is discussed in detail in the [order docs](./order_docs.md).
7272

7373
`HomogeneousContainerType`s also contain some utility functions. They implement `primitive_type` and `precision` to get the properties of the internal `FixedSizeType` (even if that type is inside another `HomogeneousContainerType`). They also implement `switch_basic_type` which creates a new `HomogeneousContainerType` which is similar to the current `HomogeneousContainerType`. The only difference is that the `FixedSizeType` is exchanged. This is useful when we want to preserve information about the container but need to change the type. For example, when we divide an integer by another we get a floating point type. When we divide a NumPy array or a CuPy array of integers by an integer (or array of integers) we get a NumPy/CuPy array of floating point numbers (with default Python precision). In order to preserve the container type we therefore call `switch_basic_type`. So for the division in the case of NumPy arrays, we want to change the type from `np.ndarray[int]` to `np.ndarray[float]`. This is done in one line:
7474
```python
@@ -89,4 +89,8 @@ for container in new_container_types:
8989

9090
The `switch_basic_type` cannot be implemented generally in `PyccelType` as there is no logical interpretation for an inhomogeneous `ContainerType`, however the function is also implemented (as the identity function) for `FixedSizeType`s so `switch_basic_type` can be used without the need for type checks (generally inhomogeneous containers will not be valid arguments to classes which may need to use the `switch_basic_type` function).
9191

92+
`HomogeneousContainerType`s also contain a `switch_rank` function. This function is similar to `switch_basic_type` in that it is used to obtain a type which is similar in all but one characteristic. It is usually used to reduce the rank of an object, for example when calculating the type of a slice, however in the future it can also be used to increase the size of the type (e.g. to implement `np.newaxis`), in this case an order may need to be provided to add additional context. Increasing the rank is only possible for multi-dimensional types (e.g. `NumpyNDArrayType`) however the rank can be decreased for any `ContainerType`. If the rank is reduced by more than the `container_rank`, this function is called recursively.
93+
94+
For multi-dimensional `HomogeneousContainerType`s (e.g. `NumpyNDArrayType`) the function `swap_order` is also implemented. This inverts the ordering, changing from 'C' to 'F' or 'F' to 'C' if the rank is greater than 1.
95+
9296
In order to access the internal `FixedSizeType`, `PyccelType` also implements a `datatype` property. This makes more sense in the case of a `HomogeneousContainerType` however it is also implemented (as the identity function) for `FixedSizeType`s so the low-level type can be obtained without the need for type checks.

docs/ndarrays.md

Lines changed: 88 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ Generally a variable in Pyccel should always keep its initial type, this also tr
2222
```Python
2323
import numpy as np
2424

25-
a = np.array([1, 2, 3], dtype=float)
26-
#(some code...)
27-
a = np.array([1, 2, 3], dtype=int)
25+
if __name__ == '__main__':
26+
a = np.array([1, 2, 3], dtype=float)
27+
#(some code...)
28+
a = np.array([1, 2, 3], dtype=int)
2829
```
2930

3031
_OUTPUT_ :
@@ -46,9 +47,10 @@ Pyccel calls its own garbage collector when needed, but has a set of rules to do
4647
```Python
4748
import numpy as np
4849

49-
a = np.ones((10, 20))
50-
#(some code...)
51-
a = np.ones(10)
50+
if __name__ == '__main__':
51+
a = np.ones((10, 20))
52+
#(some code...)
53+
a = np.ones(10)
5254
```
5355

5456
_OUTPUT_ :
@@ -66,9 +68,10 @@ This limitation is due to the fact that the rank of Fortran allocatable objects
6668
```Python
6769
import numpy as np
6870

69-
a = np.array([1, 2, 3, 4, 5])
70-
b = np.array([1, 2, 3])
71-
a = b
71+
if __name__ == '__main__':
72+
a = np.array([1, 2, 3, 4, 5])
73+
b = np.array([1, 2, 3])
74+
a = b
7275
```
7376

7477
_OUTPUT_ :
@@ -139,10 +142,11 @@ This limitation is due to the fact that the rank of Fortran allocatable objects
139142
```Python
140143
import numpy as np
141144

142-
a = np.ones(10)
143-
b = a[:5]
144-
#(some code...)
145-
a = np.zeros(20)
145+
if __name__ == '__main__':
146+
a = np.ones(10)
147+
b = a[:5]
148+
#(some code...)
149+
a = np.zeros(20)
146150
```
147151

148152
_OUTPUT_ :
@@ -157,7 +161,7 @@ This limitation is set since we need to free the previous data when we reallocat
157161

158162
### Slicing and indexing ###
159163

160-
The indexing and slicing in Pyccel handles only the basic indexing of [numpy arrays](https://numpy.org/doc/stable/user/basics.indexing.html).
164+
The indexing and slicing in Pyccel handles only the basic indexing of [numpy arrays](https://numpy.org/doc/stable/user/basics.indexing.html). When multiple indexing expressions are used on the same variable Pyccel squashes them into one object. This means that we do not handle multiple slice indices applied to the same variable (e.g. `a[1::2][2:]`). This is not recommended anyway as it makes code hard to read.
161165

162166
Some examples:
163167

@@ -166,8 +170,9 @@ Some examples:
166170
```Python
167171
import numpy as np
168172

169-
a = np.array([1, 3, 4, 5])
170-
a[0] = 0
173+
if __name__ == '__main__':
174+
a = np.array([1, 3, 4, 5])
175+
a[0] = 0
171176
```
172177

173178
- C equivalent:
@@ -211,8 +216,9 @@ Some examples:
211216
```Python
212217
import numpy as np
213218

214-
a = np.ones((10, 20))
215-
b = a[2:, :5]
219+
if __name__ == '__main__':
220+
a = np.ones((10, 20))
221+
b = a[2:, :5]
216222
```
217223

218224
- C equivalent:
@@ -257,10 +263,11 @@ Some examples:
257263
```Python
258264
import numpy as np
259265

260-
a = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
261-
b = a[1]
262-
c = b[2]
263-
print(c)
266+
if __name__ == '__main__':
267+
a = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
268+
b = a[1]
269+
c = b[2]
270+
print(c)
264271
```
265272

266273
- C equivalent:
@@ -311,6 +318,65 @@ Some examples:
311318
end program prog_ex
312319
```
313320

321+
- Python code:
322+
323+
```Python
324+
import numpy as np
325+
326+
if __name__ == '__main__':
327+
a = np.array([1, 2, 3, 4, 5, 6, 7, 8])
328+
b = a[1::2][2]
329+
print(b)
330+
```
331+
332+
- C equivalent:
333+
334+
```C
335+
#include <stdlib.h>
336+
#include "ndarrays.h"
337+
#include <stdint.h>
338+
#include <string.h>
339+
#include <stdio.h>
340+
#include <inttypes.h>
341+
int main()
342+
{
343+
t_ndarray a = {.shape = NULL};
344+
int64_t b;
345+
a = array_create(1, (int64_t[]){INT64_C(8)}, nd_int64, false, order_c);
346+
int64_t Dummy_0000[] = {INT64_C(1), INT64_C(2), INT64_C(3), INT64_C(4), INT64_C(5), INT64_C(6), INT64_C(7), INT64_C(8)};
347+
memcpy(&a.nd_int64[INT64_C(0)], Dummy_0000, 8 * a.type_size);
348+
b = GET_ELEMENT(a, nd_int64, INT64_C(5));
349+
printf("%"PRId64"\n", b);
350+
free_array(&a);
351+
return 0;
352+
}
353+
```
354+
355+
- Fortran equivalent:
356+
357+
```Fortran
358+
program prog_prog_tmp_index
359+
360+
use tmp_index
361+
362+
use, intrinsic :: ISO_C_Binding, only : i64 => C_INT64_T
363+
use, intrinsic :: ISO_FORTRAN_ENV, only : stdout => output_unit
364+
implicit none
365+
366+
integer(i64), allocatable :: a(:)
367+
integer(i64) :: b
368+
369+
allocate(a(0:7_i64))
370+
a = [1_i64, 2_i64, 3_i64, 4_i64, 5_i64, 6_i64, 7_i64, 8_i64]
371+
b = a(5_i64)
372+
write(stdout, '(I0)', advance="yes") b
373+
if (allocated(a)) then
374+
deallocate(a)
375+
end if
376+
377+
end program prog_prog_tmp_index
378+
```
379+
314380
## NumPy [ndarray](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html) functions/properties progress in Pyccel ##
315381

316382
- Supported [types](https://numpy.org/devdocs/user/basics.types.html):

pyccel/ast/basic.py

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ def rank(self):
534534
Number of dimensions of the object. If the object is a scalar then
535535
this is equal to 0.
536536
"""
537-
return self._rank # pylint: disable=no-member
537+
return self.class_type.rank
538538

539539
@property
540540
def dtype(self):
@@ -557,7 +557,7 @@ def order(self):
557557
('F') format. This is only relevant if rank > 1. When it is not relevant
558558
this function returns None.
559559
"""
560-
return self._order # pylint: disable=no-member
560+
return self.class_type.order
561561

562562
@property
563563
def class_type(self):
@@ -570,33 +570,6 @@ def class_type(self):
570570
"""
571571
return self._class_type # pylint: disable=no-member
572572

573-
@classmethod
574-
def static_rank(cls):
575-
"""
576-
Number of dimensions of the object.
577-
578-
Number of dimensions of the object. If the object is a scalar then
579-
this is equal to 0.
580-
581-
This function is static and will return an AttributeError if the
582-
class does not have a predetermined rank.
583-
"""
584-
return cls._rank # pylint: disable=no-member
585-
586-
@classmethod
587-
def static_order(cls):
588-
"""
589-
The data layout ordering in memory.
590-
591-
Indicates whether the data is stored in row-major ('C') or column-major
592-
('F') format. This is only relevant if rank > 1. When it is not relevant
593-
this function returns None.
594-
595-
This function is static and will return an AttributeError if the
596-
class does not have a predetermined order.
597-
"""
598-
return cls._order # pylint: disable=no-member
599-
600573
@classmethod
601574
def static_type(cls):
602575
"""
@@ -625,8 +598,6 @@ def copy_attributes(self, x):
625598
The node from which the attributes should be copied.
626599
"""
627600
self._shape = x.shape
628-
self._rank = x.rank
629-
self._order = x.order
630601
self._class_type = x.class_type
631602

632603

pyccel/ast/bind_c.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ def __init__(self, var, original_res_var, scope, **kwargs):
341341
name = original_res_var.name
342342
self._shape = [scope.get_temporary_variable(PythonNativeInt(),
343343
name=f'{name}_shape_{i+1}')
344-
for i in range(original_res_var._rank)]
344+
for i in range(original_res_var.rank)]
345345
self._original_res_var = original_res_var
346346
super().__init__(var, **kwargs)
347347

pyccel/ast/bitwise_operators.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@ class PyccelBitOperator(PyccelOperator):
8686
The second argument passed to the operator.
8787
"""
8888
_shape = None
89-
_rank = 0
90-
_order = None
9189
__slots__ = ('_class_type',)
9290

9391
def __init__(self, arg1, arg2):
@@ -130,12 +128,12 @@ def _calculate_type(self, arg1, arg2):
130128

131129
return class_type
132130

133-
def _set_shape_rank(self):
131+
def _set_shape(self):
134132
"""
135-
Set the shape and rank of the resulting object.
133+
Set the shape of the resulting object.
136134
137-
Set the shape and rank of the resulting object. For a PyccelBitOperator,
138-
the shape and rank are class attributes so nothing needs to be done.
135+
Set the shape of the resulting object. For a PyccelBitOperator,
136+
the shape is a class attribute so nothing needs to be done.
139137
"""
140138

141139
def __repr__(self):

0 commit comments

Comments
 (0)