Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into add-constant-field
Browse files Browse the repository at this point in the history
  • Loading branch information
havogt committed Dec 16, 2023
2 parents 23fc901 + 0d66829 commit 21a0e26
Show file tree
Hide file tree
Showing 149 changed files with 5,870 additions and 2,115 deletions.
2 changes: 1 addition & 1 deletion .gitpod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ image:
tasks:
- name: Setup venv and dev tools
init: |
ln -s /workspace/gt4py/.gitpod/.vscode /workspace/gt4py/.vscode
ln -sfn /workspace/gt4py/.gitpod/.vscode /workspace/gt4py/.vscode
python -m venv .venv
source .venv/bin/activate
pip install --upgrade pip setuptools wheel
Expand Down
38 changes: 38 additions & 0 deletions CODING_GUIDELINES.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,44 @@ We deviate from the [Google Python Style Guide][google-style-guide] only in the
- Client code (like tests, doctests and examples) should use the above style for public FieldView API
- Library code should always import the defining module and use qualified names.

### Error messages

Error messages should be written as sentences, starting with a capital letter and ending with a period (avoid exclamation marks). Try to be informative without being verbose. Code objects such as 'ClassNames' and 'function_names' should be enclosed in single quotes, and so should string values used for message interpolation.

Examples:

```python
raise ValueError(f"Invalid argument 'dimension': should be of type 'Dimension', got '{dimension.type}'.")
```

Interpolated integer values do not need double quotes, if they are indicating an amount. Example:

```python
raise ValueError(f"Invalid number of arguments: expected 3 arguments, got {len(args)}.")
```

The double quotes can also be dropped when presenting a sequence of values. In this case the message should be rephrased so the sequence is separated from the text by a colon ':'.

```python
raise ValueError(f"unexpected keyword arguments: {', '.join(set(kwarg_names} - set(expected_kwarg_names)))}.")
```

The message should be kept to one sentence if reasonably possible. Ideally the sentence should be kept short and avoid unneccessary words. Examples:

```python
# too many sentences
raise ValueError(f"Received an unexpeted number of arguments. Should receive 5 arguments, but got {len(args)}. Please provide the correct number of arguments.")
# better
raise ValueError(f"Wrong number of arguments: expected 5, got {len(args)}.")

# less extreme
raise TypeError(f"Wrong argument type. Can only accept 'int's, got '{type(arg)}' instead.")
# but can still be improved
raise TypeError(f"Wrong argument type: 'int' expected, got '{type(arg)}'")
```

The terseness vs. helpfulness tradeoff should be more in favor of terseness for internal error messages and more in favor of helpfulness for `DSLError` and it's subclassses, where additional sentences are encouraged if they point out likely hidden sources of the problem or common fixes.

### Docstrings

We generate the API documentation automatically from the docstrings using [Sphinx][sphinx] and some extensions such as [Sphinx-autodoc][sphinx-autodoc] and [Sphinx-napoleon][sphinx-napoleon]. These follow the Google Python Style Guide docstring conventions to automatically format the generated documentation. A complete overview can be found here: [Example Google Style Python Docstrings](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html#example-google).
Expand Down
5 changes: 4 additions & 1 deletion docs/development/ADRs/0008-Mapping_Domain_to_Cpp-Backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ tags: []
- **Status**: valid
- **Authors**: Hannes Vogt (@havogt)
- **Created**: 2022-06-29
- **Updated**: 2022-06-29
- **Updated**: 2023-11-08

This document proposes a (temporary) solution for mapping domain dimensions to field dimensions.

> [!NOTE]
> This ADR was written before the integration of `gt4py.storage` into `gt4py.next`, so the example is using `np_as_located_field` (now deprecated) instead of `gtx.as_field.partial`. The idea conveyed by the example remains unchanged.
## Context

The Python embedded execution for Iterator IR keeps track of the current location type of an iterator (allows safety checks) while the C++ backend does not.
Expand Down
4 changes: 3 additions & 1 deletion docs/development/ADRs/0015-Test_Exclusion_Matrices.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@ by calling `next_tests.get_processor_id()`, which returns the so-called processo
The following backend processors are defined:

```python
DACE = "dace_iterator.run_dace_iterator"
DACE_CPU = "dace_iterator.run_dace_cpu"
DACE_GPU = "dace_iterator.run_dace_gpu"
GTFN_CPU = "otf_compile_executor.run_gtfn"
GTFN_CPU_IMPERATIVE = "otf_compile_executor.run_gtfn_imperative"
GTFN_CPU_WITH_TEMPORARIES = "otf_compile_executor.run_gtfn_with_temporaries"
GTFN_GPU = "gt4py.next.program_processors.runners.gtfn.run_gtfn_gpu"
```

Following the previous example, the GTFN backend with temporaries does not support yet dynamic offsets in ITIR:
Expand Down
1 change: 1 addition & 0 deletions docs/development/ADRs/Index.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ _None_
- [0006 - C++ Backend](0006-Cpp-Backend.md)
- [0007 - Fencil Processors](0007-Fencil-Processors.md)
- [0008 - Mapping Domain to Cpp Backend](0008-Mapping_Domain_to_Cpp-Backend.md)
- [0016 - Multiple Backends and Build Systems](0016-Multiple-Backends-and-Build-Systems.md)

### Python Integration

Expand Down
66 changes: 39 additions & 27 deletions docs/user/next/QuickstartGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ from gt4py.next import float64, neighbor_sum, where

#### Fields

Fields store data as a multi-dimensional array, and are defined over a set of named dimensions. The code snippet below defines two named dimensions, _cell_ and _K_, and creates the fields `a` and `b` over their cartesian product using the `np_as_located_field` helper function. The fields contain the values 2 for `a` and 3 for `b` for all entries.
Fields store data as a multi-dimensional array, and are defined over a set of named dimensions. The code snippet below defines two named dimensions, _Cell_ and _K_, and creates the fields `a` and `b` over their cartesian product using the `gtx.as_field` helper function. The fields contain the values 2 for `a` and 3 for `b` for all entries.

```{code-cell} ipython3
CellDim = gtx.Dimension("Cell")
Expand All @@ -63,8 +63,20 @@ grid_shape = (num_cells, num_layers)
a_value = 2.0
b_value = 3.0
a = gtx.np_as_located_field(CellDim, KDim)(np.full(shape=grid_shape, fill_value=a_value, dtype=np.float64))
b = gtx.np_as_located_field(CellDim, KDim)(np.full(shape=grid_shape, fill_value=b_value, dtype=np.float64))
a = gtx.as_field([CellDim, KDim], np.full(shape=grid_shape, fill_value=a_value, dtype=np.float64))
b = gtx.as_field([CellDim, KDim], np.full(shape=grid_shape, fill_value=b_value, dtype=np.float64))
```

Additional numpy-equivalent constructors are available, namely `ones`, `zeros`, `empty`, `full`. These require domain, dtype, and allocator (e.g. a backend) specifications.

```{code-cell} ipython3
from gt4py._core import definitions as core_defs
array_of_ones_numpy = np.ones((grid_shape[0], grid_shape[1]))
field_of_ones = gtx.constructors.ones(
domain={I: range(grid_shape[0]), J: range(grid_shape[0])},
dtype=core_defs.dtype(np.float64),
allocator=gtx.program_processors.runners.roundtrip.backend
)
```

_Note: The interface to construct fields is provisional only and will change soon._
Expand All @@ -87,10 +99,10 @@ def add(a: gtx.Field[[CellDim, KDim], float64],
You can call field operators from [programs](#Programs), other field operators, or directly. The code snippet below shows a direct call, in which case you have to supply two additional arguments: `out`, which is a field to write the return value to, and `offset_provider`, which is left empty for now. The result of the field operator is a field with all entries equal to 5, but for brevity, only the average and the standard deviation of the entries are printed:

```{code-cell} ipython3
result = gtx.np_as_located_field(CellDim, KDim)(np.zeros(shape=grid_shape))
result = gtx.as_field([CellDim, KDim], np.zeros(shape=grid_shape))
add(a, b, out=result, offset_provider={})
print("{} + {} = {} ± {}".format(a_value, b_value, np.average(np.asarray(result)), np.std(np.asarray(result))))
print("{} + {} = {} ± {}".format(a_value, b_value, np.average(result.asnumpy()), np.std(result.asnumpy())))
```

#### Programs
Expand All @@ -113,10 +125,10 @@ def run_add(a : gtx.Field[[CellDim, KDim], float64],
You can execute the program by simply calling it:

```{code-cell} ipython3
result = gtx.np_as_located_field(CellDim, KDim)(np.zeros(shape=grid_shape))
result = gtx.as_field([CellDim, KDim], np.zeros(shape=grid_shape))
run_add(a, b, result, offset_provider={})
print("{} + {} = {} ± {}".format(b_value, (a_value + b_value), np.average(np.asarray(result)), np.std(np.asarray(result))))
print("{} + {} = {} ± {}".format(b_value, (a_value + b_value), np.average(result.asnumpy()), np.std(result.asnumpy())))
```

#### Composing field operators and programs
Expand Down Expand Up @@ -200,8 +212,8 @@ cell_to_edge_table = np.array([
Let's start by defining two fields: one over the cells and another one over the edges. The field over cells serves input for subsequent calculations and is therefore filled up with values, whereas the field over the edges stores the output of the calculations and is therefore left blank.

```{code-cell} ipython3
cell_values = gtx.np_as_located_field(CellDim)(np.array([1.0, 1.0, 2.0, 3.0, 5.0, 8.0]))
edge_values = gtx.np_as_located_field(EdgeDim)(np.zeros((12,)))
cell_values = gtx.as_field([CellDim], np.array([1.0, 1.0, 2.0, 3.0, 5.0, 8.0]))
edge_values = gtx.as_field([EdgeDim], np.zeros((12,)))
```

| ![cell_values](connectivity_cell_field.svg) |
Expand Down Expand Up @@ -244,7 +256,7 @@ def run_nearest_cell_to_edge(cell_values: gtx.Field[[CellDim], float64], out : g
run_nearest_cell_to_edge(cell_values, edge_values, offset_provider={"E2C": E2C_offset_provider})
print("0th adjacent cell's value: {}".format(np.asarray(edge_values)))
print("0th adjacent cell's value: {}".format(edge_values.asnumpy()))
```

Running the above snippet results in the following edge field:
Expand All @@ -271,7 +283,7 @@ def run_sum_adjacent_cells(cells : gtx.Field[[CellDim], float64], out : gtx.Fiel
run_sum_adjacent_cells(cell_values, edge_values, offset_provider={"E2C": E2C_offset_provider})
print("sum of adjacent cells: {}".format(np.asarray(edge_values)))
print("sum of adjacent cells: {}".format(edge_values.asnumpy()))
```

For the border edges, the results are unchanged compared to the previous example, but the inner edges now contain the sum of the two adjacent cells:
Expand All @@ -295,8 +307,8 @@ This function takes 3 input arguments:
In the case where the true and false branches are either fields or scalars, the resulting output will be a field including all dimensions from all inputs. For example:

```{code-cell} ipython3
mask = gtx.np_as_located_field(CellDim, KDim)(np.zeros(shape=grid_shape, dtype=bool))
result_where = gtx.np_as_located_field(CellDim, KDim)(np.zeros(shape=grid_shape))
mask = gtx.as_field([CellDim, KDim], np.zeros(shape=grid_shape, dtype=bool))
result_where = gtx.as_field([CellDim, KDim], np.zeros(shape=grid_shape))
b = 6.0
@gtx.field_operator
Expand All @@ -305,16 +317,16 @@ def conditional(mask: gtx.Field[[CellDim, KDim], bool], a: gtx.Field[[CellDim, K
return where(mask, a, b)
conditional(mask, a, b, out=result_where, offset_provider={})
print("where return: {}".format(np.asarray(result_where)))
print("where return: {}".format(result_where.asnumpy()))
```

**Tuple implementation:**

The `where` supports the return of tuples of fields. To perform promotion of dimensions and dtype of the output, all arguments are analyzed and promoted as in the above section.

```{code-cell} ipython3
result_1 = gtx.np_as_located_field(CellDim, KDim)(np.zeros(shape=grid_shape))
result_2 = gtx.np_as_located_field(CellDim, KDim)(np.zeros(shape=grid_shape))
result_1 = gtx.as_field([CellDim, KDim], np.zeros(shape=grid_shape))
result_2 = gtx.as_field([CellDim, KDim], np.zeros(shape=grid_shape))
@gtx.field_operator
def _conditional_tuple(mask: gtx.Field[[CellDim, KDim], bool], a: gtx.Field[[CellDim, KDim], float64], b: float
Expand All @@ -328,7 +340,7 @@ result_1: gtx.Field[[CellDim, KDim], float64], result_2: gtx.Field[[CellDim, KDi
_conditional_tuple(mask, a, b, out=(result_1, result_2))
conditional_tuple(mask, a, b, result_1, result_2, offset_provider={})
print("where tuple return: {}".format((np.asarray(result_1), np.asarray(result_2))))
print("where tuple return: {}".format((result_1.asnumpy(), result_2.asnumpy())))
```

The `where` builtin also allows for nesting of tuples. In this scenario, it will first perform an unrolling:
Expand All @@ -338,13 +350,13 @@ The `where` builtin also allows for nesting of tuples. In this scenario, it will
and then combine results to match the return type:

```{code-cell} ipython3
a = gtx.np_as_located_field(CellDim, KDim)(np.full(shape=grid_shape, fill_value=2.0, dtype=np.float64))
b = gtx.np_as_located_field(CellDim, KDim)(np.full(shape=grid_shape, fill_value=3.0, dtype=np.float64))
c = gtx.np_as_located_field(CellDim, KDim)(np.full(shape=grid_shape, fill_value=4.0, dtype=np.float64))
d = gtx.np_as_located_field(CellDim, KDim)(np.full(shape=grid_shape, fill_value=5.0, dtype=np.float64))
a = gtx.as_field([CellDim, KDim], np.full(shape=grid_shape, fill_value=2.0, dtype=np.float64))
b = gtx.as_field([CellDim, KDim], np.full(shape=grid_shape, fill_value=3.0, dtype=np.float64))
c = gtx.as_field([CellDim, KDim], np.full(shape=grid_shape, fill_value=4.0, dtype=np.float64))
d = gtx.as_field([CellDim, KDim], np.full(shape=grid_shape, fill_value=5.0, dtype=np.float64))
result_1 = gtx.np_as_located_field(CellDim, KDim)(np.zeros(shape=grid_shape))
result_2 = gtx.np_as_located_field(CellDim, KDim)(np.zeros(shape=grid_shape))
result_1 = gtx.as_field([CellDim, KDim], np.zeros(shape=grid_shape))
result_2 = gtx.as_field([CellDim, KDim], np.zeros(shape=grid_shape))
@gtx.field_operator
def _conditional_tuple_nested(
Expand All @@ -363,7 +375,7 @@ def conditional_tuple_nested(
_conditional_tuple_nested(mask, a, b, c, d, out=((result_1, result_2), (result_2, result_1)))
conditional_tuple_nested(mask, a, b, c, d, result_1, result_2, offset_provider={})
print("where nested tuple return: {}".format(((np.asarray(result_1), np.asarray(result_2)), (np.asarray(result_2), np.asarray(result_1)))))
print("where nested tuple return: {}".format(((result_1.asnumpy(), result_2.asnumpy()), (result_2.asnumpy(), result_1.asnumpy()))))
```

#### Implementing the pseudo-laplacian
Expand Down Expand Up @@ -402,7 +414,7 @@ edge_weights = np.array([
[0, -1, -1], # cell 5
], dtype=np.float64)
edge_weight_field = gtx.np_as_located_field(CellDim, C2EDim)(edge_weights)
edge_weight_field = gtx.as_field([CellDim, C2EDim], edge_weights)
```

Now you have everything to implement the pseudo-laplacian. Its field operator requires the cell field and the edge weights as inputs, and outputs a cell field of the same shape as the input.
Expand All @@ -428,14 +440,14 @@ def run_pseudo_laplacian(cells : gtx.Field[[CellDim], float64],
out : gtx.Field[[CellDim], float64]):
pseudo_lap(cells, edge_weights, out=out)
result_pseudo_lap = gtx.np_as_located_field(CellDim)(np.zeros(shape=(6,)))
result_pseudo_lap = gtx.as_field([CellDim], np.zeros(shape=(6,)))
run_pseudo_laplacian(cell_values,
edge_weight_field,
result_pseudo_lap,
offset_provider={"E2C": E2C_offset_provider, "C2E": C2E_offset_provider})
print("pseudo-laplacian: {}".format(np.asarray(result_pseudo_lap)))
print("pseudo-laplacian: {}".format(result_pseudo_lap.asnumpy()))
```

As a closure, here is an example of chaining field operators, which is very simple to do when working with fields. The field operator below executes the pseudo-laplacian, and then calls the pseudo-laplacian on the result of the first, in effect, calculating the laplacian of a laplacian.
Expand Down
9 changes: 7 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -330,21 +330,26 @@ markers = [
'requires_dace: tests that require `dace` package',
'requires_gpu: tests that require a NVidia GPU (`cupy` and `cudatoolkit` are required)',
'uses_applied_shifts: tests that require backend support for applied-shifts',
'uses_can_deref: tests that require backend support for can_deref',
'uses_constant_fields: tests that require backend support for constant fields',
'uses_dynamic_offsets: tests that require backend support for dynamic offsets',
'uses_floordiv: tests that require backend support for floor division',
'uses_if_stmts: tests that require backend support for if-statements',
'uses_index_fields: tests that require backend support for index fields',
'uses_lift_expressions: tests that require backend support for lift expressions',
'uses_negative_modulo: tests that require backend support for modulo on negative numbers',
'uses_origin: tests that require backend support for domain origin',
'uses_reduction_over_lift_expressions: tests that require backend support for reduction over lift expressions',
'uses_reduction_with_only_sparse_fields: tests that require backend support for with sparse fields',
'uses_scan_in_field_operator: tests that require backend support for scan in field operator',
'uses_sparse_fields: tests that require backend support for sparse fields',
'uses_strided_neighbor_offset: tests that require backend support for strided neighbor offset',
'uses_tuple_args: tests that require backend support for tuple arguments',
'uses_tuple_returns: tests that require backend support for tuple results',
'uses_zero_dimensional_fields: tests that require backend support for zero-dimensional fields'
'uses_zero_dimensional_fields: tests that require backend support for zero-dimensional fields',
'uses_cartesian_shift: tests that use a Cartesian connectivity',
'uses_unstructured_shift: tests that use a unstructured connectivity',
'uses_scan: tests that uses scan',
'checks_specific_error: tests that rely on the backend to produce a specific error message'
]
norecursedirs = ['dist', 'build', 'cpp_backend_tests/build*', '_local/*', '.*']
testpaths = 'tests'
Expand Down
Loading

0 comments on commit 21a0e26

Please sign in to comment.