GridTools
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    ###### tags: `frontend` `shaping` A Frontend Specification for the Functional GT4Py Model (Deprecated) ======================================================= [TOC] ## Introduction This document collects both the concepts exposed to GT4Py users to implement computations and the syntax used to represent the concepts. GT4Py aims to to define an interface to specify computations between n-dimensional data arrays, called **Fields**, defined on top of a **Grid** (or *Mesh*), which is a discretized representation of a 3D physical space. Computations are defined locally in a subset of the grid points called the **Domain** of the computation. The frontend component expose these concepts with a specific syntax to the users, who then define the data arrays, grids and computations that will be processed by the toolchain component to generate a valid implementation. The frontend is in charge of mapping these concepts to the IR concepts defined in the [Iterator IR spec](https://github.com/GridTools/concepts/wiki/Iterator-View) and thus, in general, both sets of concepts are very similar. ## Concepts ### Dimension A *Dimension* is a just a *tag* or type for indices. ### Index An integer number tagged with a [dimension](#Dimension), i.e. an element of an axis. __Syntax proposal (WIP)__: ``` Index[Dim](int_idx) ``` ### Axis An *Axis* of a [dimension](#Dimension) *D* can be generally defined as an ordered set of [indices](#Index) of *D* (i.e. a spatially discretized version of a _dimension_). For this project, we will use a slightly more restricted definition of *Axis* as just a range (`(start, end, step)`) of index values. ### Position A tuple of [indices](#Index) indicating a position on the grid / mesh. __Syntax proposal (WIP)__: ```python= Position[*Axis](*int_indices) ``` ### Domain Cartesian product of [axes](#Axis) (encoded as a tuple of *axes*). __Syntax proposal (WIP)__: ```python= Domain[*Axis]((i_start, i_stop), ...) ``` ### Field A mapping from [positions](#Position) to values of a certain datatype (e.g. int, float, tuple of int). GT4Py assumes that actual implementations of the *Field* concept use a contiguous memory buffer representation. __Syntax proposal (WIP)__: - Since `Field` is just a concept, the actual allocation interface could use the `Storage` class or other Array-like libraries with the `__gt_data_interface__`. TODO: revise the current implementation of `__gt_data_interface__` and adapt it to this proposal. - If/When [PEP646](https://www.python.org/dev/peps/pep-0646/) gets approved the syntax for typing could be: ```python= Axes = TypeVarTuple('Axes') DTypeT = TypeVar('DTypeT') class Field(Generic[DTypeT, *Axes]): ... field: Field[DTypeT, *Axes] # Alternative class Space(Generic[*Axes]): ... field: Field[Space[*Axes], DTypeT] ``` - Currently, typing could be done by something like: ```python= Axis1T = TypeVar('Axis1T') Axis2T = TypeVar('Axis2T') Axis3T = TypeVar('Axis3T') SpaceT = TypeVar('SpaceT') ... DTypeT = TypeVar('DTypeT') class Space1D(Generic[Axis1T]): ... class Space2D(Generic[Axis1T, Axis2T]): ... class Space3D(Generic[Axis1T, Axis2T, Axis3T]): ... class Field(Generic[SpaceT, DTypeT]): ... Field1D = Field[Space1D[Axis1T], DTypeT] Field2D = Field[Space2D[Axis1T, Axis2T], DTypeT] ... f: Field1D[VAxis, float] ``` ### Connectivity A *Connectivity* is a mapping from an [index](#Index) to a list of indices. The length of the list is either limited, e.g. for the connectivity from vertex to neighboring vertices, or fixed, e.g. from edge to vertex. __Syntax proposal (WIP)__: Each connectivity is its own type implementing the following protocol: ```python= class Connectivity(Protocol[From_Axis, To_Axis]): max_neighbors: ClassVar[int] has_skip_values: ClassVar[bool] # TODO: Aliases LEFT = 0 RIGHT = 1 ... ``` ### Local View _(Alternative name: LocatedView? Accessor?)_ A local-view of a field is a representation of the value of the field at a specific position. Intuitively, it can be thought of as a tuple of a field together with a position, which can be converted to a value by index. It can be used as the actual value for any value operation and it additionally supports an extra operation to create another local-view placed on a different position, by mapping or shifting its current one. __Syntax proposal (WIP)__: - Typing: `LocalView[PositionT, SpaceT, DType]` - Arithmetic operations and tuple access: just as regular Python values ```python= new_value = view + 3.5 + tuple_view[2] ``` - Creating new views: + Mapping position using `Connectivity`: _Connectivity_ will map the view position (or the selected axis) to a tuple of indices that will be expanded in place. ```python= view: LocalView[...] V2E: Connectivity[...] # General syntax: View(Connectivity(NeighborId)) new_view = view(V2E(1)) # new_view(position=V2E.map(view.position)[1]) # If there are named aliases in connectivity: View(Connectivity.NeighborAlias) new_view = view(V2E.SECOND) # Concatenation of mappings new_view = view(AB(0) | BC(2) | CD(1)) # Mapping only some components of current position (other components are left identical) new_view = view(AxisA >> AB(0)) ``` + Shifting one axis of the position using a literal offset: ```python= new_view = view(I + 3) ``` ### Operator A pure functional _kernel_ computing a value (of any accepted data type, including tuples) from different [local-views](#Local-View) passed as arguments. #### Local operator An operator accepting point-wise [local-views](#Local-View). __Syntax proposal (WIP)__: - A Python function decorated with `@gt.local_operator(...)` - Keyword arguments: TODO - Accepted data arguments are `LocalView` - If the current iteration position is needed, an extra keyword-only argument `position: Position` should be added to the signature. + Identification of the position argument? * [ ] by keyword name: `position` * [ ] by type hint: `Position` * [ ] both: `position: Position` - Question: explicit or implicit `lift` and `deref` ?? - Accepted statements inside the body: - Assignments to scalar temporaries (using arithmetic and ternary operators) - _Shifting_/creation of new [views ](#Local-View) - Conditionals or ternary operators * [ ] Only ternary operators ```python= @local_operator def op(...): return ... if ... else ... ``` * [ ] Every branch needs to return ```python= @local_operator def op(...): if b: return ... else: return ... @local_operator def op(...): if b: return ... return ... ``` * [X] Symbols should always be defined in the code-path of their usage. In other words: - If a symbol is defined in both branches it may be used outside of the conditional - If a symbol is only defined in a single branch it may only be used inside that branch unless it was already defined previously (as this is equal to assigning the previous value to the again). ```python= @local_operator def op(...): a = 1 if some_condition: a = 2 b = 10 else: tmp = 20 b = tmp # equal to writing a = 1 a, b = (2, 10) if some_condition else (a, 20) ``` * [ ] Unrestricted `if`: Symbols may be used regardless of whether it is possible to statically prove they were assigned. ```python= @local_operator def op(...): if some_condition: a = ... else: b = ... if some_other_condition_equivalent_to_the_first_one: return a else: return b ``` * [ ] ~~All symbols directly or indirectly involved in the return expression should always be defined regardless of the control-flow~~ This option was withdrawn as it would allow symbols not being used in the return expression to have undefined values, which we would like to avoid to avoid errors further down the toolchain. - call builtins - return values - Restrictions for assignments and uses of variables: * Intuitively: Every time a variable is used, it has to be guaranteed (_statically_) that the variable is initialized at that point. * _More formally_: For each statement, there is a set of initialized variables before and after that statement. A simple forward flow analysis can be used to correctly deduce those sets for each statement. If-conditions (or more generally _path constraints_) are ignored (hence this is a static algorithm as it ignores runtime information like the conditions to if statements). Because our control flow graph is very simple (no loops, no goto, etc) this can be done in a single pass (no fixed-point iteration required). * Further reading: * Definite assignment analysis: https://en.wikipedia.org/wiki/Definite_assignment_analysis * Data-flow analysis: https://en.wikipedia.org/wiki/Data-flow_analysis - Weird behaviour with symbol binding & _Fields_: ```python= # Question: are the following two examples equivalent? @gt.local_operator def bindings1(input_a: Iterator, input_b: Iterator, condition: Iterator): x: Iterator = input_a evaluate_if[...] if condition: x: Iterator = input_b y: Iterator = x(I + 1) return x @gt.local_operator def bindings2(input_a: Iterator, input_b: Iterator, condition: Iterator): tmp_it: Iterator = x(I + 1) tmp_it2: Iterator = another_op[...](a,b) tmp_it: Value = x(I+1) + 2. #tmp_it3: Iterator = x if condition else y# x: Iterator = input_b(x+1) if condition else input_a(x+1) y: Iterator = x(I + 1) return y # Answer: No # Because in `bindings2` the condition `condition` will be lifted: y = input_b[I + 1] if condition[I + 1] else input_a[I + 1] # Whereas as in `bindings1` the condition `condition` won't be shifted: y = input_b[I + 1] if condition else input_a[I + 1] # Lessons: # * Conditions to if-statements are point-wise (have a local value) # and have eager-evaluation. # * Symbol bindings are local. # * When lifting an expression it can change from eager-evaluation to # lazy-evaluation. # * Implicit lifting can mean that eager-evaluation gets changed to # lazy-evaluation. ``` - Question regarding temporaries: Can they be accessed with off-sets? - Some simple examples in pseudo code for the structured cartesian use case: ```python= @local_operator def op(flat_field, column_field, full_field, scalar): a: value = flat_field * column_field + full_field * scalar #a: value = deref(flat_field) * deref(column_field) + full_field * scalar a:view = compute_blabla[...](flat_field, column_field, full_field, scalar) # a = lift(compute_something)(flat_field, column_field, full_field, scalar) # can we access temporaries with offset and if so what does it mean: # if ...: # return a[i + 1] # elif ...: # return a[k + 1] # elif ...: # return a[i + 1, k + 1] # return a ##------------ def op(flat: LocalView[I, J], full: LocalView[I, J, K]) -> value: return flat*full(K+1) # lift explicit todo EGP # all axis not part of the iteration domain but in a local view # need to be explicitly defined (e.g. by shift) @local_operator(domain=Domain[Vertex]) def some_other_unstructured_op(v: LocalView[Vertex], e: LocalView[Edge]): # domain: [Vertex] tmp = e(V2E[0]) * v tmp2 = e.field[UNDEFINED] + 1. return tmp + tmp2 op_view = op[...](flat, full_at_k) return op_view(K+1) #+ flat(K+1) @local_operator(domain=Domain[Cell]) def some_other_unstructured_op(c: LocalView[Cell], e: LocalView[Edge]): val_left = 2*e(C2E, 0)**2 val_right = 2*e**2 for e_nb_vals in e(C2E) return 0.5 * (val_left + val_right) @local_operator(domain=Domain[I, J]) def some_other_op(flat: LocalView[I, J], full: LocalView[I, J, K]): # domain: [I, J] field_full_at_k = field_full(K[3]) op_view = op[...](flat, full_at_k) return op_view(K+1) #+ flat(K+1) @local_operator(domain=Domain[I, J]) def some_other_op(normal: LocalView[I, J], staggered: LocalView[I_s, J]): # domain: [I, J] tmp1 = normal + 1 tmp2 = staggered(I-1/2) + 1 return normal + staggered(I-1/2) @program def main(field_flat, field_full): some_other_op[IndexRange(I, 0, 10), IndexRange(J, 0, 10)](field_flat, field_full_at_k) ##------------ @local_operator def op(a: LocalView[I, J], full_b: LocalView[I, J, K]): class LocalView: field: Field position: Position = UNDEFINED def __call__(self, offset_spec) -> LocalView: return LocalView(self.field, shift(self.position, offset_spec)) @property def __value__(self): return self.field[self.position] a = a + full_b # field_a.__value__ + full_field.__value__ a_2 = a(I+1) * 2 return a_2 another_view:view = compute_something(flat_field, column_field, full_field, scalar) # a = lift(compute_something)(flat_field, column_field, full_field, scalar) # can we access temporaries with offset and if so what does it mean: if ...: return a[i + 1] elif ...: return a[k + 1] elif ...: return a[i + 1, k + 1] return a ``` __Examples__: ```python= # TODO def local_operator(...) -> Operator[]: ... @gt.local_operator(...) def operator_definition(a: LocalView, b: LocalView) -> Value: return a + b @gt.local_operator(...) def operator_definition( a: LocalView, b: LocalView, *, position: Position ) -> Value: return a + b if position[0] > 3 else 0 ``` #### Column operator An operator accepting and returning _ColumnViews_. Those are used for specialized processing of fields _columns_ (usually vertical columns). Currently the only way to directly create column operators is by using the `scan()` builtin, which applies a _scan pass_ function over a specific axis while carrying along state. Combined column operators can be also defined by grouping calls to column operators in a single function __Syntax proposal (WIP)__: `scan()` builtin: - `scan(scan_pass_op, *, forward: bool = True, init=None)` builtin. Alternative: `scan(scan_pass_op, *, reverse: bool = False, init=None)` builtin - Signature of scan pass operator: `scan_pass_op(state: Any, *views: LocalView) -> Tuple[Any, Value] # (state, output)` - Accepted data arguments and statements inside the body as in [local operator](#Local-operator) `column_operator(...)`: - A Python function decorated with `@gt.column_operator(...)` - Keyword arguments: TODO - Accepted data arguments are `LocalView` __Examples__: ```python= @gt.local_operator def tridiag_forward( state: Tuple[float, float], a: LocaLView[], b : ..., c, d ): if state is None: return (c / b, d / b) cp_km1, dp_km1 = state cp_k = c / (b - a * cp_km1) dp_k = (d - a * dp_km1) / (b - a * cp_km1) return (cp_k, dp_k) @gt.local_operator def tridiag_backward(x_kp1: float, cp: ..., dp): if x_kp1 is None: return dp return dp - cp * x_kp1 @gt.column_operator def solve_tridiag(a, b, c, d): cp, dp = scan(tridiag_forward, True, None)(a, b, c, d) return scan(tridiag_backward, forward=False, init=None)(cp, dp) @gt.program(sequential_axis=K) def solve_tridiag(i_size, j_size, k_size, a, b, c, d, x): domain = IndexRange[IDim](0, i_size)*IndexRange[JDim](0, j_size)*IndexRange[KDim](0, k_size) solve_tridiag[domain](a, b, c, d, out=[x]) # --- Option 2: directly defined a scan pass using scan as a decorator --- @gt.scan(forward=True, init=None) @gt.local_operator def tridiag_forward( state: Tuple[float, float], a: LocalView[], b : ..., c, d ): ... @gt.column_operator def solve_tridiag(a, b, c, d): cp, dp = tridiag_forward(a, b, c, d) return tridiag_backward(cp, dp) ``` ### Program A sequence of field operations (and entry point from the host language to GT4Py). Currently, the only allowed field operation is the application of local/column operators. __Syntax proposal (WIP)__: - Typing: ```python= P = ParamSpec("P") class Program(Protocol[P]): __call__: Callable[P, None] def build(**kwargs: Any) -> Program[P]: """Build a program using different compile/backend settings.""" ... ``` - A Python function decorated with `@gt.program(...)`. - If all the required build arguments are provided in the decorator, it builds a `Program` object directly, otherwise it would return a lazy program object that needs to be explicitly built using `.build(**kwargs)`. ```python # Lazy application @gt.program def prog(...): ... compiled_program = prog.build(backend=..., ) ``` - Keyword arguments: + _connectivities_: list of `Connectivity` type definitions + _sequential_axis_: Axis (for all sequential/column operators) + _backend_: str ? + _options_: Mapping[str, Any] ? + `gt.globals` vs `externals` run-time args?? - Compile-time constants ? + [ ] externals dict-like passed as argument at compilation ```python= @gt.operator(...) def op(a, ...): from __externals__ import A return a*A @gt.program(externals={"A": 32.34}) def program(): ... ``` + [ ] direct binding to python symbols (maybe only for _frozen_ objects?) ```python= # option 1 A = 3.5 # option 2 (safer) A = gt.global(3.5, name="A", dtype=float) @gt.operator(...) def op(a, ...): return a*A ``` + [ ] frozen dataclass like ```python= class ModelConstants(gt.constant_container): b = 1 @gt.operator(...) def op(a, ...): # Option 1: ModelConstants.b ``` + [ ] partial ```python= # functools partial inspired # https://docs.python.org/3/library/functools.html#functools.partial @gt.operator(...) def op(A, a, ...): return a*A @gt.program def program(A, a, ...): op(A, a, ...) gt.partial(program, A=32.34) ``` + [ ] both - Run-time values ? + [ ] global variables defined as `gt.global()` + [ ] direct binding to python symbols + [ ] run-time arguments ```python= @gt.operator(...) def op(dt: float, a, ...): return dt*a @gt.program def program(dt: float, ...): ... op[domain](dt, a) ``` + [ ] both - Accepted data arguments are `Field`s and scalars (run-time parameters) + Question: should be scalar tuples supported ?? How to enforce fixed length? - Accepted statements inside the body: - Operator application (taken from Numba syntax): ```python= operator_name[domain_definition](*input_fields, out=[*output_fields]) ``` - Domain/var definitions: TODO __Examples__: ```python= def program(connectivities: List[Connectivity]) -> Program: ... ASDF = gt.global(3) @gt.program(connectivities=[Conn1, Conn2, A2B]) def my_program(domain_indication: Any, field_a: Field[], field_b: Field) -> None: # Operator application domain = ... # TODO operator[domain](field_a, field_b, out=[field_b]) # Option A another_build = my_program.build(connectivities=OTHER_CONNS) my_program((3,10), field_a, field_b, connectivities=[...]) ``` ## Alternative Positional Proposal In this proposal positions and indices respectively are treated as first-class-citizens. The datastructures introduced here corresponding 1-to-1 to the mathematical concepts given at the end of the document. ### Field operator *Type signature* `FieldOperator` ```python= __init__(local_op: Union[LocalOperator, ColumnOperator], *, domain) __call__(*args: Union[Field, Value], *, out: Union[Field, Tuple[Field, ...]]) -> Field ``` *Construction* Field operators are constructed from local- or column-operators either by specification of a domain or by lifting them. (The `__init__` function was only given for completeness previously) ```python= # only valid in @gt.program context field_op: FieldOperator = op[domain] # only valid in @gt.local_operator context lazy_field_op: FieldOperator = op[...] # lift ``` *Call* The usual function call syntax is used to evaluate a field operator for a given set of fields and values. At the `gt.program` level field operators need to be called with the additional `out` argument specifying field(s) into which the results should be materialized. As such a `gt.program` is stateful. ```python= # context: gt.program @gt.program(...) def program(..., f_out): ... # 1. construct field operator from local operator `op`, # 2. evaluate in all points of the `domain` with given fields # and materialize into `f_out` return op[domain](f_1, ..., f_n, out=f_out) ``` This syntax is inspired by the [numba kernel call syntax](https://numba.pydata.org/numba-doc/latest/cuda/kernels.html). ### Local operator Local operators are defined by decoration of some (python) function `op` with `@gt.local_operator` decorator. *Definition signature* (unstructured + cartesian) ```python= op(*args: Union[Field, DT], *, position: Position) -> Value ``` - `args`: set of arguments being either a Field or a value. - `position`: mandatory keyword argument *Definition signature* (cartesian) For the cartesian case the local operator may be defined and called without a position argument. ```python= op(*args: Union[Accessor, DT]) -> Value op(*args: Union[Accessor, DT], *, position: Position) -> Value ``` - `args`: set of arguments being either an Accessor or a value. - `position`: mandatory keyword argument *Type signature* `LocalOperator` The decorator returns a `LocalOperator` object with the following signature. Note that all overloads of `__call__` may be used regardless of the signature of the decorated function. ```python= __init__(op: Callable, domain: Optional[Type[DomainT]]) __call__(*args: Union[Field, DT], *, position: Position) -> Value __call__(*args: Union[Accessor, DT], *, position: Position) -> Value __call__(*args: Union[Accessor, DT]) -> Value ``` If a `LocalOperator` is called with accessors their focal point must agree (can be checked easily both at the IR level and during embedded execution). As such they - together with the type of the domain passed to the decorator - implicitly provide the position. This is an additional requirement not given by the semantic-model but introduced in order to guide the user to write comprehensible code that maintains the perspective of a *local operator*, i.e. a function defined on __one__ position. #### Usage __Definition__ ```python= @gt.local_operator def sum_vnb(inp: Field[[V], DT], *, position: Position[V]): (v,) = position return sum(inp[v2] for v2 in neighbors(v, V2V)) @gt.local_operator(domain=Domain[I, J]) def sum_vnb(inp_sx: Accessor[[I+1/2, J], DT], inp_sy: Accessor[[I, J+1/2], DT]): # todo: define how the accessor relates to the domain # accessors can not be created they are (at least conceptually) # constructed by the decorator and their focal point is # the current position potentially shifted based on the index # convention (here from I, J to I+1/2, J or I, J+1/2) return inp_sx[I-1/2]+inp[I+1/2]+inp_sy[J-1/2]+inp_sy[J+1/2] ``` __Evaluation__ Local operators may either be called inside of another local operator (by direct call or after lifting) ```python= @gt.local_operator def sum_vnb_wrapper(inp: Field[[V], DT], *, position: Position[V]): (v,) = position # direct call return sum_vnb(inp, position=neighbor(v, (V2V, 0))) # lifting # instructive version # 1. lift local operator to field operator sum_vnb_fop: FieldOperator = sum_vnb[...] # 2. field operator evaluation into a field sumvnb_field: Field = sum_vnb_fop(inp) # 3. access at a neighbor position return sumvnb_field[neighbor(v, (V2V, 0))] # compact version return sum_vnb[...](inp)[neighbor(v, (V2V, 0))] ``` or at the program level by transforming them to a field operator. ```python= @gt.program def fencil(...): # 1. transformation into a field operator # `sum_vnb[vertices]` # 2. materializing into `out` field sum_vnb[vertices](inp, out=out) ``` __Composition__ ```python= @gt.local_operator def loc_op(inp: Field[[V], DT], *, position: Position[V]): # direct call + lift return loc_op1(local_op2[...](inp), position=position) # lift + lift + access return loc_op1[...](local_op2[...](inp))[position] ``` WIP: ```python= @gt.local_operator def compute_nb_vals_avg(nb_vals: Field[V, Field[[ConnT], DT]], *, position: Position[V]): (v,) = position return sum(nb_vals[v])/len(nb_vals[v]) @gt.local_operator def field_getitem_with_nbs(self, nbs): return self[v] for v in nbs @gt.local_operator def loc_op(val: Field[[V], DT], *, position): nbs1 = neighbors(v, V2V) nbs2 = neighbors(v, V2V, V2V) # creation of a sparse field nb_vals: Field[[V], Field[V2V, DT]] = field_getitem_with_nbs[...](val, nbsX) nb_vals = val[nbsX] # doesn't match numpy syntax nb_vals = val.remap(v, nbs) nb_vals = (lambda v: val[v] for v in nbsX)[...]() # inline local op return compute_nb_vals_avg(nb_vals, position=position) ``` <!--in which case the position has to be passed manually together with a domain at the program level where they are automatically lifted to a field operator such that the local operator is called for each position in the domain.--> ### Column operator A Python function decorated with `@gt.column_operator(init: DT=init)`, where `init` is an initial value passed to `op`. *Signature* ```python= op(state: DT, *args: Union[Field, DT], *, position: Position) -> Field[[Dim], DT] ``` where `DIM` as the specified column dimension. - `state`: value from the previous call to `op` else `init` - `args`: set of arguments being either a Field or a value. - `position`: mandatory keyword argument ```python= @gt.column_operator def column_sum(state: Union[DT, Tuple[DT]], column: Field[[K], DT]): *_, k = position return column[k] ``` ### Accessing neighbors Neighbors can be accessed using the built-in functions `neighbor` for accessing a single neighbor or `neighbors` for accessing a set of neighbors. *Signature* ```python= neighbor(idx: Index, conn: Type[ConnT], int) -> Index neighbors(idx: Index, *shifts: Tuple[Type[ConnT], int]) -> List[Index] ``` with `ConnT = TypeVar("ConnT", bound=Connectivity)` *Neighbor aliasing* Since indices are first-class-citizens there is no need to label or alias them. ```python= v_left, v_right = vertices(e) ``` #### Field `__init__(domain: Union[Domain[*Dim], None], image: np.array)` `__getitem__(self, pos: Position)` ## Mathematical concepts ### Positional This section contains the definition of all concepts in pure mathematical terms. [WIP] Not thoroughly connected to the rest of the document. __Value__ A *value* is either a number, a tuple of numbers, or a field. __Dimension__ A *dimension* is a just a tag or type for [indices](#index) ([wikipedia](https://en.wikipedia.org/wiki/Dimension)). __Index__ An index is an integer in $\mathbb{Z}$ tagged with a [dimension](#Dimension), i.e. an element of an axis. __Neighbor Dimension__ Similar to a dimension a *neighbor dimension* is just a tag or type, but for [local indices](#local_index) __Local index__ A local index is an integer number tagged with a [neighbor dimension]. __Index range__ An *index range* is the set of all integers between two integers $a, b \in \mathbb{Z}$, e.g $\{1, 2, 3\}$, $\{-1, 0, 1\}$ ([wikipedia](https://en.wikipedia.org/wiki/Interval_(mathematics)#Integer_intervals)) todo: index type __Domain__ A *domain* is a cartesian product of index ranges. todo: index dims only once todo: enforce canonical representation todo: elements of domain are positions <!--__Axis__ The set of all possible [indices](#index) in a particular dimension.--> __Position__ A tuple of indices indicating a position on the grid / mesh. __Field__ A *field* is a function $f: D \to V, pos \mapsto v$, with $D$ a domain and $V$ a set of values. __Connectivity__ A *connectivity* is a function $f: pos \mapsto (nb\_pos_1, \ldots, nb\_pos_n)$, with $pos$ a position and $nb\_pos_i$ a set of positions neighboring $pos$. __Field operator__ A *field operator* is (higher-order) function $op: (f_1, \ldots, f_n) \mapsto f_{\text{out}}$, where $f_1, \ldots, f_n, f_{out}$ are a set fields. In other words a field operator is a function mapping fields to a field. TODO: Apply local operator to all points Note the difference in notation to the local operator originating from the higher-order function nature of the field operator. Instead of specifying the spaces of the functions arguments and value we are denoting the arguments and the value directly. __Local operator__ A *local operator* is a (higher-order) function $\hat{op}: (pos, f_1, \ldots, f_n) \mapsto v$, where $pos$ is a position, element of the domain $D$, $f_1, \ldots, f_n$ a set of fields, and $v \in V$ a value. TODO: spaces __Column operator__ A *column operator* is a (higher-order) function $\hat{op_c}: (pos, f_1, \ldots, f_n) \mapsto f_c$, where $pos$ is a (todo: horizontal) position, $f_1, \ldots, f_n$ a set of fields, and $f_c$ a column, i.e. a field whose domain is one dimensional. __Local view / iterator / accessor__ A *local view* is a tuple $(pos, f)$ where $pos$ is a position and $f$ a field. It can be used as the actual value for any value operation and it additionally supports an extra operation to create another local-view placed on a different position, by mapping or shifting its current one. <!--__Shift__ $$\text{shift}((pos, f), offset) = ()$$ $$\text{shift}(f, dim, o)(pos) = f(pos_0, \ldots, pos_{dim}+o, \ldots, pos_n) \tag{Cartesian}$$ <-- imprecise -- $$\text{shift}(f, nb\_dim, l\_idx)(pos) = f((conn(nb\_dim)(pos))_{l\_idx}) \tag{Unstructured}$$--> __Lift__ The (higher-order) function $lift: \hat{op} \to op$ transforms a local operator or column operator $\hat{op}$ to a field operator $op$ by partial application of all arguments after the $pos$ argument of $\hat{op}$. $$\underbrace{\text{lift}(\hat{op})}_{= op}(f_1, \ldots, f_n) = pos \mapsto\hat{op}(pos, f_1, \ldots, f_n)$$ Note that for $\hat{op}$ a column operator the resulting field operator $op$ returns a field of columns. To avoid this we can (optionally) define a different lift for column operators $\hat{op_c}$ as: $$\underbrace{\text{lift}(\hat{op_c})}_{= op}(f_1, \ldots, f_n) = (pos, k) \mapsto \hat{op_c}(pos, f_1, \ldots, f_n)(k)$$ Let $\text{shift}$ be a function such that the following holds $\text{shift}(f, o)(pos) = f(pos+o)$ then: $$\text{shift}(\text{lift}(\hat{op})(f_1, \ldots, f_n), o)(pos) = \hat{op}(pos, \text{shift}(f_1, o), \ldots, \text{shift}(f_n, o))$$ In other words shifting a lifted local operator is equal to shifting the arguments. __Scan pass__ A *scan pass* is a function $\hat{op_s}: (pos, state, f_1, \ldots, f_n) \mapsto v$, where $pos$ is a position, *state* is a value, $f_1, \ldots, f_n$ a set of fields, and $v \in V$ a value. __Scan__ The function $scan_C: (\hat{op_s}, dir, init) \mapsto \hat{op_c}$ transforms a scan pass $\hat{op_s}$, a direction $dir \in \{-1, 1\}$ (either forward $1$ or backward $-1$), and an initial value $init$ into a column operator $\hat{op_c}$, where $\hat{op_c}$ is defined as $$\begin{aligned}&\hat{op_c}(pos, f_1, \ldots, f_n) \\ &= k \mapsto \begin{cases} \hat{op_s}((pos, k_{0}), init, f_1, \ldots, f_n) & | \text{ if } k = k_{0} \\ \hat{op_s}\Big((pos, k), \hat{op_c}\big((pos, k-dir), f_1, \ldots, f_n\big), f_1, \ldots, f_n\Big) \end{cases}\end{aligned}$$ with $k_0$ being either the first, resp. last, position of the column $C$ for $dir = 1$, resp. $dir=-1$. Alternatively we can make $scan$ transform into a field operator $op$ instead of a column operator: $$\begin{aligned}&op(f_1, \ldots, f_1) = \\ &= (pos, k) \mapsto \begin{cases} \hat{op_s}((pos, k_{0}), init, f_1, \ldots, f_n) & | \text{ if } k = k_{0} \\ \hat{op_s}\Big((pos, k), \hat{op_c}\big((pos, k-dir), f_1, \ldots, f_n\big), f_1, \ldots, f_n\Big) \end{cases}\end{aligned}$$ __Map__ The (higher-order) function $map: (\hat{op}, D) \mapsto f_{out}$ takes a local operator $\hat{op}$ and a domain $D$ and returns a field $f_{out}: D \to V, pos \mapsto \hat{op}(pos, f_1, \ldots, f_n)$. ### Iterator view __Iterator__ An *iterator* is a function $o \mapsto f(\text{shift}_{pos}(pos, o))$ from an offset $o$ to the value of some field $f$ at some position $pos$ shifted by $o$. $\text{shift}_{it}(o) = it \mapsto (o \mapsto it(o))$ It can be used as the actual value for any value operation $f(pos)$ (`deref`) and it additionally supports an extra operation to create another iterator placed on a different position $\text{shift}_{it}$ (`shift`). __Lift__ The (higher-order) function $lift: \hat{op} \to op$ transforms a stencil $\hat{op}$ returning a value to a stencil returning an iterator by shifting $\hat{op}$'s arguments. $$ \text{lift}(\hat{op})(it_1, \ldots, it_n) = (it_1, \ldots, it_n) \mapsto (o \mapsto\hat{op}(\text{shift}_{it}(it_1)(o), \ldots, \text{shift}_{it}(it_n)(o))) $$ As such $\text{lift}$ allows to evaluate a stencil returning values at an offset. <!--__Relation to the iterator view__ By replacing each field $f_i: pos \mapsto f_i(pos)$ within the above representation (except for the map operation) by an iterator $it: o \mapsto \text{shift}(f_i, o)(pos_i)$ we arrive at the iterator view.--> Notes: - The lifted iterator of the iterator view needs to store different data than the operator passed from the apply_stencil call. As such iterators can not be encoded using a single (mathematical representation) easily. - remove integer requirement for indices - domain is not an isolated concept, but tied to a field - the current axis concept does not match previous discussions with EGP (universe concept). Is it even useful as what we called universe must be tied to a field right? Scratchpad: ```python= var_offset=1 if some_cond else 2 var_offset=shift(K, 1) if some_cond else shift(K, 2) var_offset(it) ```

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully