gridpoints 1.0.6__tar.gz → 1.0.7__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.
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: gridpoints
3
+ Version: 1.0.7
4
+ Summary: fast multidimensional sorting tool for point clouds using the monotonic lagrangian grid
5
+ Author: Armand de Cacqueray
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Neighborhood-Grid/CubeNet
8
+ Project-URL: Repository, https://github.com/Neighborhood-Grid/CubeNet
9
+ Keywords: sorting,point-clouds,multi-indexing,grid,neighborhood-grid,monotonic-lagrangian-grid,squarenet,nearest-neighbor
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Provides-Extra: demo
14
+ Requires-Dist: squarenet; extra == "demo"
15
+ Dynamic: license-file
16
+
17
+ ## ❒ Gridpoints — Multi dimensional sort for point clouds
18
+
19
+ `Gridpoints` maps raw indexes of unstructured **point clouds** to structured grids multi-index through a **bijective transformation**:
20
+ One point, one cell,
21
+ ```text
22
+ n <-> [i,j,…](n).
23
+ ```
24
+ It replaces and enhances the [squarenet](https://github.com/Neighborhood-Grid/SquareNet) project with a more robust points sorting algorithm. The goal is
25
+ to build a spatialy coherent multi index to later store and query the point cloud efficiently, based on adaptative axes (lines, columns, and more in 3D+) as showed on following exemple :
26
+
27
+ <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/ballexemple.png">
28
+
29
+ What it does: Take raw point cloud `P(N, D)` and find a grid shape and an index permutation `order` such that `Pgrid(I, J, …, D)` = P[order].reshape(*gridshape, D) is sorted along every axis of the grid. E.g. in 3D, for Pgrid = (x, y, z):
30
+ ```text
31
+ x[i+1, j, k] >= x[i, j, k]
32
+ y[i, j+1, k] >= y[i, j, k]
33
+ z[i, j, k+1] >= z[i, j, k]
34
+ ```
35
+
36
+ → On the `Pgrid` view of `P`, neighbor queries become a simple stencil look-up
37
+ ```text
38
+ neighborhood(Pgrid[i, j, k]) = {Pgrid[i±di, j±dj, k±dk] | (di, dj, dk) ≤ R},
39
+ ````
40
+ where `R` is a radius cutoff to determine, allowing local operations in linear time.
41
+ → Geometric operations (convolution, clustering, interpolation …) can then be applied **in linear time** directly on the `Pgrid` view instead of relying on complex graph or point-cloud methods
42
+
43
+ `P` can be a NumPy, PyTorch or CuPy array of any dimension (N, D). To allow natural padding when the grid has more cells than they are points in the cloud, NaNs and Infs are supported in a consistent manner:
44
+ - nans → random position
45
+ - (+-) infs → border of the grid
46
+
47
+ This allow to gridsort prime or variable number of points, as long as one is ready to deal with void/special grid cells.
48
+
49
+ Expected runtime for sorting 1 million points: CPU: < 10s (numpy), GPU: < 500 ms (torch cuda)
50
+
51
+ `When not to use gridpoints ?`
52
+ - high dimension: the package is implemented to support arbitrary dimension, but sweetspot is really 2D/3D. dimensions 4-6 might still be reasonable depending on the task, but anything above 8D is generally too high dimensional for gridpoints.
53
+ - weird geometries. Supported datasets goes beyond smooth convex distributions: map of Indonesia, a sponge, a donuts, an elephant, an eggshell (by specifying a suitable gridshape e.g. (128,128,2) to `gridpoints.sort()`). You can see various exemples in the [plot](https://github.com/Neighborhood-Grid/CubeNet/blob/main/plots) folder But with some limits. Bad fits: a spider web, a wind turbine, same eggshell with a naive 3d sort (gridshape = (32,32,32)) ... the issue is not that gridpoints cannot sort these distributions, but that it will produce a poor representation of the geometry.
54
+ - small point clouds: beyond a few hundred points, local operations in linear time is not worth the overhead, because naive quadratic implementations will probably be simultaneously simpler and faster.
55
+
56
+ ---
57
+
58
+ ### Installation
59
+
60
+ ```bash
61
+ pip install gridpoints # core only
62
+ pip install gridpoints[demo] # for the demonstration notebook.
63
+ ```
64
+ Full Demo: [notebook.ipynb](https://github.com/Neighborhood-Grid/CubeNet/blob/main/notebook.ipynb)
65
+
66
+ ### Quickstart
67
+
68
+ ```python
69
+ import gridpoints as grid
70
+ import numpy as np
71
+
72
+ # Raw point cloud (numpy, pytorch or cupy)
73
+ A = np.random.rand(1_000_000, 3)
74
+
75
+ # Sorted view: place the points inside the grid
76
+ order = grid.argsort(A, gridshape=(100, 100, 100))
77
+ Bflat = A[order] #flat but sorted with a grid layout (C-order)
78
+ Bgrid = Bflat.reshape(100, 100, 100, 3) #reshaped as a grid
79
+
80
+ # Rest of your pipeline, working with grids
81
+ Cgrid = apply_something(Bgrid) #(100, 100, 100, *)
82
+
83
+ # Back to the original points indexing
84
+ Cflat = Cgrid.reshape(1_000_000, *)
85
+ orderinv = grid.invert_permutation(order)
86
+ C = Cflat[orderinv] # matches the initial points order
87
+ ```
88
+
89
+ ---
90
+
91
+ ### Note on rectangular gridshapes
92
+
93
+ The eggshell example illustrates the importance of selecting a suitable grid-shape decomposition. This can itself be challenging when the point cloud is assumed to follow a complex topological pattern, such as a thin surface rather than a full volumetric domain. While the precise decomposition is not particularly important (e.g. (18,20,16) vs. (16,15,24) will generally make little difference), the orders of magnitude of the different dimensions do matter and should be tuned to the dataset: (18,20,16) and (36,40,4) can lead to substantially different results. As mentioned, nan/inf padding can help accomodate integer-factorization constraints
94
+
95
+ Another possibility for smoothing out topology-specific effects is to perform the sort after randomly projecting the dataset onto a lower-dimensional subspace. Random projections are known to approximately preserve pairwise distances, as formalized by the Johnson–Lindenstrauss lemma.
96
+
97
+
98
+ ### Note on the cutoff radius R
99
+
100
+ There is no strict theoretical guarantee about what the cutof radius R should be for a given task. E.g the relative grid position between a point and its nearest neighbors can't be garanted to be in the exact adjacent grid cells. What is guaranteed from the sorted ordering is only **grid monotonicity**: *x* coordinates increase along rows, *y* coordinates along columns, and so on.
101
+
102
+ As an example, empirical results in 2-D show that `R = 5` is enough for ~99 % of the nearest neighbors; some outlier neighbors will sit further apart for complex geometries with pronounced peaks, holes or any non-smoothness. When a stricter neighborhood is required, or in high dimensional setting, the solution might be to build an assembly of grid experts, each working on a rotated / projected view of the points, as discussed in [this topic](https://github.com/glotzerlab/freud/discussions/1417). An other possibility would be to tile the grid: each tile is enhanced with location metadata (e.g. a bounding box), allowing for pruning pairs of tiles with a distance certified to be far enough for a given criterion.
103
+
104
+
105
+ ### Note on efficient stencil operations
106
+
107
+ The typical use-case of `Gridpoints` is to allow fast local operations on arbitrary point clouds using stencil kernels:
108
+
109
+ ```text
110
+ output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
111
+ ```
112
+ To go beyond standard (slow) python loops, this kind of kernel computation 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. On GPU, tiling the grid and leveraging a custom `triton` kernel might be particularly efficient.
@@ -0,0 +1,96 @@
1
+ ## ❒ Gridpoints — Multi dimensional sort for point clouds
2
+
3
+ `Gridpoints` maps raw indexes of unstructured **point clouds** to structured grids multi-index through a **bijective transformation**:
4
+ One point, one cell,
5
+ ```text
6
+ n <-> [i,j,…](n).
7
+ ```
8
+ It replaces and enhances the [squarenet](https://github.com/Neighborhood-Grid/SquareNet) project with a more robust points sorting algorithm. The goal is
9
+ to build a spatialy coherent multi index to later store and query the point cloud efficiently, based on adaptative axes (lines, columns, and more in 3D+) as showed on following exemple :
10
+
11
+ <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/ballexemple.png">
12
+
13
+ What it does: Take raw point cloud `P(N, D)` and find a grid shape and an index permutation `order` such that `Pgrid(I, J, …, D)` = P[order].reshape(*gridshape, D) is sorted along every axis of the grid. E.g. in 3D, for Pgrid = (x, y, z):
14
+ ```text
15
+ x[i+1, j, k] >= x[i, j, k]
16
+ y[i, j+1, k] >= y[i, j, k]
17
+ z[i, j, k+1] >= z[i, j, k]
18
+ ```
19
+
20
+ → On the `Pgrid` view of `P`, neighbor queries become a simple stencil look-up
21
+ ```text
22
+ neighborhood(Pgrid[i, j, k]) = {Pgrid[i±di, j±dj, k±dk] | (di, dj, dk) ≤ R},
23
+ ````
24
+ where `R` is a radius cutoff to determine, allowing local operations in linear time.
25
+ → Geometric operations (convolution, clustering, interpolation …) can then be applied **in linear time** directly on the `Pgrid` view instead of relying on complex graph or point-cloud methods
26
+
27
+ `P` can be a NumPy, PyTorch or CuPy array of any dimension (N, D). To allow natural padding when the grid has more cells than they are points in the cloud, NaNs and Infs are supported in a consistent manner:
28
+ - nans → random position
29
+ - (+-) infs → border of the grid
30
+
31
+ This allow to gridsort prime or variable number of points, as long as one is ready to deal with void/special grid cells.
32
+
33
+ Expected runtime for sorting 1 million points: CPU: < 10s (numpy), GPU: < 500 ms (torch cuda)
34
+
35
+ `When not to use gridpoints ?`
36
+ - high dimension: the package is implemented to support arbitrary dimension, but sweetspot is really 2D/3D. dimensions 4-6 might still be reasonable depending on the task, but anything above 8D is generally too high dimensional for gridpoints.
37
+ - weird geometries. Supported datasets goes beyond smooth convex distributions: map of Indonesia, a sponge, a donuts, an elephant, an eggshell (by specifying a suitable gridshape e.g. (128,128,2) to `gridpoints.sort()`). You can see various exemples in the [plot](https://github.com/Neighborhood-Grid/CubeNet/blob/main/plots) folder But with some limits. Bad fits: a spider web, a wind turbine, same eggshell with a naive 3d sort (gridshape = (32,32,32)) ... the issue is not that gridpoints cannot sort these distributions, but that it will produce a poor representation of the geometry.
38
+ - small point clouds: beyond a few hundred points, local operations in linear time is not worth the overhead, because naive quadratic implementations will probably be simultaneously simpler and faster.
39
+
40
+ ---
41
+
42
+ ### Installation
43
+
44
+ ```bash
45
+ pip install gridpoints # core only
46
+ pip install gridpoints[demo] # for the demonstration notebook.
47
+ ```
48
+ Full Demo: [notebook.ipynb](https://github.com/Neighborhood-Grid/CubeNet/blob/main/notebook.ipynb)
49
+
50
+ ### Quickstart
51
+
52
+ ```python
53
+ import gridpoints as grid
54
+ import numpy as np
55
+
56
+ # Raw point cloud (numpy, pytorch or cupy)
57
+ A = np.random.rand(1_000_000, 3)
58
+
59
+ # Sorted view: place the points inside the grid
60
+ order = grid.argsort(A, gridshape=(100, 100, 100))
61
+ Bflat = A[order] #flat but sorted with a grid layout (C-order)
62
+ Bgrid = Bflat.reshape(100, 100, 100, 3) #reshaped as a grid
63
+
64
+ # Rest of your pipeline, working with grids
65
+ Cgrid = apply_something(Bgrid) #(100, 100, 100, *)
66
+
67
+ # Back to the original points indexing
68
+ Cflat = Cgrid.reshape(1_000_000, *)
69
+ orderinv = grid.invert_permutation(order)
70
+ C = Cflat[orderinv] # matches the initial points order
71
+ ```
72
+
73
+ ---
74
+
75
+ ### Note on rectangular gridshapes
76
+
77
+ The eggshell example illustrates the importance of selecting a suitable grid-shape decomposition. This can itself be challenging when the point cloud is assumed to follow a complex topological pattern, such as a thin surface rather than a full volumetric domain. While the precise decomposition is not particularly important (e.g. (18,20,16) vs. (16,15,24) will generally make little difference), the orders of magnitude of the different dimensions do matter and should be tuned to the dataset: (18,20,16) and (36,40,4) can lead to substantially different results. As mentioned, nan/inf padding can help accomodate integer-factorization constraints
78
+
79
+ Another possibility for smoothing out topology-specific effects is to perform the sort after randomly projecting the dataset onto a lower-dimensional subspace. Random projections are known to approximately preserve pairwise distances, as formalized by the Johnson–Lindenstrauss lemma.
80
+
81
+
82
+ ### Note on the cutoff radius R
83
+
84
+ There is no strict theoretical guarantee about what the cutof radius R should be for a given task. E.g the relative grid position between a point and its nearest neighbors can't be garanted to be in the exact adjacent grid cells. What is guaranteed from the sorted ordering is only **grid monotonicity**: *x* coordinates increase along rows, *y* coordinates along columns, and so on.
85
+
86
+ As an example, empirical results in 2-D show that `R = 5` is enough for ~99 % of the nearest neighbors; some outlier neighbors will sit further apart for complex geometries with pronounced peaks, holes or any non-smoothness. When a stricter neighborhood is required, or in high dimensional setting, the solution might be to build an assembly of grid experts, each working on a rotated / projected view of the points, as discussed in [this topic](https://github.com/glotzerlab/freud/discussions/1417). An other possibility would be to tile the grid: each tile is enhanced with location metadata (e.g. a bounding box), allowing for pruning pairs of tiles with a distance certified to be far enough for a given criterion.
87
+
88
+
89
+ ### Note on efficient stencil operations
90
+
91
+ The typical use-case of `Gridpoints` is to allow fast local operations on arbitrary point clouds using stencil kernels:
92
+
93
+ ```text
94
+ output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
95
+ ```
96
+ To go beyond standard (slow) python loops, this kind of kernel computation 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. On GPU, tiling the grid and leveraging a custom `triton` kernel might be particularly efficient.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gridpoints"
7
- version = "1.0.6"
7
+ version = "1.0.7"
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.6"
10
+ __version__ = "1.0.7"
@@ -46,7 +46,7 @@ def inplace_sort(X, axis):
46
46
  xp, backend_name = get_backend(X)
47
47
  if backend_name == 'torch':
48
48
  X.copy_(X.sort(dim=axis)[0])
49
- if backend_name == "cupy":
49
+ elif backend_name == "cupy":
50
50
  X[...] = xp.sort(xp.ascontiguousarray(X), axis=axis)
51
51
  else:
52
52
  X.sort(axis=axis)
@@ -75,15 +75,17 @@ def argmax(arr, axis):
75
75
  return arr.argmax(dim=axis)
76
76
  return arr.argmax(axis=axis)
77
77
 
78
- def random_noise(shape, reference_array):
78
+ def random_noise(shape, reference_array, eps = None):
79
+ if eps is None:
80
+ eps = EPS
79
81
  xp, backend_name = get_backend(reference_array)
80
82
  if backend_name == 'torch':
81
- return EPS * xp.rand(*shape, device=reference_array.device)
83
+ return eps * xp.rand(*shape, device=reference_array.device)
82
84
  elif backend_name == 'cupy':
83
- return EPS * xp.random.rand(*shape)
85
+ return eps * xp.random.rand(*shape)
84
86
  else:
85
87
  rng = xp.random.default_rng(42)
86
- return EPS * rng.random(shape)
88
+ return eps * rng.random(shape)
87
89
 
88
90
  def copy(arr):
89
91
  _, backend_name = get_backend(arr)
@@ -91,9 +93,15 @@ def copy(arr):
91
93
  return arr.clone()
92
94
  return arr.copy()
93
95
 
94
- def nan_to_num(arr):
96
+ def nan_infs_to_num_and_tiebreaker(arr, safe = False):
95
97
  xp, backend = get_backend(arr)
96
98
  out = copy(arr)
99
+ eps = EPS
100
+ if safe: #better precision when adding epsilon noise tiebreaker
101
+ #and latter when applying diagonal rotations.
102
+ #probably unnecessary (default safe = False)
103
+ out = astype(out, "float64")
104
+ eps = 10**(-10)
97
105
 
98
106
  for d in range(arr.shape[1]):
99
107
  x = arr[:, d]
@@ -110,7 +118,7 @@ def nan_to_num(arr):
110
118
  if backend == 'torch' else xp.random.randint(len(vals), size=n))
111
119
  out[nan, d] = vals[idx]
112
120
 
113
- return out + random_noise(out.shape, out)
121
+ return out + random_noise(out.shape, out, eps = eps)
114
122
 
115
123
  def all_true(tensor):
116
124
  _, backend_name = get_backend(tensor)
@@ -20,14 +20,16 @@ def sort_diag_trick(X):
20
20
  rotate back the diagonals
21
21
  """
22
22
  xp, _ = get_backend(X)
23
- bigM = (X.max() - X.min()) + 1#bigM for safety, protect
23
+ #bigM for safety, protect
24
24
  #overlapping diagonals
25
+ bigM = (X.max() - X.min()) + 1
25
26
 
26
27
  n, m = X.shape[-2:]
27
-
28
- if m > n: #Optional, maximal parallelisation
28
+
29
+ #move the smallest axis to last position
30
+ if m > n:
29
31
  X = xp.moveaxis(X, [-1, -2], [-2, -1])
30
- n, m = m, n
32
+ n, m = m, n #now n >= m
31
33
 
32
34
  i = arange(n, 'int', X)[:, None]
33
35
  j = arange(m, 'int', X)[None, :]
@@ -56,20 +58,16 @@ def sort_tridiag_trick(X):
56
58
  and rotate back the tridiagonals
57
59
  """
58
60
  xp, _ = get_backend(X)
59
- bigM = (X.max() - X.min()) + 1#bigM for safety, protect
61
+ #bigM for safety, protect
60
62
  #overlapping diagonals
63
+ bigM = (X.max() - X.min()) + 1
61
64
 
62
- a, b, c = X.shape[-3:]
63
-
64
- #Optional, maximal parallelisation:
65
- #move the smallest axis to last position
66
- #before the tridiagonal rotation
67
- if (a < b) and (a < c):
68
- X = xp.moveaxis(X, [-3, -1], [-1, -3])
69
- a, c = c, a
70
- elif b < c:
71
- X = xp.moveaxis(X, [-2, -1], [-1, -2])
72
- b, c = c, b
65
+ #move the smallest axes to last positions
66
+ abc = X.shape
67
+ dst_axes = (-3, -2, -1)
68
+ src_axes = tuple(sorted(dst_axes, key=lambda ax: -abc[ax]))
69
+ X = xp.moveaxis(X, src_axes, dst_axes)
70
+ a, b, c = X.shape[-3:] # now a >= b >= c
73
71
 
74
72
  i = arange(a, 'int', X)[:, None, None]
75
73
  j = arange(b, 'int', X)[None, :, None]
@@ -81,7 +79,7 @@ def sort_tridiag_trick(X):
81
79
  dj_ = dj % b
82
80
  dires = di // a
83
81
  djres = dj // b
84
- groupkey = bigM * (dires * (2 * (b + c)) + djres)
82
+ groupkey = bigM * (dires * 4 + djres)
85
83
 
86
84
  X[..., di_, dj_, k] = X[..., i, j, k] - groupkey #Trirotation
87
85
  # ========================
@@ -8,7 +8,7 @@ import math
8
8
  import warnings
9
9
  from .array_libs import (
10
10
  arange, get_backend, all_true,
11
- inplace_sort, empty, astype, nan_to_num,
11
+ inplace_sort, empty, astype, nan_infs_to_num_and_tiebreaker,
12
12
  random_permutation, argmax, copy, flip
13
13
  )
14
14
  from .diagonal_tricks import sort_diag_trick, sort_tridiag_trick
@@ -52,16 +52,25 @@ def argsort(X, gridshape, verbose = 2, level=2, init="kdtree", n_iter=40):
52
52
  monotonically sorted grid.
53
53
  """
54
54
  N, D = X.shape
55
- assert math.prod(gridshape) == N
56
- assert len(gridshape) == D
57
-
55
+ assert len(gridshape) == D, (
56
+ f"gridshape={gridshape} has {len(gridshape)} entries but the points are {D}D: "
57
+ "gridshape needs exactly one specified shape per axis. "
58
+ "For example, with 3D points, (32, 32) is invalid (z shape is missing), "
59
+ "while (32, 32, 1) is valid (z size is 1)."
60
+ )
61
+ assert math.prod(gridshape) == N, (
62
+ f"a grid with given gridshape ={gridshape} contains {math.prod(gridshape)} cells "
63
+ f"but there are N={N} points. "
64
+ "If there are fewer points than cells, pad the points with "
65
+ "dummy points (e.g. NaN/inf). "
66
+ "If there are more points than cells, use a bigger gridshape."
67
+ )
58
68
  level = min(level, X.shape[-1])
69
+
70
+ if verbose >=1:
71
+ print(f"[gridsort] initialisation: {init}", end = "")
59
72
  if init == "kdtree":
60
- if verbose >=1:
61
- print("performing kdtree initialisation")
62
73
  order = kdtree_order(X, gridshape)
63
- if verbose >=1:
64
- print("done")
65
74
  if level == 1:
66
75
  return order
67
76
  else:
@@ -69,6 +78,10 @@ def argsort(X, gridshape, verbose = 2, level=2, init="kdtree", n_iter=40):
69
78
  if init == "shuffle":
70
79
  order = random_permutation(order)
71
80
 
81
+ if verbose >=1:
82
+ print("done")
83
+
84
+
72
85
  ordernew, converged = _mlg_step(X[order], gridshape, level=level, n_iter=n_iter, verbose = verbose)
73
86
  order = order[ordernew]
74
87
 
@@ -111,7 +124,7 @@ def sort(X, gridshape, verbose = 2, level=2, init="kdtree", inplace=False, n_ite
111
124
  rather than pushed to grid boundaries.
112
125
  verbose : int, default 2
113
126
  0 = silent, 1 = log progress, 2 = 1 + warn if convergence failed
114
- level : {1, 2, 3}, default=1
127
+ level : {1, 2, 3}, default=2
115
128
  Maximum order of lattice directions considered. Higher values improve
116
129
  grid quality at the expense of computational speed. Note that `level=3`
117
130
  is only supported if D >= 3.
@@ -251,14 +264,15 @@ def _sort_loop(loop, directions, shape, n_iter, verbose):
251
264
  if fully_sorted:
252
265
  break
253
266
 
254
- print("it:", it)
267
+ if verbose:
268
+ print("\n it:", it, "converged:", fully_sorted)
255
269
  return (cycle_to_idx[grid]).ravel(), fully_sorted
256
270
 
257
271
  def _mlg_step(X, shape, level, n_iter, verbose):
258
272
  xp, _ = get_backend(X)
259
273
  directions_raw = xp.vstack(_direction_blocks(level=level, xp=xp, dim=X.shape[-1]))
260
274
  directions = astype(directions_raw, 'float', reference_array=X)
261
- X_float = nan_to_num(astype(X, 'float'))
275
+ X_float = nan_infs_to_num_and_tiebreaker(astype(X, 'float'))
262
276
 
263
277
  loop = _build_monotonic_lagrangian_loop(X_float, directions)
264
278
  if verbose >=1:
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: gridpoints
3
+ Version: 1.0.7
4
+ Summary: fast multidimensional sorting tool for point clouds using the monotonic lagrangian grid
5
+ Author: Armand de Cacqueray
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Neighborhood-Grid/CubeNet
8
+ Project-URL: Repository, https://github.com/Neighborhood-Grid/CubeNet
9
+ Keywords: sorting,point-clouds,multi-indexing,grid,neighborhood-grid,monotonic-lagrangian-grid,squarenet,nearest-neighbor
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Provides-Extra: demo
14
+ Requires-Dist: squarenet; extra == "demo"
15
+ Dynamic: license-file
16
+
17
+ ## ❒ Gridpoints — Multi dimensional sort for point clouds
18
+
19
+ `Gridpoints` maps raw indexes of unstructured **point clouds** to structured grids multi-index through a **bijective transformation**:
20
+ One point, one cell,
21
+ ```text
22
+ n <-> [i,j,…](n).
23
+ ```
24
+ It replaces and enhances the [squarenet](https://github.com/Neighborhood-Grid/SquareNet) project with a more robust points sorting algorithm. The goal is
25
+ to build a spatialy coherent multi index to later store and query the point cloud efficiently, based on adaptative axes (lines, columns, and more in 3D+) as showed on following exemple :
26
+
27
+ <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/ballexemple.png">
28
+
29
+ What it does: Take raw point cloud `P(N, D)` and find a grid shape and an index permutation `order` such that `Pgrid(I, J, …, D)` = P[order].reshape(*gridshape, D) is sorted along every axis of the grid. E.g. in 3D, for Pgrid = (x, y, z):
30
+ ```text
31
+ x[i+1, j, k] >= x[i, j, k]
32
+ y[i, j+1, k] >= y[i, j, k]
33
+ z[i, j, k+1] >= z[i, j, k]
34
+ ```
35
+
36
+ → On the `Pgrid` view of `P`, neighbor queries become a simple stencil look-up
37
+ ```text
38
+ neighborhood(Pgrid[i, j, k]) = {Pgrid[i±di, j±dj, k±dk] | (di, dj, dk) ≤ R},
39
+ ````
40
+ where `R` is a radius cutoff to determine, allowing local operations in linear time.
41
+ → Geometric operations (convolution, clustering, interpolation …) can then be applied **in linear time** directly on the `Pgrid` view instead of relying on complex graph or point-cloud methods
42
+
43
+ `P` can be a NumPy, PyTorch or CuPy array of any dimension (N, D). To allow natural padding when the grid has more cells than they are points in the cloud, NaNs and Infs are supported in a consistent manner:
44
+ - nans → random position
45
+ - (+-) infs → border of the grid
46
+
47
+ This allow to gridsort prime or variable number of points, as long as one is ready to deal with void/special grid cells.
48
+
49
+ Expected runtime for sorting 1 million points: CPU: < 10s (numpy), GPU: < 500 ms (torch cuda)
50
+
51
+ `When not to use gridpoints ?`
52
+ - high dimension: the package is implemented to support arbitrary dimension, but sweetspot is really 2D/3D. dimensions 4-6 might still be reasonable depending on the task, but anything above 8D is generally too high dimensional for gridpoints.
53
+ - weird geometries. Supported datasets goes beyond smooth convex distributions: map of Indonesia, a sponge, a donuts, an elephant, an eggshell (by specifying a suitable gridshape e.g. (128,128,2) to `gridpoints.sort()`). You can see various exemples in the [plot](https://github.com/Neighborhood-Grid/CubeNet/blob/main/plots) folder But with some limits. Bad fits: a spider web, a wind turbine, same eggshell with a naive 3d sort (gridshape = (32,32,32)) ... the issue is not that gridpoints cannot sort these distributions, but that it will produce a poor representation of the geometry.
54
+ - small point clouds: beyond a few hundred points, local operations in linear time is not worth the overhead, because naive quadratic implementations will probably be simultaneously simpler and faster.
55
+
56
+ ---
57
+
58
+ ### Installation
59
+
60
+ ```bash
61
+ pip install gridpoints # core only
62
+ pip install gridpoints[demo] # for the demonstration notebook.
63
+ ```
64
+ Full Demo: [notebook.ipynb](https://github.com/Neighborhood-Grid/CubeNet/blob/main/notebook.ipynb)
65
+
66
+ ### Quickstart
67
+
68
+ ```python
69
+ import gridpoints as grid
70
+ import numpy as np
71
+
72
+ # Raw point cloud (numpy, pytorch or cupy)
73
+ A = np.random.rand(1_000_000, 3)
74
+
75
+ # Sorted view: place the points inside the grid
76
+ order = grid.argsort(A, gridshape=(100, 100, 100))
77
+ Bflat = A[order] #flat but sorted with a grid layout (C-order)
78
+ Bgrid = Bflat.reshape(100, 100, 100, 3) #reshaped as a grid
79
+
80
+ # Rest of your pipeline, working with grids
81
+ Cgrid = apply_something(Bgrid) #(100, 100, 100, *)
82
+
83
+ # Back to the original points indexing
84
+ Cflat = Cgrid.reshape(1_000_000, *)
85
+ orderinv = grid.invert_permutation(order)
86
+ C = Cflat[orderinv] # matches the initial points order
87
+ ```
88
+
89
+ ---
90
+
91
+ ### Note on rectangular gridshapes
92
+
93
+ The eggshell example illustrates the importance of selecting a suitable grid-shape decomposition. This can itself be challenging when the point cloud is assumed to follow a complex topological pattern, such as a thin surface rather than a full volumetric domain. While the precise decomposition is not particularly important (e.g. (18,20,16) vs. (16,15,24) will generally make little difference), the orders of magnitude of the different dimensions do matter and should be tuned to the dataset: (18,20,16) and (36,40,4) can lead to substantially different results. As mentioned, nan/inf padding can help accomodate integer-factorization constraints
94
+
95
+ Another possibility for smoothing out topology-specific effects is to perform the sort after randomly projecting the dataset onto a lower-dimensional subspace. Random projections are known to approximately preserve pairwise distances, as formalized by the Johnson–Lindenstrauss lemma.
96
+
97
+
98
+ ### Note on the cutoff radius R
99
+
100
+ There is no strict theoretical guarantee about what the cutof radius R should be for a given task. E.g the relative grid position between a point and its nearest neighbors can't be garanted to be in the exact adjacent grid cells. What is guaranteed from the sorted ordering is only **grid monotonicity**: *x* coordinates increase along rows, *y* coordinates along columns, and so on.
101
+
102
+ As an example, empirical results in 2-D show that `R = 5` is enough for ~99 % of the nearest neighbors; some outlier neighbors will sit further apart for complex geometries with pronounced peaks, holes or any non-smoothness. When a stricter neighborhood is required, or in high dimensional setting, the solution might be to build an assembly of grid experts, each working on a rotated / projected view of the points, as discussed in [this topic](https://github.com/glotzerlab/freud/discussions/1417). An other possibility would be to tile the grid: each tile is enhanced with location metadata (e.g. a bounding box), allowing for pruning pairs of tiles with a distance certified to be far enough for a given criterion.
103
+
104
+
105
+ ### Note on efficient stencil operations
106
+
107
+ The typical use-case of `Gridpoints` is to allow fast local operations on arbitrary point clouds using stencil kernels:
108
+
109
+ ```text
110
+ output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
111
+ ```
112
+ To go beyond standard (slow) python loops, this kind of kernel computation 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. On GPU, tiling the grid and leveraging a custom `triton` kernel might be particularly efficient.
gridpoints-1.0.6/PKG-INFO DELETED
@@ -1,96 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: gridpoints
3
- Version: 1.0.6
4
- Summary: fast multidimensional sorting tool for point clouds using the monotonic lagrangian grid
5
- Author: Armand de Cacqueray
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/Neighborhood-Grid/CubeNet
8
- Project-URL: Repository, https://github.com/Neighborhood-Grid/CubeNet
9
- Keywords: sorting,point-clouds,multi-indexing,grid,neighborhood-grid,monotonic-lagrangian-grid,squarenet,nearest-neighbor
10
- Requires-Python: >=3.9
11
- Description-Content-Type: text/markdown
12
- License-File: LICENSE
13
- Provides-Extra: demo
14
- Requires-Dist: squarenet; extra == "demo"
15
- Dynamic: license-file
16
-
17
- ## ❒ Gridpoints — Multi dimensional sort for point clouds
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 more powerfull sorting algorithm.
20
-
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
35
- or other point-cloud techniques.
36
-
37
- `P` can be a NumPy, PyTorch or CuPy array of any dimension (N, D).
38
- To allow natural padding when the grid has more cells than points,
39
- NaNs and Infs are supported in a consistent manner:
40
- - nans → random position
41
- - (+-) infs → border of the grid
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.
44
-
45
- Expected runtime for sorting 1 million points: CPU → < 10s (numpy), GPU → < 500 ms (torch cuda)
46
-
47
- ---
48
-
49
- ### Installation
50
-
51
- ```bash
52
- pip install gridpoints # core only
53
- pip install gridpoints[demo] # for the demonstration notebook, see `notebook.ipynb`
54
- ```
55
-
56
- ### Quickstart
57
-
58
- ```python
59
- import gridpoints as grid
60
- import numpy as np
61
-
62
- # Raw point cloud (numpy, pytorch or cupy)
63
- A = np.random.rand(1_000_000, 3)
64
-
65
- # Sorted view: place the points inside the grid
66
- order = grid.argsort(A, gridshape=(100, 100, 100))
67
- Bflat = A[order]
68
- Bgrid = Bflat.reshape(100, 100, 100, 3)
69
-
70
- # Rest of your pipeline, working with grids
71
- Cgrid = apply_something(Bgrid)
72
-
73
- # Back to the original points indexing
74
- Cflat = Cgrid.reshape(-1, 3)
75
- orderinv = grid.invert_permutation(order)
76
- C = Cflat[orderinv] # matches the initial points order
77
- ```
78
-
79
- <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/ballexemple.png">
80
-
81
- ---
82
-
83
- ### Note on the cutoff radius R
84
-
85
- There is no strict theoretical guarantee about what the cutof radius R should be for a given task. E.g the relative grid position between a point and its nearest neighbors can't be garanted to be in the exact adjacent grid cells. What is guaranteed from the sorted ordering is only **grid monotonicity**: *x* coordinates increase along rows, *y* coordinates along columns, and so on.
86
-
87
- As an example, empirical results in 2-D show that `R = 5` is enough for ~99 % of the nearest neighbors; some outlier neighbors will sit further apart for complex geometries with pronounced peaks, holes or any non-smoothness. When a stricter neighborhood is required, or in high dimensional setting, the best practice is to build an assembly of grid experts, each working on a rotated / projected view of the points, as discussed in [this topic](https://github.com/glotzerlab/freud/discussions/1417).
88
-
89
- ### Note on efficient stencil operations
90
-
91
- The typical use-case of `Gridpoints` is to allow fast local operations on arbitrary point clouds using stencil kernels:
92
-
93
- ```text
94
- output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
95
- ```
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,80 +0,0 @@
1
- ## ❒ Gridpoints — Multi dimensional sort for point clouds
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 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
19
- or other point-cloud techniques.
20
-
21
- `P` can be a NumPy, PyTorch or CuPy array of any dimension (N, D).
22
- To allow natural padding when the grid has more cells than points,
23
- NaNs and Infs are supported in a consistent manner:
24
- - nans → random position
25
- - (+-) infs → border of the grid
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.
28
-
29
- Expected runtime for sorting 1 million points: CPU → < 10s (numpy), GPU → < 500 ms (torch cuda)
30
-
31
- ---
32
-
33
- ### Installation
34
-
35
- ```bash
36
- pip install gridpoints # core only
37
- pip install gridpoints[demo] # for the demonstration notebook, see `notebook.ipynb`
38
- ```
39
-
40
- ### Quickstart
41
-
42
- ```python
43
- import gridpoints as grid
44
- import numpy as np
45
-
46
- # Raw point cloud (numpy, pytorch or cupy)
47
- A = np.random.rand(1_000_000, 3)
48
-
49
- # Sorted view: place the points inside the grid
50
- order = grid.argsort(A, gridshape=(100, 100, 100))
51
- Bflat = A[order]
52
- Bgrid = Bflat.reshape(100, 100, 100, 3)
53
-
54
- # Rest of your pipeline, working with grids
55
- Cgrid = apply_something(Bgrid)
56
-
57
- # Back to the original points indexing
58
- Cflat = Cgrid.reshape(-1, 3)
59
- orderinv = grid.invert_permutation(order)
60
- C = Cflat[orderinv] # matches the initial points order
61
- ```
62
-
63
- <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/ballexemple.png">
64
-
65
- ---
66
-
67
- ### Note on the cutoff radius R
68
-
69
- There is no strict theoretical guarantee about what the cutof radius R should be for a given task. E.g the relative grid position between a point and its nearest neighbors can't be garanted to be in the exact adjacent grid cells. What is guaranteed from the sorted ordering is only **grid monotonicity**: *x* coordinates increase along rows, *y* coordinates along columns, and so on.
70
-
71
- As an example, empirical results in 2-D show that `R = 5` is enough for ~99 % of the nearest neighbors; some outlier neighbors will sit further apart for complex geometries with pronounced peaks, holes or any non-smoothness. When a stricter neighborhood is required, or in high dimensional setting, the best practice is to build an assembly of grid experts, each working on a rotated / projected view of the points, as discussed in [this topic](https://github.com/glotzerlab/freud/discussions/1417).
72
-
73
- ### Note on efficient stencil operations
74
-
75
- The typical use-case of `Gridpoints` is to allow fast local operations on arbitrary point clouds using stencil kernels:
76
-
77
- ```text
78
- output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
79
- ```
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.
@@ -1,96 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: gridpoints
3
- Version: 1.0.6
4
- Summary: fast multidimensional sorting tool for point clouds using the monotonic lagrangian grid
5
- Author: Armand de Cacqueray
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/Neighborhood-Grid/CubeNet
8
- Project-URL: Repository, https://github.com/Neighborhood-Grid/CubeNet
9
- Keywords: sorting,point-clouds,multi-indexing,grid,neighborhood-grid,monotonic-lagrangian-grid,squarenet,nearest-neighbor
10
- Requires-Python: >=3.9
11
- Description-Content-Type: text/markdown
12
- License-File: LICENSE
13
- Provides-Extra: demo
14
- Requires-Dist: squarenet; extra == "demo"
15
- Dynamic: license-file
16
-
17
- ## ❒ Gridpoints — Multi dimensional sort for point clouds
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 more powerfull sorting algorithm.
20
-
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
35
- or other point-cloud techniques.
36
-
37
- `P` can be a NumPy, PyTorch or CuPy array of any dimension (N, D).
38
- To allow natural padding when the grid has more cells than points,
39
- NaNs and Infs are supported in a consistent manner:
40
- - nans → random position
41
- - (+-) infs → border of the grid
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.
44
-
45
- Expected runtime for sorting 1 million points: CPU → < 10s (numpy), GPU → < 500 ms (torch cuda)
46
-
47
- ---
48
-
49
- ### Installation
50
-
51
- ```bash
52
- pip install gridpoints # core only
53
- pip install gridpoints[demo] # for the demonstration notebook, see `notebook.ipynb`
54
- ```
55
-
56
- ### Quickstart
57
-
58
- ```python
59
- import gridpoints as grid
60
- import numpy as np
61
-
62
- # Raw point cloud (numpy, pytorch or cupy)
63
- A = np.random.rand(1_000_000, 3)
64
-
65
- # Sorted view: place the points inside the grid
66
- order = grid.argsort(A, gridshape=(100, 100, 100))
67
- Bflat = A[order]
68
- Bgrid = Bflat.reshape(100, 100, 100, 3)
69
-
70
- # Rest of your pipeline, working with grids
71
- Cgrid = apply_something(Bgrid)
72
-
73
- # Back to the original points indexing
74
- Cflat = Cgrid.reshape(-1, 3)
75
- orderinv = grid.invert_permutation(order)
76
- C = Cflat[orderinv] # matches the initial points order
77
- ```
78
-
79
- <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/ballexemple.png">
80
-
81
- ---
82
-
83
- ### Note on the cutoff radius R
84
-
85
- There is no strict theoretical guarantee about what the cutof radius R should be for a given task. E.g the relative grid position between a point and its nearest neighbors can't be garanted to be in the exact adjacent grid cells. What is guaranteed from the sorted ordering is only **grid monotonicity**: *x* coordinates increase along rows, *y* coordinates along columns, and so on.
86
-
87
- As an example, empirical results in 2-D show that `R = 5` is enough for ~99 % of the nearest neighbors; some outlier neighbors will sit further apart for complex geometries with pronounced peaks, holes or any non-smoothness. When a stricter neighborhood is required, or in high dimensional setting, the best practice is to build an assembly of grid experts, each working on a rotated / projected view of the points, as discussed in [this topic](https://github.com/glotzerlab/freud/discussions/1417).
88
-
89
- ### Note on efficient stencil operations
90
-
91
- The typical use-case of `Gridpoints` is to allow fast local operations on arbitrary point clouds using stencil kernels:
92
-
93
- ```text
94
- output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
95
- ```
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