gridpoints 1.0.3__tar.gz → 1.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gridpoints
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: fast multidimensional sorting tool for point clouds using the monotonic lagrangian grid
5
5
  Author: Armand de Cacqueray
6
6
  License: MIT
@@ -16,25 +16,31 @@ Dynamic: license-file
16
16
 
17
17
  ## ❒ Gridpoints — Multi dimensional sort for point clouds
18
18
 
19
- `Gridpoints` maps unstructured **point clouds** to structured grids through a **bijective transformation**: one point, one cell, no overlap, fully invertible. It replaces and enhances the squarenet project with a faster sorting algorithm.
19
+ `Gridpoints` maps unstructured **point clouds** to structured grids through a **bijective transformation**: one point, one cell, no overlap, fully invertible. It replaces and enhances the squarenet project with a more powerfull sorting algorithm.
20
20
 
21
- Take raw point cloud `X(N, D)`
22
- → find a grid shape and a permutation `order` such that
23
- Xgrid = X[order].reshape(*gridshape, D) is sorted along every axis of the grid.
24
- → On the `Xgrid` view of `X`, neighbor queries become a simple stencil look-up
25
- neighborhood[i, j, k] = {Xgrid[i±di, j±dj, k±dk] | (di, dj, dk) ≤ R}, where `R` is a radius cutoff to determine,
26
- allowing local operations in linear time.
27
- → Standard operations (grid convolution, clustering, …) can then be applied
28
- directly on the `Xgrid` view instead of relying on complex graph convolutions
21
+ What it does: Take raw point cloud `P(N, D)` and find a grid shape and an index permutation `order` such that Pgrid = P[order].reshape(*gridshape, D)
22
+ is sorted along every axis of the grid. E.g. in 3D, for Pgrid = (x, y, z):
23
+ ```text
24
+ x[i+1, j, k] >= x[i, j, k]
25
+ y[i, j+1, k] >= y[i, j, k]
26
+ z[i, j, k+1] >= z[i, j, k]
27
+ ```
28
+ → On the `Pgrid` view of `P`, neighbor queries become a simple stencil look-up :
29
+ ```text
30
+ neighborhood[i, j, k] = {Pgrid[i±di, j±dj, k±dk] | (di, dj, dk) ≤ R},
31
+ ````
32
+ where `R` is a radius cutoff to determine, allowing local operations in linear time.
33
+ → Standard operations (convolution, clustering, …) can then be applied
34
+ directly on the `Pgrid` view instead of relying on complex graph convolutions
29
35
  or other point-cloud techniques.
30
36
 
31
- `X` can be a NumPy, PyTorch or CuPy array of any dimension (N, D).
37
+ `P` can be a NumPy, PyTorch or CuPy array of any dimension (N, D).
32
38
  To allow natural padding when the grid has more cells than points,
33
39
  NaNs and Infs are supported in a consistent manner:
34
40
  - nans → random position
35
41
  - (+-) infs → border of the grid
36
- This allow to deal with prime or variable N, as long as one is ready to
37
- deal with void/special grid cells.
42
+
43
+ This allow to gridsort prime or variable number of points N, as long as one is ready to deal with void/special grid cells.
38
44
 
39
45
  Expected runtime for sorting 1 million points: CPU → < 10s, GPU → < 500 ms
40
46
 
@@ -54,20 +60,20 @@ import gridpoints as grid
54
60
  import numpy as np
55
61
 
56
62
  # Raw point cloud (numpy, pytorch or cupy)
57
- X = np.random.rand(1_000_000, 3)
63
+ A = np.random.rand(1_000_000, 3)
58
64
 
59
65
  # Sorted view: place the points inside the grid
60
- order = grid.argsort(X, gridshape=(100, 100, 100))
61
- Yflat = X[order]
62
- Ygrid = Yflat.reshape(100, 100, 100, 3)
66
+ order = grid.argsort(A, gridshape=(100, 100, 100))
67
+ Bflat = A[order]
68
+ Bgrid = Bflat.reshape(100, 100, 100, 3)
63
69
 
64
70
  # Rest of your pipeline, working with grids
65
- Zgrid = apply_something(Ygrid)
71
+ Cgrid = apply_something(Bgrid)
66
72
 
67
73
  # Back to the original points indexing
68
- Zflat = Zgrid.reshape(-1, 2)
74
+ Cflat = Cgrid.reshape(-1, 3)
69
75
  orderinv = grid.invert_permutation(order)
70
- Z = Zflat[orderinv] # matches the initial points order
76
+ C = Cflat[orderinv] # matches the initial points order
71
77
  ```
72
78
 
73
79
  <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/ballexemple.png">
@@ -85,6 +91,6 @@ As an example, empirical results in 2-D show that `R = 5` is enough for ~99 % of
85
91
  The typical use-case of `Gridpoints` is to allow fast local operations on arbitrary point clouds using stencil kernels:
86
92
 
87
93
  ```text
88
- output(i, j, k) = f( Xgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
94
+ output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
89
95
  ```
90
96
  To go beyond standard (slow) python loops, this can be accelerated with native grid convolution operations of standard libraries whenever possible, or with `pystencils` or `taichi` compilers for complex/non linear grid kernels.
@@ -1,24 +1,30 @@
1
1
  ## ❒ Gridpoints — Multi dimensional sort for point clouds
2
2
 
3
- `Gridpoints` maps unstructured **point clouds** to structured grids through a **bijective transformation**: one point, one cell, no overlap, fully invertible. It replaces and enhances the squarenet project with a faster sorting algorithm.
4
-
5
- Take raw point cloud `X(N, D)`
6
- → find a grid shape and a permutation `order` such that
7
- Xgrid = X[order].reshape(*gridshape, D) is sorted along every axis of the grid.
8
- → On the `Xgrid` view of `X`, neighbor queries become a simple stencil look-up
9
- neighborhood[i, j, k] = {Xgrid[i±di, j±dj, k±dk] | (di, dj, dk) ≤ R}, where `R` is a radius cutoff to determine,
10
- allowing local operations in linear time.
11
- → Standard operations (grid convolution, clustering, …) can then be applied
12
- directly on the `Xgrid` view instead of relying on complex graph convolutions
3
+ `Gridpoints` maps unstructured **point clouds** to structured grids through a **bijective transformation**: one point, one cell, no overlap, fully invertible. It replaces and enhances the squarenet project with a more powerfull sorting algorithm.
4
+
5
+ What it does: Take raw point cloud `P(N, D)` and find a grid shape and an index permutation `order` such that Pgrid = P[order].reshape(*gridshape, D)
6
+ is sorted along every axis of the grid. E.g. in 3D, for Pgrid = (x, y, z):
7
+ ```text
8
+ x[i+1, j, k] >= x[i, j, k]
9
+ y[i, j+1, k] >= y[i, j, k]
10
+ z[i, j, k+1] >= z[i, j, k]
11
+ ```
12
+ → On the `Pgrid` view of `P`, neighbor queries become a simple stencil look-up :
13
+ ```text
14
+ neighborhood[i, j, k] = {Pgrid[i±di, j±dj, k±dk] | (di, dj, dk) ≤ R},
15
+ ````
16
+ where `R` is a radius cutoff to determine, allowing local operations in linear time.
17
+ → Standard operations (convolution, clustering, …) can then be applied
18
+ directly on the `Pgrid` view instead of relying on complex graph convolutions
13
19
  or other point-cloud techniques.
14
20
 
15
- `X` can be a NumPy, PyTorch or CuPy array of any dimension (N, D).
21
+ `P` can be a NumPy, PyTorch or CuPy array of any dimension (N, D).
16
22
  To allow natural padding when the grid has more cells than points,
17
23
  NaNs and Infs are supported in a consistent manner:
18
24
  - nans → random position
19
25
  - (+-) infs → border of the grid
20
- This allow to deal with prime or variable N, as long as one is ready to
21
- deal with void/special grid cells.
26
+
27
+ This allow to gridsort prime or variable number of points N, as long as one is ready to deal with void/special grid cells.
22
28
 
23
29
  Expected runtime for sorting 1 million points: CPU → < 10s, GPU → < 500 ms
24
30
 
@@ -38,20 +44,20 @@ import gridpoints as grid
38
44
  import numpy as np
39
45
 
40
46
  # Raw point cloud (numpy, pytorch or cupy)
41
- X = np.random.rand(1_000_000, 3)
47
+ A = np.random.rand(1_000_000, 3)
42
48
 
43
49
  # Sorted view: place the points inside the grid
44
- order = grid.argsort(X, gridshape=(100, 100, 100))
45
- Yflat = X[order]
46
- Ygrid = Yflat.reshape(100, 100, 100, 3)
50
+ order = grid.argsort(A, gridshape=(100, 100, 100))
51
+ Bflat = A[order]
52
+ Bgrid = Bflat.reshape(100, 100, 100, 3)
47
53
 
48
54
  # Rest of your pipeline, working with grids
49
- Zgrid = apply_something(Ygrid)
55
+ Cgrid = apply_something(Bgrid)
50
56
 
51
57
  # Back to the original points indexing
52
- Zflat = Zgrid.reshape(-1, 2)
58
+ Cflat = Cgrid.reshape(-1, 3)
53
59
  orderinv = grid.invert_permutation(order)
54
- Z = Zflat[orderinv] # matches the initial points order
60
+ C = Cflat[orderinv] # matches the initial points order
55
61
  ```
56
62
 
57
63
  <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/ballexemple.png">
@@ -69,6 +75,6 @@ As an example, empirical results in 2-D show that `R = 5` is enough for ~99 % of
69
75
  The typical use-case of `Gridpoints` is to allow fast local operations on arbitrary point clouds using stencil kernels:
70
76
 
71
77
  ```text
72
- output(i, j, k) = f( Xgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
78
+ output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
73
79
  ```
74
- To go beyond standard (slow) python loops, this can be accelerated with native grid convolution operations of standard libraries whenever possible, or with `pystencils` or `taichi` compilers for complex/non linear grid kernels.
80
+ To go beyond standard (slow) python loops, this can be accelerated with native grid convolution operations of standard libraries whenever possible, or with `pystencils` or `taichi` compilers for complex/non linear grid kernels.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gridpoints"
7
- version = "1.0.3"
7
+ version = "1.0.5"
8
8
  description = "fast multidimensional sorting tool for point clouds using the monotonic lagrangian grid "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -7,4 +7,4 @@ __all__ = (
7
7
  print_zoom,
8
8
  )
9
9
 
10
- __version__ = "1.0.3"
10
+ __version__ = "1.0.5"
@@ -108,14 +108,14 @@ def print_zoom(Ygrid, center, side=1):
108
108
  else:
109
109
  i, j, k = center
110
110
  print("axis 0:")
111
- for ii in range(i-len, i+side+1):
111
+ for ii in range(i-side, i+side+1):
112
112
  print(_format_point(Ygrid[ii, j, k]), end=" ")
113
113
  print()
114
114
  print("axis 1:")
115
- for jj in range(j-len, j+side+1):
115
+ for jj in range(j-side, j+side+1):
116
116
  print(_format_point(Ygrid[i, jj, k]), end=" ")
117
117
  print()
118
118
  print("axis 2:")
119
- for kk in range(k-len, k+side+1):
119
+ for kk in range(k-side, k+side+1):
120
120
  print(_format_point(Ygrid[i, j, kk]), end=" ")
121
121
  print()
@@ -8,4 +8,4 @@ __all__ = (
8
8
  "invert_permutation",
9
9
  )
10
10
 
11
- __version__ = "1.0.3"
11
+ __version__ = "1.0.5"
@@ -10,7 +10,7 @@ like arange, copy, shuffle etc.
10
10
  _FLOAT = 'float32'
11
11
  _INT = 'int32'
12
12
 
13
- EPS = 10**(-10)
13
+ EPS = 10**(-5) if (_FLOAT == 'float32') else 10**(-10)
14
14
 
15
15
 
16
16
  def get_dtype(dtp, xp):
@@ -100,8 +100,8 @@ def nan_to_num(arr):
100
100
  finite = xp.isfinite(x)
101
101
  vals = x[finite]
102
102
 
103
- out[xp.isposinf(x), d] = 2 * vals.max()
104
- out[xp.isneginf(x), d] = 2 * vals.min()
103
+ out[xp.isposinf(x), d] = vals.max() + 1
104
+ out[xp.isneginf(x), d] = vals.min() - 1
105
105
 
106
106
  nan = xp.isnan(x)
107
107
  n = nan.sum().item() if backend == 'torch' else int(nan.sum())
@@ -164,4 +164,4 @@ def transpose(X, axes):
164
164
  _, name = get_backend(X)
165
165
  if name == "torch":
166
166
  return X.permute(*axes)
167
- return X.transpose(axes)
167
+ return X.transpose(axes)
@@ -18,7 +18,7 @@ from .kdtree import kdtree_order
18
18
  # Sorting API
19
19
  # ============================================
20
20
 
21
- def argsort(X, gridshape, level=2, init="kdtree", n_iter=50, verbose = 2):
21
+ def argsort(X, gridshape, verbose = 2, level=2, init="kdtree", n_iter=40):
22
22
  """Compute a multi-directional monotonic sorted ordering of a point cloud.
23
23
 
24
24
  Parameters
@@ -33,6 +33,8 @@ def argsort(X, gridshape, level=2, init="kdtree", n_iter=50, verbose = 2):
33
33
  Note: Point clouds can be padded with +/- infinity placeholders to reach
34
34
  a valid grid size. NaNs can also be used, but will be placed randomly
35
35
  rather than pushed to grid boundaries.
36
+ verbose : int, default 2
37
+ 0 = silent, 1 = log progress, 2 = 1 + warn if convergence failed
36
38
  level : {1, 2, 3}, default=2
37
39
  Maximum order of lattice directions considered. Higher values improve
38
40
  grid quality at the expense of computational speed. Note that `level=3`
@@ -40,19 +42,14 @@ def argsort(X, gridshape, level=2, init="kdtree", n_iter=50, verbose = 2):
40
42
  init : {'kdtree', 'shuffle'} or None, default="kdtree"
41
43
  Initialization method for the permutation order. Try init = None
42
44
  if the point cloud is already nearly sorted.
43
- n_iter : int, default=50
45
+ n_iter : int, default=40
44
46
  Maximum number of optimization iterations.
45
- verbose : int, default 2
46
- 0 = silent, 1 = log progress, 2 = 1 + warn if convergence failed
47
47
 
48
48
  Returns
49
49
  -------
50
50
  order : ndarray of shape (N,)
51
51
  Permutation indices such that `X[order].reshape(*shape, D)` forms a
52
52
  monotonically sorted grid.
53
- converged : bool
54
- `True` if the algorithm converged within `n_iter` iterations, `False` otherwise.
55
- If not converged, the fonction will also raise a convergence warning
56
53
  """
57
54
  N, D = X.shape
58
55
  assert math.prod(gridshape) == N
@@ -65,9 +62,8 @@ def argsort(X, gridshape, level=2, init="kdtree", n_iter=50, verbose = 2):
65
62
  order = kdtree_order(X, gridshape)
66
63
  if verbose >=1:
67
64
  print("done")
68
- converged = True
69
65
  if level == 1:
70
- return order, converged
66
+ return order
71
67
  else:
72
68
  order = arange(len(X), 'int', reference_array=X)
73
69
  if init == "shuffle":
@@ -77,27 +73,28 @@ def argsort(X, gridshape, level=2, init="kdtree", n_iter=50, verbose = 2):
77
73
  order = order[ordernew]
78
74
 
79
75
  if not converged:
80
- while level >= 2:
76
+ while level >= 2 and not converged:
81
77
  level = level - 1
82
78
  ordernew, converged = _mlg_step(
83
79
  X[order], gridshape, level=level, n_iter=n_iter, verbose = verbose
84
80
  )
85
81
  order = order[ordernew]
86
- if not converged and verbose >=2:
87
- warnings.warn(
88
- f"Algorithm failed to converge after {n_iter} iterations. " \
89
- "Consider checking if the grid structure is already suitable" \
90
- "for the given task, which is higly probable, or increase the" \
91
- "`n_iter` parameter if strict convergence is required.",
92
- category=ConvergenceWarning,
93
- stacklevel=2,
94
- )
82
+ if not converged and verbose >=2:
83
+ warnings.warn(
84
+ f"Algorithm failed to converge after {n_iter} iterations.\n " \
85
+ "Consider checking if the grid structure is already suitable \n" \
86
+ "for the given task, which is higly probable, or increase the \n" \
87
+ "`n_iter` parameter if strict convergence is required.\n",
88
+ "(to shutup this warning, run the sort with verbose <= 1)",
89
+ category=ConvergenceWarning,
90
+ stacklevel=2
91
+ )
95
92
  if verbose:
96
93
  print("done")
97
- return order, converged
94
+ return order
98
95
 
99
96
 
100
- def sort(X, gridshape, level=2, init="kdtree", inplace=False, n_iter=50, verbose = 2):
97
+ def sort(X, gridshape, verbose = 2, level=2, init="kdtree", inplace=False, n_iter=40):
101
98
  """Sort a point cloud `X` using the monotonic Lagrangian algorithm.
102
99
 
103
100
  Parameters
@@ -112,6 +109,8 @@ def sort(X, gridshape, level=2, init="kdtree", inplace=False, n_iter=50, verbose
112
109
  Note: Point clouds can be padded with +/- infinity placeholders to reach
113
110
  a valid grid size. NaNs can also be used, but will be placed randomly
114
111
  rather than pushed to grid boundaries.
112
+ verbose : int, default 2
113
+ 0 = silent, 1 = log progress, 2 = 1 + warn if convergence failed
115
114
  level : {1, 2, 3}, default=1
116
115
  Maximum order of lattice directions considered. Higher values improve
117
116
  grid quality at the expense of computational speed. Note that `level=3`
@@ -121,10 +120,8 @@ def sort(X, gridshape, level=2, init="kdtree", inplace=False, n_iter=50, verbose
121
120
  if the point cloud is already nearly sorted.
122
121
  inplace : bool, default=False
123
122
  If True, modify the point cloud `X` in place.
124
- n_iter : int, default=50
123
+ n_iter : int, default=40
125
124
  Maximum number of optimization iterations.
126
- verbose : int, default 2
127
- 0 = silent, 1 = log progress, 2 = 1 + warn if convergence failed
128
125
 
129
126
  Returns
130
127
  -------
@@ -132,7 +129,7 @@ def sort(X, gridshape, level=2, init="kdtree", inplace=False, n_iter=50, verbose
132
129
  The sorted point cloud such that `X_sorted.reshape(*shape, D)` forms a
133
130
  monotonically sorted grid. Returns `None` if `inplace=True`.
134
131
  """
135
- order, _ = argsort(X, gridshape, level=level, init = init, n_iter=n_iter,verbose = verbose)
132
+ order = argsort(X, gridshape, verbose = verbose, level=level, init = init, n_iter=n_iter)
136
133
 
137
134
  if inplace:
138
135
  X[:] = X[order]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gridpoints
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: fast multidimensional sorting tool for point clouds using the monotonic lagrangian grid
5
5
  Author: Armand de Cacqueray
6
6
  License: MIT
@@ -16,25 +16,31 @@ Dynamic: license-file
16
16
 
17
17
  ## ❒ Gridpoints — Multi dimensional sort for point clouds
18
18
 
19
- `Gridpoints` maps unstructured **point clouds** to structured grids through a **bijective transformation**: one point, one cell, no overlap, fully invertible. It replaces and enhances the squarenet project with a faster sorting algorithm.
19
+ `Gridpoints` maps unstructured **point clouds** to structured grids through a **bijective transformation**: one point, one cell, no overlap, fully invertible. It replaces and enhances the squarenet project with a more powerfull sorting algorithm.
20
20
 
21
- Take raw point cloud `X(N, D)`
22
- → find a grid shape and a permutation `order` such that
23
- Xgrid = X[order].reshape(*gridshape, D) is sorted along every axis of the grid.
24
- → On the `Xgrid` view of `X`, neighbor queries become a simple stencil look-up
25
- neighborhood[i, j, k] = {Xgrid[i±di, j±dj, k±dk] | (di, dj, dk) ≤ R}, where `R` is a radius cutoff to determine,
26
- allowing local operations in linear time.
27
- → Standard operations (grid convolution, clustering, …) can then be applied
28
- directly on the `Xgrid` view instead of relying on complex graph convolutions
21
+ What it does: Take raw point cloud `P(N, D)` and find a grid shape and an index permutation `order` such that Pgrid = P[order].reshape(*gridshape, D)
22
+ is sorted along every axis of the grid. E.g. in 3D, for Pgrid = (x, y, z):
23
+ ```text
24
+ x[i+1, j, k] >= x[i, j, k]
25
+ y[i, j+1, k] >= y[i, j, k]
26
+ z[i, j, k+1] >= z[i, j, k]
27
+ ```
28
+ → On the `Pgrid` view of `P`, neighbor queries become a simple stencil look-up :
29
+ ```text
30
+ neighborhood[i, j, k] = {Pgrid[i±di, j±dj, k±dk] | (di, dj, dk) ≤ R},
31
+ ````
32
+ where `R` is a radius cutoff to determine, allowing local operations in linear time.
33
+ → Standard operations (convolution, clustering, …) can then be applied
34
+ directly on the `Pgrid` view instead of relying on complex graph convolutions
29
35
  or other point-cloud techniques.
30
36
 
31
- `X` can be a NumPy, PyTorch or CuPy array of any dimension (N, D).
37
+ `P` can be a NumPy, PyTorch or CuPy array of any dimension (N, D).
32
38
  To allow natural padding when the grid has more cells than points,
33
39
  NaNs and Infs are supported in a consistent manner:
34
40
  - nans → random position
35
41
  - (+-) infs → border of the grid
36
- This allow to deal with prime or variable N, as long as one is ready to
37
- deal with void/special grid cells.
42
+
43
+ This allow to gridsort prime or variable number of points N, as long as one is ready to deal with void/special grid cells.
38
44
 
39
45
  Expected runtime for sorting 1 million points: CPU → < 10s, GPU → < 500 ms
40
46
 
@@ -54,20 +60,20 @@ import gridpoints as grid
54
60
  import numpy as np
55
61
 
56
62
  # Raw point cloud (numpy, pytorch or cupy)
57
- X = np.random.rand(1_000_000, 3)
63
+ A = np.random.rand(1_000_000, 3)
58
64
 
59
65
  # Sorted view: place the points inside the grid
60
- order = grid.argsort(X, gridshape=(100, 100, 100))
61
- Yflat = X[order]
62
- Ygrid = Yflat.reshape(100, 100, 100, 3)
66
+ order = grid.argsort(A, gridshape=(100, 100, 100))
67
+ Bflat = A[order]
68
+ Bgrid = Bflat.reshape(100, 100, 100, 3)
63
69
 
64
70
  # Rest of your pipeline, working with grids
65
- Zgrid = apply_something(Ygrid)
71
+ Cgrid = apply_something(Bgrid)
66
72
 
67
73
  # Back to the original points indexing
68
- Zflat = Zgrid.reshape(-1, 2)
74
+ Cflat = Cgrid.reshape(-1, 3)
69
75
  orderinv = grid.invert_permutation(order)
70
- Z = Zflat[orderinv] # matches the initial points order
76
+ C = Cflat[orderinv] # matches the initial points order
71
77
  ```
72
78
 
73
79
  <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/ballexemple.png">
@@ -85,6 +91,6 @@ As an example, empirical results in 2-D show that `R = 5` is enough for ~99 % of
85
91
  The typical use-case of `Gridpoints` is to allow fast local operations on arbitrary point clouds using stencil kernels:
86
92
 
87
93
  ```text
88
- output(i, j, k) = f( Xgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
94
+ output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
89
95
  ```
90
96
  To go beyond standard (slow) python loops, this can be accelerated with native grid convolution operations of standard libraries whenever possible, or with `pystencils` or `taichi` compilers for complex/non linear grid kernels.
File without changes
File without changes