Repository: GridTools/gt4py
Pull request #1185 (2023-02-23 15:15:10)

ITIR type inference fix stencil closure location constraints
This PR introduces same changes to the ITIR type inference concerning the location and its propagation of iterators:
- Properly propagate constraints imposed on defined location of closure inputs from closure outputs.
- Uncouple current location of fencil arguments and stencil arguments, i.e. on fencil level the iterator arguments have current location `ANYWHERE` and only its other properties are propagated to the stencils of a closure (e.g. dtype, defined_location, ...)


Patch:
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index e21d755d..be2217fb 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,21 +1,35 @@
-## Description
+<!--
+Delete this comment and add a proper description of the changes contained in this PR. The text here will be used in the commit message since the approved PRs are always squash-merged. The preferred format is:
 
-Describe the content of the PR and links to related issues, bugs of features.
+- PR Title: <type>[<scope>]: <one-line-summary>
 
-## Requirements
+    <type>:
+        - build: Changes that affect the build system or external dependencies
+        - ci: Changes to our CI configuration files and scripts
+        - docs: Documentation only changes
+        - feat: A new feature
+        - fix: A bug fix
+        - perf: A code change that improves performance
+        - refactor: A code change that neither fixes a bug nor adds a feature
+        - style: Changes that do not affect the meaning of the code
+        - test: Adding missing tests or correcting existing tests
+
+    <scope>: cartesian | eve | next | storage
+    # ONLY if changes are limited to a specific subsytem
 
-Before submitting this PR, please make sure:
+- PR Description:
 
-- [ ] You have run the code checks, tests and documentation build successfully
-- [ ] All fixes and all new functionality are tested and documentation is up to date
-- [ ] You looked at the [review checklist](https://github.com/GridTools/gt4py/blob/main/REVIEW_CHECKLIST.txt)
+    Description of the main changes with links to appropriate issues/documents/references/...
+-->
 
-Additionally, if this PR contains code authored by new contributors:
+## Description
+
+## Requirements
 
-- [ ] All the authors are covered by a valid contributor assignment agreement,
-signed by the employer if needed, provided to ETH Zurich
-- [ ] The names of all the new contributors have been added to an updated
-version of the AUTHORS.rst file included in the PR
- 
+- [ ] All fixes and/or new features come with corresponding tests.
+- [ ] Important design decisions have been documented in the approriate ADR inside the [docs/development/ADRs/](docs/development/ADRs/Index.md) folder.
 
+If this PR contains code authored by new contributors please make sure:
 
+- [ ] All the authors are covered by a valid contributor assignment agreement provided to ETH Zurich and signed by the employer if needed.
+- [ ] The PR contains an updated version of the `AUTHORS.md` file adding the names of all the new contributors.
diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml
index 44ddd824..ff0d92a0 100644
--- a/.github/workflows/code-quality.yml
+++ b/.github/workflows/code-quality.yml
@@ -19,7 +19,7 @@ jobs:
         python-version: "3.10"
         cache: 'pip'
         cache-dependency-path: |
-          **/setup.cfg
+          **/pyproject.toml
           **/constraints.txt
           **/requirements-dev.txt
     - uses: pre-commit/action@v3.0.0
diff --git a/.github/workflows/daily-ci.yml b/.github/workflows/daily-ci.yml
index a05200ea..c3a204ab 100644
--- a/.github/workflows/daily-ci.yml
+++ b/.github/workflows/daily-ci.yml
@@ -45,7 +45,7 @@ jobs:
         python-version: ${{ matrix.python-version }}
         cache: 'pip'
         cache-dependency-path: |
-          **/setup.cfg
+          **/pyproject.toml
           **/constraints.txt
           **/requirements-dev.txt
     - name: Install tox
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a424317a..fecc6243 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,7 +1,6 @@
-#
-# This file contains 'cog' markers (https://nedbatchelder.com/code/cog/) 
+# This file contains 'cog' snippets (https://nedbatchelder.com/code/cog/) 
 # to keep version numbers in sync with 'constraints.txt'
-#
+
 default_language_version:
   python: python3.10
 repos:
@@ -24,7 +23,7 @@ repos:
   rev: v3.0.0-alpha.4
   hooks:
   - id: prettier
-    types_or: [json]
+    types_or: [markdown, json]
 
 - repo: https://github.com/Lucas-C/pre-commit-hooks
   rev: v1.1.9
@@ -93,8 +92,9 @@ repos:
     ##[[[cog
     ## import re
     ## constraints = open("constraints.txt").read()
-    ## for pkg in ["darglint", "flake8-bugbear", "flake8-builtins", "flake8-debugger", "flake8-docstrings", "flake8-eradicate", "flake8-mutable", "pygments"]:
-    ## 	print(f"- {pkg}==" + str(re.search(f'\n{pkg}==([0-9\.]*)', constraints)[1]))
+    ## for pkg in ["darglint", "flake8-bugbear", "flake8-builtins", "flake8-debugger", "flake8-docstrings",
+    ##             "flake8-eradicate", "flake8-mutable", "flake8-pyproject", "pygments"]:
+    ##     print(f"- {pkg}==" + str(re.search(f'\n{pkg}==([0-9\.]*)', constraints)[1]))
     ##]]]
     - darglint==1.8.1
     - flake8-bugbear==23.2.13
@@ -103,6 +103,7 @@ repos:
     - flake8-docstrings==1.7.0
     - flake8-eradicate==1.4.0
     - flake8-mutable==1.2.0
+    - flake8-pyproject==1.2.2
     - pygments==2.14.0
     ##[[[end]]]
     # - flake8-rst-docstrings  # Disabled for now due to random false positives
@@ -146,13 +147,12 @@ repos:
   - id: mypy
     additional_dependencies:  # versions from constraints.txt
     ##[[[cog
-    ## import configparser, re
+    ## import re, tomli
     ## constraints = open("constraints.txt").read()
-    ## config = configparser.ConfigParser(); config.read('setup.cfg')
-    ## requires = config['options']['install_requires'].splitlines()
-    ## packages = [re.match('^([\w-][\w\d-]*)', r)[1] for r in requires if r.strip()]
+    ## project = tomli.loads(open("pyproject.toml").read())
+    ## packages = [re.match('^([\w-][\w\d-]*)', r)[1] for r in project["project"]["dependencies"] if r.strip()]
     ## for pkg in packages:
-    ## 	print(f"- {pkg}==" + str(re.search(f'\n{pkg}==([0-9\.]*)', constraints)[1]))
+    ##     print(f"- {pkg}==" + str(re.search(f'\n{pkg}==([0-9\.]*)', constraints)[1]))
     ##]]]
     - astunparse==1.6.3
     - attrs==22.2.0
@@ -166,6 +166,7 @@ repos:
     - devtools==0.10.0
     - frozendict==2.3.5
     - gridtools-cpp==2.2.3
+    - importlib-resources==5.12.0
     - jinja2==3.1.2
     - lark==1.1.5
     - mako==1.2.4
@@ -175,7 +176,7 @@ repos:
     - packaging==23.0
     - pybind11==2.10.3
     - scipy==1.10.1
-    - setuptools==67.2.0
+    - setuptools==67.5.1
     - tabulate==0.9.0
     - toolz==0.12.0
     - typing-extensions==4.5.0
diff --git a/AUTHORS.md b/AUTHORS.md
new file mode 100644
index 00000000..9363948b
--- /dev/null
+++ b/AUTHORS.md
@@ -0,0 +1,31 @@
+# Authors
+
+<!-- List format (alphabetical order):  Surname, Name. Employer/Affiliation -->
+
+- Benedicic, Lucas. ETH Zurich - CSCS
+- Cruz, Felipe. ETH Zurich - CSCS
+- Dahm, Johann. Allen Institute for AI
+- Davis, Eddie. Allen Institute for AI
+- Deconinck, Florian. SSAI/NASA-GSFC
+- Ehrengruber, Till. ETH Zurich - CSCS
+- Farabullini, Nicoletta. ETH Zurich - C2SM
+- George, Rhea. Allen Institute for AI
+- González Paredes, Enrique. ETH Zurich - CSCS
+- Gopal, Abishek. ETH Zurich - EXCLAIM
+- Groner, Linus. ETH Zurich - CSCS
+- Häuselmann, Rico. ETH Zurich - CSCS
+- Kardos, Péter. ETH Zurich - EXCLAIM
+- Kellerhals, Samuel. ETH Zurich - EXCLAIM
+- Luz, Magdalena. ETH Zurich - EXCLAIM
+- Madonna, Alberto. ETH Zurich - CSCS
+- Mariotti, Kean. ETH Zurich - CSCS
+- Müller, Christoph. MeteoSwiss
+- Osuna, Carlos. MeteoSwiss
+- Röthlin, Matthias. MeteoSwiss
+- Serafini, Giacomo. MeteoSwiss
+- Szenes, Kalman. ETH Zurich - CSCS
+- Thaler, Felix. ETH Zurich - CSCS
+- Ubbiali, Stefano. ETH Zurich
+- Vogt, Hannes. ETH Zurich - CSCS
+- Weber, Benjamin. MeteoSwiss
+- Wicky, Tobias. Allen Institute for AI
diff --git a/AUTHORS.rst b/AUTHORS.rst
deleted file mode 100644
index 10bac07c..00000000
--- a/AUTHORS.rst
+++ /dev/null
@@ -1,33 +0,0 @@
-=======
-Authors
-=======
-
-.. List format (alphabetical order):  Surname, Name. Employer/Affiliation
-
-* Benedicic, Lucas. ETH Zurich - CSCS
-* Cruz, Felipe. ETH Zurich - CSCS
-* Dahm, Johann. Allen Institute for AI
-* Davis, Eddie. Allen Institute for AI
-* Deconinck, Florian. SSAI/NASA-GSFC
-* Ehrengruber, Till. ETH Zurich - CSCS
-* Farabullini, Nicoletta. ETH Zurich - C2SM
-* George, Rhea. Allen Institute for AI
-* González Paredes, Enrique. ETH Zurich - CSCS
-* Gopal, Abishek. ETH Zurich - EXCLAIM
-* Groner, Linus. ETH Zurich - CSCS
-* Häuselmann, Rico. ETH Zurich - CSCS
-* Kardos, Péter. ETH Zurich - EXCLAIM
-* Kellerhals, Samuel. ETH Zurich - EXCLAIM
-* Luz, Magdalena. ETH Zurich - EXCLAIM
-* Madonna, Alberto. ETH Zurich - CSCS
-* Mariotti, Kean. ETH Zurich - CSCS
-* Müller, Christoph. MeteoSwiss
-* Osuna, Carlos. MeteoSwiss
-* Röthlin, Matthias. MeteoSwiss
-* Serafini, Giacomo. MeteoSwiss
-* Szenes, Kalman. ETH Zurich - CSCS
-* Thaler, Felix. ETH Zurich - CSCS
-* Ubbiali, Stefano. ETH Zurich
-* Vogt, Hannes. ETH Zurich - CSCS
-* Weber, Benjamin. MeteoSwiss
-* Wicky, Tobias. Allen Institute for AI
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9da5f884..519f7ff1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,21 +4,22 @@ Notable changes to this project will be documented in this file. The format is b
 
 ## [1.0.1] - 2023-02-20
 
-First version including the experimental `gt4py.next` aka *Declarative GT4Py*. The `gt4py.next` package is excluded from semantic versioning.
+First version including the experimental `gt4py.next` aka _Declarative GT4Py_. The `gt4py.next` package is excluded from semantic versioning.
 
 ### Cartesian
 
 - Parametrized dtype: see option 4 of [the gtscript concept workshop](https://github.com/GridTools/concepts/blob/master/collaboration/gtscript-workshop/GTScript-Syntax-Discussion.md#gtscript-syntax-discussed-issues-20200829])
 
-
 ## [1.0.0] - 2022-12-21
 
 ### Added
+
 - Remove the `Storage` classes and implement the new _"No Storage"_ concept.
 - Support interfaces for calling stencils with arbitrary buffer objects (for details see [docs/arrays.rst](docs/gt4py/arrays.rst) and [PR #868](https://github.com/GridTools/gt4py/pull/868)).
 - Updated documentation, logo and license headers.
 
 ### Changed
+
 - Important `gt4py` package reorganization and modularization.
 - Moved most of existing functionality into `gt4py.cartesian`.
 - Moved `gtc` package into `gt4py.cartesian`.
diff --git a/CODING_GUIDELINES.md b/CODING_GUIDELINES.md
new file mode 100644
index 00000000..c4b9c91a
--- /dev/null
+++ b/CODING_GUIDELINES.md
@@ -0,0 +1,123 @@
+# Coding Guidelines
+
+## Software design
+
+Designing a clean architecture for a software product is a difficult task and developers can easily overlook it under pressure or when they are eager to deliver exciting new features. However, choosing the quick solution instead of a clean one will likely lead to a large amount of additional work in the future (_technical debt_).
+
+To keep technical debt at acceptable levels, design and implementation of new features should follow best practices:
+
+1. Make sure your code is always covered by automatic testing to ensure its correctness. Where unit tests are impractical, use integration tests.
+2. Adhere to the [SOLID](https://en.wikipedia.org/wiki/SOLID) principles of software design.
+3. Do not reinvent the wheel: if someone solved your problem within the project or in a third party library, consider using their solution or extending it before writing a new component for the same purpose.
+4. _You ain't gonna need it_ ([YAGNI](https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it)): do not design solutions for problems that might come up in the future, as chances are that you will never need that code. Focus on current problems and prepare for future requirements by writing clean code.
+5. _Do not repeat yourself_ ([DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)): if you are writing the same code snippet in several places, extract it into a function.
+6. Use meaningful names: the purpose of an object should be clear from its name. Usually, class names are nouns and function names are verbs.
+
+Remember that important design decisions should be properly documented for future reference and to share the knowledge with other developers. We decided to use lightweight _Architecture Decision Records_ (ADRs) for this purpose. The full list of ADRs and documentation for writing new ones can be found in [docs/functional/architecture/Index.md](docs/functional/architecture/Index.md).
+
+## Code Style
+
+We follow the [Google Python Style Guide][google-style-guide] with a few minor changes (mentioned below). Since the best way to remember something is to understand the reasons behind it, make sure you go through the style guide at least once, paying special attention to the discussions in the _Pros_, _Cons_, and _Decision_ subsections.
+
+We deviate from the [Google Python Style Guide][google-style-guide] only in the following points:
+
+- We use [`flake8`][flake8] with some plugins instead of [`pylint`][pylint].
+- We use [`black`][black] and [`isort`][isort] for source code and imports formatting, which may work differently than indicated by the guidelines in section [_3. Python Style Rules_](https://google.github.io/styleguide/pyguide.html#3-python-style-rules). For example, maximum line length is set to 100 instead of 79 (although docstring lines should still be limited to 79).
+- According to subsection [_2.19 Power Features_](https://google.github.io/styleguide/pyguide.html#219-power-features), direct use of _power features_ (e.g. custom metaclasses, import hacks, reflection) should be avoided, but standard library classes that internally use these power features are accepted. Following the same spirit, we allow the use of power features in infrastructure code with similar functionality and scope as the Python standard library.
+- According to subsection [_3.19.12 Imports For Typing_](https://google.github.io/styleguide/pyguide.html#31912-imports-for-typing), symbols from `typing` and `collections.abc` modules used in type annotations _"can be imported directly to keep common annotations concise and match standard typing practices"_. Following the same spirit, we allow symbols to be imported directly from third-party or internal modules when they only contain a collection of frequently used typying definitions.
+
+### Common questions
+
+- `pass` vs `...` (`Ellipsis`)
+
+  `pass` is the _no-op_ statement in Python and `...` is a literal value (called _Ellipsis_) introduced for slicing collections of unknown number of dimensions. Although they are very different in nature, both of them are used in places where a statement is required purely for syntactic reasons, and there is not yet a clear standard practice in the community about when to use one or the other. We decided to align with the common pattern of using `...` in the body of empty function definitions working as placeholders for actual implementations defined somewhere else (e.g. type stubs, abstract methods and methods appearing in `Protocol` classes) and `pass` in any other place where its usage is mixed with actual statements.
+
+  ```python
+  # Correct use of `...` as the empty body of an abstract method
+  class AbstractFoo:
+     @abstractmethod
+     def bar(self) -> Bar:
+        ...
+
+  # Correct use of `pass` when mixed with other statements
+  try:
+     resource.load(id=42)
+  except ResourceException:
+     pass
+  ```
+
+### 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).
+
+Sphinx supports the [reStructuredText][sphinx-rest] (reST) markup language for defining additional formatting options in the generated documentation, however section [_3.8 Comments and Docstrings_](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) of the Google Python Style Guide does not specify how to use markups in docstrings. As a result, we decided to forbid reST markup in docstrings, except for the following cases:
+
+- Cross-referencing other objects using Sphinx text roles for the [Python domain](https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#the-python-domain) (as explained [here](https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#python-roles)).
+- Very basic formatting markup to improve _readability_ of the generated documentation without obscuring the source docstring (e.g. ` ``literal`` ` strings, bulleted lists).
+
+We highly encourage the [doctest][doctest] format for code examples in docstrings. In fact, doctest runs code examples and makes sure they are in sync with the codebase.
+
+### Module structure
+
+In general, you should structure new Python modules in the following way:
+
+1. _shebang_ line: `#! /usr/bin/env python3` (only for **executable scripts**!).
+2. License header (see `LICENSE_HEADER.txt`).
+3. Module docstring.
+4. Imports, alphabetically ordered within each block (fixed automatically by `isort`):
+   1. Block of imports from the standard library.
+   2. Block of imports from general third party libraries using standard shortcuts when customary (e.g. `numpy as np`).
+   3. Block of imports from specific modules of the project.
+5. Definition of exported symbols (optional, mainly for re-exporting symbols from other modules):
+
+```python
+__all__ = ["func_a", "CONST_B"]
+```
+
+6. Public constants and typing definitions.
+7. Module contents organized in a convenient way for understanding how the pieces of code fit together, usually defining functions before classes.
+
+Try to keep sections and items logically ordered, add section separator comments to make section boundaries explicit when needed. If there is not a single evident logical order, pick the order you consider best or use alphabetical order.
+
+Consider configuration files as another type of source code and apply the same criteria, using comments when possible for better readability.
+
+### Ignoring QA errors
+
+You may occasionally need to disable checks from _quality assurance_ (QA) tools (e.g. linters, type checkers, etc.) on specific lines as some tool might not be able to fully understand why a certain piece of code is needed. This is usually done with special comments, e.g. `# type: ignore`. However, you should **only** ignore QA errors when you fully understand their source and rewriting your code to pass QA checks would make it less readable. Additionally, you should add a brief comment for future reference, e.g.:
+
+```python
+f = lambda: 'empty'  # noqa: E731  # assign lambda expression for testing
+```
+
+## Testing
+
+Testing components is a critical part of a software development project. We follow standard practices in software development and write unit, integration, and regression tests. Note that even though [doctests][doctest] are great for documentation purposes, they lack many features and are difficult to debug. Hence, they should not be used as replacement for proper unit tests except in trivial cases.
+
+TODO: add missing test conventions.
+
+<!--
+TODO: add test conventions:
+TODO:    - to organize tests inside the `tests/` folder
+TODO:    - to name tests
+TODO:    - to use pytest features (fixtures, markers, etc.)
+TODO:    - to generate mock objects and data for tests (e.g. pytest-factoryboy, pytest-cases)
+TODO:    - to use pytest plugins
+
+Links with plugins:
+https://towardsdatascience.com/pytest-plugins-to-love-%EF%B8%8F-9c71635fbe22
+https://testandcode.com/116
+-->
+
+<!-- Reference links -->
+
+[black]: https://black.readthedocs.io/en/stable/
+[doctest]: https://docs.python.org/3/library/doctest.html
+[flake8]: https://flake8.pycqa.org/
+[google-style-guide]: https://google.github.io/styleguide/pyguide.html
+[isort]: https://pycqa.github.io/isort/
+[pre-commit]: https://pre-commit.com/
+[pylint]: https://pylint.pycqa.org/
+[sphinx]: https://www.sphinx-doc.org
+[sphinx-autodoc]: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html
+[sphinx-napoleon]: https://sphinxcontrib-napoleon.readthedocs.io/en/latest/index.html#
+[sphinx-rest]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
diff --git a/CODING_GUIDELINES.rst b/CODING_GUIDELINES.rst
deleted file mode 100644
index d0bb15d3..00000000
--- a/CODING_GUIDELINES.rst
+++ /dev/null
@@ -1,89 +0,0 @@
-==================
-Coding Guidelines
-==================
-
-Code design
------------
-Before accepting changes we assess the design according to the following guidelines
-
-0. Look at each piece of code and ask yourself: Would I point to this in a job
-   interview as an example of well crafted code according to my best abilities?
-
-1. If it isn't tested and verified, it isn't scientific. This is a scientific
-   software library.
-
-2. Separate concerns: any unit of code should have one concern and one only.
-   The implementation details of any unit of code should not be known to any
-   other unit of code.
-
-3. Do not repeat yourself (DRY):
-   `https://en.wikipedia.org/wiki/Don%27t_repeat_yourself`_. Always check if
-   functionality exists elsewhere before implementing. Also recognize when
-   multiple specific implementations of an algorithm can be replaced with a
-   single generic one.
-
-4. Do not reinvent the wheel: Instead of writing a framework to solve a problem
-   consider existing solutions.
-
-5. Names should state the intent of code objects. If the intent of a code
-   expression **might** not be immediately obvious it deserves a name which
-   makes it obvious.
-
-Docstrings
-----------
-In general we consider that a well-named simple function with type annotations
-does not require a docstring.  When a long-form docstring is appropriate,
-use `NumPy format <https://developer.lsst.io/python/numpydoc.html>`__. Prefer
-python type annotations over describing parameter / return types in the
-docstring.
-
-- Consider new public classes / functions: they should have a docstring if
- + Their purpose is not obvious from the name (also consider renaming)
- + Their body is complex
- + Someone might want to use them interactively (from a shell or notebook)
-
-- Check existing docstrings: do they need to be expanded or updated?
-
-- Check long-form docstrings: do they use the NumPy format?
- + Do they duplicate type annotations?
-
-Code style
-----------
-In general, `PEP8 <https://www.python.org/dev/peps/pep-0008/>`__ is a good
-starting point for coding style and should be applied where not superseeded
-by auotmatic checks or below guidelines.
-
-A selection of **general** recommendations from PEP8:
-
-- prefer absolute imports (``from x import y``) over relative imports (``from
-  .x import y``)
-
-- prefer ``from a_module import AClass`` over ``import a_module``
-
-- use ``X is None`` and ``Y is not None`` to compare to ``None``
-
-- use ``set`` for unodered collections
-
-- use iterators and generators to iterate over large data sets
-
-- block comments should refer to the code following them and should be indented
-  to the same level
-
-Module structure
-++++++++++++++++
-Python modules should be structured in the following order:
-
-1. Shebang line, ``#! /usr/bin/env python`` (only for executable scripts)
-
-2. License header (``LICENSE_HEADER.txt``) and module-level comments
-
-3. Module-level docstring
-
-4. ``__all__ = [...]`` statement, if present
-
-5. Imports (sorting and grouping automated by pre-commit hook)
-
-6. Private module variables, functions and classes (names start with
-   underscore)
-
-7. Public module variables, functions and classes
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..46796477
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,246 @@
+# Contributing
+
+Contributions to GT4Py are welcome and greatly appreciated. Proper credit will be given to contributors by adding their names to the [AUTHORS.md](AUTHORS.md) file. Note that [ETH Zurich](https://ethz.ch/en.html) is the owner of the GridTools project and the GT4Py library, therefore external contributors must sign a contributor assignment agreement.
+
+## Types of Contributions
+
+### Report Bugs
+
+Report bugs at [https://github.com/gridtools/gt4py/issues](https://github.com/gridtools/gt4py/issues).
+
+If you are reporting a bug, please include:
+
+- Your operating system name and version.
+- Python interpreter version.
+- Any details about your local setup that might be helpful in troubleshooting.
+- Detailed steps to reproduce the bug.
+
+### Fix Bugs
+
+Look through the [GitHub issues](https://github.com/gridtools/gt4py/issues) for bugs. Anything tagged with `bug` and `help wanted` is open to whomever wants to implement it.
+
+### Implement Features
+
+Look through the [GitHub issues](https://github.com/gridtools/gt4py/issues) for features. Anything tagged with `enhancement` and `help wanted` is open to whomever wants to implement it.
+
+### Write Documentation
+
+GT4Py could always use more documentation, whether as part of the official GT4Py docs, in docstrings, or even on the web in blog posts and articles.
+
+### Submit Feedback
+
+The best way to send feedback is to file an issue at [https://github.com/gridtools/gt4py/issues](https://github.com/gridtools/gt4py/issues).
+
+If you are proposing a feature:
+
+- Explain in detail how it would work.
+- Keep the scope as narrow as possible, to make it easier to implement.
+- Remember that this is an open-source project and that contributions
+  are welcome :)
+
+## Getting Started
+
+Ready to start contributing? We use a [fork and pull request](https://www.atlassian.com/git/tutorials/comparing-workflows/forking-workflow) workflow for contributions. Pull requests need to pass all the automated checks as well as a review before they can be merged. To set up properly your local development environment follow these steps:
+
+1. Fork the [GT4Py](https://github.com/gridtools/gt4py) repo on GitHub.
+
+2. Clone your fork locally and check out the relevant branch:
+
+   ```bash
+   $ git clone git@github.com:your_name_here/gt4py.git
+   $ cd gt4py
+   $ git checkout main
+   ```
+
+3. Follow instructions in the [README.md](README.md) file to set up an environment for local development. For example:
+
+   ```bash
+   $ tox --devenv .venv
+   $ source .venv/bin/activate
+   ```
+
+4. Create a branch for local development:
+
+   ```bash
+   $ git checkout -b name-of-your-bugfix-or-feature
+   ```
+
+   Now you can make your changes locally. Make sure you follow the project code style documented in [CODING_GUIDELINES.md](CODING_GUIDELINES.md).
+
+5. When you're done making changes, check that your code complies with the project code style and other quality assurance (QA) practices using `pre-commit`. Additionally, make sure that unit and regression tests pass for all supported Python versions by running `tox`:
+
+   ```bash
+   $ pre-commit run
+   $ tox
+   ```
+
+   Read [Testing](#testing) section below for further details.
+
+6. Commit your changes and push your branch to GitHub:
+
+   ```bash
+    $ git add .
+    $ git commit -m "Your detailed description of your changes."
+    $ git push origin name-of-your-bugfix-or-feature
+   ```
+
+7. Submit a pull request (PR) on [GT4Py's GitHub page](https://github.com/gridtools/gt4py).
+
+## Testing
+
+### Quality Assurance
+
+We use [pre-commit](https://pre-commit.com/) to run several auto-formatting and linting tools. You should always execute it locally before opening a pull request. `pre-commit` can be installed as a _git hook_ to automatically check the staged changes before committing:
+
+```bash
+# Install pre-commit as a git hook and initialized all the configured tools
+pre-commit install --install-hooks
+```
+
+Alternatively, it can be executed on demand from the command line:
+
+```bash
+# Check only the staged changes
+pre-commit run
+
+# Check all the files in the repository: -a / --all-files
+pre-commit run -a
+
+# Run only some of the tools (e.g. flake8)
+pre-commit run -a flake8
+```
+
+### Unit and Regression Tests
+
+In the GT4Py project we use the [pytest](https://pytest.org/) framework for testing our code. `pytest` comes with a very convenient CLI tool to run tests. For example:
+
+```bash
+# Run tests inside `path/to/test/folder`
+pytest path/to/test/folder
+
+# Run tests stopping immediately on first error: -x / --exitfirst
+pytest -x tests/
+
+# Run tests matching the pattern: -k pattern (supports boolean operators)
+pytest -k pattern tests/
+
+# Run tests in parallel: -n NUM_OF_PROCS (or `auto`)
+pytest -n auto tests/
+
+# Run only tests that failed last time: --lf / --last-failed
+pytest --lf tests/
+
+# Run all the tests starting with the tests that failed last time:
+# --ff / --failed-first
+pytest --ff tests/
+
+# Run tests with more informative output:
+#   -v / --verbose          - increase verbosity
+#   -l / --showlocalsflag   - show locals in tracebacks
+#   -s                      - show tests outputs to stdout
+pytest -v -l -s tests/
+```
+
+Check `pytest` documentation (`pytest --help`) for all the options to select and execute tests.
+
+We recommended you to use `tox` for most development-related tasks, like running the complete test suite in different environments. `tox` runs the package installation script in properly isolated environments to run tests (or other tasks) in a reproducible way. A simple way to start with tox could be:
+
+```bash
+# List all the available task environments
+tox list
+
+# Run a specific task environment
+tox run -e cartesian-py38-internal-cpu
+```
+
+Check `tox` documentation (`tox --help`) for the complete reference.
+
+<!--
+TODO: add test coverage instructions
+Additionally, `tox` is configured to generate HTML test coverage reports in `tests/_reports/coverage_html/` at the end. -->
+
+## Pull Requests (PRs) and Merge Guidelines
+
+Before submitting a pull request, check that it meets the following criteria:
+
+1. The pull request should include tests.
+2. If the pull request adds functionality, it should be documented both in the code docstrings and in the official documentation.
+3. If the pull request contains important design changes, it should contain a new ADR documenting the rationale behind the final decision.
+4. The pull request should have a proper description of its intent and the main changes in the code. In general this description should be used as commit message if the pull request is approved (check point **6.** below).
+5. If the pull request contains code authored by first-time contributors, check they are covered by a contributor agreement and they have been added to [AUTHORS.md](AUTHORS.md) file.
+6. Pick one reviewer and try to contact them directly to let them know about the pull request. If there is no feedback in 24h/48h try to contact them again or pick another reviewer.
+7. Once the pull request has been approved, it should be squash-merged as soon as possible with a meaningful description of the changes. Although it is optional, we encourage the use of the [Conventional Commits][conventional-commits] specification for writing informative and automation-friendly commit messages (_commit types: `build`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `feature`, `style`, `test`_).
+
+## Releasing Process
+
+This section documents the process of releasing new versions of GT4Py and it is only useful to core members of the development team.
+
+Currently, GT4Py releases are published as commit tags in the main GitHub repository (although they will be soon available in TestPyPi and PyPI). To create a new release you should:
+
+1. Make sure all the expected changes (new features, bug fixes, documentation changes, etc.) are already included in the main public branch.
+
+2. Use `bump2version` to update the version number.
+
+   ```bash
+   $ bump2version minor # or patch
+   ```
+
+3. Update the [CHANGELOG.md](CHANGELOG.md) file to document the changes included in the new release. This process can be fully or partially automated if commit messages follow the [Conventional Commits][conventional-commits] convention as suggested in the section about [Pull Request and Merge Guidelines](#pull-request-and-merge-guidelines).
+
+4. Commit the changes with the following commit message:
+
+   ```bash
+   $ git commit -m 'Releasing 0.{M}.{m}.{p} version.'
+   ```
+
+5. Add a new lightweight tag like: `v0.{M}.{m}.{p}`
+
+   ```bash
+   $ git tag v0.{M}.{m}.{p}
+   ```
+
+6. Push the new commit and tag directly to the upstream repo:
+
+   ```bash
+   $ git push upstream
+   $ git push upstream v0.{M}.{m}.{p}
+   ```
+
+7. Go to the GitHub website and verify that the new tag has been successfully created.
+
+## Tools
+
+As mentioned above, we use several tools to help us write high-quality code. New tools could be added in the future, especially if they do not add a large overhead to our workflow and they bring extra benefits to keep our codebase in shape. The most important ones which we currently rely on are:
+
+- [Black][black] for autoformatting source code.
+- [isort][isort] for autoformatting import statements.
+- [Flake8][flake8] for style enforcement and code linting.
+- [pre-commit][pre-commit] for automating the execution of QA tools.
+- [pytest][pytest] for writing readable tests, extended with:
+  - [Coverage.py][coverage] and [pytest-cov][pytest-cov] for test coverage reports.
+  - [pytest-xdist][pytest-xdist] for running tests in parallel.
+- [tox][tox] for testing and task automation with different environments.
+- [sphinx][sphinx] for generating documentation, extended with:
+  - [sphinx-autodoc][sphinx-autodoc] and [sphinx-napoleon][sphinx-napoleon] for extracting API documentation from docstrings.
+  - [jupytext][jupytext] for writing new user documentation with code examples.
+
+<!-- Reference links -->
+
+[black]: https://black.readthedocs.io/en/stable/
+[commitizen]: https://commitizen-tools.github.io/commitizen/
+[conventional-commits]: https://www.conventionalcommits.org/en/v1.0.0/#summary
+[coverage]: https://coverage.readthedocs.io/
+[flake8]: https://flake8.pycqa.org/
+[google-style-guide]: https://google.github.io/styleguide/pyguide.html
+[isort]: https://pycqa.github.io/isort/
+[jupytext]: https://jupytext.readthedocs.io/
+[pre-commit]: https://pre-commit.com/
+[pylint]: https://pylint.pycqa.org/
+[pytest]: https://docs.pytest.org/
+[pytest-cov]: https://pypi.org/project/pytest-cov/
+[pytest-xdist]: https://pytest-xdist.readthedocs.io/en/latest/
+[sphinx]: https://www.sphinx-doc.org
+[sphinx-autodoc]: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html
+[sphinx-napoleon]: https://sphinxcontrib-napoleon.readthedocs.io/en/latest/index.html#
+[sphinx-rest]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
+[tox]: https://tox.wiki/en/latest/#
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
deleted file mode 100644
index be3b46d7..00000000
--- a/CONTRIBUTING.rst
+++ /dev/null
@@ -1,75 +0,0 @@
-============
-Contributing
-============
-
-Contributions to GT4Py are welcome, and they are greatly appreciated, whether
-they be a well-described issue or contributions to the GT4Py code.
-
-Proper credit will be given to contributors by adding their names to the
-AUTHORS_ file. ETH Zurich owns the GT4Py library, and external
-contributors must sign a contributor assignment agreement.
-
-Bug reports and feature requests
---------------------------------
-
-We always welcome both, don't hesitate opening an issue directly in the `GitHub
-repository <https://github.com/GridTools/gt4py/issues>`_. If possible please
-add a minimal snippet (or jupyter notebook) of code that illustrates the
-problem or feature. The clearer the description the quicker we can get back to
-you!
-
-Contributing code
------------------
-
-We use a [fork and pull request](https://www.atlassian.com/git/tutorials/comparing-workflows/forking-workflow) workflow for code contributions.
-Pull requests need to pass all the automated checks as well as a review before
-they can be merged. You should set up your development environment as
-recommended for developers in the README_, and run the automated checks
-locally before creating your pull request.
-
-The review will look at difficult to automate things, for example the first
-time you contribute you will be asked to sign a legal agreement and to add your
-name to AUTHORS_. More things the reviewer will consider are listed below in
-`Review Process`_
-
-The reviewer will also be replying to any questions you might have about
-requested changes and willing to give advice on how to implement some of
-the requests.
-
-The full guidelines are explained in CODING_GUIDELINES_.
-
-
-.. _README: https://github.com/GridTools/gt4py/blob/main/README.rst
-.. _AUTHORS: https://github.com/GridTools/gt4py/blob/main/AUTHORS.rst
-.. _CODING_GUIDELINES: https://github.com/GridTools/gt4py/blob/main/CODING_GUIDELINES.rst
-
-Review Process
---------------
-
-Very large pull requests may receive only a partial review at first.
-This is to be able to respond within a reasonably short time frame while
-still taking time to fairly assess all the changes.
-
-We use CODING_GUIDELINES_ as a reference for reviewing contributions from
-a code style and design standpoint.
-
-Further points to be considered are:
-
-- Check all authors are covered by a contributor agreement and added to AUTHORS_
-
-- All added or changed functionality must have a test that fails before and
-  passes after changes and documents the correct behaviour.
-
-- The documentation (docstrings as well as ``/docs`` must be kept in sync.
-
-- New files are not allowed to skip ``pre-commit`` checks. Look for additions
-  to exclude lists in  pre-commit-config_. The only exception is that
-  test modules might skip ``mypy`` checks.
-
-- If a file currently excluded from checks in pre-commit-config_  is changed
-  substantially, the reviewer may request dropping it from the exclude list and
-  cleaning up the remaining issues.  The remaining issues must be small
-  compared to the size of the PR.
-
-
-.. _pre-commit-config: https://github.com/GridTools/gt4py/blob/main/.pre-commit-config.yaml
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 4c1f37c7..00000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,5 +0,0 @@
-include *.md
-include *.rst
-include *.toml
-include *.txt
-graft src/gt4py/cartesian/backend/templates
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..7c313375
--- /dev/null
+++ b/README.md
@@ -0,0 +1,123 @@
+[![logo](https://raw.githubusercontent.com/GridTools/gt4py/gh-pages/v1.0/_static/logo.svg)](https://GridTools.github.io/gt4py)
+
+![license](https://img.shields.io/github/license/GridTools/gt4py)
+[![slack](https://img.shields.io/badge/slack-join-orange?logo=slack&labelColor=3a3a3a)](https://join.slack.com/t/gridtools/shared_invite/zt-1mceuj747-59swuowC3MKAuCFyNAnc1g)
+
+![test-cartesian](https://github.com/GridTools/gt4py/actions/workflows/test-cartesian.yml/badge.svg?branch=main)
+![test-next](https://github.com/GridTools/gt4py/actions/workflows/test-next.yml/badge.svg?branch=main)
+![test-storage](https://github.com/GridTools/gt4py/actions/workflows/test-storage.yml/badge.svg?branch=main)
+![test-eve](https://github.com/GridTools/gt4py/actions/workflows/test-eve.yml/badge.svg?branch=main)
+![qa](https://github.com/GridTools/gt4py/actions/workflows/code-quality.yml/badge.svg?branch=main)
+
+# GT4Py: GridTools for Python
+
+GT4Py is a Python library for generating high performance implementations of stencil kernels from a high-level definition using regular Python functions. GT4Py is part of the GridTools framework, a set of libraries and utilities to develop performance portable applications in the area of weather and climate modeling.
+
+**NOTE:** The `gt4py.next` subpackage contains a new version of GT4Py which is not compatible with the current _stable_ version defined in `gt4py.cartesian`. The new version is highly experimental, it only works with unstructured meshes and it requires `python >= 3.10`.
+
+## 📃 Description
+
+GT4Py is a Python library for expressing computational motifs as found in weather and climate applications. These computations are expressed in a domain specific language (GTScript) which is translated to high-performance implementations for CPUs and GPUs.
+
+The DSL expresses computations on a 3-dimensional Cartesian grid. The horizontal axes (`I`, `J`) are always computed in parallel, while the vertical (`K`) can be iterated in sequential, forward or backward, order. Cartesian offsets are expressed relative to a center index.
+
+In addition, GT4Py provides functions to allocate arrays with memory layout suited for a particular backend.
+
+The following backends are supported:
+
+- `numpy`: Pure-Python backend
+- `gt:cpu_ifirst`: GridTools C++ CPU backend using `I`-first data ordering
+- `gt:cpu_kfirst`: GridTools C++ CPU backend using `K`-first data ordering
+- `gt:gpu`: GridTools backend for CUDA
+- `cuda`: CUDA backend minimally using utilities from GridTools
+- `dace:cpu`: Dace code-generated CPU backend
+- `dace:gpu`: Dace code-generated GPU backend
+
+## 🚜 Installation
+
+GT4Py can be installed as a regular Python package using `pip` (or any other PEP-517 frontend). As usual, we strongly recommended to create a new virtual environment to work on this project.
+
+The performance backends also require the `Boost <https://www.boost.org/>`\_\_ library, a dependency of [GridTools C++](https://github.com/GridTools/gridtools), which needs to be installed by the user.
+
+## ⚙ Configuration
+
+If GridTools or Boost are not found in the compiler's standard include path, or a custom version is desired, then a couple configuration environment variables will allow the compiler to use them:
+
+- `GT_INCLUDE_PATH`: Path to the GridTools installation.
+- `BOOST_ROOT`: Path to a boost installation.
+
+Other commonly used environment variables are:
+
+- `CUDA_ARCH`: Set the compute capability of the NVIDIA GPU if it is not detected automatically by `cupy`.
+- `CXX`: Set the C++ compiler.
+- `GT_CACHE_DIR_NAME`: Name of the compiler's cache directory (defaults to `.gt_cache`)
+- `GT_CACHE_ROOT`: Path to the compiler cache (defaults to `./`)
+
+More options and details are available in [`config.py`](https://github.com/GridTools/gt4py/blob/main/src/gt4py/cartesian/config.py>).
+
+## 📖 Documentation
+
+GT4Py uses Sphinx documentation. To build the documentation install the dependencies in `requirements-dev.txt`
+
+```bash
+pip install -r ./gt4py/requirements-dev.txt
+```
+
+and then build the docs with
+
+```bash
+cd gt4py/docs
+make html  # run 'make help' for a list of targets
+```
+
+## 🛠 Development Instructions
+
+Follow the installation instructions below to initialize a development virtual environment containing an _editable_ installation of the GT4Py package. Make sure you read the [CONTRIBUTING.md](CONTRIBUTING.md) and [CODING_GUIDELINES.md](CODING_GUIDELINES.md) documents before you start working on the project.
+
+### Recommended Installation using `tox`
+
+If [tox](https://tox.wiki/en/latest/) is already installed in your system (`tox` is available in PyPI and many other package managers), the easiest way to create a virtual environment ready for development is:
+
+```bash
+# Clone the repository
+git clone https://github.com/gridtools/gt4py.git
+cd gt4py
+
+# Create the development environment in any location (usually `.venv`)
+# selecting one of the following templates:
+#     dev-py310       -> base environment
+#     dev-py310-atlas -> base environment + atlas4py bindings
+tox devenv -e dev-py310 .venv
+
+# Finally, activate the environment
+source .venv/bin/activate
+```
+
+### Manual Installation
+
+Alternatively, a development environment can be created from scratch installing the frozen dependencies packages :
+
+```bash
+# Clone the repository
+git clone https://github.com/gridtools/gt4py.git
+cd gt4py
+
+# Create a (Python 3.10) virtual environment (usually at `.venv`)
+python3.10 -m venv .venv
+
+# Activate the virtual environment and update basic packages
+source .venv/bin/activate
+pip install --upgrade wheel setuptools pip
+
+# Install the required development tools
+pip install -r requirements-dev.txt
+# Install GT4Py project in editable mode
+pip install -e .
+
+# Optionally, install atlas4py bindings directly from the repo
+# pip install git+https://github.com/GridTools/atlas4py#egg=atlas4py
+```
+
+## ⚖️ License
+
+GT4Py is licensed under the terms of the [GPLv3](https://github.com/GridTools/gt4py/blob/main/LICENSE.txt).
diff --git a/README.rst b/README.rst
deleted file mode 100644
index ecb9c8ce..00000000
--- a/README.rst
+++ /dev/null
@@ -1,138 +0,0 @@
-|logo|
-
-.. |logo| image:: https://raw.githubusercontent.com/GridTools/gt4py/gh-pages/v1.0/_static/logo.svg
-   :alt:
-.. _logo: https://GridTools.github.io/gt4py
-
-|license| |slack|_
-
-|test-cartesian| |test-next| |test-storage| |test-eve| |qa|
-
-.. |license| image:: https://img.shields.io/github/license/GridTools/gt4py
-   :alt:
-.. |slack| image:: https://img.shields.io/badge/slack-join-orange?logo=slack&labelColor=3a3a3a
-   :alt:
-.. _slack: https://join.slack.com/t/gridtools/shared_invite/zt-1mceuj747-59swuowC3MKAuCFyNAnc1g
-
-.. |test-cartesian| image:: https://github.com/GridTools/gt4py/actions/workflows/test-cartesian.yml/badge.svg?branch=main
-   :alt:
-.. |test-next| image:: https://github.com/GridTools/gt4py/actions/workflows/test-next.yml/badge.svg?branch=main
-   :alt:
-.. |test-storage| image:: https://github.com/GridTools/gt4py/actions/workflows/test-storage.yml/badge.svg?branch=main
-   :alt:
-.. |test-eve| image:: https://github.com/GridTools/gt4py/actions/workflows/test-eve.yml/badge.svg?branch=main
-   :alt:
-.. |qa| image:: https://github.com/GridTools/gt4py/actions/workflows/code-quality.yml/badge.svg?branch=main
-   :alt:
-
-
-Python library for generating high-performance implementations of stencil kernels for weather and climate modeling from a domain-specific language (DSL).
-
-
-⚡️ Quick Start
----------------
-
-GT4Py requires Python 3.8+ and uses the standard Python packaging method, so can be installed using `pip`.
-
-It is recommended to install the package in a virtual environment. For example:
-
-.. code-block:: bash
-
-    python -m venv venv
-    source venv/bin/activate
-    # For the CUDA backends add the '[cudaXXX]' optional dependency
-    pip install gt4py[cuda11x]
-
-
-📖 Description
---------------
-
-GT4Py is a Python library for expressing computational motifs as found in weather and climate applications. These computations are expressed in a domain specific language (GTScript) which is translated to high-performance implementations for CPUs and GPUs.
-
-The DSL expresses computations on a 3-dimensional Cartesian grid. The horizontal axes (``I``, ``J``) are always computed in parallel, while the vertical (``K``) can be iterated in sequential, forward or backward, order. Cartesian offsets are expressed relative to a center index.
-
-In addition, GT4Py provides functions to allocate arrays with memory layout suited for a particular backend.
-
-The following backends are supported:
-
-- ``numpy``: Pure-Python backend
-- ``gt:cpu_ifirst``: GridTools C++ CPU backend using ``I``-first data ordering
-- ``gt:cpu_kfirst``: GridTools C++ CPU backend using ``K``-first data ordering
-- ``gt:gpu``: GridTools backend for CUDA
-- ``cuda``: CUDA backend minimally using utilities from GridTools
-- ``dace:cpu``: Dace code-generated CPU backend
-- ``dace:gpu``: Dace code-generated GPU backend
-
-🚜 Installation
----------------
-
-For testing GT4Py with the ``numpy`` backend, all dependencies are included in the ``setup.cfg`` and are automatically installed. The performance backends also require
-
-1. the `Boost <https://www.boost.org/>`__ library, a dependency of GridTools C++, which needs to be installed by the user.
-
-Options
-~~~~~~~
-
-If GridTools or Boost are not found in the compiler's standard include path, or a custom version is desired, then a couple configuration environment variables will allow the compiler to use them:
-
-- ``GT_INCLUDE_PATH``: Path to the GridTools installation.
-- ``BOOST_ROOT``: Path to a boost installation.
-
-Other commonly used environment variables are:
-
-- ``CUDA_ARCH``: Set the compute capability of the NVIDIA GPU if it is not detected automatically by ``cupy``.
-- ``CXX``: Set the C++ compiler.
-- ``GT_CACHE_DIR_NAME``: Name of the compiler's cache directory (defaults to ``.gt_cache``)
-- ``GT_CACHE_ROOT``: Path to the compiler cache (defaults to ``./``)
-
-More options and details in `config.py <https://github.com/GridTools/gt4py/blob/main/src/gt4py/cartesian/config.py>`__.
-
-
-Documentation
-~~~~~~~~~~~~~
-
-GT4Py uses Sphinx documentation. To build the documentation install the dependencies in ``requirements-dev.txt``
-
-.. code-block:: bash
-
-   pip install -r ./gt4py/requirements-dev.txt
-
-and then build the docs with
-
-.. code-block:: bash
-
-   cd gt4py/docs
-   make html  # run 'make help' for a list of targets
-
-
-Development
-~~~~~~~~~~~
-
-For developing GT4Py we recommend to clone the repository and use an *editable* installation of GT4Py:
-
-.. code-block:: bash
-
-   git clone https://github.com/gridtools/gt4py.git
-   pip install -e ./     # pip install -e ./[cudaXX] for GPU support
-   pip install -r requirements-dev.txt
-   pre-commit install-hooks
-
-Dependencies for running tests locally and for linting and formatting source are listed in `requirements-dev.txt`.
-
-There are notebooks in the `examples/ <https://github.com/GridTools/gt4py/tree/main/examples/cartesian>`__ folder that can be run using IPython notebooks on Jupyter.
-
-.. code-block:: bash
-
-   pip install jupyterlab matplotlib
-   jupyter-lab
-
-There are two options to run the unit and integration tests in ``tests/``:
-
-1. Use tox: ``pip install tox && tox -r -e py39-all-cpu``.
-2. Install the development requirements: ``pip install -r requirements-dev.txt``, then ``pytest`` can execute the tests directly.
-
-
-⚖️ License
-----------
-
-GT4Py is licensed under the terms of the `GPLv3 <https://github.com/GridTools/gt4py/blob/main/LICENSE.txt>`__.
diff --git a/constraints.txt b/constraints.txt
index 04180d34..ac8b82ef 100644
--- a/constraints.txt
+++ b/constraints.txt
@@ -2,14 +2,14 @@
 # This file is autogenerated by pip-compile with Python 3.8
 # by the following command:
 #
-#    "tox run -e requirements-py38"
+#    "tox run -e requirements-common"
 #
 aenum==3.1.11             # via dace
 alabaster==0.7.13         # via sphinx
 asttokens==2.2.1          # via devtools
 astunparse==1.6.3 ; python_version < "3.9"  # via dace, gt4py (pyproject.toml)
 attrs==22.2.0             # via flake8-bugbear, flake8-eradicate, gt4py (pyproject.toml), hypothesis, jsonschema, pytest
-babel==2.11.0             # via sphinx
+babel==2.12.1             # via sphinx
 black==23.1.0             # via gt4py (pyproject.toml)
 boltons==23.0.0           # via gt4py (pyproject.toml)
 build==0.10.0             # via pip-tools
@@ -19,14 +19,14 @@ certifi==2022.12.7        # via requests
 cffi==1.15.1              # via cryptography
 cfgv==3.3.1               # via pre-commit
 chardet==5.1.0            # via tox
-charset-normalizer==3.0.1  # via requests
+charset-normalizer==3.1.0  # via requests
 clang-format==15.0.7      # via -r requirements-dev.in, gt4py (pyproject.toml)
 click==8.1.3              # via black, flask, gt4py (pyproject.toml), pip-tools
 cmake==3.25.2             # via -r requirements-dev.in, gt4py (pyproject.toml)
 cogapp==3.3.0             # via -r requirements-dev.in
 colorama==0.4.6           # via tox
-coverage==7.2.0           # via -r requirements-dev.in, pytest-cov
-cryptography==39.0.1      # via types-paramiko, types-pyopenssl, types-redis
+coverage==7.2.1           # via -r requirements-dev.in, pytest-cov
+cryptography==39.0.2      # via types-paramiko, types-pyopenssl, types-redis
 cytoolz==0.12.1           # via gt4py (pyproject.toml)
 dace==0.14.2              # via gt4py (pyproject.toml)
 darglint==1.8.1           # via -r requirements-dev.in
@@ -35,21 +35,22 @@ devtools==0.10.0          # via -r requirements-dev.in, gt4py (pyproject.toml)
 dill==0.3.6               # via dace
 distlib==0.3.6            # via virtualenv
 docutils==0.18.1          # via restructuredtext-lint, sphinx, sphinx-rtd-theme
-eradicate==2.1.0          # via flake8-eradicate
+eradicate==2.2.0          # via flake8-eradicate
 exceptiongroup==1.1.0     # via hypothesis, pytest
 execnet==1.9.0            # via pytest-cache, pytest-xdist
 executing==1.2.0          # via devtools
 factory-boy==3.2.1        # via -r requirements-dev.in, pytest-factoryboy
-faker==17.0.0             # via factory-boy
-fastjsonschema==2.16.2    # via nbformat
+faker==17.6.0             # via factory-boy
+fastjsonschema==2.16.3    # via nbformat
 filelock==3.9.0           # via tox, virtualenv
-flake8==5.0.4             # via -r requirements-dev.in, flake8-bugbear, flake8-builtins, flake8-debugger, flake8-docstrings, flake8-eradicate, flake8-mutable, flake8-rst-docstrings
+flake8==5.0.4             # via -r requirements-dev.in, flake8-bugbear, flake8-builtins, flake8-debugger, flake8-docstrings, flake8-eradicate, flake8-mutable, flake8-pyproject, flake8-rst-docstrings
 flake8-bugbear==23.2.13   # via -r requirements-dev.in
 flake8-builtins==2.1.0    # via -r requirements-dev.in
 flake8-debugger==4.1.2    # via -r requirements-dev.in
 flake8-docstrings==1.7.0  # via -r requirements-dev.in
 flake8-eradicate==1.4.0   # via -r requirements-dev.in
 flake8-mutable==1.2.0     # via -r requirements-dev.in
+flake8-pyproject==1.2.2   # via -r requirements-dev.in
 flake8-rst-docstrings==0.3.0  # via -r requirements-dev.in
 flask==2.2.3              # via dace
 frozendict==2.3.5         # via gt4py (pyproject.toml)
@@ -59,7 +60,7 @@ identify==2.5.18          # via pre-commit
 idna==3.4                 # via requests
 imagesize==1.4.1          # via sphinx
 importlib-metadata==6.0.0  # via flask, sphinx
-importlib-resources==5.12.0  # via jsonschema
+importlib-resources==5.12.0 ; python_version < "3.9"  # via gt4py (pyproject.toml), jsonschema
 inflection==0.5.1         # via pytest-factoryboy
 iniconfig==2.0.0          # via pytest
 isort==5.12.0             # via -r requirements-dev.in
@@ -67,13 +68,13 @@ itsdangerous==2.1.2       # via flask
 jinja2==3.1.2             # via flask, gt4py (pyproject.toml), sphinx
 jsonschema==4.17.3        # via nbformat
 jupyter-core==5.2.0       # via nbformat
-jupytext==1.14.4          # via -r requirements-dev.in
+jupytext==1.14.5          # via -r requirements-dev.in
 lark==1.1.5               # via gt4py (pyproject.toml)
 mako==1.2.4               # via gt4py (pyproject.toml)
 markdown-it-py==2.2.0     # via jupytext, mdit-py-plugins
 markupsafe==2.1.2         # via jinja2, mako, werkzeug
 mccabe==0.7.0             # via flake8
-mdit-py-plugins==0.3.4    # via jupytext
+mdit-py-plugins==0.3.5    # via jupytext
 mdurl==0.1.2              # via markdown-it-py
 mpmath==1.2.1             # via sympy
 mypy==1.0.1               # via -r requirements-dev.in
@@ -84,16 +85,16 @@ ninja==1.11.1             # via gt4py (pyproject.toml)
 nodeenv==1.7.0            # via pre-commit
 numpy==1.24.2             # via dace, gt4py (pyproject.toml), scipy, types-jack-client
 ordered-set==4.1.0        # via deepdiff
-orjson==3.8.6             # via deepdiff
+orjson==3.8.7             # via deepdiff
 packaging==23.0           # via black, build, gt4py (pyproject.toml), pyproject-api, pytest, sphinx, tox
 pathspec==0.11.0          # via black
-pip-tools==6.12.2         # via -r requirements-dev.in
-pipdeptree==2.5.0         # via -r requirements-dev.in
+pip-tools==6.12.3         # via -r requirements-dev.in
+pipdeptree==2.5.2         # via -r requirements-dev.in
 pkgutil-resolve-name==1.3.10  # via jsonschema
-platformdirs==3.0.0       # via black, jupyter-core, tox, virtualenv
+platformdirs==3.1.0       # via black, jupyter-core, tox, virtualenv
 pluggy==1.0.0             # via pytest, tox
 ply==3.11                 # via dace
-pre-commit==3.1.0         # via -r requirements-dev.in
+pre-commit==3.1.1         # via -r requirements-dev.in
 psutil==5.9.4             # via -r requirements-dev.in, pytest-xdist
 pybind11==2.10.3          # via gt4py (pyproject.toml)
 pycodestyle==2.9.1        # via flake8, flake8-debugger
@@ -104,7 +105,7 @@ pygments==2.14.0          # via -r requirements-dev.in, flake8-rst-docstrings, s
 pyproject-api==1.5.0      # via tox
 pyproject-hooks==1.0.0    # via build
 pyrsistent==0.19.3        # via jsonschema
-pytest==7.2.1             # via -r requirements-dev.in, gt4py (pyproject.toml), pytest-cache, pytest-cov, pytest-factoryboy, pytest-xdist
+pytest==7.2.2             # via -r requirements-dev.in, gt4py (pyproject.toml), pytest-cache, pytest-cov, pytest-factoryboy, pytest-xdist
 pytest-cache==1.0         # via -r requirements-dev.in
 pytest-cov==4.0.0         # via -r requirements-dev.in
 pytest-factoryboy==2.5.1  # via -r requirements-dev.in
@@ -130,7 +131,7 @@ sphinxcontrib-serializinghtml==1.1.5  # via sphinx
 sympy==1.9                # via dace, gt4py (pyproject.toml)
 tabulate==0.9.0           # via gt4py (pyproject.toml)
 toml==0.10.2              # via jupytext
-tomli==2.0.1              # via black, build, coverage, mypy, pyproject-api, pyproject-hooks, pytest, tox
+tomli==2.0.1              # via -r requirements-dev.in, black, build, coverage, flake8-pyproject, mypy, pyproject-api, pyproject-hooks, pytest, tox
 toolz==0.12.0             # via cytoolz, gt4py (pyproject.toml)
 tox==4.4.6                # via -r requirements-dev.in
 traitlets==5.9.0          # via jupyter-core, nbformat
@@ -142,9 +143,9 @@ types-backports==0.1.3    # via types-all
 types-backports-abc==0.5.2  # via types-all
 types-bleach==6.0.0.1     # via types-all
 types-boto==2.49.18.6     # via types-all
-types-cachetools==5.3.0.3  # via types-all
+types-cachetools==5.3.0.4  # via types-all
 types-certifi==2021.10.8.3  # via types-all
-types-cffi==1.15.1.6      # via types-jack-client
+types-cffi==1.15.1.7      # via types-jack-client
 types-characteristic==14.3.7  # via types-all
 types-chardet==5.0.4.2    # via types-all
 types-click==7.1.8        # via types-all, types-flask
@@ -158,7 +159,7 @@ types-dateparser==1.1.4.8  # via types-all
 types-datetimerange==2.0.0.3  # via types-all
 types-decorator==5.1.8.3  # via types-all
 types-deprecated==1.2.9.1  # via types-all
-types-docopt==0.6.11.2    # via types-all
+types-docopt==0.6.11.3    # via types-all
 types-docutils==0.19.1.6  # via types-all
 types-emoji==2.1.0.2      # via types-all
 types-enum34==1.1.8       # via types-all
@@ -172,7 +173,7 @@ types-futures==3.3.8      # via types-all
 types-geoip2==3.0.0       # via types-all
 types-ipaddress==1.0.8    # via types-all, types-maxminddb
 types-itsdangerous==1.1.6  # via types-all
-types-jack-client==0.5.10.6  # via types-all
+types-jack-client==0.5.10.7  # via types-all
 types-jinja2==2.11.9      # via types-all, types-flask
 types-kazoo==0.1.3        # via types-all
 types-markdown==3.4.2.5   # via types-all
@@ -185,10 +186,10 @@ types-openssl-python==0.1.3  # via types-all
 types-orjson==3.6.2       # via types-all
 types-paramiko==3.0.0.4   # via types-all, types-pysftp
 types-pathlib2==2.3.0     # via types-all
-types-pillow==9.4.0.16    # via types-all
+types-pillow==9.4.0.17    # via types-all
 types-pkg-resources==0.1.3  # via types-all
 types-polib==1.2.0.0      # via types-all
-types-protobuf==4.21.0.7  # via types-all
+types-protobuf==4.22.0.0  # via types-all
 types-pyaudio==0.2.16.6   # via types-all
 types-pycurl==7.45.2.3    # via types-all
 types-pyfarmhash==0.3.1.1  # via types-all
@@ -198,14 +199,14 @@ types-pymysql==1.0.19.5   # via types-all
 types-pyopenssl==23.0.0.4  # via types-redis
 types-pyrfc3339==1.1.1.3  # via types-all
 types-pysftp==0.2.17.2    # via types-all
-types-python-dateutil==2.8.19.8  # via types-all, types-datetimerange
+types-python-dateutil==2.8.19.10  # via types-all, types-datetimerange
 types-python-gflags==3.1.7.2  # via types-all
 types-python-slugify==8.0.0.1  # via types-all
 types-pytz==2022.7.1.2    # via types-all, types-tzlocal
 types-pyvmomi==8.0.0.1    # via types-all
 types-pyyaml==6.0.12.8    # via types-all
-types-redis==4.5.1.3      # via types-all
-types-requests==2.28.11.14  # via types-all
+types-redis==4.5.1.4      # via types-all
+types-requests==2.28.11.15  # via types-all
 types-retry==0.9.9.2      # via types-all
 types-routes==2.5.0       # via types-all
 types-scribe==2.0.0       # via types-all
@@ -219,19 +220,19 @@ types-tornado==5.1.1      # via types-all
 types-typed-ast==1.5.8.4  # via types-all
 types-tzlocal==4.2.2.3    # via types-all
 types-ujson==5.7.0.1      # via types-all
-types-urllib3==1.26.25.7  # via types-requests
+types-urllib3==1.26.25.8  # via types-requests
 types-waitress==2.1.4.5   # via types-all
 types-werkzeug==1.0.9     # via types-all, types-flask
 types-xxhash==3.0.5.2     # via types-all
 typing-extensions==4.5.0  # via black, gt4py (pyproject.toml), mypy, pytest-factoryboy
 urllib3==1.26.14          # via requests
-virtualenv==20.19.0       # via pre-commit, tox
+virtualenv==20.20.0       # via pre-commit, tox
 websockets==10.4          # via dace
 werkzeug==2.2.3           # via flask
 wheel==0.38.4             # via astunparse, pip-tools
 xxhash==3.0.0             # via gt4py (pyproject.toml)
-zipp==3.14.0              # via importlib-metadata, importlib-resources
+zipp==3.15.0              # via importlib-metadata, importlib-resources
 
 # The following packages are considered to be unsafe in a requirements file:
-pip==23.0                 # via pip-tools
-setuptools==67.2.0        # via -r requirements-dev.in, gt4py (pyproject.toml), nodeenv, pip-tools, sphinxcontrib-jquery
+pip==23.0.1               # via pip-tools
+setuptools==67.5.1        # via -r requirements-dev.in, gt4py (pyproject.toml), nodeenv, pip-tools, sphinxcontrib-jquery
diff --git a/docs/development/ADRs/0001-Field_View_Frontend_Design.md b/docs/development/ADRs/0001-Field_View_Frontend_Design.md
index e144b604..cb04bec2 100644
--- a/docs/development/ADRs/0001-Field_View_Frontend_Design.md
+++ b/docs/development/ADRs/0001-Field_View_Frontend_Design.md
@@ -11,7 +11,6 @@ tags: [frontend]
 
 This document loosely tracks how the pieces of the Field View implementation fit together and which design decisions were deliberate and why.
 
-
 ## Guiding principles
 
 Overall we tried to avoid:
@@ -27,7 +26,7 @@ And we tried to achieve:
 
 In order to achieve that we had to compromise in some aspects like, for example, when it comes to AST / IR nodes having or not having optional attributes.
 
-We tried to avoid (1) because we have seen in previous projects that a long chain of IRs often leads to bad coupling problems, where a change on one end has to ripple through every IR and lowering along the way. 
+We tried to avoid (1) because we have seen in previous projects that a long chain of IRs often leads to bad coupling problems, where a change on one end has to ripple through every IR and lowering along the way.
 
 We tried to avoid (2) partially to avoid merge conflicts, by keeping the logic in passes we could work on them separately and mostly orthogonally. Specifically we wanted to avoid metadata logic (such as type detection rules) in validators because it decreases the visibility of said logic and increases the learning curve for new developers.
 
@@ -37,51 +36,67 @@ In the past (4) has always been treated as an afterthought and never made it int
 
 In order to help with maintainability, shallow learning curve and user experience, we decided to catch all compile time errors before lowering. That means the AST and dialect passes, as well as the parser in between, emit user-level warnings and errors, whenever possible with source location info. This also means if the lowering encounters an invalid dialect node it is not a user error. The only exception to this rule are errors in the lowering due to things not being implemented yet. These errors should be clearly marked as such.
 
-
 ## The different dialects
 
 There exist two dialects right now. The FOAST and PAST. We will in the following refer to dialect AST as the AST of the respective dialect.
 
 #### What to keep
+
 ##### Type hierarchy and type info in expressions
+
 Symbols as well as expressions must have a type in all dialects. This deviates from the Python AST because all dialects are statically typed. The types have their own hierarchy of `eve` nodes. It also allows for the type info to be associated with source locations easily -> better debugging experience.
 
 ##### Source locations
+
 All nodes that represent code must hold the location of that code.
 
 ##### Correspondence Python AST <-> dialect AST
+
 In some cases there are Python constructs which would directly map to a function call in the iterator model (such as binary operators). It was chosen to still represent them as binary operators to keep the analogy to Python AST and clearly separate parsing and lowering. The correspondence between Python and dialect AST also means it would be relatively easy to "unparse" the dialect AST into valid Python code for debugging.
 
 ##### Symbol types are not dialect nodes
+
 The symbol types in all dialects are implemented as simple dataclasses to ensure compatibility and reusability between different IRs. They are just _leaf_ nodes (not recursed into during tree traversals) describing which kind of symbol is defined in an IR node.
 
 #### What could be changed
+
 ##### Ad-hoc pass management
+
 The `DialectParser` has ended up being the main entry point for running all required passes to create a dialect AST that can be lowered to Iterator IR. This is not by design but simply because it was convenient. This is expected to change if a general pass manager is ever implemented. The two abstract class methods `._preprocess_definition_ast` and `._postprocess_definition_ast` encode the required order of AST passes.
 
 ### Field Operator AST (FOAST)
+
 Essentially this is a Field Operator flavoured Python AST dialect. The tree structure follows the supported subset of Python AST. It uses the symbol table concept from `eve` because it was at the time a ready to use way of keeping track of type information, and names of temporaries.
 
 #### What could be changed
+
 ##### Symbol table
+
 The symbol table concept is not essential to it, replacing it would touch:
-* The handling of type information from externals and parameters
-* The inlining of assign statements into the return statement before lowering to Iterator IR
+
+- The handling of type information from externals and parameters
+- The inlining of assign statements into the return statement before lowering to Iterator IR
 
 ### Field Operator Parser
+
 Parses from Python AST into FOAST.
 
 #### What to keep
+
 ##### Assumptions on simplified AST
+
 Some assumptions are made on the input AST which allow the visitor methods to stay quite simple. There are passes to be run on the AST before parsing which make it meet these assumptions. This pattern should be kept and extended. If Python AST allows for many cases which cause trouble in parsing, eliminate unneeded cases on the AST before parsing into FOAST.
 
 ### Field Operator Lowering
+
 Lowers from FOAST into IteratorIR. FOAST expressions returning fields are transformed into ITIR expressions returning iterators with an additional `deref` at the very end.
 
 This step makes use of the type information on expressions, for example to decide which names are fields and therefore have to be dereferenced. As of yet, there is no way to pass typing or source location info on to Iterator IR though.
 
 #### What to keep
+
 ##### Assume FOAST is correct
+
 As per guiding principle (5), the lowering should not worry if the FOAST is incorrect or invalid. It is the responsibility of the previous parser and passes to sanitize the user code.
 
 ### Program AST (PAST)
@@ -97,13 +112,16 @@ The syntax to call field operators feels unnatural as their signature suggests t
 The PAST is an independent dialect and (code-wise) only shares symbol types with the FOAST. This was a deliberate choice as there is currently no good way to share nodes and we wanted to allow changing each of the dialects independently until they become stable. Nonetheless nodes that exist in both dialects should stay in sync so that they could be generalized in the future.
 
 #### PAST parser
+
 Parses from Python AST into PAST.
 
 #### PAST Lowering
+
 Lowers from PAST to Iterator IR.
 
-__Field operator out argument slicing__
+**Field operator out argument slicing**
 The lowering contains some complex logic to emulate slicing of fields in order to restrict the output domain of a field operator call. This contradics guiding principle (3) and should be removed in the future. The concept of specifying the domain for Field operators was not investigated during the frontend design as we concentrated on the beautified iterator dialect where the output domain is explicitly given when lifting a local operator to a field operator. The alternative to also explicitly specify the domain for calls to field operators was rejected as it:
+
 - would require introducing significantly more syntax in order to specify domains, which was not only infeasible to implement in time, but there also didn't exist an accepted syntax to do so.
 - no intuitive syntax was found to do this
   - `field_operator(in_field, out=out_field, domain=output_domain)`: Possible, but rather verbose.
@@ -112,6 +130,7 @@ The lowering contains some complex logic to emulate slicing of fields in order t
 ### Decorators (@program, @field_operator)
 
 #### What to keep
+
 The decorators are designed to be constructed from the root node of their respective dialect contrary to a Python function object. This decision has been made to allow programmatically generating instances thereof.
 
 The Program decorator is not aware of the concept of a FieldOperator but instead uses a generic mechanism (`GTCallable`) to inject functions into the resulting ITIR. This allows to keep the the various decorators independent from each other and avoids coupling the typing system (which is conceptually on a lower level), to them (e.g. `make_symbol_type_from_value` uses the `__gt_type__` method).
diff --git a/docs/development/ADRs/0002-Field_View_Lowering.md b/docs/development/ADRs/0002-Field_View_Lowering.md
index 0d4b6f74..368921c0 100644
--- a/docs/development/ADRs/0002-Field_View_Lowering.md
+++ b/docs/development/ADRs/0002-Field_View_Lowering.md
@@ -12,6 +12,7 @@ tags: [frontend]
 ## Updates:
 
 ### 2022-04-08: Added scalar literal support
+
 The rationale that "any FOAST node should be lowered to an ITIR iterator expression" has changed to
 
 > Any non-scalar FOAST Node should be lowered to an ITIR iterator expression. Scalar FOAST nodes should be lowered to an ITIR value expression.
@@ -23,6 +24,7 @@ The lowering of parent nodes now has to deal with two kinds of expressions. In p
 Since the lowering currently does not have a usecase for lifting lowered child nodes if they are value expressions, the corresponding `to_iterator` method has been omitted. It can be introduced at a later time, if a usecase for it is found, it should be analogous to `to_value` and therefore easy to implement (in about four lines).
 
 ### 2022-02-22: Added reduction lowering
+
 Lowering reductions is not completely straightforward (at the time of writing) because expressions inside a reduction in field view may contain fields shifted to neighbor dimensions.
 
 These are lowered to partially shifted iterators which can not be dereferenced directly. The iterator builtin `reduce` therefore takes an expression which works on values after they have been shifted to a concrete neighbor and dereferenced by the system. Everywhere else it remains easier to lower everything to an iterator expression.
@@ -30,9 +32,10 @@ These are lowered to partially shifted iterators which can not be dereferenced d
 This leads to the following solution, likely subject to change when the behaviour of `reduce` or partially shifted iterators change:
 
 The expression inside the reduction is lowered using a separate `NodeTranslator` instance which
-1) collects shifted (and non-shifted) iterators in the course of the lowering
-2) lowers everything under the assumption that everything is a value and
-3) reinserts the (shifted) iterators as arguments to the `reduce` call.
+
+1. collects shifted (and non-shifted) iterators in the course of the lowering
+2. lowers everything under the assumption that everything is a value and
+3. reinserts the (shifted) iterators as arguments to the `reduce` call.
 
 This solution is proposed also because the special rules for `reduce`, which are likely to change are isolated in this separate lowering parser.
 
@@ -49,7 +52,7 @@ def temp(a):
   return tmp
 ```
 
-Would need to be turned into a single expression. While this case is trivial to solve by hand by simply replacing of ``a`` for ``tmp`` (yielding `deref(a)`), we require an algorithm that works in all cases.
+Would need to be turned into a single expression. While this case is trivial to solve by hand by simply replacing of `a` for `tmp` (yielding `deref(a)`), we require an algorithm that works in all cases.
 
 ## Algorithm Choice
 
@@ -57,23 +60,23 @@ Would need to be turned into a single expression. While this case is trivial to
 
 The choice of algorithm was guided by:
 
-* Yield correct Iterator IR with as little special-casing as possible
-* As simple and readable to code as possible
-* Avoid passing information down into subtree visitors (as long as that does not clash with simplicity and readability)
+- Yield correct Iterator IR with as little special-casing as possible
+- As simple and readable to code as possible
+- Avoid passing information down into subtree visitors (as long as that does not clash with simplicity and readability)
 
 ### Algorithm
 
 We chose the following algorithm:
 
-1. Lower the return value expression into an **iterator expression** and store it in ``current_expression``
+1. Lower the return value expression into an **iterator expression** and store it in `current_expression`
 
 2. For each assign statement in reverse order:
 
-  1. lower the right-hand side of the assign statement into an **iterator expression**
+3. lower the right-hand side of the assign statement into an **iterator expression**
 
-  2. wrap ``current_expression`` in a let-expression (see below) that exposes the lowered right-hand side as the left-hand side.
+4. wrap `current_expression` in a let-expression (see below) that exposes the lowered right-hand side as the left-hand side.
 
-3. dereference the ``current_expression``.
+5. dereference the `current_expression`.
 
 Or in pseudocode:
 
@@ -85,7 +88,7 @@ for assign in reversed(assigns):
 DEREF(current_expression)
 ```
 
-The let expression ``let VAR = INIT_FORM in FORM`` written out in iterator view looks as follows:
+The let expression `let VAR = INIT_FORM in FORM` written out in iterator view looks as follows:
 
 ```python
 (lambda VAR: FORM)(INIT_FORM)
@@ -140,7 +143,7 @@ a + b -> lift(lambda a, b: plus(deref(a), deref(b)))(a, b)
 a + 1 -> lift(lambda a: plus(deref(a), deref(lift(lambda: 1)())))(a)
 ```
 
-One might be tempted to eliminate the ``deref(lift(lambda: 1)())`` as extraneous. However, the same thing could be rewritten as
+One might be tempted to eliminate the `deref(lift(lambda: 1)())` as extraneous. However, the same thing could be rewritten as
 
 ```
 tmp = 1
@@ -148,7 +151,7 @@ a + tmp -> (lambda tmp: lift(lambda a: plus(deref(a), deref(tmp))))((lift lambda
 ```
 
 Where the algorithm makes the assumption that every assignment target (or let variable) is an iterator expression.
-This means, while the ``deref(lift(...))`` could be avoided in some cases, it would require special casing.
+This means, while the `deref(lift(...))` could be avoided in some cases, it would require special casing.
 This would mean complicating the lowering without gaining correctness, and therefore contradicts our guiding principles.
 
 ## Iterator IR helpers
@@ -171,4 +174,4 @@ Since temporary variables are no longer inlined, the renaming that happens in th
 
 ## Operator signature FOAST <-> ITIR
 
-On iterator level the arguments to all stencils / functions used inside a fencil closure need to be iterators (due to the compiled backend using a single SID composite to pass the arguments). Following the FOAST value <-> ITIR value, FOAST field <-> ITIR iterator correspondence, all field operator arguments whose type on FOAST level is a value, i.e. scalar or composite thereof, are expected to be values on ITIR level by the rest of the lowering. As a consequence we transform all values into iterators before calling field operators (to satisfy the former constraint) and deref them immediately inside every field operator (to satify the latter constraint).
\ No newline at end of file
+On iterator level the arguments to all stencils / functions used inside a fencil closure need to be iterators (due to the compiled backend using a single SID composite to pass the arguments). Following the FOAST value <-> ITIR value, FOAST field <-> ITIR iterator correspondence, all field operator arguments whose type on FOAST level is a value, i.e. scalar or composite thereof, are expected to be values on ITIR level by the rest of the lowering. As a consequence we transform all values into iterators before calling field operators (to satisfy the former constraint) and deref them immediately inside every field operator (to satify the latter constraint).
diff --git a/docs/development/ADRs/0003-Iterator_View_Tuple_Support_for_Fields.md b/docs/development/ADRs/0003-Iterator_View_Tuple_Support_for_Fields.md
index 0501d463..c01dd9a8 100644
--- a/docs/development/ADRs/0003-Iterator_View_Tuple_Support_for_Fields.md
+++ b/docs/development/ADRs/0003-Iterator_View_Tuple_Support_for_Fields.md
@@ -4,7 +4,7 @@ tags: [iterator]
 
 # Iterator View Tuple Support for Fields
 
-- **Status**: valid 
+- **Status**: valid
 - **Authors**: Hannes Vogt (@havogt)
 - **Created**: 2022-02-16
 - **Updated**: 2022-02-16
@@ -18,10 +18,12 @@ This document covers how fields of tuples and tuples of fields are treated at th
 A stencil in iterator view has a single return value, which can be a tuple. How do we map a tuple return value to field(s)?
 
 If the return value of a stencil is a tuple there are 2 possible relations between these values:
+
 - they are dimensions of the same kind (e.g. the 3 components of wind velocity),
 - they are of different kind, i.e. 2 completely unrelated quantities that just happen to be computed in the same stencil (e.g. temperature and pressure).
-  
+
 The natural representation of the 2 cases are:
+
 - a single field of tuples,
 - a tuple of fields.
 
diff --git a/docs/development/ADRs/0004-Lifted_Stencils_with_Tuple_Return.md b/docs/development/ADRs/0004-Lifted_Stencils_with_Tuple_Return.md
index 24f9fe49..dd0db70f 100644
--- a/docs/development/ADRs/0004-Lifted_Stencils_with_Tuple_Return.md
+++ b/docs/development/ADRs/0004-Lifted_Stencils_with_Tuple_Return.md
@@ -11,7 +11,6 @@ tags: [iterator]
 
 What's the return type of a lifted stencil that has tuple return type? Is it tuple of iterator or iterator of tuple?
 
-
 ## Context
 
 Example
@@ -29,6 +28,7 @@ lift(stencil)()
 We return an iterator of tuples.
 
 There are several reasons why this is attractive:
+
 - At least for simple cases, the values returned from a stencil are most likely uniform and need to be treated uniformly, e.g. in a subsequent shift.
 - Implementation-wise, it is not straight-forward to implement tuple of iterators. E.g. on the Python-side the return type (tuple vs scalar) of the function is not known when `lift` is applied, i.e. when a lift-iterator needs to be constructed. The solution could be to execute-once arbitrary input then analyze the result (this would be ok, as our language doesn't have side-effects).
 - In general, transformations that remove unused elements are easier to implement than transformations that try to match things. See the following example.
@@ -60,6 +60,7 @@ def non_uniform(inp0,inp1,inp2):
 In a naive (non-optimized) implementation of the `non_uniform` case we pay for 4 extra shifts (ptr updates) that are not needed.
 
 ### Tuple of Iterators
+
 ```python
 def uniform(inp0, inp1, inp2):
     res = lift(foo)(inp0, inp1, inp2)
diff --git a/docs/development/ADRs/0005-Extending_Iterator_IR.md b/docs/development/ADRs/0005-Extending_Iterator_IR.md
index 1025d20f..587df90c 100644
--- a/docs/development/ADRs/0005-Extending_Iterator_IR.md
+++ b/docs/development/ADRs/0005-Extending_Iterator_IR.md
@@ -11,7 +11,6 @@ tags: [general]
 
 When you should and when you shouldn't add an extension to Iterator IR.
 
-
 ## Decision
 
 Iterator IR is the central structure of the Declarative GT4Py, it is the interface between frontend and backend, and the target of (most) optimizations.
@@ -19,6 +18,6 @@ IR elements and the interactions with each other need to be well understood and
 
 Therefore, if it can be avoided, the IR should not be extended. Instead, required features can be implemented first in the backend where they are needed. If later we discover the features are needed in several backends, they can be promoted to the IR.
 
-### Example 
+### Example
 
 A recent example is the allocation of temporaries. We decided to add this feature only to the C++ backend for now, it's a performance optimization that we don't need in Python execution. While adding temporary allocation to the IR, seems straight-forward, it requires several new builtins, e.g. to express domain size relative to the domain passed by the user.
diff --git a/docs/development/ADRs/0006-Cpp-Backend.md b/docs/development/ADRs/0006-Cpp-Backend.md
index 1e50b6ba..a034710e 100644
--- a/docs/development/ADRs/0006-Cpp-Backend.md
+++ b/docs/development/ADRs/0006-Cpp-Backend.md
@@ -4,7 +4,7 @@ tags: [backend]
 
 # C++ Backend
 
-- **Status**: valid 
+- **Status**: valid
 - **Authors**: Hannes Vogt (@havogt)
 - **Created**: 2022-02-18
 - **Updated**: 2022-04-22
@@ -16,6 +16,7 @@ Overview of C++ backend decisions.
 The backend will not be able to transform the IR, therefore any patterns that need IR transformations need to happen before C++ code generation.
 
 The following built-ins require to be presented in a canonicalized form
+
 - `scan`
 - `lift` if interpreted as temporary (see below).
 
@@ -27,6 +28,7 @@ The following built-ins require to be presented in a canonicalized form
 
 We introduce an IR which is close to the generated code.
 It is different to Iterator IR in the following:
+
 - no `lift` built-in
 - allows allocation of temporaries
 - Iterator IR built-ins are replaced by the corresponding C++ operator, e.g. `plus(a,b)` -> `a+b`
@@ -45,12 +47,12 @@ Additionally, we decided to unroll the reduction, before generating the backend
 
 These are reasons why reductions might be introduced in the backend at some point:
 
-1) Performance implications for check for skip values.
+1. Performance implications for check for skip values.
 
    If we use the reduce builtin, we can implement an early exit in the reduction loop, assuming there are no non-skipped values after the first skip value. This assumption might be problematic anyway as we might have to reorder neighbor tables, if the mesh generator does not comply with this assumption.
 
-2) Implementation as runtime loops.
-   
+2. Implementation as runtime loops.
+
    If we would implement the reduction as runtime loops (with compile time bounds), we could leave the decision of unrolling to the C++ compiler. Note that this would require to implement `shift`s with runtime offsets (currently offsets are required to be compile-time).
 
 Note: Originally, we introduced `reduce` to be able to check for skip values only once for all iterators in the reduce. However the single check can be implemented in the unrolled version, too.
@@ -75,6 +77,7 @@ def unrolled_reduce(a,b):
 ## Summary
 
 The IR, when presented to the C++ backend must have the following properties:
+
 - `scan` canonicalized
 - `lift` that are interpreted as temporaries are canonicalized, no other (inline) `lift`s
 - no `reduce`
diff --git a/docs/development/ADRs/0007-Fencil-Processors.md b/docs/development/ADRs/0007-Fencil-Processors.md
index ec144fed..dc772d0b 100644
--- a/docs/development/ADRs/0007-Fencil-Processors.md
+++ b/docs/development/ADRs/0007-Fencil-Processors.md
@@ -13,7 +13,7 @@ The backend registry was deleted and replaced by calling the function instead of
 
 In contrast to "backend" we can clearly define a **program processor**: Any function that takes an IteratorIR representation of a program as a parameter along with all the inputs needed to call the program is a **program processor**. What kind of processor depends on the behaviour of that function.
 
-This was done to 
+This was done to
 
 1. Give a single semantic meaning to calling a `@fendef` decorated function or FieldView program: executing the program.
 1. Reduce the potential for circular imports.
@@ -22,47 +22,53 @@ This was done to
 ## Changelog
 
 ### 2022-10-05
+
 - updated to reflect name change `fencil_processors` -> `program_processors`
 
 ## Context
 
 ### The fencil wrapper `__call__` used to have unclear semantics
+
 Previously, any function with the signature `(itir.FencilDefinition, *args, **kwargs) -> Any` could be registered as a backend and called via the `__call__` method of the return type of the iterator or FieldView decorator. Semantically this meant that calling a program can not be expected to execute the program without first knowing what the "backend" method actually does.
 
 This was useful for testing because it allowed parametrize tests with any kind of program processor (coupled with whether the program processor should execute the program and hence the result should be validated).
 
 ### The registry was not particularly useful and promoted circular imports
+
 All of these program processors were registered as "backends" in a registry. At first glance this may have appeared helpful, since the module in which the processor function was defined could simply register it with a name, and from anywhere else it could be picked by that name. However, the registering action would only run on import of the "backend" module, so for this to work all of them had to be imported somewhere along the line before any of them could be used. This was done inside the `iterator` subpackage, which made it possible to run into circular imports when importing from `iterator` inside the "backend" module.
 
 ### Preparing for compiled backends integration
+
 While working on integrating foreign language (non-python) backends (E.g. generating C++ code, compiling a python extension and loading it back) it turned out to be desirable to explicitly separate different kinds of endpoints for program processing. In the envisioned end form of that system creating a python extension, compiling it and loading it back is decoupled from the foreign language code generator. The fencil wrapper's `__call__` method therefore needs to distinguish between 1) something that can execute the program 2) something that generates code which can be turned into an executable python extension and 3) everything else like debugging utilities etc.
 
 ## Decision
 
 ### Clarifying nomenclature
-Anything that doesn't execute a program, but instead generates text from it is now called a `program_formatter`.
-
 
+Anything that doesn't execute a program, but instead generates text from it is now called a `program_formatter`.
 
 ### Clarifying the `__call__` semantics
+
 The following was done to clarify the semantics of fencil wrapper `__call__` (and FieldView Program `__call__`), in the case of using a non-embedded backend: The `__call__` method now checks that the backend it was passed was decorated with `@program_processors.processor_interface.program_executor`. In addition another method was introduced on the fencil / Program wrappers (`.format_itir` / `.format_itir`) to cover all other currently existing program processors, as they all generate a text string from the program.
 
 ### The backend registry
+
 The registry was removed for now. Any code using a backend by key was changed to import the backend function and use that instead.
 
 ### Preparing for later
+
 In addition to the `program_executor` decorator, a `program_formatter` decorator was added. Both of these mark the decorated function so that different kinds of backends can be distinguished and they can be either directly executed or their output compiled, loaded back and then executed. Most likely that will involve an additional program processor kind with it's own decorator and entry point function.
 
 ### Preserving testability
+
 In order to preserve the ease of testing any processor on all test cases, a dispatcher had to be added to the test suite, which allows that.
 
 ## Consequences
 
-* Backends have to be explicitly imported before being used (used to be implicitly imported)
-* Adding a true plugin architecture for backends has not become harder or easier
-* Calling a program now always means executing it
-* Adding a new kind of program processor and dispatching based on that kind is easy now
-
+- Backends have to be explicitly imported before being used (used to be implicitly imported)
+- Adding a true plugin architecture for backends has not become harder or easier
+- Calling a program now always means executing it
+- Adding a new kind of program processor and dispatching based on that kind is easy now
 
 ## Alternatives Considered
 
diff --git a/docs/development/ADRs/0008-Mapping_Domain_to_Cpp-Backend.md b/docs/development/ADRs/0008-Mapping_Domain_to_Cpp-Backend.md
index 7d128789..23b75c6d 100644
--- a/docs/development/ADRs/0008-Mapping_Domain_to_Cpp-Backend.md
+++ b/docs/development/ADRs/0008-Mapping_Domain_to_Cpp-Backend.md
@@ -4,7 +4,7 @@ tags: []
 
 # [Mapping Dimensions to the C++-Backend]
 
-- **Status**: valid 
+- **Status**: valid
 - **Authors**: Hannes Vogt (@havogt)
 - **Created**: 2022-06-29
 - **Updated**: 2022-06-29
@@ -17,7 +17,7 @@ The Python embedded execution for Iterator IR keeps track of the current locatio
 
 ### Python side
 
-On the Python side, we label dimensions of fields with the location type, e.g. `Edge` or `Vertex`. The domain uses `named_ranges` that uses the same location types to express *where* to iterate, e.g. `named_range(Vertex, range(0, 100))` is an iteration over the `Vertex` dimension, no order in the domain is required. Additionally, the `Connectivity` (aka `NeighborTableOffsetProvider` in the current implementation) describes the mapping between location types. 
+On the Python side, we label dimensions of fields with the location type, e.g. `Edge` or `Vertex`. The domain uses `named_ranges` that uses the same location types to express _where_ to iterate, e.g. `named_range(Vertex, range(0, 100))` is an iteration over the `Vertex` dimension, no order in the domain is required. Additionally, the `Connectivity` (aka `NeighborTableOffsetProvider` in the current implementation) describes the mapping between location types.
 
 ### C++ side
 
@@ -50,6 +50,7 @@ class Dimension:
 # the following field doesn't have correct default order, as without remapping we would interpret first dimension as dim::horizontal
 np_as_located_field(Dimension("K", DimensionKind.VERTICAL), Dimension("Vertex", DimensionKind.HORIZONTAL))
 ```
+
 ### Mapping of Cartesian Offsets to Dimensions
 
 When lowering from iterator IR to gtfn_ir, we replace Cartesian offset tags by Dimension tags.
diff --git a/docs/development/ADRs/0009-Compiled-Backend-Integration.md b/docs/development/ADRs/0009-Compiled-Backend-Integration.md
index d75c0056..273f9544 100644
--- a/docs/development/ADRs/0009-Compiled-Backend-Integration.md
+++ b/docs/development/ADRs/0009-Compiled-Backend-Integration.md
@@ -4,7 +4,7 @@ tags: [backend]
 
 # Integration of compiled backends
 
-- **Status**: superseded 
+- **Status**: superseded
 - **Authors**: Peter Kardos (@petiaccja), Rico Häuselmann (@DropD)
 - **Created**: 2022-07-13
 - **Updated**: 2022-09-12
@@ -12,6 +12,7 @@ tags: [backend]
 Summary of the key design choices made for the live execution of generated C++ (and other compiled) code.
 
 **This document is superseded by**
+
 - [0011 - On The Fly Compilation](0011-_On_The_Fly_Compilation.md), which talks about the general architecture and design of on the fly compilation, and
 - [0012 - GridTools C++ OTF](0011-_GridTools_Cpp_OTF.md), which talks the specifics of on-the-fly compilation of the GridTools C++ backend generated code.
 
@@ -20,10 +21,11 @@ Summary of the key design choices made for the live execution of generated C++ (
 As per the current state, gt4py can execute iterator IR by generating Python code from it which is loaded back as a module. Additionally, gt4py can also emit equivalent C++ code from iterator IR. The integration of the compiled backends focuses on compiling the emitted C++ code and calling it from Python, effectively executing iterator IR live by translating it to machine code.
 
 The process of executing ITIR this way consists of the following steps:
-1. Generate C++ code equivalent to ITIR (*fencil code*)
-2. Generate C++ code that wraps the fencil code and can be accessed from Python (*binding code*)
+
+1. Generate C++ code equivalent to ITIR (_fencil code_)
+2. Generate C++ code that wraps the fencil code and can be accessed from Python (_binding code_)
 3. Compile the fencil and binding codes into machine code (a dynamically linked library)
-4. Load the dynamic library into Python and extract the interfaces as Python objects 
+4. Load the dynamic library into Python and extract the interfaces as Python objects
 5. Call the interfaces from Python with the fencil's arguments
 
 Step 1 is the core responsibility of a compiled backend. For interoperability between steps, the `fencil_processors.source_modules` subpackage provides utilities and data structures which should simplify implementation and guide design, without restricting the backend implementer (they are opt-in). Steps 2-4 should rely on `fencil_processors.builders` with little code required on the backend side. For backends where additional functionality is required, it is expected that the missing functionality be implemented inside those subpackages. Step 5 should typically simply relay the fencil arguments to the result of step 4 without additional code required.
@@ -40,7 +42,7 @@ The chosen design is so that there is a clear interface for every step from ITIR
 
 Translate IteratorIR into "backend language", for example C++ using GridTools.
 
-The `SourceModuleGenerator` protocol in `fencil_processors.processor_interface` interface defines the interface for the first step, from IteratorIR to `fencil_processors.source_modules.source_modules.SourceModule`. This step can optionally make use of a `ProgramFormatter` (also defined in `processor_interface.py`) to deliver the source code along with information accessible to further steps. 
+The `SourceModuleGenerator` protocol in `fencil_processors.processor_interface` interface defines the interface for the first step, from IteratorIR to `fencil_processors.source_modules.source_modules.SourceModule`. This step can optionally make use of a `ProgramFormatter` (also defined in `processor_interface.py`) to deliver the source code along with information accessible to further steps.
 
 The output of this step is a `SourceModule` instance, which is safely hashable.
 
@@ -92,25 +94,26 @@ Boost.Python and pybind11 are very similar. Since boost is not a requirement for
 ### Decision
 
 We chose the universal interface for the following advantages:
+
 - Promotes code reuse: slim bindings for different C++ backends would share a large fraction of their code
 - Decoupling fencil generators from binding generators allows testing the two components separately
 - Forces the separation of responsibilities between binding and fencil code generation
 
 However there are some downsides and implementation issues:
-- Although the proper architecture is in place, the current implementation still leaves the fencil and binding code somewhat intertwined
-- Currently only two C++ backends are foreseen: GridTools CPU and GridTools CUDA. These backends have close to or completely identical C++ code. This situation might make code reuse less significant. 
 
+- Although the proper architecture is in place, the current implementation still leaves the fencil and binding code somewhat intertwined
+- Currently only two C++ backends are foreseen: GridTools CPU and GridTools CUDA. These backends have close to or completely identical C++ code. This situation might make code reuse less significant.
 
 ## Connection to FieldView frontend
 
 We decided to keep compiled backend completely independent of the fields view frontend. The input the compiled backends is thus iterator IR and additional information necessary for code generation.
 
 Reasons:
+
 - There may be multiple frontends, but all of them would emit iterator IR and therefore would be compatible with compiled backends out of the box
 - The FieldView frontend must import compiled backends. If compiled backends were aware of FieldView features, that would lead to a circular dependency between the frontend and backend.
 - The work to support starting from iterator IR directly would have to be done in any case.
 
-
 ## Limitations
 
 The main goal of this project is to implement the complete pipeline from FieldView to machine code and demonstrate that it's working. To keep the scope of the project reasonable, feature completeness is not targeted.
@@ -118,6 +121,7 @@ The main goal of this project is to implement the complete pipeline from FieldVi
 ### Build system project
 
 The `fencil_processors.builders.cpp.build.CMakeProject` class design should not be considered final because of the following pitfalls:
+
 - the blocking `configure` and `build` functions may need to be converted to asynchronous operations to support parallel compilation of multiple fencils
 - support needs to be added to switch between debug and release builds, as well as to conditionally enable debug information for release builds
 - support needs to be added to enable compiler optimizations and tuning for target hardware
@@ -139,10 +143,11 @@ In this implementation, only fencil having scalar and field arguments can be exe
 To keep things simple, GridTools CUDA is not supported in this first iteration.
 
 To add support, we have to decide on the interface that is exposed to the user:
+
 1. There are two completely separate backends for CPU and CUDA
 2. There is a single backend and the device is selected by a flag
 
-The existing code can be extended for either options in the future. 
+The existing code can be extended for either options in the future.
 
 ### Unstructured grids
 
diff --git a/docs/development/ADRs/0010-Domain_in_Field_View.md b/docs/development/ADRs/0010-Domain_in_Field_View.md
index f75a828f..cb4ec313 100644
--- a/docs/development/ADRs/0010-Domain_in_Field_View.md
+++ b/docs/development/ADRs/0010-Domain_in_Field_View.md
@@ -10,7 +10,7 @@ tags: [frontend]
 - **Updated**: 2022-08-24
 
 In the context of the ICON port we needed multiple `out` arguments in calls to `field_operator`s from `program`s. However,
-the domain was deduced relative to *the* `out` argument. We decided to deduce the domain from the first field for now as described below.
+the domain was deduced relative to _the_ `out` argument. We decided to deduce the domain from the first field for now as described below.
 
 ## Context
 
diff --git a/docs/development/ADRs/0011-On_The_Fly_Compilation.md b/docs/development/ADRs/0011-On_The_Fly_Compilation.md
index 4bf4f1da..4768c8c8 100644
--- a/docs/development/ADRs/0011-On_The_Fly_Compilation.md
+++ b/docs/development/ADRs/0011-On_The_Fly_Compilation.md
@@ -1,5 +1,5 @@
 ---
-tags: [backend,bindings,build,compile,otf]
+tags: [backend, bindings, build, compile, otf]
 ---
 
 # On The Fly Compilation
@@ -81,6 +81,7 @@ Container_Boundary(workflow, 'Workflow', 'otf.workflow.Workflow') {
 
 @enduml
 -->
+
 ![](_static/0011-on-the-fly-overview.svg)
 
 The main use case is to execute GT4Py programs in a performance portable way from Python. Other use cases may include generating performance portable code to be integrated in external applications, generating code for debugging purposes, pre-compiling libraries of GT4Py programs for use from separate Python applications, etc.
@@ -119,34 +120,40 @@ As a special case a `BindingStep` for a `ProgramSource`, which does not require
 ## Architecture
 
 The goals of the on-the-fly compilation (OTFC) architecture and design are:
+
 - guide and inform the design and implementation of future components in this part of the code (code generators, builders, bindings generators, caching strategies, etc)
 - ensure consistency and readability of components
 - keep components easy to reason about
 - keep components (including future ones) intercompatible as much as reasonably possible
 - allow per-use-case flexible composition of components
 
-The chosen architecture is composable workflows. Such workflows can be composed of workflows and workflow steps, which implement the workflow protocol (`otf.workflow.Workflow`), provided each step's input argument type(s) matches the preceding step's return type(s). Any step customization should happen during workflow composition and *not* by passing options and flags along the workflow.
+The chosen architecture is composable workflows. Such workflows can be composed of workflows and workflow steps, which implement the workflow protocol (`otf.workflow.Workflow`), provided each step's input argument type(s) matches the preceding step's return type(s). Any step customization should happen during workflow composition and _not_ by passing options and flags along the workflow.
 
 The completed workflow can then be called with the input argument(s) of the first workflow step and it's return type will be the same as the last step's.
 
 ## Design Decisions
 
 ### Linear Workflows
+
 - decided: 2022-09-13
 - revised: --
 
 For the first implementation, workflows are chosen to be linear. That is to say each step is followed by exactly zero or one step. No branching, step-skipping or short-circuiting is implemented.
 
 #### Reason
+
 Not required at this stage and simplifies implementation
 
 #### Consequences
+
 Caching is therefore currently not implemented as a separate step and is therefore the concern of other steps (currently only the build step).
 
 #### Revise If
+
 There is a good use case that justifies the extra effort. Such as implementing caching steps as conditionals based on which subsequent steps are switched or skipped, or which might complete the workflow early.
 
 ### Statically Typed Workflows
+
 - decided: 2022-09-13
 - revised: --
 
@@ -172,53 +179,62 @@ CombinedStep(
 ```
 
 #### Reason
+
 It is in line with the rest of GT4Py to make static type checking for it's code objects as useful as possible. Any linear (and with extensions, non-linear) workflow can be represented in this way.
 
 #### Revise If
+
 There has to be a very strong reason to drop static type checking for composition and / or the resulting workflow. The author of this document can not imagine one at present.
 
 ### Shared Bindings Step
+
 The workflow step from the `ProgramSource` stage to the `CompilableSource` stage, adding python bindings via `pybind11` was designed to be shared between C++ backends. In practice it is not quite there yet, but close enough for the current stage of development.
 
 The same decision should be followed for other language bindings, wherever possible.
 
 #### Consequences
+
 The `otf.stages.ProgramSource` class has been designed to carry enough information for the bindings generator to convert from the common python call interface to whatever the backend C++ code requires.
 
 #### Reason
+
 The advantage is that compiled programs can be called with the same arguments independently of which (C++) backends were used, without forcing backend implementers to reinvent the wheel when it comes to converting to data structures their backend's C++ side knows how to work with.
 
 #### Revise If
+
 Should it turn out that future backends are not sufficiently similar to warrant sharing this step, it may make sense to specialize these for each C++ backend instead. Most likely some code will still be shareable between these though.
 
 ### Shared Compilation Step
-The build step goes from `otf.stages.CompilableSource` to `otf.stages.CompiledProgram`. The current implementation is implemented in `otf.compilation.compiler` and can use either of the build systems in `otf.compilation.build_systems.cmake` or `otf.compilation.build_systems.compiledb`. 
+
+The build step goes from `otf.stages.CompilableSource` to `otf.stages.CompiledProgram`. The current implementation is implemented in `otf.compilation.compiler` and can use either of the build systems in `otf.compilation.build_systems.cmake` or `otf.compilation.build_systems.compiledb`.
 
 `cmake.CMakeProject` creates a new full CMake project for each program. `compiledb.CompiledbProject` creates a new CMake project only if it would lead to a different CMake configuration, such as diverging dependencies or build types etc.
 
 These can be extended to cover more than C++ but this should be done when such use cases arise, similar to the `pybind11` bindings step.
 
-
 ### Build System Classes Track Build Progress In a JSON File
+
 Any build system used to build GT4Py programs tracks it's progress and results via a JSON file in it's root folder. The content is a serialization of an `otf.compilation.build_data.BuildData` instance.
 
 #### Reason
+
 This greatly simplifies accessing already compiled and cached (across runs) GT4Py programs as well as checking whether such a cached program exists. The alternative would be to go through the following steps:
 
-1) Translation
-2) Binding
-3) Use heuristics to decide which build system was used last time
-4) Re-create the build system with the cached path as root
-5) Use the build-system's heuristics to decide if a compiled program exists or where to load it from.
+1. Translation
+2. Binding
+3. Use heuristics to decide which build system was used last time
+4. Re-create the build system with the cached path as root
+5. Use the build-system's heuristics to decide if a compiled program exists or where to load it from.
 
 Whereas when using the `BuildData` one only requires to run steps 1) and 2) to get the cached project folder and the rest can be accessed from there. These steps can be skipped as well by implementing more aggressive caching (for production environments where the GT4Py source is kept constant).
 
 Looking at the build systems implemented at the time of writing, `CMakeProject` and `CompiledbProject`, one might wrongly conclude that existence of certain directories or files suffice. This is only the case because `CompiledbProject` is based on `CMakeProject` and they therefore share some implementation details, which are by no means guaranteed to be standardizeable across multiple conceptually different build systems.
 
 #### Revise If
+
 It can be proven that all existing and future build systems can be made to behave closely enough for one single set of heuristics to decide the following based on project directory contents.
 
-- build status 
+- build status
 - extension module location
 - name of the program inside the extension module
 
@@ -227,9 +243,11 @@ Or, it has been shown that inter-run caching of GT4Py programs is not desirable.
 ## Alternatives Considered
 
 ### Pipeline Architecture
+
 A pipeline architecture was considered, but this would require the input and output argument of every step be the same. This would lead to a data type with many states, able to represent anything from source code to executable GT4Py programs. This was not considered wise.
 
 ### Ad-hoc Workflows Through Function Composition
+
 At first, workflows were to be simply composed by the result of each step being passed as an argument to the following one. The resulting code was deemed hard to read and, potentially, to maintain. Example:
 
 ```python=
@@ -245,7 +263,7 @@ result = step3(
 )
 ```
 
-Note that the steps occur in reverse order when the code is read top to bottom. In addition the customization of steps can easily get mixed in with the *flow* of the workflow. Finally it is common to refactor code like that for readability to something like:
+Note that the steps occur in reverse order when the code is read top to bottom. In addition the customization of steps can easily get mixed in with the _flow_ of the workflow. Finally it is common to refactor code like that for readability to something like:
 
 ```python=
 intermediary_1 = step1(...)
diff --git a/docs/development/ADRs/0012-GridTools_Cpp_OTF_Steps.md b/docs/development/ADRs/0012-GridTools_Cpp_OTF_Steps.md
index 7460c86f..a8cf594b 100644
--- a/docs/development/ADRs/0012-GridTools_Cpp_OTF_Steps.md
+++ b/docs/development/ADRs/0012-GridTools_Cpp_OTF_Steps.md
@@ -1,5 +1,5 @@
 ---
-tags: [backend,cpp,gridtools,bindings,otf]
+tags: [backend, cpp, gridtools, bindings, otf]
 ---
 
 # GridTools CPP OTF Steps
@@ -51,10 +51,10 @@ When called the `GTFNExecutor` instance composes a workflow from the above steps
 
 In the earlier, Cartesian version of GT4Py backends started out as monoliths with just IR as input and executable program for output.
 
-* Advantages:
+- Advantages:
   - in some cases simpler to write
   - not limited by inter-step interfaces, any information can be available at any step.
-* Disadvantages:
+- Disadvantages:
   - hard to share code between backends
   - more design decisions required for implementing a new backend
   - leads to harder to maintain backends
@@ -64,7 +64,7 @@ In the earlier, Cartesian version of GT4Py backends started out as monoliths wit
 
 The bindings generator and builder steps could have been implemented without consideration for sharing code with future backends.
 
-* Advantages:
+- Advantages:
   - faster to implement
-* Disadvantages:
+- Disadvantages:
   - slows down the next backend implementation
diff --git a/docs/development/ADRs/0013-Scalar_vs_0d_Fields.md b/docs/development/ADRs/0013-Scalar_vs_0d_Fields.md
index 56830a10..44e5dc26 100644
--- a/docs/development/ADRs/0013-Scalar_vs_0d_Fields.md
+++ b/docs/development/ADRs/0013-Scalar_vs_0d_Fields.md
@@ -79,6 +79,7 @@ If needed, we might add functionality to extract the scalar value from a 0-d fie
 ### Notes on lowering
 
 This decision doesn't change the current definition of iterator ir or the `fn` C++ backend:
+
 - a stencil (any lowered field_operator) will only accept iterators (i.e. `deref` is required);
 - `fn` will accept SIDs, scalars have to we wrapped in a helper, e.g. `global_parameter`.
 
@@ -92,7 +93,7 @@ Example: A reduction over a dimension of a field returns a field with one dimens
 
 However, we would need implement it with either possibly strange side-effects (making builtin types, e.g. `float` a subclass of Field) or create custom types for scalars (which is not desirable from a usage perspective, e.g. `gt4py.float`) to make this properly work with Python typing.
 
-It would allow to make some cases work that are forbidden by the now decided rules, e.g. 
+It would allow to make some cases work that are forbidden by the now decided rules, e.g.
 
 ```python=
 @field_operator
diff --git a/docs/development/ADRs/Index.md b/docs/development/ADRs/Index.md
index 08dd5e2d..1bbfd62d 100644
--- a/docs/development/ADRs/Index.md
+++ b/docs/development/ADRs/Index.md
@@ -2,11 +2,11 @@
 
 This document contains links to all _Architecture Decision Record_ (ADR) documents written in this project. Existing ADRs should convey the rationale for the design decisions in the current codebase and their associated trade-offs.
 
-
 ## Writing ADRs
+
 New ADRs should be written at the time when significant decisions for the project are taken, and they should capture both the final decision and the thought process that led to it. Basically, they should explain why a feature is built the current way and not some other way. Note that even if the reasons might look simple for the author, it could be complicated for others to understand the chosen option without the proper context.
 
-Using ADRs to document the context and the design process of the technical decisions gives other team members more information about how the framework components work and how they fit within the whole design.  In the long term, it also helps the decision maker to recall the forces driving his decisions.
+Using ADRs to document the context and the design process of the technical decisions gives other team members more information about how the framework components work and how they fit within the whole design. In the long term, it also helps the decision maker to recall the forces driving his decisions.
 
 Writing a new ADR is simple:
 
@@ -14,41 +14,49 @@ Writing a new ADR is simple:
 2. Add a link to the new ADR file to the fitting topic in the index section below.
 3. Open a PR to merge the changes into the main branch and let the team know about the new ADR.
 
-
 ## Index by Topic
 
 ### General architecture #general
+
 - [0005 - Extending Iterator IR](0005-Extending_Iterator_IR.md)
- 
-### Frontend and Parsing #frontend 
+
+### Frontend and Parsing #frontend
+
 - [0001 - Field View Frontend Design](0001-Field_View_Frontend_Design.md)
 - [0002 - Field View Lowering](0002-Field_View_Lowering.md)
 - [0010 - Domain in Field View](0010-Domain_in_Field_View.md)
 - [0013 - Scalar vs 0d-Fields](0013-Scalar_vs_0d_Fields.md)
 
 ### Iterator IR #iterator
+
 - [0003 - Iterator View Tuple Support for Fields](0003-Iterator_View_Tuple_Support_for_Fields.md)
 - [0004 - Lifted Stencils with Tuple Return](0004-Lifted_Stencils_with_Tuple_Return.md)
 
 ### Embedded Execution
+
 _None_
 
 ### Transformations
+
 _None_
 
 ### Backends and Code Generation
+
 - [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)
 
 ### Python Integration
+
 - [0011 - On The Fly Compilation](0011-On_The_Fly_Compilation.md)
 - [0012 - GridTools C++ OTF](0011-_GridTools_Cpp_OTF.md)
 
 ### Miscellanea
+
 _None_
 
 ### Superseded
+
 - [0009 - Compiled Backend Integration](0009-Compiled-Backend-Integration.md)
 
 ## Other References
diff --git a/docs/development/ADRs/Template.md b/docs/development/ADRs/Template.md
index 11005652..155014f6 100644
--- a/docs/development/ADRs/Template.md
+++ b/docs/development/ADRs/Template.md
@@ -17,8 +17,8 @@ tags: []
 
 A list of **Concerns**
 
-* [driver 1, e.g., a force, facing concern, ...]
-* [driver 2, e.g., a force, facing concern, ...]
+- [driver 1, e.g., a force, facing concern, ...]
+- [driver 2, e.g., a force, facing concern, ...]
 
 ]
 
@@ -28,34 +28,31 @@ A list of **Concerns**
 
 It has been Chosen option X because "[justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force force | ... | comes out best (see below)]. ]
 
-
 ## Consequences
 
 [ What becomes easier or more difficult to do because of this change?
 
 Describe both the positive (e.g., improvement of quality attribute satisfaction, follow-up decisions required, ...) and negative (e.g., compromising quality attribute, follow-up decisions required, ...)] outcomes of this decision. ]
 
-
 ## Alternatives Considered
 
 ### [option 1]
 
 [example | description | pointer to more information | ...] <!-- optional -->
 
-* Good, because [argument a]
-* Bad, because [argument c]
-* ...
+- Good, because [argument a]
+- Bad, because [argument c]
+- ...
 
 ### [option 2]
 
 [example | description | pointer to more information | ...] <!-- optional -->
 
-* Good, because [argument a]
-* Bad, because [argument c]
-* ... 
-
+- Good, because [argument a]
+- Bad, because [argument c]
+- ...
 
 ## References <!-- optional -->
 
 - [Someone - Something](https://someone.com/a-title-about-something)
-- ... 
+- ...
diff --git a/docs/development/CI/infrastructure.md b/docs/development/CI/infrastructure.md
index a9d6fe32..0f5ab82a 100644
--- a/docs/development/CI/infrastructure.md
+++ b/docs/development/CI/infrastructure.md
@@ -7,9 +7,9 @@ Any test job that runs on CI is encoded in automation tools like tox and pre-com
 The following workflows are currently active:
 
 ```mermaid
-%%{ 
+%%{
     init: {
-        'flowchart': { 'curve': 'basis' }, 
+        'flowchart': { 'curve': 'basis' },
         'theme': 'neutral'
     }
 }%%
@@ -25,12 +25,12 @@ flowchart LR
     src_cab[cartesian backend sources changed] --> sto["Test Storage (CPU)"]
 
     src_sto[storage sources changed] --> sto
-    src_sto --> car 
+    src_sto --> car
     src_sto --> nxt
 
     src_nxt[next sources changed] --> nxt
 
-    cfg_wfl[workflows changed] --> car 
+    cfg_wfl[workflows changed] --> car
     cfg_wfl --> eve
     cfg_wfl --> nxt
     cfg_wfl --> sto
@@ -58,7 +58,7 @@ flowchart LR
     style nxt fill:LightBlue,stroke:CornFlowerBlue,text:black;
 
     linkStyle 0 stroke:#999,stroke-width:2px;
-    
+
     linkStyle 1 stroke:MediumOrchid,stroke-width:2px;
     linkStyle 2 stroke:MediumOrchid,stroke-width:2px;
     linkStyle 3 stroke:MediumOrchid,stroke-width:2px;
diff --git a/docs/user/next/QuickstartGuide.md b/docs/user/next/QuickstartGuide.md
index abfdfdac..44bdeea1 100644
--- a/docs/user/next/QuickstartGuide.md
+++ b/docs/user/next/QuickstartGuide.md
@@ -29,6 +29,7 @@ pip install git+https://github.com/gridtools/gt4py.git@functional
 ### Key concepts and application structure
 
 This section introduces three concepts for storing and manipulating data:
+
 - [Fields](#Fields),
 - [Field operators](#Field-operators), and
 - [Programs](#Programs).
@@ -52,7 +53,7 @@ from gt4py.next.iterator.embedded import np_as_located_field, NeighborTableOffse
 
 #### 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 `np_as_located_field` helper function. The fields contain the values 2 for `a` and 3 for `b` for all entries.
 
 ```{code-cell} ipython3
 CellDim = Dimension("Cell")
@@ -68,7 +69,7 @@ a = np_as_located_field(CellDim, KDim)(np.full(shape=grid_shape, fill_value=a_va
 b = np_as_located_field(CellDim, KDim)(np.full(shape=grid_shape, fill_value=b_value, dtype=np.float64))
 ```
 
-*Note: The interface to construct fields is provisional only and will change soon.*
+_Note: The interface to construct fields is provisional only and will change soon._
 
 +++
 
@@ -122,13 +123,13 @@ print("{} + {} = {} ± {}".format(b_value, (a_value + b_value), np.average(np.as
 
 #### Composing field operators and programs
 
-When writing complex applications, you have to decompose it into *field operators*, *programs*, and Python (or other) code that glues it all together. The general advice is to follow best practices and write short and concise field operators that serve a single purpose. To maximise automatic optimization, use external glue code sparingly and group your field operators inside *programs* as much as you can. Consequently, directly calling field operators is primarily used for debugging purposes.
+When writing complex applications, you have to decompose it into _field operators_, _programs_, and Python (or other) code that glues it all together. The general advice is to follow best practices and write short and concise field operators that serve a single purpose. To maximise automatic optimization, use external glue code sparingly and group your field operators inside _programs_ as much as you can. Consequently, directly calling field operators is primarily used for debugging purposes.
 
 +++
 
 ### Operations on unstructured meshes
 
-This section introduces additional APIs through a slightly more elaborate application that performs a laplacian-like operation on an unstructured mesh. Within the context of this guide, we define the *pseudo-laplacian* for a cell as the sum of the *edge differences* around the cell. For example, the pseudo-laplacian for cell \#1, which is surrounded by edges \#7, \#8 and \#9, is expressed by the formula:
+This section introduces additional APIs through a slightly more elaborate application that performs a laplacian-like operation on an unstructured mesh. Within the context of this guide, we define the _pseudo-laplacian_ for a cell as the sum of the _edge differences_ around the cell. For example, the pseudo-laplacian for cell \#1, which is surrounded by edges \#7, \#8 and \#9, is expressed by the formula:
 
 $$\begin{aligned}\text{pseudolap}(cell_1) =\,& \text{edge_diff}_7 + \text{edge_diff}_8 + \text{edge_diff}_9 \end{aligned}$$.
 
@@ -139,6 +140,7 @@ $$\begin{aligned} \text{edge_diff}_7 =\,& \text{edge_diff}_{0,1} = \text{value_o
 The sign of the edge difference in the sum of the pseudo-laplacian is always such that the neighbor cell is subtracted from the subject cell, not the other way around.
 
 This section approaches the pseudo-laplacian by introducing the required APIs progressively through the following subsections:
+
 - [Defining the mesh and the connectivities (adjacencies) between cells and edges](#Defining-the-mesh-and-its-connectivities)
 - [Using connectivities in field operators](#Using-connectivities-in-field-operators)
 - [Using reductions on connected mesh elements](#Using-reductions-on-connected-mesh-elements)
@@ -151,8 +153,8 @@ This section approaches the pseudo-laplacian by introducing the required APIs pr
 The examples related to unstructured meshes use the mesh below. The edges (in blue) and the cells (in red) are numbered with zero-based indices.
 
 | ![grid_topo](connectivity_numbered_grid.svg) |
-|:--:| 
-| *The mesh with the indices* |
+| :------------------------------------------: |
+|         _The mesh with the indices_          |
 
 +++
 
@@ -204,15 +206,15 @@ cell_values = np_as_located_field(CellDim)(np.array([1.0, 1.0, 2.0, 3.0, 5.0, 8.
 edge_values = np_as_located_field(EdgeDim)(np.zeros((12,)))
 ```
 
-| ![cell_values](connectivity_cell_field.svg) | 
-|:--:| 
-| *Cell values* |
+| ![cell_values](connectivity_cell_field.svg) |
+| :-----------------------------------------: |
+|                _Cell values_                |
 
 +++
 
-You can transform fields (or tuples of fields) over one domain to another domain by using the call operator of the source field with a *field offset* as argument. This transform uses the connectivity between the source and target domains to find the values of adjacent mesh elements.
+You can transform fields (or tuples of fields) over one domain to another domain by using the call operator of the source field with a _field offset_ as argument. This transform uses the connectivity between the source and target domains to find the values of adjacent mesh elements.
 
-To understand this transform, you can look at the edge-to-cell connectivity table `edge_to_cell_table` listed above. This table has the same shape as the output of the transform, that is, one dimension over the edges and another *local* dimension. The table stores indices into a field over cells, the transform essentially gives you another field where the indices have been replaced with the values in the cell field at the corresponding indices.
+To understand this transform, you can look at the edge-to-cell connectivity table `edge_to_cell_table` listed above. This table has the same shape as the output of the transform, that is, one dimension over the edges and another _local_ dimension. The table stores indices into a field over cells, the transform essentially gives you another field where the indices have been replaced with the values in the cell field at the corresponding indices.
 
 Another way to look at it is that transform uses the edge-to-cell connectivity to look up all the cell neighbors of edges, and associates the values of those neighbor cells with each edge.
 
@@ -223,7 +225,7 @@ E2CDim = Dimension("E2C", kind=DimensionKind.LOCAL)
 E2C = FieldOffset("E2C", source=CellDim, target=(EdgeDim,E2CDim))
 ```
 
-Note that the field offset does not contain the actual connectivity table, that's provided through an *offset provider*:
+Note that the field offset does not contain the actual connectivity table, that's provided through an _offset provider_:
 
 ```{code-cell} ipython3
 E2C_offset_provider = NeighborTableOffsetProvider(edge_to_cell_table, EdgeDim, CellDim, 2)
@@ -250,8 +252,8 @@ print("0th adjacent cell's value: {}".format(np.asarray(edge_values)))
 Running the above snippet results in the following edge field:
 
 | ![nearest_cell_values](connectivity_numbered_grid.svg) | $\mapsto$ | ![grid_topo](connectivity_edge_0th_cell.svg) |
-|:--:| :--: | :--: |
-| *Domain (edges)* |  | *Edge values* |
+| :----------------------------------------------------: | :-------: | :------------------------------------------: |
+|                    _Domain (edges)_                    |           |                _Edge values_                 |
 
 +++
 
@@ -268,7 +270,7 @@ def sum_adjacent_cells(cells : Field[[CellDim], float64]) -> Field[[EdgeDim], fl
 @program
 def run_sum_adjacent_cells(cells : Field[[CellDim], float64], out : Field[[EdgeDim], float64]):
     sum_adjacent_cells(cells, out=out)
-    
+
 run_sum_adjacent_cells(cell_values, edge_values, offset_provider={"E2C": E2C_offset_provider})
 
 print("sum of adjacent cells: {}".format(np.asarray(edge_values)))
@@ -277,21 +279,22 @@ print("sum of adjacent cells: {}".format(np.asarray(edge_values)))
 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:
 
 | ![nearest_cell_values](connectivity_numbered_grid.svg) | $\mapsto$ | ![cell_values](connectivity_edge_cell_sum.svg) |
-|:--:| :--: | :--: |
-| *Domain (edges)* |  | *Edge values* |
+| :----------------------------------------------------: | :-------: | :--------------------------------------------: |
+|                    _Domain (edges)_                    |           |                 _Edge values_                  |
 
-+++ 
++++
 
 #### Using conditionals on fields
 
 Additionally to the `neighbor_sum` function, other builtins have been implemented. One of these is the `where`.
 This function takes 3 input arguments:
- - mask: a field with dtype boolean
- - true branch: a tuple, a field, or a scalar
- - false branch: a tuple, a field, of a scalar
-The mask can be directly a field of booleans (e.g. `Field[[CellDim], bool]`) or an expression evaluating to this type (e.g. `Field[[CellDim], float64] > 3`).
-The `where` builtin loops over each entry of the mask and returns values corresponding to the same indexes of either the true or the false branch. 
-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:
+
+- mask: a field with dtype boolean
+- true branch: a tuple, a field, or a scalar
+- false branch: a tuple, a field, of a scalar
+  The mask can be directly a field of booleans (e.g. `Field[[CellDim], bool]`) or an expression evaluating to this type (e.g. `Field[[CellDim], float64] > 3`).
+  The `where` builtin loops over each entry of the mask and returns values corresponding to the same indexes of either the true or the false branch.
+  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 = np_as_located_field(CellDim, KDim)(np.zeros(shape=grid_shape, dtype=bool))
@@ -302,7 +305,7 @@ b = 6.0
 def conditional(mask: Field[[CellDim, KDim], bool], a: Field[[CellDim, KDim], float64], b: float
 ) -> Field[[CellDim, KDim], float64]:
     return where(mask, a, b)
-    
+
 conditional(mask, a, b, out=result_where, offset_provider={})
 print("where return: {}".format(np.asarray(result_where)))
 ```
@@ -319,20 +322,20 @@ result_2 = np_as_located_field(CellDim, KDim)(np.zeros(shape=grid_shape))
 def _conditional_tuple(mask: Field[[CellDim, KDim], bool], a: Field[[CellDim, KDim], float64], b: float
 ) -> tuple[Field[[CellDim, KDim], float64], Field[[CellDim, KDim], float64]]:
     return where(mask, (a, b), (b, a))
-    
+
 @program
-def conditional_tuple(mask: Field[[CellDim, KDim], bool], a: Field[[CellDim, KDim], float64], b: float, 
+def conditional_tuple(mask: Field[[CellDim, KDim], bool], a: Field[[CellDim, KDim], float64], b: float,
 result_1: Field[[CellDim, KDim], float64], result_2: Field[[CellDim, KDim], float64]
 ):
      _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))))
 ```
 
-The `where` builtin also allows for nesting of tuples. In this scenario, it will first perform an unrolling: 
+The `where` builtin also allows for nesting of tuples. In this scenario, it will first perform an unrolling:
 
-```where(mask, ((a, b), (b, a)), ((c, d), (d, c)))``` --> ```where(mask, (a, b), (c, d))``` and ```where(mask, (b, a), (d, c))```
+`where(mask, ((a, b), (b, a)), ((c, d), (d, c)))` --> `where(mask, (a, b), (c, d))` and `where(mask, (b, a), (d, c))`
 
 and then combine results to match the return type:
 
@@ -353,7 +356,7 @@ def _conditional_tuple_nested(
     tuple[Field[[CellDim, KDim], float64], Field[[CellDim, KDim], float64]],
 ]:
     return where(mask, ((a, b), (b, a)), ((c, d), (d, c)))
-    
+
 @program
 def conditional_tuple_nested(
     mask: Field[[CellDim, KDim], bool], a: Field[[CellDim, KDim], float64], b: Field[[CellDim, KDim], float64], c: Field[[CellDim, KDim], float64], d: Field[[CellDim, KDim], float64],
@@ -446,4 +449,4 @@ As a closure, here is an example of chaining field operators, which is very simp
 def pseudo_laplap(cells : Field[[CellDim], float64],
                   edge_weights : Field[[CellDim, C2EDim], float64]) -> Field[[CellDim], float64]:
     return pseudo_lap(pseudo_lap(cells, edge_weights), edge_weights)
-```
\ No newline at end of file
+```
diff --git a/pyproject.toml b/pyproject.toml
index 8849ca1c..4d9ab7df 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -2,6 +2,96 @@
 build-backend = "setuptools.build_meta"
 requires = ["setuptools>=60.6", "wheel", "cython"]
 
+# ---- Project description ----
+# -- Standard options (PEP 621) --
+[project]
+authors = [{name = "ETH Zurich", email = "gridtools@cscs.ch"}]
+classifiers = [
+  "Development Status :: 4 - Beta",
+  "Environment :: Console",
+  "Environment :: GPU :: NVIDIA CUDA",
+  "Intended Audience :: Science/Research",
+  "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
+  "Operating System :: POSIX",
+  "Programming Language :: Python",
+  "Programming Language :: Python :: 3.8",
+  "Programming Language :: Python :: 3.9",
+  "Programming Language :: Python :: 3.10",
+  "Programming Language :: Python :: Implementation :: CPython",
+  "Topic :: Scientific/Engineering :: Atmospheric Science",
+  "Topic :: Scientific/Engineering :: Mathematics",
+  "Topic :: Scientific/Engineering :: Physics"
+]
+dependencies = [
+  "astunparse>=1.6.3;python_version<'3.9'",
+  "attrs>=21.3",
+  "black>=22.3",
+  "boltons>=20.0",
+  "cached-property>=1.5",
+  "click>=7.1",
+  "cmake>=3.22",
+  "cytoolz>=0.11",
+  "deepdiff>=5.8",
+  "devtools>=0.6",
+  "frozendict>=2.3",
+  "gridtools-cpp>=2.2.3,==2.*",
+  "importlib-resources>=5.0;python_version<'3.9'",
+  "jinja2>=2.10",
+  "lark>=1.1.2",
+  "mako>=1.1",
+  "networkx>=2.4",
+  "ninja>=1.10",
+  "numpy>=1.15",
+  "packaging>=20.0",
+  "pybind11>=2.5",
+  "scipy>=1.4",
+  "setuptools>=60.6",
+  "tabulate>=0.8",
+  "toolz>=0.11",
+  "typing-extensions>=4.2",
+  "xxhash>=1.4.4,<3.1.0"
+]
+description = "Python library for generating high-performance implementations of stencil kernels for weather and climate modeling from a domain-specific language (DSL)"
+dynamic = ["version"]
+keywords = [
+  "gridtools",
+  "stencil",
+  "weather",
+  "climate",
+  "performance",
+  "portable",
+  "hpc"
+]
+license = {text = "GPL-3.0-or-later"}
+name = "gt4py"
+readme = "README.md"
+requires-python = ">=3.8"
+
+[project.optional-dependencies]
+cuda = ["cupy"]
+cuda110 = ["cupy-cuda110"]
+cuda111 = ["cupy-cuda111"]
+cuda112 = ["cupy-cuda112"]
+cuda113 = ["cupy-cuda113"]
+cuda114 = ["cupy-cuda114"]
+cuda115 = ["cupy-cuda115"]
+cuda116 = ["cupy-cuda116"]
+cuda117 = ["cupy-cuda117"]
+cuda11x = ["cupy-cuda11x"]
+dace = ["dace>=0.14.2,<0.15", "sympy"]
+formatting = ["clang-format>=9.0"]
+testing = ["hypothesis>=4.14", "pytest>=6.1"]
+
+[project.scripts]
+gtpyc = "gt4py.cartesian.cli:gtpyc"
+
+[project.urls]
+Documentation = "https://gridtools.github.io/gt4py"
+Homepage = "https://gridtools.github.io/"
+Source = "https://github.com/GridTools/gt4py"
+
+# ---- Other tools ----
+# -- black --
 [tool.black]
 exclude = '''
 /(
@@ -19,4 +109,222 @@ exclude = '''
 '''
 include = '\.pyi?$'
 line-length = 100
+max-line-length = 100
 target_version = ["py310"]
+
+# -- coverage --
+[tool.coverage]
+
+[tool.coverage.html]
+directory = 'tests/_reports/coverage_html'
+
+[tool.coverage.paths]
+source = ['src/', '.tox/py*/lib/python3.*/site-packages/']
+
+[tool.coverage.report]
+# Regexes for lines to exclude from consideration
+exclude_lines = [
+  'raise AssertionError',  # Don't complain if tests don't hit defensive assertion code
+  'raise NotImplementedError',  # Don't complain if tests don't hit defensive assertion code
+  'if 0:',  # Don't complain if non-runnable code isn't run
+  'if __name__ == .__main__.:'  # Don't complain if non-runnable code isn't run
+]
+ignore_errors = true
+
+[tool.coverage.run]
+branch = true
+source_pkgs = ['gt4py']
+
+# -- flake8  --
+# flake8-pyproject plugin is used to load configuration settings
+# from this file, since flake8 doesn't support it natively.
+[tool.flake8]
+count = true
+doctests = true
+exclude = [
+  '.eggs',
+  '.gt_cache',
+  '.ipynb_checkpoints',
+  '.tox',
+  '_local/',
+  'build',
+  'dist',
+  'docs',
+  'tests/_disabled',
+  'setup.py'
+]
+ignore = [
+  'B008',  # Do not perform function calls in argument defaults
+  'B028',  # Consider replacing f"'{foo}'" with f"{foo!r}"  # TODO: review this ignore
+  'D1',  # Public code object needs docstring
+  'DAR',  # Disable dargling errors by default
+  'E203',  # Whitespace before ':' (black formatter breaks this sometimes)
+  'E501',  # Line too long (using Bugbear's B950 warning)
+  'W503'  # Line break occurred before a binary operator
+]
+max-complexity = 15
+max-line-length = 100
+per-file-ignores = ['src/gt4py/eve/extended_typing.py:F401,F405']
+rst-roles = [
+  'py:mod, mod',
+  'py:func, func',
+  'py:data, data',
+  'py:const, const',
+  'py:class, class',
+  'py:meth, meth',
+  'py:attr, attr',
+  'py:exc, exc',
+  'py:obj, obj'
+]
+
+# -- isort --
+[tool.isort]
+combine_as_imports = true
+group_by_package = true
+known_first_party = ["gt4py", "tests", "__externals__", "__gtscript__"]
+known_third_party = [
+  "attr",
+  "black",
+  "boltons",
+  "cached_property",
+  "click",
+  "dace",
+  "devtools",
+  "factory",
+  "hypothesis",
+  "importlib_resources",
+  "jinja2",
+  "mako",
+  "networkx",
+  "numpy",
+  "packaging",
+  "pybind11",
+  "pytest",
+  "pytest_factoryboy",
+  "setuptools",
+  "tabulate",
+  "typing_extensions",
+  "xxhash"
+]
+lexicographical = true
+line_length = 100
+lines_after_imports = 2
+profile = "black"
+skip_gitignore = true
+skip_glob = ["*.venv/**", "_local/**"]
+
+# -- mypy  --
+[tool.mypy]
+disallow_incomplete_defs = true
+exclude = ['^setup\.py$', '^build/.*py$', '^ci/*.py$', '^tests/*.py$']
+ignore_missing_imports = true
+install_types = true
+namespace_packages = false
+no_implicit_optional = true
+no_impliit_reexport = true
+# pretty = true
+show_column_numbers = true
+show_error_codes = true
+warn_redundant_casts = true
+warn_unused_configs = true
+warn_unused_ignores = true
+
+# GT4Py configs
+[[tool.mypy.overrides]]
+ignore_missing_imports = false
+module = "gt4py.*"
+
+[[tool.mypy.overrides]]
+# The following ignore_errors are only temporary.
+# TODO: Fix errors and enable these settings.
+disallow_incomplete_defs = false
+disallow_untyped_defs = false
+follow_imports = "silent"
+module = "gt4py.cartesian.*"
+warn_unused_ignores = false
+
+[[tool.mypy.overrides]]
+ignore_errors = true
+module = "gt4py.cartesian.backend.pyext_builder"
+
+[[tool.mypy.overrides]]
+ignore_errors = true
+module = "gt4py.cartesian.frontend.nodes"
+
+[[tool.mypy.overrides]]
+ignore_errors = true
+module = "gt4py.cartesian.frontend.node_util"
+
+[[tool.mypy.overrides]]
+ignore_errors = true
+module = "gt4py.cartesian.frontend.gtscript_frontend"
+
+[[tool.mypy.overrides]]
+ignore_errors = true
+module = "gt4py.cartesian.frontend.defir_to_gtir"
+
+[[tool.mypy.overrides]]
+ignore_errors = true
+module = "gt4py.cartesian.frontend.meta"
+
+[[tool.mypy.overrides]]
+disallow_untyped_defs = true
+module = "gt4py.eve.*"
+
+[[tool.mypy.overrides]]
+module = "gt4py.eve.extended_typing"
+warn_unused_ignores = false
+
+[[tool.mypy.overrides]]
+# TODO: Make this false and fix errors
+allow_untyped_defs = true
+follow_imports = "silent"
+module = "gt4py.storage.*"
+warn_unused_ignores = false
+
+[[tool.mypy.overrides]]
+# # TODO: this should be changed to true after a transition period
+disallow_incomplete_defs = false
+module = "gt4py.next.*"
+
+[[tool.mypy.overrides]]
+ignore_errors = true
+module = "gt4py.next.ffront.decorator"
+
+[[tool.mypy.overrides]]
+ignore_errors = true
+module = "gt4py.next.type_system.type_translation"
+
+[[tool.mypy.overrides]]
+ignore_errors = true
+module = "gt4py.next.iterator.runtime"
+
+[[tool.mypy.overrides]]
+ignore_errors = true
+module = "gt4py.next.iterator.transforms.global_tmps"
+
+# -- pytest --
+[tool.pytest]
+
+[tool.pytest.ini_options]
+markers = [
+  "requires_atlas",  # mark tests that require 'atlas4py' bindings package
+  "requires_dace",  # mark tests that require 'dace' package
+  "requires_gpu:"  # mark tests that require a NVidia GPU (assume 'cupy' and 'cudatoolkit' are installed)
+]
+norecursedirs = ["dist", "build", "cpp_backend_tests/build*", "_local/*", ".*"]
+testpaths = "tests"
+
+# -- Setuptools-specific --
+[tool.setuptools]
+platforms = ["Linux", "Mac"]
+
+[tool.setuptools.dynamic]
+version = {attr = "gt4py.__about__.__version__"}
+
+[tool.setuptools.package-data]
+"*" = ["*.in", "*.txt"]
+"gt4py" = ["py.typed", "*.md", "*.rst"]
+
+[tool.setuptools.packages]
+find = {namespaces = false, where = ["src"]}
diff --git a/requirements-dev.in b/requirements-dev.in
index efa78a78..e6856231 100644
--- a/requirements-dev.in
+++ b/requirements-dev.in
@@ -1,7 +1,7 @@
 clang-format>=9.0
 cmake>=3.18.1
 cogapp>=3.3
-coverage>=5.0
+coverage[toml]>=5.0
 darglint>=1.6
 devtools>=0.6
 factory-boy>=3.1
@@ -12,6 +12,7 @@ flake8-debugger>=4.0.0
 flake8-docstrings>=1.5.0
 flake8-eradicate>=1.0.0
 flake8-mutable>=1.2.0
+flake8-pyproject>=1.2.2
 flake8-rst-docstrings>=0.0.14
 hypothesis>=4.14
 isort>=5.10
@@ -30,5 +31,6 @@ pytest-xdist[psutil]>=2.4
 setuptools>=40.8.0
 sphinx>=4.4
 sphinx_rtd_theme>=1.0
+tomli>=2.0.1
 tox>=4.2
 types-all
diff --git a/requirements-dev.txt b/requirements-dev.txt
index c3e9bfb3..5ab0b95d 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -2,16 +2,16 @@
 # This file is autogenerated by pip-compile with Python 3.8
 # by the following command:
 #
-#    "tox run -e requirements-py38"
+#    "tox run -e requirements-common"
 #
 aenum==3.1.11             # via dace
 alabaster==0.7.13         # via sphinx
 asttokens==2.2.1          # via devtools
 astunparse==1.6.3 ; python_version < "3.9"  # via dace, gt4py (pyproject.toml)
 attrs==22.2.0             # via flake8-bugbear, flake8-eradicate, gt4py (pyproject.toml), hypothesis, jsonschema, pytest
-babel==2.11.0             # via sphinx
+babel==2.12.1             # via sphinx
 black==23.1.0             # via gt4py (pyproject.toml)
-boltons==21.0.0           # via gt4py (pyproject.toml)
+boltons==23.0.0           # via gt4py (pyproject.toml)
 build==0.10.0             # via pip-tools
 cached-property==1.5.2    # via gt4py (pyproject.toml)
 cachetools==5.3.0         # via tox
@@ -19,14 +19,14 @@ certifi==2022.12.7        # via requests
 cffi==1.15.1              # via cryptography
 cfgv==3.3.1               # via pre-commit
 chardet==5.1.0            # via tox
-charset-normalizer==3.0.1  # via requests
+charset-normalizer==3.1.0  # via requests
 clang-format==15.0.7      # via -r requirements-dev.in, gt4py (pyproject.toml)
 click==8.1.3              # via black, flask, gt4py (pyproject.toml), pip-tools
 cmake==3.25.2             # via -r requirements-dev.in, gt4py (pyproject.toml)
 cogapp==3.3.0             # via -r requirements-dev.in
 colorama==0.4.6           # via tox
-coverage[toml]==7.1.0     # via -r requirements-dev.in, pytest-cov
-cryptography==39.0.1      # via types-paramiko, types-pyopenssl, types-redis
+coverage[toml]==7.2.1     # via -r requirements-dev.in, pytest-cov
+cryptography==39.0.2      # via types-paramiko, types-pyopenssl, types-redis
 cytoolz==0.12.1           # via gt4py (pyproject.toml)
 dace==0.14.2              # via gt4py (pyproject.toml)
 darglint==1.8.1           # via -r requirements-dev.in
@@ -35,31 +35,32 @@ devtools==0.10.0          # via -r requirements-dev.in, gt4py (pyproject.toml)
 dill==0.3.6               # via dace
 distlib==0.3.6            # via virtualenv
 docutils==0.18.1          # via restructuredtext-lint, sphinx, sphinx-rtd-theme
-eradicate==2.1.0          # via flake8-eradicate
+eradicate==2.2.0          # via flake8-eradicate
 exceptiongroup==1.1.0     # via hypothesis, pytest
 execnet==1.9.0            # via pytest-cache, pytest-xdist
 executing==1.2.0          # via devtools
 factory-boy==3.2.1        # via -r requirements-dev.in, pytest-factoryboy
-faker==16.8.1             # via factory-boy
-fastjsonschema==2.16.2    # via nbformat
+faker==17.6.0             # via factory-boy
+fastjsonschema==2.16.3    # via nbformat
 filelock==3.9.0           # via tox, virtualenv
-flake8==5.0.4             # via -r requirements-dev.in, flake8-bugbear, flake8-builtins, flake8-debugger, flake8-docstrings, flake8-eradicate, flake8-mutable, flake8-rst-docstrings
-flake8-bugbear==23.1.20   # via -r requirements-dev.in
+flake8==5.0.4             # via -r requirements-dev.in, flake8-bugbear, flake8-builtins, flake8-debugger, flake8-docstrings, flake8-eradicate, flake8-mutable, flake8-pyproject, flake8-rst-docstrings
+flake8-bugbear==23.2.13   # via -r requirements-dev.in
 flake8-builtins==2.1.0    # via -r requirements-dev.in
 flake8-debugger==4.1.2    # via -r requirements-dev.in
 flake8-docstrings==1.7.0  # via -r requirements-dev.in
 flake8-eradicate==1.4.0   # via -r requirements-dev.in
 flake8-mutable==1.2.0     # via -r requirements-dev.in
+flake8-pyproject==1.2.2   # via -r requirements-dev.in
 flake8-rst-docstrings==0.3.0  # via -r requirements-dev.in
-flask==2.2.2              # via dace
-frozendict==2.3.4         # via gt4py (pyproject.toml)
+flask==2.2.3              # via dace
+frozendict==2.3.5         # via gt4py (pyproject.toml)
 gridtools-cpp==2.2.3      # via gt4py (pyproject.toml)
-hypothesis==6.68.1        # via -r requirements-dev.in, gt4py (pyproject.toml)
-identify==2.5.17          # via pre-commit
+hypothesis==6.68.2        # via -r requirements-dev.in, gt4py (pyproject.toml)
+identify==2.5.18          # via pre-commit
 idna==3.4                 # via requests
 imagesize==1.4.1          # via sphinx
 importlib-metadata==6.0.0  # via flask, sphinx
-importlib-resources==5.12.0  # via jsonschema
+importlib-resources==5.12.0 ; python_version < "3.9"  # via gt4py (pyproject.toml), jsonschema
 inflection==0.5.1         # via pytest-factoryboy
 iniconfig==2.0.0          # via pytest
 isort==5.12.0             # via -r requirements-dev.in
@@ -67,33 +68,33 @@ itsdangerous==2.1.2       # via flask
 jinja2==3.1.2             # via flask, gt4py (pyproject.toml), sphinx
 jsonschema==4.17.3        # via nbformat
 jupyter-core==5.2.0       # via nbformat
-jupytext==1.14.4          # via -r requirements-dev.in
+jupytext==1.14.5          # via -r requirements-dev.in
 lark==1.1.5               # via gt4py (pyproject.toml)
 mako==1.2.4               # via gt4py (pyproject.toml)
-markdown-it-py==2.1.0     # via jupytext, mdit-py-plugins
+markdown-it-py==2.2.0     # via jupytext, mdit-py-plugins
 markupsafe==2.1.2         # via jinja2, mako, werkzeug
 mccabe==0.7.0             # via flake8
-mdit-py-plugins==0.3.3    # via jupytext
+mdit-py-plugins==0.3.5    # via jupytext
 mdurl==0.1.2              # via markdown-it-py
 mpmath==1.2.1             # via sympy
-mypy==1.0.0               # via -r requirements-dev.in
+mypy==1.0.1               # via -r requirements-dev.in
 mypy-extensions==1.0.0    # via black, mypy
 nbformat==5.7.3           # via jupytext
 networkx==3.0             # via dace, gt4py (pyproject.toml)
 ninja==1.11.1             # via gt4py (pyproject.toml)
 nodeenv==1.7.0            # via pre-commit
-numpy==1.23.5             # via dace, gt4py (pyproject.toml), scipy, types-jack-client
+numpy==1.24.2             # via dace, gt4py (pyproject.toml), scipy, types-jack-client
 ordered-set==4.1.0        # via deepdiff
-orjson==3.8.6             # via deepdiff
+orjson==3.8.7             # via deepdiff
 packaging==23.0           # via black, build, gt4py (pyproject.toml), pyproject-api, pytest, sphinx, tox
 pathspec==0.11.0          # via black
-pip-tools==6.12.2         # via -r requirements-dev.in
-pipdeptree==2.3.3         # via -r requirements-dev.in
+pip-tools==6.12.3         # via -r requirements-dev.in
+pipdeptree==2.5.2         # via -r requirements-dev.in
 pkgutil-resolve-name==1.3.10  # via jsonschema
-platformdirs==3.0.0       # via black, jupyter-core, tox, virtualenv
+platformdirs==3.1.0       # via black, jupyter-core, tox, virtualenv
 pluggy==1.0.0             # via pytest, tox
 ply==3.11                 # via dace
-pre-commit==3.0.4         # via -r requirements-dev.in
+pre-commit==3.1.1         # via -r requirements-dev.in
 psutil==5.9.4             # via -r requirements-dev.in, pytest-xdist
 pybind11==2.10.3          # via gt4py (pyproject.toml)
 pycodestyle==2.9.1        # via flake8, flake8-debugger
@@ -104,7 +105,7 @@ pygments==2.14.0          # via -r requirements-dev.in, flake8-rst-docstrings, s
 pyproject-api==1.5.0      # via tox
 pyproject-hooks==1.0.0    # via build
 pyrsistent==0.19.3        # via jsonschema
-pytest==7.2.1             # via -r requirements-dev.in, gt4py (pyproject.toml), pytest-cache, pytest-cov, pytest-factoryboy, pytest-xdist
+pytest==7.2.2             # via -r requirements-dev.in, gt4py (pyproject.toml), pytest-cache, pytest-cov, pytest-factoryboy, pytest-xdist
 pytest-cache==1.0         # via -r requirements-dev.in
 pytest-cov==4.0.0         # via -r requirements-dev.in
 pytest-factoryboy==2.5.1  # via -r requirements-dev.in
@@ -114,7 +115,7 @@ pytz==2022.7.1            # via babel
 pyyaml==6.0               # via dace, jupytext, pre-commit
 requests==2.28.2          # via dace, sphinx
 restructuredtext-lint==1.4.0  # via flake8-rst-docstrings
-scipy==1.10.0             # via gt4py (pyproject.toml)
+scipy==1.10.1             # via gt4py (pyproject.toml)
 six==1.16.0               # via asttokens, astunparse, python-dateutil
 snowballstemmer==2.2.0    # via pydocstyle, sphinx
 sortedcontainers==2.4.0   # via hypothesis
@@ -130,41 +131,41 @@ sphinxcontrib-serializinghtml==1.1.5  # via sphinx
 sympy==1.9                # via dace, gt4py (pyproject.toml)
 tabulate==0.9.0           # via gt4py (pyproject.toml)
 toml==0.10.2              # via jupytext
-tomli==2.0.1              # via black, build, coverage, mypy, pyproject-api, pyproject-hooks, pytest, tox
+tomli==2.0.1              # via -r requirements-dev.in, black, build, coverage, flake8-pyproject, mypy, pyproject-api, pyproject-hooks, pytest, tox
 toolz==0.12.0             # via cytoolz, gt4py (pyproject.toml)
-tox==4.4.5                # via -r requirements-dev.in
+tox==4.4.6                # via -r requirements-dev.in
 traitlets==5.9.0          # via jupyter-core, nbformat
-types-aiofiles==22.1.0.8  # via types-all
+types-aiofiles==22.1.0.9  # via types-all
 types-all==1.0.0          # via -r requirements-dev.in
-types-annoy==1.17.8.1     # via types-all
+types-annoy==1.17.8.2     # via types-all
 types-atomicwrites==1.4.5.1  # via types-all
 types-backports==0.1.3    # via types-all
 types-backports-abc==0.5.2  # via types-all
-types-bleach==6.0.0.0     # via types-all
-types-boto==2.49.18.5     # via types-all
-types-cachetools==5.3.0.0  # via types-all
+types-bleach==6.0.0.1     # via types-all
+types-boto==2.49.18.6     # via types-all
+types-cachetools==5.3.0.4  # via types-all
 types-certifi==2021.10.8.3  # via types-all
-types-cffi==1.15.1.5      # via types-jack-client
+types-cffi==1.15.1.7      # via types-jack-client
 types-characteristic==14.3.7  # via types-all
-types-chardet==5.0.4.1    # via types-all
+types-chardet==5.0.4.2    # via types-all
 types-click==7.1.8        # via types-all, types-flask
-types-click-spinner==0.1.13.2  # via types-all
-types-colorama==0.4.15.7  # via types-all
-types-contextvars==2.4.7  # via types-all
-types-croniter==1.3.2.4   # via types-all
+types-click-spinner==0.1.13.3  # via types-all
+types-colorama==0.4.15.8  # via types-all
+types-contextvars==2.4.7.1  # via types-all
+types-croniter==1.3.2.6   # via types-all
 types-cryptography==3.3.23.2  # via types-all, types-openssl-python, types-pyjwt
 types-dataclasses==0.6.6  # via types-all
-types-dateparser==1.1.4.7  # via types-all
-types-datetimerange==2.0.0.1  # via types-all
-types-decorator==5.1.8.2  # via types-all
-types-deprecated==1.2.9   # via types-all
-types-docopt==0.6.11.1    # via types-all
-types-docutils==0.19.1.3  # via types-all
-types-emoji==2.1.0.1      # via types-all
+types-dateparser==1.1.4.8  # via types-all
+types-datetimerange==2.0.0.3  # via types-all
+types-decorator==5.1.8.3  # via types-all
+types-deprecated==1.2.9.1  # via types-all
+types-docopt==0.6.11.3    # via types-all
+types-docutils==0.19.1.6  # via types-all
+types-emoji==2.1.0.2      # via types-all
 types-enum34==1.1.8       # via types-all
 types-fb303==1.0.0        # via types-all, types-scribe
 types-filelock==3.2.7     # via types-all
-types-first==2.0.5        # via types-all
+types-first==2.0.5.1      # via types-all
 types-flask==1.1.6        # via types-all
 types-freezegun==1.1.10   # via types-all
 types-frozendict==2.0.9   # via types-all
@@ -172,66 +173,66 @@ types-futures==3.3.8      # via types-all
 types-geoip2==3.0.0       # via types-all
 types-ipaddress==1.0.8    # via types-all, types-maxminddb
 types-itsdangerous==1.1.6  # via types-all
-types-jack-client==0.5.10.5  # via types-all
+types-jack-client==0.5.10.7  # via types-all
 types-jinja2==2.11.9      # via types-all, types-flask
 types-kazoo==0.1.3        # via types-all
-types-markdown==3.4.2.4   # via types-all
+types-markdown==3.4.2.5   # via types-all
 types-markupsafe==1.1.10  # via types-all, types-jinja2
 types-maxminddb==1.5.0    # via types-all, types-geoip2
-types-mock==5.0.0.4       # via types-all
-types-mypy-extensions==1.0.0.1  # via types-all
+types-mock==5.0.0.5       # via types-all
+types-mypy-extensions==1.0.0.3  # via types-all
 types-nmap==0.1.6         # via types-all
 types-openssl-python==0.1.3  # via types-all
 types-orjson==3.6.2       # via types-all
-types-paramiko==3.0.0.3   # via types-all, types-pysftp
+types-paramiko==3.0.0.4   # via types-all, types-pysftp
 types-pathlib2==2.3.0     # via types-all
-types-pillow==9.4.0.12    # via types-all
+types-pillow==9.4.0.17    # via types-all
 types-pkg-resources==0.1.3  # via types-all
-types-polib==1.1.12.1     # via types-all
-types-protobuf==4.21.0.6  # via types-all
-types-pyaudio==0.2.16.5   # via types-all
-types-pycurl==7.45.2.2    # via types-all
-types-pyfarmhash==0.3.1   # via types-all
+types-polib==1.2.0.0      # via types-all
+types-protobuf==4.22.0.0  # via types-all
+types-pyaudio==0.2.16.6   # via types-all
+types-pycurl==7.45.2.3    # via types-all
+types-pyfarmhash==0.3.1.1  # via types-all
 types-pyjwt==1.7.1        # via types-all
 types-pymssql==2.1.0      # via types-all
-types-pymysql==1.0.19.3   # via types-all
-types-pyopenssl==23.0.0.3  # via types-redis
-types-pyrfc3339==1.1.1.2  # via types-all
-types-pysftp==0.2.17.1    # via types-all
-types-python-dateutil==2.8.19.7  # via types-all, types-datetimerange
-types-python-gflags==3.1.7.1  # via types-all
-types-python-slugify==8.0.0.0  # via types-all
-types-pytz==2022.7.1.0    # via types-all, types-tzlocal
-types-pyvmomi==8.0.0.0    # via types-all
-types-pyyaml==6.0.12.6    # via types-all
-types-redis==4.5.1.1      # via types-all
-types-requests==2.28.11.13  # via types-all
-types-retry==0.9.9.1      # via types-all
+types-pymysql==1.0.19.5   # via types-all
+types-pyopenssl==23.0.0.4  # via types-redis
+types-pyrfc3339==1.1.1.3  # via types-all
+types-pysftp==0.2.17.2    # via types-all
+types-python-dateutil==2.8.19.10  # via types-all, types-datetimerange
+types-python-gflags==3.1.7.2  # via types-all
+types-python-slugify==8.0.0.1  # via types-all
+types-pytz==2022.7.1.2    # via types-all, types-tzlocal
+types-pyvmomi==8.0.0.1    # via types-all
+types-pyyaml==6.0.12.8    # via types-all
+types-redis==4.5.1.4      # via types-all
+types-requests==2.28.11.15  # via types-all
+types-retry==0.9.9.2      # via types-all
 types-routes==2.5.0       # via types-all
 types-scribe==2.0.0       # via types-all
-types-simplejson==3.18.0.0  # via types-all
-types-singledispatch==4.0.0.0  # via types-all
-types-six==1.16.21.4      # via types-all
-types-tabulate==0.9.0.0   # via types-all
-types-termcolor==1.1.6    # via types-all
-types-toml==0.10.8.3      # via types-all
+types-simplejson==3.18.0.1  # via types-all
+types-singledispatch==4.0.0.1  # via types-all
+types-six==1.16.21.6      # via types-all
+types-tabulate==0.9.0.1   # via types-all
+types-termcolor==1.1.6.1  # via types-all
+types-toml==0.10.8.5      # via types-all
 types-tornado==5.1.1      # via types-all
-types-typed-ast==1.5.8.3  # via types-all
-types-tzlocal==4.2.2.2    # via types-all
-types-ujson==5.7.0.0      # via types-all
-types-urllib3==1.26.25.6  # via types-requests
-types-waitress==2.1.4.4   # via types-all
+types-typed-ast==1.5.8.4  # via types-all
+types-tzlocal==4.2.2.3    # via types-all
+types-ujson==5.7.0.1      # via types-all
+types-urllib3==1.26.25.8  # via types-requests
+types-waitress==2.1.4.5   # via types-all
 types-werkzeug==1.0.9     # via types-all, types-flask
-types-xxhash==3.0.5.1     # via types-all
-typing-extensions==4.4.0  # via black, gt4py (pyproject.toml), mypy, pytest-factoryboy
+types-xxhash==3.0.5.2     # via types-all
+typing-extensions==4.5.0  # via black, gt4py (pyproject.toml), mypy, pytest-factoryboy
 urllib3==1.26.14          # via requests
-virtualenv==20.19.0       # via pre-commit, tox
+virtualenv==20.20.0       # via pre-commit, tox
 websockets==10.4          # via dace
-werkzeug==2.2.2           # via flask
+werkzeug==2.2.3           # via flask
 wheel==0.38.4             # via astunparse, pip-tools
 xxhash==3.0.0             # via gt4py (pyproject.toml)
-zipp==3.14.0              # via importlib-metadata, importlib-resources
+zipp==3.15.0              # via importlib-metadata, importlib-resources
 
 # The following packages are considered to be unsafe in a requirements file:
-pip==23.0                 # via pip-tools
-setuptools==67.2.0        # via -r requirements-dev.in, gt4py (pyproject.toml), nodeenv, pip-tools, sphinxcontrib-jquery
+pip==23.0.1               # via pip-tools
+setuptools==67.5.1        # via -r requirements-dev.in, gt4py (pyproject.toml), nodeenv, pip-tools, sphinxcontrib-jquery
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 25b22557..00000000
--- a/setup.cfg
+++ /dev/null
@@ -1,310 +0,0 @@
-# This file is mainly used to configure package creation with setuptools.
-# Documentation:
-# http://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files
-#
-# Additionally, it stores settings of other development tools (linters, type checkers, etc..
-
-[metadata]
-name = gt4py
-version = attr: gt4py.__about__.__version__
-author = ETH Zurich
-author_email = gridtools@cscs.ch
-description = Python library for generating high-performance implementations of stencil kernels for weather and climate modeling from a domain-specific language (DSL)
-license = GPL-3.0-or-later
-license_files = LICENSE.txt
-long_description = file: README.rst
-long_description_content_type = text/x-rst
-url = https://gridtools.github.io/gt4py
-project_urls =
-    Repository = https://github.com/GridTools/gt4py
-    Homepage = https://gridtools.github.io/
-    Documentation = https://gridtools.github.io/gt4py
-platforms = Linux, Mac
-
-# From: https://pypi.python.org/pypi?%3Aaction=list_classifiers
-classifiers =
-    Development Status :: 4 - Beta
-    Environment :: Console
-    Environment :: GPU :: NVIDIA CUDA
-    Intended Audience :: Science/Research
-    License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
-    Operating System :: POSIX
-    Programming Language :: Python
-    Programming Language :: Python :: 3.8
-    Programming Language :: Python :: 3.9
-    Programming Language :: Python :: 3.10
-    Programming Language :: Python :: Implementation :: CPython
-    Topic :: Scientific/Engineering :: Atmospheric Science
-    Topic :: Scientific/Engineering :: Mathematics
-    Topic :: Scientific/Engineering :: Physics
-
-[options]
-zip_safe = False
-packages = find:
-package_dir =
-    =src
-include_package_data = True
-python_requires = >= 3.8
-install_requires =
-    astunparse>=1.6.3;python_version<'3.9'
-    attrs>=21.3
-    black>=22.3
-    boltons>=20.0
-    cached-property>=1.5
-    click>=7.1
-    cmake>=3.22
-    cytoolz>=0.11
-    deepdiff>=5.8
-    devtools>=0.6
-    frozendict>=2.3
-    gridtools-cpp>=2.2.3,==2.*
-    jinja2>=2.10
-    lark>=1.1.2
-    mako>=1.1
-    networkx>=2.4
-    ninja>=1.10
-    numpy>=1.15
-    packaging>=20.0
-    pybind11>=2.5
-    scipy>=1.4
-    setuptools>=60.6
-    tabulate>=0.8
-    toolz>=0.11
-    typing-extensions>=4.2
-    xxhash>=1.4.4,<3.1.0
-
-[options.extras_require]
-# Add here additional requirements for extra features, to install with:
-# `pip install gt4py[cuda90]` like:
-cuda =
-    cupy
-cuda110 =
-    cupy-cuda110
-cuda111 =
-    cupy-cuda111
-cuda112 =
-    cupy-cuda112
-cuda113 =
-    cupy-cuda113
-cuda114 =
-    cupy-cuda114
-cuda115 =
-    cupy-cuda115
-cuda116 =
-    cupy-cuda116
-cuda117 =
-    cupy-cuda117
-cuda11x =
-    cupy-cuda11x
-dace =
-    dace>=0.14.2,<0.15
-    sympy
-formatting =
-    clang-format>=9.0
-testing =
-    hypothesis>=4.14
-    pytest>=6.1
-
-[options.entry_points]
-# CLI entry points to be installed
-console_scripts =
-    gtpyc = gt4py.cartesian.cli:gtpyc
-
-[options.packages.find]
-where = src
-
-#---- Other tools ----
-#-- coverage --
-[coverage:run]
-branch = True
-source_pkgs =
-    gt4py
-
-[coverage:paths]
-source =
-    src/
-    .tox/py*/lib/python3.*/site-packages/
-
-[coverage:report]
-# Regexes for lines to exclude from consideration
-exclude_lines =
-    # Don't complain if tests don't hit defensive assertion code:
-    raise AssertionError
-    raise NotImplementedError
-
-    # Don't complain if non-runnable code isn't run:
-    if 0:
-    if __name__ == .__main__.:
-
-ignore_errors = True
-
-[coverage:html]
-directory = tests/_reports/coverage_html
-
-#-- flake8 --
-[flake8]
-# Some sane defaults for the code style checker flake8
-max-line-length = 100
-max-complexity = 15
-doctests = true
-ignore =
-    B008    # Do not perform function calls in argument defaults
-    B028    # Consider replacing f"'{foo}'" with f"{foo!r}"  # TODO: review this ignore
-    D1      # Public code object needs docstring
-    DAR     # Disable dargling errors by default
-    E203    # Whitespace before ':' (black formatter breaks this sometimes)
-    E501    # Line too long (using Bugbear's B950 warning)
-    W503    # Line break occurred before a binary operator
-
-exclude =
-    .eggs
-    .gt_cache
-    .ipynb_checkpoints
-    .tox
-    _local/
-    build
-    dist
-    docs
-    tests/_disabled
-    setup.py
-
-per-file-ignores =
-    src/gt4py/eve/extended_typing.py:F401,F405
-
-rst-roles =
-    py:mod, mod,
-    py:func, func,
-    py:data, data,
-    py:const, const,
-    py:class, class,
-    py:meth, meth,
-    py:attr, attr,
-    py:exc, exc,
-    py:obj, obj,
-
-#-- isort --
-[tool:isort]
-skip_glob =
-    *.venv/**
-    _local/**
-skip_gitignore = true
-profile = black
-combine_as_imports = True
-line_length = 100
-lines_after_imports = 2
-lexicographical = True
-group_by_package = True
-default_section = THIRDPARTY
-sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
-known_first_party = gt4py,tests,__externals__,__gtscript__
-known_third_party = attr,black,boltons,cached_property,click,dace,devtools,factory,hypothesis,jinja2,mako,networkx,numpy,packaging,pkg_resources,pybind11,pytest,pytest_factoryboy,setuptools,tabulate,typing_extensions,xxhash
-
-#-- mypy --
-[mypy]
-# Global options
-install_types = True
-ignore_missing_imports = True
-
-# File paths and patterns
-namespace_packages = False
-exclude = (?x)(
-          ^setup\.py$
-          | ^build/.*py$
-          | ^ci/*.py$
-          | ^tests/*.py$
-          )
-
-# Check options
-disallow_incomplete_defs = True
-no_implicit_optional = True
-no_implicit_reexport = True
-
-# Warnings
-warn_unused_configs = True
-warn_unused_ignores = True
-warn_redundant_casts = True
-
-# Formatting
-#pretty = True
-show_column_numbers = True
-show_error_codes = True
-
-# External packages
-[mypy-gridtools_cpp.*]
-ignore_missing_imports = False
-
-# GT4Py configs
-[mypy-gt4py.*]
-ignore_missing_imports = False
-
-[mypy-gt4py.cartesian.*]
-# TODO: Fix errors and enable these settings
-disallow_incomplete_defs = False
-disallow_untyped_defs = False
-follow_imports = silent
-warn_unused_ignores = False
-
-[mypy-gt4py.cartesian.backend.pyext_builder]
-ignore_errors = True
-
-[mypy-gt4py.cartesian.frontend.nodes]
-ignore_errors = True
-
-[mypy-gt4py.cartesian.frontend.node_util]
-ignore_errors = True
-
-[mypy-gt4py.cartesian.frontend.gtscript_frontend]
-ignore_errors = True
-
-[mypy-gt4py.cartesian.frontend.defir_to_gtir]
-ignore_errors = True
-
-[mypy-gt4py.cartesian.frontend.meta]
-ignore_errors = True
-
-[mypy-gt4py.eve.*]
-disallow_untyped_defs = True
-
-[mypy-gt4py.eve.extended_typing]
-warn_unused_ignores = False
-
-[mypy-gt4py.storage.*]
-follow_imports = silent
-warn_unused_ignores = False
-# TODO: Make this False and fix errors
-allow_untyped_defs = True
-
-[mypy-gt4py.next.*]
-## TODO: this should be changed to True after a transition period
-disallow_incomplete_defs = False
-
-## TODO: the following ignore_errors are only temporary, while
-# we work in fixing the wrong typings
-[mypy-gt4py.next.ffront.decorator]
-ignore_errors = True
-
-[mypy-gt4py.next.type_system.type_translation]
-ignore_errors = True
-
-[mypy-gt4py.next.iterator.runtime]
-ignore_errors = True
-
-[mypy-gt4py.next.iterator.transforms.global_tmps]
-ignore_errors = True
-
-#-- pytest --
-[tool:pytest]
-# Set coverage options but do not run it by default
-addopts = --cov-config=setup.cfg
-markers =
-    requires_atlas: mark tests that require 'atlas4py' bindings package
-    requires_dace: mark tests that require 'dace' package
-    requires_gpu: mark tests that require a NVidia GPU (assume 'cupy' and 'cudatoolkit' are installed)
-
-norecursedirs =
-    dist
-    build
-    cpp_backend_tests/build*
-    _local/*
-    .*
-testpaths = tests
diff --git a/src/gt4py/cartesian/backend/module_generator.py b/src/gt4py/cartesian/backend/module_generator.py
index ef8aa10f..5d5714f8 100644
--- a/src/gt4py/cartesian/backend/module_generator.py
+++ b/src/gt4py/cartesian/backend/module_generator.py
@@ -14,10 +14,16 @@
 
 import abc
 import numbers
-import os
+import sys
 from dataclasses import dataclass, field
 from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Set, cast
 
+
+if sys.version_info >= (3, 9):
+    import importlib.resources as importlib_resources
+else:
+    import importlib_resources  # type: ignore[no-redef]
+
 import jinja2
 import numpy
 
@@ -119,7 +125,7 @@ class BaseModuleGenerator(abc.ABC):
     ORIGIN_ARG_NAME = "_origin_"
     SPLITTERS_NAME = "_splitters_"
 
-    TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), "templates", "stencil_module.py.in")
+    TEMPLATE_RESOURCE = "stencil_module.py.in"
 
     _builder: Optional["StencilBuilder"]
     args_data: ModuleData
@@ -128,8 +134,11 @@ class BaseModuleGenerator(abc.ABC):
     def __init__(self, builder: Optional["StencilBuilder"] = None):
         self._builder = builder
         self.args_data = ModuleData()
-        with open(self.TEMPLATE_PATH, "r") as f:
-            self.template = jinja2.Template(f.read())
+        self.template = jinja2.Template(
+            importlib_resources.files("gt4py.cartesian.backend.templates")
+            .joinpath(self.TEMPLATE_RESOURCE)
+            .read_text()
+        )
 
     def __call__(
         self,
diff --git a/src/gt4py/cartesian/backend/templates/__init__.py b/src/gt4py/cartesian/backend/templates/__init__.py
new file mode 100644
index 00000000..6c43e2f1
--- /dev/null
+++ b/src/gt4py/cartesian/backend/templates/__init__.py
@@ -0,0 +1,13 @@
+# GT4Py - GridTools Framework
+#
+# Copyright (c) 2014-2023, ETH Zurich
+# All rights reserved.
+#
+# This file is part of the GT4Py project and the GridTools framework.
+# GT4Py is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or any later
+# version. See the LICENSE.txt file at the top-level directory of this
+# distribution for a copy of the license or check <https://www.gnu.org/licenses/>.
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
diff --git a/src/gt4py/eve/codegen.py b/src/gt4py/eve/codegen.py
index f5f5e156..ebaa551a 100644
--- a/src/gt4py/eve/codegen.py
+++ b/src/gt4py/eve/codegen.py
@@ -112,10 +112,7 @@ def format_python_source(
 ) -> str:
     """Format Python source code using black formatter."""
     python_versions = python_versions or {f"{sys.version_info.major}{sys.version_info.minor}"}
-    target_versions = set(
-        black.TargetVersion[f"PY{v.replace('.', '')}"]  # type: ignore[attr-defined]
-        for v in python_versions
-    )
+    target_versions = set(black.TargetVersion[f"PY{v.replace('.', '')}"] for v in python_versions)
 
     formatted_source = black.format_str(
         source,
diff --git a/src/gt4py/next/ffront/experimental.py b/src/gt4py/next/ffront/experimental.py
new file mode 100644
index 00000000..7dbd4331
--- /dev/null
+++ b/src/gt4py/next/ffront/experimental.py
@@ -0,0 +1,40 @@
+# GT4Py - GridTools Framework
+#
+# Copyright (c) 2014-2023, ETH Zurich
+# All rights reserved.
+#
+# This file is part of the GT4Py project and the GridTools framework.
+# GT4Py is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or any later
+# version. See the LICENSE.txt file at the top-level directory of this
+# distribution for a copy of the license or check <https://www.gnu.org/licenses/>.
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from dataclasses import dataclass
+
+from gt4py.next.type_system import type_specifications as ts
+
+
+@dataclass
+class BuiltInFunction:
+    __gt_type: ts.FunctionType
+
+    def __call__(self, *args, **kwargs):
+        """Act as an empty place holder for the built in function."""
+
+    def __gt_type__(self):
+        return self.__gt_type
+
+
+as_offset = BuiltInFunction(
+    ts.FunctionType(
+        args=[
+            ts.DeferredType(constraint=ts.OffsetType),
+            ts.DeferredType(constraint=ts.FieldType),
+        ],
+        kwargs={},
+        returns=ts.DeferredType(constraint=ts.OffsetType),
+    )
+)
diff --git a/src/gt4py/next/ffront/fbuiltins.py b/src/gt4py/next/ffront/fbuiltins.py
index 2f7f1f7b..3b135410 100644
--- a/src/gt4py/next/ffront/fbuiltins.py
+++ b/src/gt4py/next/ffront/fbuiltins.py
@@ -18,6 +18,7 @@ from dataclasses import dataclass
 from numpy import float32, float64, int32, int64
 
 from gt4py.next.common import Dimension, DimensionKind, Field
+from gt4py.next.ffront.experimental import as_offset  # noqa F401
 from gt4py.next.iterator import runtime
 from gt4py.next.type_system import type_specifications as ts
 
@@ -192,6 +193,7 @@ FUN_BUILTIN_NAMES = [
     "broadcast",
     "where",
     "astype",
+    "as_offset",
 ] + MATH_BUILTIN_NAMES
 
 BUILTIN_NAMES = TYPE_BUILTIN_NAMES + FUN_BUILTIN_NAMES
diff --git a/src/gt4py/next/ffront/foast_passes/type_deduction.py b/src/gt4py/next/ffront/foast_passes/type_deduction.py
index d316d6bd..84d1022c 100644
--- a/src/gt4py/next/ffront/foast_passes/type_deduction.py
+++ b/src/gt4py/next/ffront/foast_passes/type_deduction.py
@@ -718,6 +718,35 @@ class FieldOperatorTypeDeduction(traits.VisitorWithSymbolTableTrait, NodeTransla
             location=node.location,
         )
 
+    def _visit_as_offset(self, node: foast.Call, **kwargs) -> foast.Call:
+        arg_0 = node.args[0].type
+        arg_1 = node.args[1].type
+        assert isinstance(arg_0, ts.OffsetType)
+        assert isinstance(arg_1, ts.FieldType)
+        if not type_info.is_integral(arg_1):
+            raise FieldOperatorTypeDeductionError.from_foast_node(
+                node,
+                msg=f"Incompatible argument in call to `{node.func.id}`. "
+                f"Excepted integer for offset field dtype, but got {arg_1.dtype}"
+                f"{node.location}",
+            )
+
+        if arg_0.source not in arg_1.dims:
+            raise FieldOperatorTypeDeductionError.from_foast_node(
+                node,
+                msg=f"Incompatible argument in call to `{node.func.id}`. "
+                f"{arg_0.source} not in list of offset field dimensions {arg_1.dims}. "
+                f"{node.location}",
+            )
+
+        return foast.Call(
+            func=node.func,
+            args=node.args,
+            kwargs=node.kwargs,
+            type=arg_0,
+            location=node.location,
+        )
+
     def _visit_where(self, node: foast.Call, **kwargs) -> foast.Call:
         mask_type = cast(ts.FieldType, node.args[0].type)
         true_branch_type = node.args[1].type
diff --git a/src/gt4py/next/ffront/foast_to_itir.py b/src/gt4py/next/ffront/foast_to_itir.py
index 0b2a2cc4..a9065669 100644
--- a/src/gt4py/next/ffront/foast_to_itir.py
+++ b/src/gt4py/next/ffront/foast_to_itir.py
@@ -17,8 +17,6 @@ import itertools
 from dataclasses import dataclass, field
 from typing import Any, Callable
 
-import numpy as np
-
 from gt4py.eve import NodeTranslator
 from gt4py.next.common import DimensionKind
 from gt4py.next.ffront import (
@@ -318,9 +316,12 @@ class FieldOperatorLowering(NodeTranslator):
             return self._lift_if_field(node)(
                 im.call_("not_")(to_value(node.operand)(self.visit(node.operand, **kwargs)))
             )
+
+        dtype = type_info.extract_dtype(node.type)
+        assert type_info.is_arithmetic(dtype)
         return self._lift_if_field(node)(
             im.call_(node.op.value)(
-                im.literal_("0", "int"),
+                im.literal_("0", dtype.kind.name.lower()),
                 to_value(node.operand)(self.visit(node.operand, **kwargs)),
             )
         )
@@ -357,10 +358,19 @@ class FieldOperatorLowering(NodeTranslator):
     def _visit_shift(self, node: foast.Call, **kwargs) -> itir.FunCall:
         match node.args[0]:
             case foast.Subscript(value=foast.Name(id=offset_name), index=int(offset_index)):
-                return im.shift_(offset_name, offset_index)(self.visit(node.func, **kwargs))
+                shift_offset = im.shift_(offset_name, offset_index)
             case foast.Name(id=offset_name):
-                return im.shift_(offset_name)(self.visit(node.func, **kwargs))
-        raise FieldOperatorLoweringError("Unexpected shift arguments!")
+                shift_offset = im.shift_(offset_name)
+            case foast.Call(func=foast.Name(id="as_offset")):
+                func_args = node.args[0]
+                offset_dim = func_args.args[0]
+                assert isinstance(offset_dim, foast.Name)
+                shift_offset = im.shift_(
+                    offset_dim.id, im.deref_(self.visit(func_args.args[1], **kwargs))
+                )
+            case _:
+                raise FieldOperatorLoweringError("Unexpected shift arguments!")
+        return shift_offset(self.visit(node.func, **kwargs))
 
     def _make_reduction_expr(
         self,
@@ -388,20 +398,23 @@ class FieldOperatorLowering(NodeTranslator):
         )
 
     def _visit_max_over(self, node: foast.Call, **kwargs) -> itir.FunCall:
-        # TODO(tehrengruber): replace greater_ with max_ builtin as soon as itir supports it
-        init_expr = itir.Literal(value=str(np.finfo(np.float64).min), type="float64")
+        dtype = type_info.extract_dtype(node.type)
+        min_value, _ = type_info.arithmetic_bounds(dtype)
+        init_expr = itir.Literal(value=str(min_value), type=dtype.kind.name.lower())
         return self._make_reduction_expr(
             node,
-            lambda expr: im.call_("if_")(im.greater_("acc", expr), "acc", expr),
+            lambda expr: im.call_("maximum")("acc", expr),
             init_expr,
             **kwargs,
         )
 
     def _visit_min_over(self, node: foast.Call, **kwargs) -> itir.FunCall:
-        init_expr = itir.Literal(value=str(np.finfo(np.float64).max), type="float64")
+        dtype = type_info.extract_dtype(node.type)
+        _, max_value = type_info.arithmetic_bounds(dtype)
+        init_expr = itir.Literal(value=str(max_value), type=dtype.kind.name.lower())
         return self._make_reduction_expr(
             node,
-            lambda expr: im.call_("if_")(im.less_("acc", expr), "acc", expr),
+            lambda expr: im.call_("minimum")("acc", expr),
             init_expr,
             **kwargs,
         )
@@ -548,7 +561,11 @@ class InsideReductionLowering(FieldOperatorLowering):
         if node.op is dialect_ast_enums.UnaryOperator.NOT:
             return im.call_(node.op.value)(self.visit(node.operand, **kwargs))
 
-        return im.call_(node.op.value)(im.literal_("0", "int"), self.visit(node.operand, **kwargs))
+        dtype = type_info.extract_dtype(node.type)
+        assert type_info.is_arithmetic(dtype)
+        return im.call_(node.op.value)(
+            im.literal_("0", dtype.kind.name.lower()), self.visit(node.operand, **kwargs)
+        )
 
     def _visit_shift(self, node: foast.Call, **kwargs) -> itir.SymRef:  # type: ignore[override]
         uid = f"{node.func.id}__{self._sequential_id()}"
diff --git a/src/gt4py/next/ffront/itir_makers.py b/src/gt4py/next/ffront/itir_makers.py
index f54af3b7..9c9ae206 100644
--- a/src/gt4py/next/ffront/itir_makers.py
+++ b/src/gt4py/next/ffront/itir_makers.py
@@ -75,7 +75,7 @@ def ensure_expr(literal_or_expr: Union[str, int, itir.Expr]) -> itir.Expr:
     return literal_or_expr
 
 
-def ensure_offset(str_or_offset: Union[str, itir.OffsetLiteral]) -> itir.OffsetLiteral:
+def ensure_offset(str_or_offset: Union[str, int, itir.OffsetLiteral]) -> itir.OffsetLiteral:
     """
     Convert Python literals into an OffsetLiteral and let OffsetLiterals pass unchanged.
 
@@ -87,7 +87,7 @@ def ensure_offset(str_or_offset: Union[str, itir.OffsetLiteral]) -> itir.OffsetL
     >>> ensure_offset(itir.OffsetLiteral(value="J"))
     OffsetLiteral(value='J')
     """
-    if isinstance(str_or_offset, str):
+    if isinstance(str_or_offset, (str, int)):
         return itir.OffsetLiteral(value=str_or_offset)
     return str_or_offset
 
@@ -258,7 +258,7 @@ def shift_(offset, value=None):
     Examples
     --------
     >>> shift_("i", 0)("a")
-    FunCall(fun=FunCall(fun=SymRef(id=SymbolRef('shift')), args=[OffsetLiteral(value='i'), Literal(value='0', type='int')]), args=[SymRef(id=SymbolRef('a'))])
+    FunCall(fun=FunCall(fun=SymRef(id=SymbolRef('shift')), args=[OffsetLiteral(value='i'), OffsetLiteral(value=0)]), args=[SymRef(id=SymbolRef('a'))])
 
     >>> shift_("V2E")("b")
     FunCall(fun=FunCall(fun=SymRef(id=SymbolRef('shift')), args=[OffsetLiteral(value='V2E')]), args=[SymRef(id=SymbolRef('b'))])
@@ -266,7 +266,7 @@ def shift_(offset, value=None):
     offset = ensure_offset(offset)
     args = [offset]
     if value is not None:
-        value = ensure_expr(value)
+        value = ensure_offset(value)
         args.append(value)
     return call_(call_("shift")(*args))
 
diff --git a/src/gt4py/next/ffront/past_passes/type_deduction.py b/src/gt4py/next/ffront/past_passes/type_deduction.py
index c01adcf1..3c8c362b 100644
--- a/src/gt4py/next/ffront/past_passes/type_deduction.py
+++ b/src/gt4py/next/ffront/past_passes/type_deduction.py
@@ -44,7 +44,7 @@ def _is_integral_scalar(expr: past.Expr) -> bool:
     return isinstance(expr.type, ts.ScalarType) and type_info.is_integral(expr.type)
 
 
-def _validate_call_params(new_func: past.Name, new_kwargs: dict):
+def _validate_operator_call(new_func: past.Name, new_kwargs: dict):
     """
     Perform checks for domain and output field types.
 
@@ -54,13 +54,10 @@ def _validate_call_params(new_func: past.Name, new_kwargs: dict):
     """
     if not isinstance(
         new_func.type,
-        (
-            ts_ffront.FieldOperatorType,
-            ts_ffront.ScanOperatorType,
-        ),
+        (ts_ffront.FieldOperatorType, ts_ffront.ScanOperatorType),
     ):
         raise GTTypeError(
-            f"Only calls `FieldOperator`s and `ScanOperators` "
+            f"Only calls `FieldOperator`s and `ScanOperator`s "
             f"allowed in `Program`, but got `{new_func.type}`."
         )
 
@@ -189,12 +186,16 @@ class ProgramTypeDeduction(traits.VisitorWithSymbolTableTrait, NodeTranslator):
         new_kwargs = self.visit(node.kwargs, **kwargs)
 
         try:
-            _validate_call_params(new_func, new_kwargs)
+            is_operator = isinstance(
+                new_func.type, (ts_ffront.FieldOperatorType, ts_ffront.ScanOperatorType)
+            )
+            if is_operator:
+                _validate_operator_call(new_func, new_kwargs)
             arg_types = [arg.type for arg in new_args]
             kwarg_types = {
                 name: expr.type
                 for name, expr in new_kwargs.items()
-                if name != "out" and name != "domain"
+                if not is_operator and name != "out" and name != "domain"
             }
 
             type_info.accepts_args(
@@ -203,16 +204,29 @@ class ProgramTypeDeduction(traits.VisitorWithSymbolTableTrait, NodeTranslator):
                 with_kwargs=kwarg_types,
                 raise_exception=True,
             )
-
-            return_type = type_info.return_type(
-                new_func.type, with_args=arg_types, with_kwargs=kwarg_types
-            )
-            if return_type != new_kwargs["out"].type:
-                raise GTTypeError(
-                    f"Expected keyword argument `out` to be of "
-                    f"type {return_type}, but got "
-                    f"{new_kwargs['out'].type}."
+            return_type = ts.VoidType()
+            if is_operator:
+                operator_return_type = type_info.return_type(
+                    new_func.type, with_args=arg_types, with_kwargs=kwarg_types
+                )
+                if operator_return_type != new_kwargs["out"].type:
+                    raise GTTypeError(
+                        f"Expected keyword argument `out` to be of "
+                        f"type {operator_return_type}, but got "
+                        f"{new_kwargs['out'].type}."
+                    )
+            elif new_func.id in ["minimum", "maximum"]:
+                if new_args[0].type != new_args[1].type:
+                    raise GTTypeError(
+                        f"First and second argument in {new_func.id} must be the same type."
+                        f"Got `{new_args[0].type}` and `{new_args[1].type}`."
+                    )
+                return_type = new_args[0].type
+            else:
+                raise AssertionError(
+                    "Only calls `FieldOperator`s, `ScanOperator`s or minimum and maximum builtins allowed"
                 )
+
         except GTTypeError as ex:
             raise ProgramTypeError.from_past_node(
                 node, msg=f"Invalid call to `{node.func.id}`."
@@ -222,7 +236,7 @@ class ProgramTypeDeduction(traits.VisitorWithSymbolTableTrait, NodeTranslator):
             func=new_func,
             args=new_args,
             kwargs=new_kwargs,
-            type=ts.VoidType(),
+            type=return_type,
             location=node.location,
         )
 
diff --git a/src/gt4py/next/ffront/past_to_itir.py b/src/gt4py/next/ffront/past_to_itir.py
index 1fdf27df..8fc6d309 100644
--- a/src/gt4py/next/ffront/past_to_itir.py
+++ b/src/gt4py/next/ffront/past_to_itir.py
@@ -14,7 +14,7 @@
 
 from __future__ import annotations
 
-from typing import Any, Generator, Optional, cast
+from typing import Optional, cast
 
 from gt4py.eve import NodeTranslator, concepts, traits
 from gt4py.next.common import Dimension, DimensionKind, GridType, GTTypeError
@@ -249,17 +249,17 @@ class ProgramLowering(traits.VisitorWithSymbolTableTrait, NodeTranslator):
         dim_i: int,
         dim: Dimension,
         node_domain: past.Dict,
-    ) -> Generator[Any, None, None]:
+    ) -> list[itir.FunCall]:
+        assert len(node_domain.values_[dim_i].elts) == 2
         keys_dims_types = cast(ts.DimensionType, node_domain.keys_[dim_i].type).dim
-        if keys_dims_types == dim:
-            assert len(node_domain.values_[dim_i].elts) == 2
-            return (self.visit(bound) for bound in node_domain.values_[dim_i].elts)
-        else:
+        if keys_dims_types != dim:
             raise GTTypeError(
                 f"Dimensions in out field and field domain are not equivalent"
                 f"Expected {dim}, but got {keys_dims_types} "
             )
 
+        return [self.visit(bound) for bound in node_domain.values_[dim_i].elts]
+
     @staticmethod
     def _compute_field_slice(node: past.Subscript):
         out_field_name: past.Name = node.value
@@ -355,3 +355,14 @@ class ProgramLowering(traits.VisitorWithSymbolTableTrait, NodeTranslator):
             fun=itir.SymRef(id=node.op.value),
             args=[self.visit(node.left, **kwargs), self.visit(node.right, **kwargs)],
         )
+
+    def visit_Call(self, node: past.Call, **kwargs) -> itir.FunCall:
+        if node.func.id in ["maximum", "minimum"] and len(node.args) == 2:
+            return itir.FunCall(
+                fun=itir.SymRef(id=node.func.id),
+                args=[self.visit(node.args[0]), self.visit(node.args[1])],
+            )
+        else:
+            raise AssertionError(
+                "Only `minimum` and `maximum` builtins supported supported currently."
+            )
diff --git a/src/gt4py/next/iterator/ARCHITECTURE.md b/src/gt4py/next/iterator/ARCHITECTURE.md
index 76f24adc..ceb73048 100644
--- a/src/gt4py/next/iterator/ARCHITECTURE.md
+++ b/src/gt4py/next/iterator/ARCHITECTURE.md
@@ -4,16 +4,16 @@ Implements the iterator view as described [here](https://github.com/GridTools/co
 
 ## Iterator view program in Python
 
-A program for the iterator view consists of Python functions decorated with `@fundef` and an entry point, *fencil*, which is a Python function decorated with `@fendef`. The *fencil* must only contain calls to the `closure(...)` function.
+A program for the iterator view consists of Python functions decorated with `@fundef` and an entry point, _fencil_, which is a Python function decorated with `@fendef`. The _fencil_ must only contain calls to the `closure(...)` function.
 
 Legal functions much not have side-effects, however, e.g., for debugging purposes, side-effects can be used in embedded execution.
 
-There are 2 modes of execution: *embedded* (direct execution in Python) and *tracing* (trace function calls -> Eve IR representation -> code generation).
-The implementations of *embedded* and *tracing* are decoupled by registering themselves (dependency inversion) in `builtins.py` (contains dispatch functions for all builtins of the model) and `runtime.py` (contains dispatch mechanism for the `fendef` and `fundef` decorators and the `closure(...)` function).
+There are 2 modes of execution: _embedded_ (direct execution in Python) and _tracing_ (trace function calls -> Eve IR representation -> code generation).
+The implementations of _embedded_ and _tracing_ are decoupled by registering themselves (dependency inversion) in `builtins.py` (contains dispatch functions for all builtins of the model) and `runtime.py` (contains dispatch mechanism for the `fendef` and `fundef` decorators and the `closure(...)` function).
 
 The builtins dispatcher is implemented in `dispatcher.py`. Implementations are registered with a key (`str`) (currently `tracing` and `embedded`). The active implementation is selected by pushing a key to the dispatcher stack.
 
-`fundef` returns a wrapper around the function, which dispatches `__call__` to a hook if a predicate is met (used for *tracing*). By default the original function is called (used in *embedded* mode).
+`fundef` returns a wrapper around the function, which dispatches `__call__` to a hook if a predicate is met (used for _tracing_). By default the original function is called (used in _embedded_ mode).
 
 `fendef` returns a wrapper that dispatches to a registered function. If `backend` is in the keyword arguments and not `None`, `fendef_codegen` will be called (can be passed as a keyword arg, by default `tracing.fendef_tracing` will be imported and used) and the result passed directly to the backend, otherwise `fendef_embedded` will be called. The fendef wrapper also has a `.format_itir()` method to invoke code generators and anything else that turns a fencil into text.
 
@@ -22,6 +22,7 @@ The builtins dispatcher is implemented in `dispatcher.py`. Implementations are r
 Embedded execution is implemented in the file `embedded.py`.
 
 Sketch:
+
 - fields are np arrays with named axes; names are instances of `CartesianAxis`
 - in `closure()`, the stencil is executed for each point in the domain, the fields are wrapped in an iterator pointing to the current point of execution.
 - `shift()` is lazy (to allow lift implementation), offsets are accumulated in the iterator and only executed when `deref()` is called.
@@ -33,6 +34,7 @@ Sketch:
 An iterator view program is traced (implemented in `tracing.py`) and represented in a tree structure defined by the nodes in (`ir.py`).
 
 Sketch:
+
 - Each builtin returns a `FunctionCall` node representing the builtin.
 - Foreach `fundef`, the signature of the wrapped function is extracted, then it is invoked with `Sym` nodes as arguments.
 - Expressions involving an `Expr` node (e.g. `Sym`) are converted to appropriate builtin calls, e.g. `4. + Sym(id='foo')` is converted to `FunCall(fun=SymRef(id='plus'), args=...)`
@@ -66,6 +68,7 @@ Generates a pretty formated version with type information
 ## Adding a new builtin
 
 Currently there are 4 places where a new builtin needs to be added
+
 - `builtin.py`: for dispatching to an actual implementation
 - `embedded.py` and `tracing.py`: for the respective implementation
 - `ir.py`: we check for consistent use of symbols, therefore if a `FunCall` to the new builtin is used, it needs to be available in the symbol table.
diff --git a/src/gt4py/next/iterator/type_inference.py b/src/gt4py/next/iterator/type_inference.py
index 5a7ea359..7283bea0 100644
--- a/src/gt4py/next/iterator/type_inference.py
+++ b/src/gt4py/next/iterator/type_inference.py
@@ -579,7 +579,7 @@ class _TypeInferrer(eve.traits.VisitorWithSymbolTableTrait, eve.NodeTranslator):
         # their parameters to inherit the constraints of the arguments in a call to them. A simple
         # way to do this is to run the type inference on the function itself and reindex its type
         # vars when referencing the function, i.e. in a `SymRef`.
-        collected_types = _infer_all(fun, offset_provider=self.offset_provider, reindex=False)
+        collected_types = infer_all(fun, offset_provider=self.offset_provider, reindex=False)
         fun_type = LetPolymorphic(dtype=collected_types.pop(id(fun)))
         assert not set(self.collected_types.keys()) & set(collected_types.keys())
         self.collected_types = {**self.collected_types, **collected_types}
@@ -594,7 +594,6 @@ class _TypeInferrer(eve.traits.VisitorWithSymbolTableTrait, eve.NodeTranslator):
         domain = self.visit(node.domain, **kwargs)
         stencil = self.visit(node.stencil, **kwargs)
         output = self.visit(node.output, **kwargs)
-        inputs = Tuple.from_elems(*self.visit(node.inputs, **kwargs))
         output_dtype = TypeVar.fresh()
         output_loc = TypeVar.fresh()
         self.constraints.add(
@@ -607,18 +606,40 @@ class _TypeInferrer(eve.traits.VisitorWithSymbolTableTrait, eve.NodeTranslator):
                     kind=Iterator(),
                     dtype=output_dtype,
                     size=Column(),
-                    current_loc=output_loc,
                     defined_loc=output_loc,
                 ),
             )
         )
+
+        inputs: list[Type] = self.visit(node.inputs, **kwargs)
+        stencil_params = []
+        for input_ in inputs:
+            stencil_param = Val(current_loc=output_loc, defined_loc=TypeVar.fresh())
+            self.constraints.add(
+                (
+                    input_,
+                    Val(
+                        kind=stencil_param.kind,
+                        dtype=stencil_param.dtype,
+                        size=stencil_param.size,
+                        # closure input and stencil param differ in `current_loc`
+                        current_loc=ANYWHERE,
+                        defined_loc=stencil_param.defined_loc,
+                    ),
+                )
+            )
+            stencil_params.append(stencil_param)
+
         self.constraints.add(
             (
                 stencil,
-                FunctionType(args=inputs, ret=Val(kind=Value(), dtype=output_dtype, size=Column())),
+                FunctionType(
+                    args=Tuple.from_elems(*stencil_params),
+                    ret=Val(kind=Value(), dtype=output_dtype, size=Column()),
+                ),
             )
         )
-        return Closure(output=output, inputs=inputs)
+        return Closure(output=output, inputs=Tuple.from_elems(*inputs))
 
     def visit_FencilDefinition(
         self,
@@ -637,13 +658,13 @@ class _TypeInferrer(eve.traits.VisitorWithSymbolTableTrait, eve.NodeTranslator):
         params = [self.visit(p, **kwargs) for p in node.params]
         self.visit(node.closures, **kwargs)
         return FencilDefinitionType(
-            name=node.id,
+            name=str(node.id),
             fundefs=Tuple.from_elems(*ftypes),
             params=Tuple.from_elems(*params),
         )
 
 
-def _infer_all(
+def infer_all(
     node: ir.Node,
     offset_provider: Optional[dict[str, Connectivity | Dimension]] = None,
     reindex: bool = True,
@@ -674,7 +695,7 @@ def infer(
     offset_provider: typing.Optional[dict[str, typing.Any]] = None,
 ) -> Type:
     """Infer the type of the given iterator IR expression."""
-    inferred_types = _infer_all(expr, offset_provider)
+    inferred_types = infer_all(expr, offset_provider)
     return inferred_types[id(expr)]
 
 
diff --git a/src/gt4py/next/program_processors/codegens/gtfn/itir_to_gtfn_ir.py b/src/gt4py/next/program_processors/codegens/gtfn/itir_to_gtfn_ir.py
index f60d2601..73c6705a 100644
--- a/src/gt4py/next/program_processors/codegens/gtfn/itir_to_gtfn_ir.py
+++ b/src/gt4py/next/program_processors/codegens/gtfn/itir_to_gtfn_ir.py
@@ -175,7 +175,7 @@ def _collect_offset_definitions(
                 connectivity.origin_axis,
                 connectivity.neighbor_axis,
             ]:
-                if not dim.kind == common.DimensionKind.HORIZONTAL:
+                if dim.kind != common.DimensionKind.HORIZONTAL:
                     raise NotImplementedError()
                 offset_definitions[dim.value] = TagDefinition(
                     name=Sym(id=dim.value), alias=_horizontal_dimension
diff --git a/src/gt4py/next/type_system/type_info.py b/src/gt4py/next/type_system/type_info.py
index 7796bdfe..1936841e 100644
--- a/src/gt4py/next/type_system/type_info.py
+++ b/src/gt4py/next/type_system/type_info.py
@@ -15,6 +15,8 @@
 import functools
 from typing import Callable, Iterator, Type, TypeGuard, cast
 
+import numpy as np
+
 from gt4py.eve.utils import XIterable, xiter
 from gt4py.next.common import Dimension, GTTypeError
 from gt4py.next.type_system import type_specifications as ts
@@ -227,6 +229,16 @@ def is_arithmetic(symbol_type: ts.TypeSpec) -> bool:
     return is_floating_point(symbol_type) or is_integral(symbol_type)
 
 
+def arithmetic_bounds(arithmetic_type: ts.ScalarType):
+    assert is_arithmetic(arithmetic_type)
+    return {
+        ts.ScalarKind.FLOAT32: (np.finfo(np.float32).min, np.finfo(np.float32).max),
+        ts.ScalarKind.FLOAT64: (np.finfo(np.float64).min, np.finfo(np.float64).max),
+        ts.ScalarKind.INT32: (np.iinfo(np.int32).min, np.iinfo(np.int32).max),
+        ts.ScalarKind.INT64: (np.iinfo(np.int64).min, np.iinfo(np.int64).max),
+    }[arithmetic_type.kind]
+
+
 def is_type_or_tuple_of_type(type_: ts.TypeSpec, expected_type: type | tuple) -> bool:
     """
     Return True if ``type_`` matches any of the expected.
diff --git a/tox.ini b/tox.ini
index 2aa4b768..0b54a2b0 100644
--- a/tox.ini
+++ b/tox.ini
@@ -28,7 +28,6 @@ labels =
     storage-py38-dace-cpu, storage-py39-dace-cpu, storage-py310-dace-cpu
 
 [testenv]
-passenv = NUM_PROCESSES
 deps = -r {tox_root}{/}requirements-dev.txt
 constrain_package_deps = true
 use_frozen_constraints = true
@@ -48,10 +47,13 @@ extras =
     cuda11x: cuda11x
 package = wheel
 wheel_build_env = .pkg
+pass_env = NUM_PROCESSES
+set_env =
+    PYTHONWARNINGS = ignore:Support for `[tool.setuptools]` in `pyproject.toml` is still *beta*:UserWarning
 
 [testenv:cartesian-py{38,39,310}-{internal,dace}-{cpu,cuda,cuda110,cuda111,cuda112,cuda113,cuda114,cuda115,cuda116,cuda117,cuda11x}]
 description = Run 'gt4py.cartesian' tests
-passenv = {[testenv]passenv}, BOOST_ROOT, BOOST_HOME, CUDA_HOME, CUDA_PATH, CXX, CC, OPENMP_CPPFLAGS, OPENMP_LDFLAGS, PIP_USER, PYTHONUSERBASE
+pass_env = {[testenv]pass_env}, BOOST_ROOT, BOOST_HOME, CUDA_HOME, CUDA_PATH, CXX, CC, OPENMP_CPPFLAGS, OPENMP_LDFLAGS, PIP_USER, PYTHONUSERBASE
 allowlist_externals =
     make
     gcc
@@ -72,11 +74,12 @@ commands =
 
 [testenv:next-py{310}-{nomesh,atlas}]
 description = Run 'gt4py.next' tests
-passenv = {[testenv]passenv}, BOOST_ROOT, BOOST_HOME, CUDA_HOME, CUDA_PATH
+pass_env = {[testenv]pass_env}, BOOST_ROOT, BOOST_HOME, CUDA_HOME, CUDA_PATH
 deps =
     -r {tox_root}{/}requirements-dev.txt
     atlas: atlas4py
 set_env =
+    {[testenv]set_env}
     PIP_EXTRA_INDEX_URL = {env:PIP_EXTRA_INDEX_URL:https://test.pypi.org/simple/}
 commands =
     nomesh: python -m pytest --cache-clear -v -n {env:NUM_PROCESSES:1} -m "not requires_atlas" {posargs} tests{/}next_tests
@@ -130,15 +133,28 @@ commands =
 #     git add _static
 # commands_post =
 
-[testenv:requirements-py{38,39,310}]
-description = Update pinned development requirements (*py38* should be used for freezing)
+[testenv:requirements-{common,py38,py39,py310}]
+description =
+    common: Update pinned development requirements
+    py38: Update requirements for testing a specific python version
+    py39: Update requirements for testing a specific python version
+    py310: Update requirements for testing a specific python version
+base_python =
+    common: py38
+    py38: py38
+    py39: py39
+    py310: py310
 deps =
     cogapp>=3.3
     pip-tools>=6.10
 package = skip
-setenv =
-    CUSTOM_COMPILE_COMMAND = "tox run -e requirements-py38"
+set_env =
+    CUSTOM_COMPILE_COMMAND = "tox run -e requirements-common"
+allowlist_externals =
+    mv
 commands =
+    -mv constraints.txt constraints.txt.old
+    -mv requirements-dev.txt requirements-dev.old
     # Generate constraints file removing extras
     # (extras are not supported by pip in constraints files)
     pip-compile -r --resolver=backtracking \
@@ -151,7 +167,6 @@ commands =
     --extra testing \
     -o constraints.txt \
     pyproject.toml requirements-dev.in
-
     # Generate actual requirements file
     # (compiling from scratch again to print actual package sources)
     pip-compile --resolver=backtracking \
@@ -163,7 +178,9 @@ commands =
     --extra testing \
     -o requirements-dev.txt \
     pyproject.toml requirements-dev.in
-    cog -r -P .pre-commit-config.yaml
+
+    # Run cog to update .pre-commit-config.yaml with new versions
+    common: cog -r -P .pre-commit-config.yaml
 
 [testenv:dev-py{38,39,310}{-atlas,}]
 description = Initialize development environment for gt4py
@@ -172,4 +189,5 @@ deps =
     atlas: atlas4py
 package = editable-legacy  # => use_develop = True
 set_env =
+    {[testenv]set_env}
     PIP_EXTRA_INDEX_URL = {env:PIP_EXTRA_INDEX_URL:https://test.pypi.org/simple/}


Test patch:
diff --git a/.github/workflows/test-cartesian.yml b/.github/workflows/test-cartesian.yml
index 4231e9fd..2c2b97aa 100644
--- a/.github/workflows/test-cartesian.yml
+++ b/.github/workflows/test-cartesian.yml
@@ -43,7 +43,7 @@ jobs:
         python-version: ${{ matrix.python-version }}
         cache: 'pip'
         cache-dependency-path: |
-          **/setup.cfg
+          **/pyproject.toml
           **/constraints.txt
           **/requirements-dev.txt
     - name: Install python dependencies
diff --git a/.github/workflows/test-eve.yml b/.github/workflows/test-eve.yml
index b030a1f6..1322c573 100644
--- a/.github/workflows/test-eve.yml
+++ b/.github/workflows/test-eve.yml
@@ -33,7 +33,7 @@ jobs:
         python-version: ${{ matrix.python-version }}
         cache: 'pip'
         cache-dependency-path: |
-          **/setup.cfg
+          **/pyproject.toml
           **/constraints.txt
           **/requirements-dev.txt
     - name: Install python dependencies
diff --git a/.github/workflows/test-next.yml b/.github/workflows/test-next.yml
index 2eb3b41d..5baeb6ac 100644
--- a/.github/workflows/test-next.yml
+++ b/.github/workflows/test-next.yml
@@ -42,7 +42,7 @@ jobs:
         python-version: ${{ matrix.python-version }}
         cache: 'pip'
         cache-dependency-path: |
-          **/setup.cfg
+          **/pyproject.toml
           **/constraints.txt
           **/requirements-dev.txt
     - name: Install python dependencies
diff --git a/.github/workflows/test-storage.yml b/.github/workflows/test-storage.yml
index fd4fde6f..1133353f 100644
--- a/.github/workflows/test-storage.yml
+++ b/.github/workflows/test-storage.yml
@@ -35,7 +35,7 @@ jobs:
         python-version: ${{ matrix.python-version }}
         cache: 'pip'
         cache-dependency-path: |
-          **/setup.cfg
+          **/pyproject.toml
           **/constraints.txt
           **/requirements-dev.txt
     - name: Install python dependencies
diff --git a/tests/cartesian_tests/integration_tests/test_code_generation.py b/tests/cartesian_tests/integration_tests/test_code_generation.py
index 75704239..462a177b 100644
--- a/tests/cartesian_tests/integration_tests/test_code_generation.py
+++ b/tests/cartesian_tests/integration_tests/test_code_generation.py
@@ -352,7 +352,9 @@ def test_higher_dimensional_fields(backend):
 def test_input_order(backend):
     @gtscript.stencil(backend=backend)
     def stencil(
-        in_field: gtscript.Field[np.float], parameter: np.float, out_field: gtscript.Field[np.float]
+        in_field: gtscript.Field[np.float64],
+        parameter: np.float64,
+        out_field: gtscript.Field[np.float64],
     ):
         with computation(PARALLEL), interval(...):
             out_field = in_field * parameter
diff --git a/tests/next_tests/ffront_tests/ffront_test_utils.py b/tests/next_tests/ffront_tests/ffront_test_utils.py
index 78b410cb..d6d3316d 100644
--- a/tests/next_tests/ffront_tests/ffront_test_utils.py
+++ b/tests/next_tests/ffront_tests/ffront_test_utils.py
@@ -119,7 +119,8 @@ def reduction_setup():
         E2VDim=e2vdim,
         V2E=FieldOffset("V2E", source=edge, target=(vertex, v2edim)),
         E2V=FieldOffset("E2V", source=vertex, target=(edge, e2vdim)),
-        inp=index_field(edge, dtype=np.int64),
+        # inp=index_field(edge, dtype=np.int64), # TODO enable once we support index_fields in bindings
+        inp=np_as_located_field(edge)(np.arange(num_edges, dtype=np.int64)),
         out=np_as_located_field(vertex)(np.zeros([num_vertices], dtype=np.int64)),
         offset_provider={
             "V2E": NeighborTableOffsetProvider(v2e_arr, vertex, edge, 4),
diff --git a/tests/next_tests/ffront_tests/test_execution.py b/tests/next_tests/ffront_tests/test_execution.py
index 7d312774..6ead6f93 100644
--- a/tests/next_tests/ffront_tests/test_execution.py
+++ b/tests/next_tests/ffront_tests/test_execution.py
@@ -17,25 +17,25 @@
 from functools import reduce
 
 import numpy as np
-import pytest as pytest
+import pytest
 
 from gt4py.next.ffront.decorator import field_operator, program, scan_operator
+from gt4py.next.ffront.experimental import as_offset
 from gt4py.next.ffront.fbuiltins import (
     Dimension,
     Field,
-    FieldOffset,
     astype,
     broadcast,
+    float32,
     float64,
     int32,
     int64,
-    max_over,
-    min_over,
+    maximum,
+    minimum,
     neighbor_sum,
     where,
 )
 from gt4py.next.ffront.foast_passes.type_deduction import FieldOperatorTypeDeductionError
-from gt4py.next.iterator.builtins import float32
 from gt4py.next.iterator.embedded import (
     NeighborTableOffsetProvider,
     index_field,
@@ -144,9 +144,6 @@ def test_tuples(fieldview_backend):
     b_I_float = np_as_located_field(IDim)(np.random.randn(size).astype("float64"))
     out_I_float = np_as_located_field(IDim)(np.zeros((size), dtype=float64))
 
-    if fieldview_backend == gtfn_cpu.run_gtfn:
-        pytest.skip("Tuples are not supported yet.")
-
     @field_operator
     def tuples(
         inp1: Field[[IDim], float64], inp2: Field[[IDim], float64]
@@ -247,7 +244,7 @@ def test_scalar_in_domain_spec_and_fo_call(fieldview_backend):
     assert (out.array() == size).all()
 
 
-def test_scalar_scan():
+def test_scalar_scan(fieldview_backend):
     size = 10
     KDim = Dimension("K", kind=DimensionKind.VERTICAL)
     qc = np_as_located_field(IDim, KDim)(np.zeros((size, size)))
@@ -259,7 +256,7 @@ def test_scalar_scan():
         qc = qc_in + carry + scalar
         return qc
 
-    @program
+    @program(backend=fieldview_backend)
     def scan_scalar(qc: Field[[IDim, KDim], float], scalar: float):
         _scan_scalar(qc, scalar, out=qc)
 
@@ -267,7 +264,10 @@ def test_scalar_scan():
     assert np.allclose(np.asarray(qc), expected)
 
 
-def test_tuple_scalar_scan():
+def test_tuple_scalar_scan(fieldview_backend):
+    if fieldview_backend in [gtfn_cpu.run_gtfn, gtfn_cpu.run_gtfn_imperative]:
+        pytest.skip("Tuple arguments are not supported in gtfn yet.")
+
     size = 10
     KDim = Dimension("K", kind=DimensionKind.VERTICAL)
     qc = np_as_located_field(IDim, KDim)(np.zeros((size, size)))
@@ -280,7 +280,7 @@ def test_tuple_scalar_scan():
     ) -> float:
         return (qc_in + state + tuple_scalar[1][0] + tuple_scalar[1][1]) / tuple_scalar[0]
 
-    @field_operator
+    @field_operator(backend=fieldview_backend)
     def scan_tuple_scalar(
         qc: Field[[IDim, KDim], float], tuple_scalar: tuple[float, tuple[float, float]]
     ) -> Field[[IDim, KDim], float]:
@@ -333,7 +333,51 @@ def test_astype_float(fieldview_backend):
     assert np.allclose(c_int32.array(), out_int_32)
 
 
-def test_nested_tuple_return():
+def test_offset_field(fieldview_backend):
+    a_I_arr = np.random.randn(size, size).astype("float64")
+    a_I_float = np_as_located_field(IDim, KDim)(a_I_arr)
+    a_I_float_1 = np_as_located_field(IDim, KDim)(
+        np.append(np.insert(a_I_arr, size, 0, axis=1), [np.array([0] * (size + 1))], axis=0)
+    )
+    offset_field_arr = np.asarray(np.ones((size - 1, size - 1)), dtype=int64)
+    offset_field_comp = np.append(
+        np.insert(offset_field_arr, size - 1, 0, axis=1), [np.array([0] * size)], axis=0
+    )
+    offset_field = np_as_located_field(IDim, KDim)(offset_field_comp)
+    out_I_float = np_as_located_field(IDim, KDim)(np.zeros((size, size), dtype=float64))
+    out_I_float_1 = np_as_located_field(IDim, KDim)(np.zeros((size, size), dtype=float64))
+
+    @field_operator(backend=fieldview_backend)
+    def offset_index_field_fo(
+        a: Field[[IDim, KDim], float64],
+        offset_field: Field[[IDim, KDim], int64],
+    ) -> Field[[IDim, KDim], float64]:
+        a_i = a(as_offset(Ioff, offset_field))
+        a_i_k = a_i(as_offset(Koff, offset_field))
+        return a_i_k
+
+    offset_index_field_fo(
+        a_I_float,
+        offset_field,
+        out=out_I_float,
+        offset_provider={"Ioff": IDim, "Koff": KDim},
+    )
+
+    @field_operator(backend=fieldview_backend)
+    def offset_index_int_fo(a: Field[[IDim, KDim], float64]) -> Field[[IDim, KDim], float64]:
+        a_i = a(Ioff[1])
+        a_i_k = a_i(Koff[1])
+        return a_i_k
+
+    offset_index_int_fo(
+        a_I_float_1, out=out_I_float_1, offset_provider={"Ioff": IDim, "Koff": KDim}
+    )
+    assert np.allclose(
+        out_I_float.array()[: size - 1, : size - 1], out_I_float_1.array()[: size - 1, : size - 1]
+    )
+
+
+def test_nested_tuple_return(fieldview_backend):
     a_I_float = np_as_located_field(IDim)(np.random.randn(size).astype("float64"))
     b_I_float = np_as_located_field(IDim)(np.random.randn(size).astype("float64"))
     out_I_float = np_as_located_field(IDim)(np.zeros((size), dtype=float64))
@@ -344,7 +388,7 @@ def test_nested_tuple_return():
     ) -> tuple[Field[[IDim], float64], tuple[Field[[IDim], float64], Field[[IDim], float64]]]:
         return (a, (a, b))
 
-    @field_operator
+    @field_operator(backend=fieldview_backend)
     def combine(a: Field[[IDim], float64], b: Field[[IDim], float64]) -> Field[[IDim], float64]:
         packed = pack_tuple(a, b)
         return packed[0] + packed[1][0] + packed[1][1]
@@ -354,7 +398,7 @@ def test_nested_tuple_return():
     assert np.allclose(2 * a_I_float.array() + b_I_float.array(), out_I_float)
 
 
-def test_tuple_return_2(reduction_setup):
+def test_tuple_return_2(reduction_setup, fieldview_backend):
     rs = reduction_setup
     Edge = rs.Edge
     Vertex = rs.Vertex
@@ -369,7 +413,7 @@ def test_tuple_return_2(reduction_setup):
         b = neighbor_sum(b(V2E), axis=V2EDim)
         return a, b
 
-    @field_operator
+    @field_operator(backend=fieldview_backend)
     def combine_tuple(a: Field[[Edge], int64], b: Field[[Edge], int64]) -> Field[[Vertex], int64]:
         packed = reduction_tuple(a, b)
         return packed[0] + packed[1]
@@ -381,7 +425,7 @@ def test_tuple_return_2(reduction_setup):
 
 
 @pytest.mark.xfail(raises=NotImplementedError)
-def test_tuple_with_local_field_in_reduction_shifted(reduction_setup):
+def test_tuple_with_local_field_in_reduction_shifted(reduction_setup, fieldview_backend):
     rs = reduction_setup
     Edge = rs.Edge
     Vertex = rs.Vertex
@@ -397,7 +441,7 @@ def test_tuple_with_local_field_in_reduction_shifted(reduction_setup):
     b = np_as_located_field(Vertex)(2 * np.ones((num_vertices,)))
     out = np_as_located_field(Edge)(np.zeros((num_edges,)))
 
-    @field_operator
+    @field_operator(backend=fieldview_backend)
     def reduce_tuple_element(
         edge_field: Field[[Edge], float64], vertex_field: Field[[Vertex], float64]
     ) -> Field[[Edge], float64]:
@@ -440,8 +484,6 @@ def test_tuple_arg(fieldview_backend):
 
 @pytest.mark.parametrize("forward", [True, False])
 def test_fieldop_from_scan(fieldview_backend, forward):
-    if fieldview_backend in [gtfn_cpu.run_gtfn, gtfn_cpu.run_gtfn_imperative]:
-        pytest.xfail("gtfn does not yet support scan pass.")
     init = 1.0
     out = np_as_located_field(KDim)(np.zeros((size,)))
     expected = np.arange(init + 1.0, init + 1.0 + size, 1)
@@ -463,8 +505,7 @@ def test_fieldop_from_scan(fieldview_backend, forward):
 
 def test_solve_triag(fieldview_backend):
     if fieldview_backend in [gtfn_cpu.run_gtfn, gtfn_cpu.run_gtfn_imperative]:
-        pytest.xfail("gtfn does not yet support scan pass.")
-
+        pytest.skip("Has a bug.")
     shape = (3, 7, 5)
     rng = np.random.default_rng()
     a_np, b_np, c_np, d_np = (rng.normal(size=shape) for _ in range(4))
@@ -507,7 +548,7 @@ def test_solve_triag(fieldview_backend):
     np.allclose(expected, out)
 
 
-def test_ternary_operator():
+def test_ternary_operator(fieldview_backend):
     a_I_float = np_as_located_field(IDim)(np.random.randn(size).astype("float64"))
     b_I_float = np_as_located_field(IDim)(np.random.randn(size).astype("float64"))
     out_I_float = np_as_located_field(IDim)(np.zeros((size), dtype=float64))
@@ -515,7 +556,7 @@ def test_ternary_operator():
     left = 2.0
     right = 3.0
 
-    @field_operator
+    @field_operator(backend=fieldview_backend)
     def ternary_field_op(
         a: Field[[IDim], float], b: Field[[IDim], float], left: float, right: float
     ) -> Field[[IDim], float]:
@@ -525,7 +566,7 @@ def test_ternary_operator():
     e = np.asarray(a_I_float) if left < right else np.asarray(b_I_float)
     np.allclose(e, out_I_float)
 
-    @field_operator
+    @field_operator(backend=fieldview_backend)
     def ternary_field_op_scalars(left: float, right: float) -> Field[[IDim], float]:
         return broadcast(3.0, (IDim,)) if left > right else broadcast(4.0, (IDim,))
 
@@ -534,7 +575,9 @@ def test_ternary_operator():
     np.allclose(e, out_I_float)
 
 
-def test_ternary_operator_tuple():
+def test_ternary_operator_tuple(fieldview_backend):
+    if fieldview_backend in [gtfn_cpu.run_gtfn, gtfn_cpu.run_gtfn_imperative]:
+        pytest.skip("Tuple arguments are not supported in gtfn yet.")
     a_I_float = np_as_located_field(IDim)(np.random.randn(size).astype("float64"))
     b_I_float = np_as_located_field(IDim)(np.random.randn(size).astype("float64"))
     out_I_float = np_as_located_field(IDim)(np.zeros((size), dtype=float64))
@@ -543,7 +586,7 @@ def test_ternary_operator_tuple():
     left = 2.0
     right = 3.0
 
-    @field_operator
+    @field_operator(backend=fieldview_backend)
     def ternary_field_op(
         a: Field[[IDim], float], b: Field[[IDim], float], left: float, right: float
     ) -> tuple[Field[[IDim], float], Field[[IDim], float]]:
@@ -562,7 +605,7 @@ def test_ternary_operator_tuple():
     np.allclose(f, out_I_float_1)
 
 
-def test_ternary_builtin_neighbor_sum(reduction_setup):
+def test_ternary_builtin_neighbor_sum(reduction_setup, fieldview_backend):
     rs = reduction_setup
     Edge = rs.Edge
     Vertex = rs.Vertex
@@ -576,7 +619,7 @@ def test_ternary_builtin_neighbor_sum(reduction_setup):
     b = np_as_located_field(Edge)(2 * np.ones((num_edges,)))
     out = np_as_located_field(Vertex)(np.zeros((num_vertices,)))
 
-    @field_operator
+    @field_operator(backend=fieldview_backend)
     def ternary_reduce(a: Field[[Edge], float], b: Field[[Edge], float]) -> Field[[Vertex], float]:
         out = neighbor_sum(b(V2E) if 2 < 3 else a(V2E), axis=V2EDim)
         return out
@@ -592,14 +635,14 @@ def test_ternary_builtin_neighbor_sum(reduction_setup):
     assert np.allclose(expected, out)
 
 
-def test_ternary_scan():
+def test_ternary_scan(fieldview_backend):
     init = 0.0
     a_float = 4
     a = np_as_located_field(KDim)(a_float * np.ones((size,)))
     out = np_as_located_field(KDim)(np.zeros((size,)))
     expected = np.asarray([i if i <= a_float else a_float + 1 for i in range(1, size + 1)])
 
-    @scan_operator(axis=KDim, forward=True, init=init)
+    @scan_operator(axis=KDim, forward=True, init=init, backend=fieldview_backend)
     def simple_scan_operator(carry: float, a: float) -> float:
         return carry if carry > a else carry + 1.0
 
@@ -684,7 +727,7 @@ def test_domain(fieldview_backend):
 
     @program
     def program_domain(a: Field[[IDim, JDim], float64]):
-        fieldop_domain(a, out=a, domain={IDim: (1, 9), JDim: (4, 6)})
+        fieldop_domain(a, out=a, domain={IDim: (minimum(1, 2), 9), JDim: (4, maximum(5, 6))})
 
     program_domain(a_IJ_float, offset_provider={})
 
@@ -855,6 +898,9 @@ def test_implicit_broadcast_mixed_dims(fieldview_backend):
 
 
 def test_tuple_unpacking(fieldview_backend):
+    if fieldview_backend in [gtfn_cpu.run_gtfn, gtfn_cpu.run_gtfn_imperative]:
+        pytest.skip("Tuple arguments are not supported in gtfn yet.")
+
     size = 10
     inp = np_as_located_field(IDim)(np.ones((size)))
     out1 = np_as_located_field(IDim)(np.ones((size)))
@@ -862,7 +908,7 @@ def test_tuple_unpacking(fieldview_backend):
     out3 = np_as_located_field(IDim)(np.ones((size)))
     out4 = np_as_located_field(IDim)(np.ones((size)))
 
-    @field_operator
+    @field_operator(backend=fieldview_backend)
     def unpack(
         inp: Field[[IDim], float64],
     ) -> tuple[
@@ -885,6 +931,9 @@ def test_tuple_unpacking(fieldview_backend):
 
 
 def test_tuple_unpacking_star_multi(fieldview_backend):
+    if fieldview_backend in [gtfn_cpu.run_gtfn, gtfn_cpu.run_gtfn_imperative]:
+        pytest.skip("Tuple arguments are not supported in gtfn yet.")
+
     size = 10
     inp = np_as_located_field(IDim)(np.ones((size)))
     out = tuple(np_as_located_field(IDim)(np.ones(size) * i) for i in range(3 * 4))
@@ -904,7 +953,7 @@ def test_tuple_unpacking_star_multi(fieldview_backend):
         Field[[IDim], float64],
     ]
 
-    @field_operator
+    @field_operator(backend=fieldview_backend)
     def unpack(
         inp: Field[[IDim], float64],
     ) -> OutType:
@@ -926,7 +975,7 @@ def test_tuple_unpacking_too_many_values(fieldview_backend):
         match=(r"Could not deduce type: Too many values to unpack \(expected 3\)"),
     ):
 
-        @field_operator
+        @field_operator(backend=fieldview_backend)
         def _star_unpack() -> tuple[int, float64, int]:
             a, b, c = (1, 2.0, 3, 4, 5, 6, 7.0)
             return a, b, c
@@ -937,13 +986,13 @@ def test_tuple_unpacking_too_many_values(fieldview_backend):
         FieldOperatorTypeDeductionError, match=(r"Assignment value must be of type tuple!")
     ):
 
-        @field_operator
+        @field_operator(backend=fieldview_backend)
         def _invalid_unpack() -> tuple[int, float64, int]:
             a, b, c = 1
             return a
 
 
-def test_constant_closure_vars():
+def test_constant_closure_vars(fieldview_backend):
     from gt4py.eve.utils import FrozenNamespace
 
     constants = FrozenNamespace(
@@ -951,7 +1000,7 @@ def test_constant_closure_vars():
         E=np.float32(2.718),
     )
 
-    @field_operator
+    @field_operator(backend=fieldview_backend)
     def consume_constants(input: Field[[IDim], np.float32]) -> Field[[IDim], np.float32]:
         return constants.PI * constants.E * input
 
diff --git a/tests/next_tests/ffront_tests/test_foast_lowering.py b/tests/next_tests/ffront_tests/test_foast_lowering.py
index 8856a38c..fb2affba 100644
--- a/tests/next_tests/ffront_tests/test_foast_lowering.py
+++ b/tests/next_tests/ffront_tests/test_foast_lowering.py
@@ -165,11 +165,15 @@ def test_unary_ops():
 
     reference = im.let(
         "tmp__0",
-        im.lift_(im.lambda__("inp")(im.plus_(0, im.deref_("inp"))))("inp"),
+        im.lift_(im.lambda__("inp")(im.plus_(im.literal_("0", "float64"), im.deref_("inp"))))(
+            "inp"
+        ),
     )(
         im.let(
             "tmp__1",
-            im.lift_(im.lambda__("tmp__0")(im.minus_(0, im.deref_("tmp__0"))))("tmp__0"),
+            im.lift_(
+                im.lambda__("tmp__0")(im.minus_(im.literal_("0", "float64"), im.deref_("tmp__0")))
+            )("tmp__0"),
         )("tmp__1")
     )
 
diff --git a/tests/next_tests/ffront_tests/test_gt4py_builtins.py b/tests/next_tests/ffront_tests/test_gt4py_builtins.py
index 8dbfd121..bf88097a 100644
--- a/tests/next_tests/ffront_tests/test_gt4py_builtins.py
+++ b/tests/next_tests/ffront_tests/test_gt4py_builtins.py
@@ -50,7 +50,7 @@ def test_maxover_execution(reduction_setup, fieldview_backend):
 
 def test_maxover_execution_negatives(reduction_setup, fieldview_backend):
     """Testing max_over functionality for negative values in array."""
-    if fieldview_backend in [gtfn_cpu.run_gtfn or fieldview_backend, gtfn_cpu.run_gtfn_imperative]:
+    if fieldview_backend in [gtfn_cpu.run_gtfn, gtfn_cpu.run_gtfn_imperative]:
         pytest.skip("not yet supported.")
 
     rs = reduction_setup
@@ -80,7 +80,7 @@ def test_minover_execution(reduction_setup, fieldview_backend):
     Vertex, V2EDim = rs.Vertex, rs.V2EDim
     in_field = np_as_located_field(Vertex, V2EDim)(rs.v2e_table)
 
-    @field_operator
+    @field_operator(backend=fieldview_backend)
     def minover_fieldoperator(input: Field[[Vertex, V2EDim], int64]) -> Field[[Vertex], int64]:
         return min_over(input, axis=V2EDim)
 
@@ -101,7 +101,7 @@ def test_minover_execution_float(reduction_setup, fieldview_backend):
     in_field = np_as_located_field(Vertex, V2EDim)(in_array)
     out_field = np_as_located_field(Vertex)(np.zeros(rs.num_vertices))
 
-    @field_operator
+    @field_operator(backend=fieldview_backend)
     def minover_fieldoperator(input: Field[[Vertex, V2EDim], float64]) -> Field[[Vertex], float64]:
         return min_over(input, axis=V2EDim)
 
@@ -113,9 +113,6 @@ def test_minover_execution_float(reduction_setup, fieldview_backend):
 
 def test_reduction_execution(reduction_setup, fieldview_backend):
     """Testing a trivial neighbor sum."""
-    if fieldview_backend in [gtfn_cpu.run_gtfn, gtfn_cpu.run_gtfn_imperative]:
-        pytest.skip("IndexFields are not supported yet.")
-
     rs = reduction_setup
     Edge = rs.Edge
     Vertex, V2EDim, V2E = rs.Vertex, rs.V2EDim, rs.V2E
@@ -156,7 +153,7 @@ def test_reduction_execution_nb(reduction_setup, fieldview_backend):
 def test_reduction_expression(reduction_setup, fieldview_backend):
     """Test reduction with an expression directly inside the call."""
     if fieldview_backend in [gtfn_cpu.run_gtfn, gtfn_cpu.run_gtfn_imperative]:
-        pytest.skip("IndexFields are not supported yet.")
+        pytest.skip("Has a bug.")
 
     rs = reduction_setup
     Vertex, V2EDim, V2E = rs.Vertex, rs.V2EDim, rs.V2E
@@ -178,14 +175,16 @@ def test_reduction_expression(reduction_setup, fieldview_backend):
     assert np.allclose(ref, rs.out.array())
 
 
-def test_conditional_nested_tuple():
+def test_conditional_nested_tuple(fieldview_backend):
+    if fieldview_backend in [gtfn_cpu.run_gtfn, gtfn_cpu.run_gtfn_imperative]:
+        pytest.skip("Tuple outputs not supported in gtfn backends")
     a_I_float = np_as_located_field(IDim)(np.random.randn(size).astype("float64"))
     b_I_float = np_as_located_field(IDim)(np.random.randn(size).astype("float64"))
     out_I_float = np_as_located_field(IDim)(np.random.randn(size).astype("float64"))
     out_I_float_1 = np_as_located_field(IDim)(np.random.randn(size).astype("float64"))
     mask = np_as_located_field(IDim)(np.zeros((size,), dtype=bool))
 
-    @field_operator
+    @field_operator(backend=fieldview_backend)
     def conditional_nested_tuple(
         mask: Field[[IDim], bool], a: Field[[IDim], float64], b: Field[[IDim], float64]
     ) -> tuple[
@@ -324,7 +323,7 @@ def test_conditional_shifted(fieldview_backend):
     out_I_float = np_as_located_field(IDim)(np.random.randn(size).astype("float64"))
     mask = np_as_located_field(IDim)(np.zeros((size,), dtype=bool))
 
-    @field_operator()
+    @field_operator
     def conditional_shifted(
         mask: Field[[IDim], bool], a: Field[[IDim], float64], b: Field[[IDim], float64]
     ) -> Field[[IDim], float64]:
diff --git a/tests/next_tests/ffront_tests/test_type_deduction.py b/tests/next_tests/ffront_tests/test_type_deduction.py
index 55efd4f1..110d8937 100644
--- a/tests/next_tests/ffront_tests/test_type_deduction.py
+++ b/tests/next_tests/ffront_tests/test_type_deduction.py
@@ -18,6 +18,7 @@ import pytest
 
 import gt4py.next.ffront.type_specifications
 from gt4py.next.common import DimensionKind, GTTypeError
+from gt4py.next.ffront.experimental import as_offset
 from gt4py.next.ffront.fbuiltins import (
     Dimension,
     Field,
@@ -684,3 +685,33 @@ def test_undefined_symbols():
 
     with pytest.raises(FieldOperatorTypeDeductionError, match="Undeclared symbol"):
         _ = FieldOperatorParser.apply_to_function(return_undefined)
+
+
+def test_as_offset_dim():
+    ADim = Dimension("ADim")
+    BDim = Dimension("BDim")
+    Boff = FieldOffset("Boff", source=BDim, target=(BDim,))
+
+    def as_offset_dim(a: Field[[ADim, BDim], float], b: Field[[ADim], int]):
+        return a(as_offset(Boff, b))
+
+    with pytest.raises(
+        FieldOperatorTypeDeductionError,
+        match=f"not in list of offset field dimensions",
+    ):
+        _ = FieldOperatorParser.apply_to_function(as_offset_dim)
+
+
+def test_as_offset_dtype():
+    ADim = Dimension("ADim")
+    BDim = Dimension("BDim")
+    Boff = FieldOffset("Boff", source=BDim, target=(BDim,))
+
+    def as_offset_dtype(a: Field[[ADim, BDim], float], b: Field[[BDim], float]):
+        return a(as_offset(Boff, b))
+
+    with pytest.raises(
+        FieldOperatorTypeDeductionError,
+        match=f"Excepted integer for offset field dtype",
+    ):
+        _ = FieldOperatorParser.apply_to_function(as_offset_dtype)
diff --git a/tests/next_tests/iterator_tests/test_type_inference.py b/tests/next_tests/iterator_tests/test_type_inference.py
index 502685bf..92c34228 100644
--- a/tests/next_tests/iterator_tests/test_type_inference.py
+++ b/tests/next_tests/iterator_tests/test_type_inference.py
@@ -15,6 +15,7 @@
 import numpy as np
 
 from gt4py.next.common import Dimension
+from gt4py.next.ffront import itir_makers as im
 from gt4py.next.iterator import ir, type_inference as ti
 from gt4py.next.iterator.embedded import NeighborTableOffsetProvider
 from gt4py.next.iterator.runtime import CartesianAxis
@@ -617,7 +618,7 @@ def test_stencil_closure():
             kind=ti.Iterator(),
             dtype=ti.TypeVar(idx=0),
             size=ti.Column(),
-            current_loc=ti.TypeVar(idx=1),
+            current_loc=ti.ANYWHERE,
             defined_loc=ti.TypeVar(idx=1),
         ),
         inputs=ti.Tuple.from_elems(
@@ -625,14 +626,14 @@ def test_stencil_closure():
                 kind=ti.Iterator(),
                 dtype=ti.TypeVar(idx=0),
                 size=ti.Column(),
-                current_loc=ti.TypeVar(idx=2),
-                defined_loc=ti.TypeVar(idx=2),
+                current_loc=ti.ANYWHERE,
+                defined_loc=ti.TypeVar(idx=1),
             ),
         ),
     )
     inferred = ti.infer(testee)
     assert inferred == expected
-    assert ti.pformat(inferred) == "(It[T₂, T₂, T₀ᶜ]) ⇒ It[T₁, T₁, T₀ᶜ]"
+    assert ti.pformat(inferred) == "(It[ANYWHERE, T₁, T₀ᶜ]) ⇒ It[ANYWHERE, T₁, T₀ᶜ]"
 
 
 def test_fencil_definition():
@@ -674,29 +675,29 @@ def test_fencil_definition():
                 kind=ti.Iterator(),
                 dtype=ti.TypeVar(idx=0),
                 size=ti.Column(),
-                current_loc=ti.TypeVar(idx=1),
+                current_loc=ti.ANYWHERE,
                 defined_loc=ti.TypeVar(idx=1),
             ),
             ti.Val(
                 kind=ti.Iterator(),
                 dtype=ti.TypeVar(idx=0),
                 size=ti.Column(),
-                current_loc=ti.TypeVar(idx=2),
-                defined_loc=ti.TypeVar(idx=2),
+                current_loc=ti.ANYWHERE,
+                defined_loc=ti.TypeVar(idx=1),
             ),
             ti.Val(
                 kind=ti.Iterator(),
-                dtype=ti.TypeVar(idx=3),
+                dtype=ti.TypeVar(idx=2),
                 size=ti.Column(),
-                current_loc=ti.TypeVar(idx=4),
-                defined_loc=ti.TypeVar(idx=4),
+                current_loc=ti.ANYWHERE,
+                defined_loc=ti.TypeVar(idx=3),
             ),
             ti.Val(
                 kind=ti.Iterator(),
-                dtype=ti.TypeVar(idx=3),
+                dtype=ti.TypeVar(idx=2),
                 size=ti.Column(),
-                current_loc=ti.TypeVar(idx=5),
-                defined_loc=ti.TypeVar(idx=5),
+                current_loc=ti.ANYWHERE,
+                defined_loc=ti.TypeVar(idx=3),
             ),
         ),
     )
@@ -704,9 +705,70 @@ def test_fencil_definition():
     assert inferred == expected
     assert (
         ti.pformat(inferred)
-        == "{f(intˢ, intˢ, intˢ, It[T₁, T₁, T₀ᶜ], It[T₂, T₂, T₀ᶜ], It[T₄, T₄, T₃ᶜ], It[T₅, T₅, T₃ᶜ])}"
+        == "{f(intˢ, intˢ, intˢ, It[ANYWHERE, T₁, T₀ᶜ], It[ANYWHERE, T₁, T₀ᶜ], It[ANYWHERE, T₃, T₂ᶜ], It[ANYWHERE, T₃, T₂ᶜ])}"
+    )
+
+
+def test_fencil_definition_same_closure_input():
+    f1 = ir.FunctionDefinition(
+        id="f1", params=[im.sym("vertex_it")], expr=im.deref_(im.shift_("E2V")("vertex_it"))
+    )
+    f2 = ir.FunctionDefinition(id="f2", params=[im.sym("vertex_it")], expr=im.deref_("vertex_it"))
+
+    testee = ir.FencilDefinition(
+        id="fencil",
+        function_definitions=[f1, f2],
+        params=[im.sym("vertex_it"), im.sym("output_edge_it"), im.sym("output_vertex_it")],
+        closures=[
+            ir.StencilClosure(
+                domain=im.call_("unstructured_domain")(
+                    im.call_("named_range")(
+                        ir.AxisLiteral(value="Edge"),
+                        ir.Literal(value="0", type="int"),
+                        ir.Literal(value="10", type="int"),
+                    )
+                ),
+                stencil=im.ref("f1"),
+                output=im.ref("output_edge_it"),
+                inputs=[im.ref("vertex_it")],
+            ),
+            ir.StencilClosure(
+                domain=im.call_("unstructured_domain")(
+                    im.call_("named_range")(
+                        ir.AxisLiteral(value="Vertex"),
+                        ir.Literal(value="0", type="int"),
+                        ir.Literal(value="10", type="int"),
+                    )
+                ),
+                stencil=im.ref("f2"),
+                output=im.ref("output_vertex_it"),
+                inputs=[im.ref("vertex_it")],
+            ),
+        ],
     )
 
+    offset_provider = {
+        "E2V": NeighborTableOffsetProvider(
+            np.empty((0, 2), dtype=np.int64), Dimension("Edge"), Dimension("Vertex"), 2, False
+        )
+    }
+    inferred_all: dict[int, ti.Type] = ti.infer_all(testee, offset_provider)
+
+    # validate locations of fencil params
+    fencil_param_types = [inferred_all[id(testee.params[i])] for i in range(3)]
+    assert fencil_param_types[0].defined_loc == ti.Location(name="Vertex")
+    assert fencil_param_types[1].defined_loc == ti.Location(name="Edge")
+    assert fencil_param_types[2].defined_loc == ti.Location(name="Vertex")
+
+    # validate locations of stencil params
+    f1_param_type: ti.Val = inferred_all[id(f1.params[0])]
+    assert f1_param_type.current_loc == ti.Location(name="Edge")
+    assert f1_param_type.defined_loc == ti.Location(name="Vertex")
+    #  f2 is polymorphic and there is no shift inside so we only get a TypeVar here
+    f2_param_type: ti.Val = inferred_all[id(f2.params[0])]
+    assert isinstance(f2_param_type.current_loc, ti.TypeVar)
+    assert isinstance(f2_param_type.defined_loc, ti.TypeVar)
+
 
 def test_fencil_definition_with_function_definitions():
     fundefs = [
@@ -794,43 +856,43 @@ def test_fencil_definition_with_function_definitions():
                 kind=ti.Iterator(),
                 dtype=ti.TypeVar(idx=4),
                 size=ti.Column(),
-                current_loc=ti.TypeVar(idx=5),
+                current_loc=ti.ANYWHERE,
                 defined_loc=ti.TypeVar(idx=5),
             ),
             ti.Val(
                 kind=ti.Iterator(),
                 dtype=ti.TypeVar(idx=4),
                 size=ti.Column(),
-                current_loc=ti.TypeVar(idx=6),
-                defined_loc=ti.TypeVar(idx=6),
+                current_loc=ti.ANYWHERE,
+                defined_loc=ti.TypeVar(idx=5),
             ),
             ti.Val(
                 kind=ti.Iterator(),
-                dtype=ti.TypeVar(idx=7),
+                dtype=ti.TypeVar(idx=6),
                 size=ti.Column(),
-                current_loc=ti.TypeVar(idx=8),
-                defined_loc=ti.TypeVar(idx=8),
+                current_loc=ti.ANYWHERE,
+                defined_loc=ti.TypeVar(idx=7),
             ),
             ti.Val(
                 kind=ti.Iterator(),
-                dtype=ti.TypeVar(idx=7),
+                dtype=ti.TypeVar(idx=6),
                 size=ti.Column(),
-                current_loc=ti.TypeVar(idx=9),
-                defined_loc=ti.TypeVar(idx=9),
+                current_loc=ti.ANYWHERE,
+                defined_loc=ti.TypeVar(idx=7),
             ),
             ti.Val(
                 kind=ti.Iterator(),
-                dtype=ti.TypeVar(idx=10),
+                dtype=ti.TypeVar(idx=8),
                 size=ti.Column(),
-                current_loc=ti.TypeVar(idx=11),
-                defined_loc=ti.TypeVar(idx=11),
+                current_loc=ti.ANYWHERE,
+                defined_loc=ti.TypeVar(idx=9),
             ),
             ti.Val(
                 kind=ti.Iterator(),
-                dtype=ti.TypeVar(idx=10),
+                dtype=ti.TypeVar(idx=8),
                 size=ti.Column(),
-                current_loc=ti.TypeVar(idx=12),
-                defined_loc=ti.TypeVar(idx=12),
+                current_loc=ti.ANYWHERE,
+                defined_loc=ti.TypeVar(idx=9),
             ),
         ),
     )
@@ -838,7 +900,7 @@ def test_fencil_definition_with_function_definitions():
     assert inferred == expected
     assert (
         ti.pformat(inferred)
-        == "{f :: (T₀) → T₀, g :: (It[T₃, T₃, T₁²]) → T₁², foo(intˢ, intˢ, intˢ, It[T₅, T₅, T₄ᶜ], It[T₆, T₆, T₄ᶜ], It[T₈, T₈, T₇ᶜ], It[T₉, T₉, T₇ᶜ], It[T₁₁, T₁₁, T₁₀ᶜ], It[T₁₂, T₁₂, T₁₀ᶜ])}"
+        == "{f :: (T₀) → T₀, g :: (It[T₃, T₃, T₁²]) → T₁², foo(intˢ, intˢ, intˢ, It[ANYWHERE, T₅, T₄ᶜ], It[ANYWHERE, T₅, T₄ᶜ], It[ANYWHERE, T₇, T₆ᶜ], It[ANYWHERE, T₇, T₆ᶜ], It[ANYWHERE, T₉, T₈ᶜ], It[ANYWHERE, T₉, T₈ᶜ])}"
     )
 
 
diff --git a/tests/next_tests/iterator_tests/test_with_toy_connectivity.py b/tests/next_tests/iterator_tests/test_with_toy_connectivity.py
index 0857e9a0..33f1c122 100644
--- a/tests/next_tests/iterator_tests/test_with_toy_connectivity.py
+++ b/tests/next_tests/iterator_tests/test_with_toy_connectivity.py
@@ -28,7 +28,6 @@ from next_tests.toy_connectivity import (
     v2v_arr,
 )
 
-from gt4py.next.common import Dimension
 from gt4py.next.iterator import transforms
 from gt4py.next.iterator.builtins import deref, lift, plus, reduce, shift
 from gt4py.next.iterator.embedded import (
@@ -37,12 +36,20 @@ from gt4py.next.iterator.embedded import (
     np_as_located_field,
 )
 from gt4py.next.iterator.runtime import fundef, offset
-from gt4py.next.program_processors.formatters import gtfn
+from gt4py.next.program_processors.formatters import gtfn, type_check
 from gt4py.next.program_processors.runners import gtfn_cpu
 
 from .conftest import run_processor
 
 
+def edge_index_field():  # TODO replace by index_field once supported in bindings
+    return np_as_located_field(Edge)(np.arange(e2v_arr.shape[0]))
+
+
+def vertex_index_field():  # TODO replace by index_field once supported in bindings
+    return np_as_located_field(Vertex)(np.arange(v2e_arr.shape[0]))
+
+
 @fundef
 def sum_edges_to_vertices(in_edges):
     return (
@@ -53,9 +60,9 @@ def sum_edges_to_vertices(in_edges):
     )
 
 
-def test_sum_edges_to_vertices(program_processor_no_gtfn_exec, lift_mode):
-    program_processor, validate = program_processor_no_gtfn_exec
-    inp = index_field(Edge)
+def test_sum_edges_to_vertices(program_processor, lift_mode):
+    program_processor, validate = program_processor
+    inp = edge_index_field()
     out = np_as_located_field(Vertex)(np.zeros([9]))
     ref = np.asarray(list(sum(row) for row in v2e_arr))
 
@@ -76,9 +83,11 @@ def sum_edges_to_vertices_reduce(in_edges):
     return reduce(plus, 0)(shift(V2E)(in_edges))
 
 
-def test_sum_edges_to_vertices_reduce(program_processor_no_gtfn_exec, lift_mode):
-    program_processor, validate = program_processor_no_gtfn_exec
-    inp = index_field(Edge)
+def test_sum_edges_to_vertices_reduce(program_processor, lift_mode):
+    program_processor, validate = program_processor
+    if program_processor == gtfn_cpu.run_gtfn_imperative:
+        pytest.skip("gtfn_imperative has a bug.")
+    inp = edge_index_field()
     out = np_as_located_field(Vertex)(np.zeros([9]))
     ref = np.asarray(list(sum(row) for row in v2e_arr))
 
@@ -99,11 +108,9 @@ def first_vertex_neigh_of_first_edge_neigh_of_cells(in_vertices):
     return deref(shift(E2V, 0)(shift(C2E, 0)(in_vertices)))
 
 
-def test_first_vertex_neigh_of_first_edge_neigh_of_cells_fencil(
-    program_processor_no_gtfn_exec, lift_mode
-):
-    program_processor, validate = program_processor_no_gtfn_exec
-    inp = index_field(Vertex)
+def test_first_vertex_neigh_of_first_edge_neigh_of_cells_fencil(program_processor, lift_mode):
+    program_processor, validate = program_processor
+    inp = vertex_index_field()
     out = np_as_located_field(Cell)(np.zeros([9]))
     ref = np.asarray(list(v2e_arr[c[0]][0] for c in c2e_arr))
 
@@ -129,6 +136,8 @@ def sparse_stencil(non_sparse, inp):
 
 def test_sparse_input_field(program_processor_no_gtfn_exec, lift_mode):
     program_processor, validate = program_processor_no_gtfn_exec
+    if program_processor == type_check.check:
+        pytest.xfail("Partial shifts not properly supported by type inference.")
     non_sparse = np_as_located_field(Edge)(np.zeros(18))
     inp = np_as_located_field(Vertex, V2E)(np.asarray([[1, 2, 3, 4]] * 9))
     out = np_as_located_field(Vertex)(np.zeros([9]))
@@ -151,6 +160,9 @@ def test_sparse_input_field(program_processor_no_gtfn_exec, lift_mode):
 
 def test_sparse_input_field_v2v(program_processor_no_gtfn_exec, lift_mode):
     program_processor, validate = program_processor_no_gtfn_exec
+    if program_processor == type_check.check:
+        pytest.xfail("Partial shifts not properly supported by type inference.")
+
     non_sparse = np_as_located_field(Edge)(np.zeros(18))
     inp = np_as_located_field(Vertex, V2V)(v2v_arr)
     out = np_as_located_field(Vertex)(np.zeros([9]))
@@ -293,11 +305,7 @@ def lift_stencil(inp):
 
 def test_lift(program_processor, lift_mode):
     program_processor, validate = program_processor
-    inp = index_field(Vertex)
-    if program_processor in [gtfn_cpu.run_gtfn, gtfn_cpu.run_gtfn_imperative]:
-        # TODO(tehrengruber): only a temporary solution until index fields are supported in the
-        #  gtfn backend.
-        inp = np_as_located_field(Vertex)(np.array([inp.field_getitem(i) for i in range(0, 9)]))
+    inp = vertex_index_field()
     out = np_as_located_field(Vertex)(np.zeros([9]))
     ref = np.asarray(np.asarray(range(9)))