diff --git a/src/pystencils/field.py b/src/pystencils/field.py index 2a74824c4e523026f5370acdb889f0ff3a11d375..5d7cb95cc4e6b6275f7fb55edb31c8ef950448c5 100644 --- a/src/pystencils/field.py +++ b/src/pystencils/field.py @@ -8,6 +8,7 @@ import re from enum import Enum from itertools import chain from typing import List, Optional, Sequence, Set, Tuple, Union +from warnings import warn import numpy as np import sympy as sp @@ -151,11 +152,15 @@ class Field: field_type=FieldType.GENERIC, ) -> "Field": """ - Creates a generic field where the field size is not fixed i.e. can be called with arrays of different sizes + Creates a generic field where the field size is not fixed i.e. can be called with arrays of different sizes. + + **Field Element Type** By default, the data type of the field entries is left undetermined until + code generation, at which point it is set to the default numerical type of the kernel. + You can specify a concrete type using the `dtype` parameter. Args: field_name: symbolic name for the field - dtype: numpy data type of the array the kernel is called with later + dtype: Data type of the field entries spatial_dimensions: see documentation of Field index_dimensions: see documentation of Field layout: tuple specifying the loop ordering of the spatial dimensions e.g. (2, 1, 0 ) means that @@ -228,7 +233,7 @@ class Field: array: np.ndarray, index_dimensions: int = 0, field_type=FieldType.GENERIC, - ) -> "Field": + ) -> Field: """Creates a field based on the layout, data type, and shape of a given numpy array. Kernels created for these kind of fields can only be called with arrays of the same layout, shape and type. @@ -272,13 +277,14 @@ class Field: field_name: str, shape: tuple[int, ...], index_dimensions: int = 0, - dtype: UserTypeSpec = np.float64, + dtype: UserTypeSpec | DynamicType = DynamicType.NUMERIC_TYPE, layout: str | tuple[int, ...] = "numpy", + memory_strides: None | Sequence[int] = None, strides: Optional[Sequence[int]] = None, field_type=FieldType.GENERIC, - ) -> "Field": + ) -> Field: """ - Creates a field with fixed sizes i.e. can be called only with arrays of the same size and layout + Creates a field with fixed sizes i.e. can be called only with arrays of the same size and layout. Args: field_name: symbolic name for the field @@ -286,14 +292,31 @@ class Field: index_dimensions: how many of the trailing dimensions are interpreted as index (as opposed to spatial) dtype: numpy data type of the array the kernel is called with later layout: full layout of array, not only spatial dimensions - strides: strides in bytes or None to automatically compute them from shape (assuming no padding) + memory_strides: Linearization strides for each dimension; + i.e. the number of elements to skip to get from one index to the next in the respective dimension. field_type: kind of field """ - if isinstance(dtype, DynamicType): - raise ValueError( - "Parameter `dtype` to `Field.create_fixed_size` does not accept `DynamicType`." + if strides is not None: + warn( + "The `strides` parameter to `Field.create_fixed_size` is deprecated " + "and will be removed in pystencils 2.1. " + "Use `memory_strides` instead; " + "beware that `memory_strides` takes the number of *elements* to skip, " + "instead of the number of bytes.", + FutureWarning ) + if memory_strides is not None: + raise ValueError("Cannot specify `memory_strides` and deprecated parameter `strides` at the same time.") + + if isinstance(dtype, DynamicType): + raise ValueError("Cannot specify the deprecated parameter `strides` together with a `DynamicType`. " + "Set `memory_strides` instead.") + + np_type = create_type(dtype).numpy_dtype + assert np_type is not None + memory_strides = tuple([s // np_type.itemsize for s in strides]) + spatial_dimensions = len(shape) - index_dimensions assert spatial_dimensions >= 1 @@ -302,7 +325,8 @@ class Field: layout, spatial_dimensions + index_dimensions ) - dtype = create_type(dtype) + if not isinstance(dtype, DynamicType): + dtype = create_type(dtype) shape_tuple = tuple(int(s) for s in shape) strides_tuple: tuple[int, ...] @@ -311,14 +335,7 @@ class Field: strides_tuple = compute_strides(shape_tuple, layout) else: assert len(strides) == len(shape_tuple) - np_type = dtype.numpy_dtype - - if np_type is None: - raise ValueError( - f"Cannot create fixed-size field of data type {dtype} which has no NumPy representation." - ) - - strides_tuple = tuple([s // np_type.itemsize for s in strides]) + strides_tuple = tuple(strides) if isinstance(dtype, PsStructType): if index_dimensions != 0: diff --git a/tests/frontend/test_field.py b/tests/frontend/test_field.py index 56c3bdabbbf348d1f0de19404db7a84289294126..6ac76f3c643b3f20c8bdb77b2e443fe0649e441a 100644 --- a/tests/frontend/test_field.py +++ b/tests/frontend/test_field.py @@ -42,7 +42,7 @@ def test_field_basic(): assert f1.ndim == f.ndim assert f1.values_per_cell() == f.values_per_cell() - f = Field.create_fixed_size("f", (10, 10), strides=(80, 8), dtype=np.float64) + f = Field.create_fixed_size("f", (10, 10), strides=(10, 1), dtype=np.float64) assert f.spatial_strides == (10, 1) assert f.index_strides == () assert f.center_vector == sp.Matrix([f.center]) @@ -61,13 +61,13 @@ def test_field_basic(): neighbor = field_access.neighbor(coord_id=0, offset=-2) assert neighbor.offsets == (-1, 1) assert "_" in neighbor._latex("dummy") - assert f.dtype == create_type("float64") + assert f.dtype == DynamicType.NUMERIC_TYPE f = Field.create_fixed_size("f", (8, 8, 2, 2, 2), index_dimensions=3) assert f.center_vector == sp.Array( [[[f(i, j, k) for k in range(2)] for j in range(2)] for i in range(2)] ) - assert f.dtype == create_type("float64") + assert f.dtype == DynamicType.NUMERIC_TYPE f = Field.create_generic("f", spatial_dimensions=5, index_dimensions=2) field_access = f[1, -1, 2, -3, 0](1, 0) diff --git a/tests/nbackend/transformations/test_canonicalize_symbols.py b/tests/nbackend/transformations/test_canonicalize_symbols.py index 2758d123417eb8e3015ed1d6b4d8cf0ba7c14611..dbc4ba10b71668af43d3a352d9cc49a8c9d61140 100644 --- a/tests/nbackend/transformations/test_canonicalize_symbols.py +++ b/tests/nbackend/transformations/test_canonicalize_symbols.py @@ -17,7 +17,7 @@ def test_deduplication(): factory = AstFactory(ctx) canonicalize = CanonicalizeSymbols(ctx) - f = Field.create_fixed_size("f", (5, 5), strides=(5, 1)) + f = Field.create_fixed_size("f", (5, 5), memory_strides=(5, 1)) x, y, z = sp.symbols("x, y, z") ispace = FullIterationSpace.create_from_slice(ctx, make_slice[:, :], f) diff --git a/tests/nbackend/transformations/test_hoist_invariants.py b/tests/nbackend/transformations/test_hoist_invariants.py index daa2760c0b376dc0bb1f2ca59703a15efc5c2312..1f27a5a4cd17d9b20e54b3c44d1e733f8374f947 100644 --- a/tests/nbackend/transformations/test_hoist_invariants.py +++ b/tests/nbackend/transformations/test_hoist_invariants.py @@ -33,7 +33,7 @@ def test_hoist_multiple_loops(): canonicalize = CanonicalizeSymbols(ctx) hoist = HoistLoopInvariantDeclarations(ctx) - f = Field.create_fixed_size("f", (5, 5), strides=(5, 1)) + f = Field.create_fixed_size("f", (5, 5), memory_strides=(5, 1)) x, y, z = sp.symbols("x, y, z") ispace = FullIterationSpace.create_from_slice(ctx, make_slice[:, :], f)