squarenet 0.2.1__tar.gz → 0.2.2__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.
Files changed (27) hide show
  1. squarenet-0.2.2/PKG-INFO +99 -0
  2. squarenet-0.2.2/README.md +80 -0
  3. {squarenet-0.2.1 → squarenet-0.2.2}/pyproject.toml +11 -2
  4. squarenet-0.2.2/src/squarenet/__init__.py +4 -0
  5. squarenet-0.2.2/src/squarenet/boards.py +71 -0
  6. squarenet-0.2.2/src/squarenet/core.py +154 -0
  7. squarenet-0.2.2/src/squarenet/squarenet.py +230 -0
  8. squarenet-0.2.2/src/squarenet/travel.py +58 -0
  9. squarenet-0.2.2/src/squarenet/utils.py +142 -0
  10. squarenet-0.2.2/src/squarenet/views.py +71 -0
  11. squarenet-0.2.2/src/squarenet.egg-info/PKG-INFO +99 -0
  12. {squarenet-0.2.1 → squarenet-0.2.2}/src/squarenet.egg-info/SOURCES.txt +4 -1
  13. {squarenet-0.2.1 → squarenet-0.2.2}/src/squarenet.egg-info/requires.txt +3 -0
  14. squarenet-0.2.1/PKG-INFO +0 -79
  15. squarenet-0.2.1/README.md +0 -65
  16. squarenet-0.2.1/src/squarenet/__init__.py +0 -6
  17. squarenet-0.2.1/src/squarenet/checkerboard.py +0 -119
  18. squarenet-0.2.1/src/squarenet/core.py +0 -138
  19. squarenet-0.2.1/src/squarenet/utils.py +0 -118
  20. squarenet-0.2.1/src/squarenet.egg-info/PKG-INFO +0 -79
  21. {squarenet-0.2.1 → squarenet-0.2.2}/LICENSE.txt +0 -0
  22. {squarenet-0.2.1 → squarenet-0.2.2}/setup.cfg +0 -0
  23. {squarenet-0.2.1 → squarenet-0.2.2}/src/squarenet/data/__init__.py +0 -0
  24. {squarenet-0.2.1 → squarenet-0.2.2}/src/squarenet/data/france.wkb +0 -0
  25. {squarenet-0.2.1 → squarenet-0.2.2}/src/squarenet/data/germany.wkb +0 -0
  26. {squarenet-0.2.1 → squarenet-0.2.2}/src/squarenet.egg-info/dependency_links.txt +0 -0
  27. {squarenet-0.2.1 → squarenet-0.2.2}/src/squarenet.egg-info/top_level.txt +0 -0
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: squarenet
3
+ Version: 0.2.2
4
+ Summary: Sparse local operations for point clouds in any dimension.
5
+ Author-email: ArmanddeCacqueray <armanddecacqueray@sfr.fr>
6
+ Project-URL: Homepage, https://github.com/ArmanddeCacqueray/SquareNet
7
+ Project-URL: Documentation, https://squarenet.readthedocs.io
8
+ Project-URL: PyPI, https://pypi.org/project/squarenet/
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE.txt
12
+ Requires-Dist: numpy
13
+ Requires-Dist: matplotlib
14
+ Provides-Extra: demo
15
+ Requires-Dist: shapely; extra == "demo"
16
+ Provides-Extra: zsquare
17
+ Requires-Dist: tensorly; extra == "zsquare"
18
+ Dynamic: license-file
19
+
20
+ -----
21
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ArmanddeCacqueray/SquareNet/blob/main/exemples/00_getting_started.ipynb)
22
+ [![PyPI version](https://img.shields.io/pypi/v/squarenet.svg)](https://pypi.org/project/squarenet/)
23
+ [![Documentation Status](https://readthedocs.org/projects/squarenet/badge/?version=latest)](https://squarenet.readthedocs.io/en/latest/)
24
+ <p align="center">
25
+ <img src="plots/logo.png" width="200" alt="Project visualization">
26
+ </p>
27
+
28
+ ❒ SquareNet
29
+
30
+ SquareNet maps unstructured **point clouds** to structured grids through a **bijective transformation**. It replaces expensive spatial queries (k-NN, radius search) with super fast **sliding window** operations. Think of it as a powerful alternative to kd-trees, voxelization, rasterization and neighborhood graphs.
31
+ ✔ Works in any dimension
32
+ ✔ Handles non-convex geometries
33
+ ✔ Scales to millions of points (fast processing)
34
+
35
+
36
+ -----
37
+
38
+ ## 🚀 Why SquareNet?
39
+
40
+ * **Speed:** $O(N)$ local operations via vectorized sliding windows.
41
+ * **Memory:** Contiguous memory access instead of irregular spatial lookups.
42
+ * **Simplicity:** Pure NumPy-based logic, no heavy spatial dependencies.
43
+
44
+ ## 📦 Installation
45
+
46
+ ```bash
47
+ pip install squarenet
48
+ ```
49
+
50
+ ## 🧠 Quick Start
51
+ -> exemples/00_getting_started.ipynb
52
+
53
+ ```python
54
+ from squarenet import SquareNet
55
+ import numpy as np
56
+
57
+ # Initialize and Fit
58
+ N = 5*11*7*13
59
+ d = 4
60
+ points = np.random.rand(N, d)
61
+
62
+ IJKL = (5, 11, 7, 13)
63
+ sqnet = SquareNet(IJ_=IJKL) # Define grid dimensions, here 4D
64
+ sqnet.fit(points)
65
+
66
+ # Map any property of the points to the grid e.g. the norm, could be anything else
67
+ Xpts = np.linalg.norm(points, axis = 1) #(N, *C)
68
+ Xmap = sqnet.map(Xpts) #(5, 11, 7, 13, *C)
69
+ Xrec = sqnet.invert_map(Xmap) #(N, *C)
70
+ ```
71
+ ## Compute Local Views
72
+ ```python
73
+ # views enhance Xpts with a view in a rectangular neighborhood
74
+ # (in the grid) with radius *wr and size *ws = 2wr+1
75
+ Xview = sqnet.views(Xpts, wr=5, invert_map = True) #(N, *C, *ws)
76
+ ```
77
+
78
+ ## 🗺️ Visualizing the Mapping
79
+
80
+ You can use the built-in checkerboard to verify neighborhood preservation:
81
+
82
+ ```python
83
+ sqnet = SquareNet(IJ_=(400, 400))
84
+ sqnet.fit("france") #require !pip install shapely
85
+ sqnet.checkerboard()
86
+ ```
87
+
88
+ ## 📈 Key Applications
89
+
90
+ * **Point Cloud Processing:** Fast local feature aggregation.
91
+ * **Kernel Methods:** Efficient sparse approximation of large kernels.
92
+ * **Deep Learning:** Pre-structuring irregular data for CNN/Transformer inputs.
93
+
94
+ <p align="center">
95
+ <img src="plots/packing.png" width="200" alt="Packing">
96
+ </p>
97
+ -----
98
+
99
+ **License:** MIT | **Author:** [ArmanddeCacqueray](mailto:armanddecacqueray@sfr.fr)
@@ -0,0 +1,80 @@
1
+ -----
2
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ArmanddeCacqueray/SquareNet/blob/main/exemples/00_getting_started.ipynb)
3
+ [![PyPI version](https://img.shields.io/pypi/v/squarenet.svg)](https://pypi.org/project/squarenet/)
4
+ [![Documentation Status](https://readthedocs.org/projects/squarenet/badge/?version=latest)](https://squarenet.readthedocs.io/en/latest/)
5
+ <p align="center">
6
+ <img src="plots/logo.png" width="200" alt="Project visualization">
7
+ </p>
8
+
9
+ ❒ SquareNet
10
+
11
+ SquareNet maps unstructured **point clouds** to structured grids through a **bijective transformation**. It replaces expensive spatial queries (k-NN, radius search) with super fast **sliding window** operations. Think of it as a powerful alternative to kd-trees, voxelization, rasterization and neighborhood graphs.
12
+ ✔ Works in any dimension
13
+ ✔ Handles non-convex geometries
14
+ ✔ Scales to millions of points (fast processing)
15
+
16
+
17
+ -----
18
+
19
+ ## 🚀 Why SquareNet?
20
+
21
+ * **Speed:** $O(N)$ local operations via vectorized sliding windows.
22
+ * **Memory:** Contiguous memory access instead of irregular spatial lookups.
23
+ * **Simplicity:** Pure NumPy-based logic, no heavy spatial dependencies.
24
+
25
+ ## 📦 Installation
26
+
27
+ ```bash
28
+ pip install squarenet
29
+ ```
30
+
31
+ ## 🧠 Quick Start
32
+ -> exemples/00_getting_started.ipynb
33
+
34
+ ```python
35
+ from squarenet import SquareNet
36
+ import numpy as np
37
+
38
+ # Initialize and Fit
39
+ N = 5*11*7*13
40
+ d = 4
41
+ points = np.random.rand(N, d)
42
+
43
+ IJKL = (5, 11, 7, 13)
44
+ sqnet = SquareNet(IJ_=IJKL) # Define grid dimensions, here 4D
45
+ sqnet.fit(points)
46
+
47
+ # Map any property of the points to the grid e.g. the norm, could be anything else
48
+ Xpts = np.linalg.norm(points, axis = 1) #(N, *C)
49
+ Xmap = sqnet.map(Xpts) #(5, 11, 7, 13, *C)
50
+ Xrec = sqnet.invert_map(Xmap) #(N, *C)
51
+ ```
52
+ ## Compute Local Views
53
+ ```python
54
+ # views enhance Xpts with a view in a rectangular neighborhood
55
+ # (in the grid) with radius *wr and size *ws = 2wr+1
56
+ Xview = sqnet.views(Xpts, wr=5, invert_map = True) #(N, *C, *ws)
57
+ ```
58
+
59
+ ## 🗺️ Visualizing the Mapping
60
+
61
+ You can use the built-in checkerboard to verify neighborhood preservation:
62
+
63
+ ```python
64
+ sqnet = SquareNet(IJ_=(400, 400))
65
+ sqnet.fit("france") #require !pip install shapely
66
+ sqnet.checkerboard()
67
+ ```
68
+
69
+ ## 📈 Key Applications
70
+
71
+ * **Point Cloud Processing:** Fast local feature aggregation.
72
+ * **Kernel Methods:** Efficient sparse approximation of large kernels.
73
+ * **Deep Learning:** Pre-structuring irregular data for CNN/Transformer inputs.
74
+
75
+ <p align="center">
76
+ <img src="plots/packing.png" width="200" alt="Packing">
77
+ </p>
78
+ -----
79
+
80
+ **License:** MIT | **Author:** [ArmanddeCacqueray](mailto:armanddecacqueray@sfr.fr)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "squarenet"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "Sparse local operations for point clouds in any dimension."
9
9
  authors = [{ name = "ArmanddeCacqueray", email = "armanddecacqueray@sfr.fr" }]
10
10
  readme = "README.md"
@@ -14,13 +14,22 @@ dependencies = [
14
14
  "matplotlib"
15
15
  ]
16
16
 
17
+ [project.urls]
18
+ Homepage = "https://github.com/ArmanddeCacqueray/SquareNet"
19
+ Documentation = "https://squarenet.readthedocs.io"
20
+ PyPI = "https://pypi.org/project/squarenet/"
21
+
17
22
  [project.optional-dependencies]
18
23
  demo = [
19
24
  "shapely"
20
25
  ]
26
+ Zsquare = [
27
+ "tensorly"
28
+ ]
29
+
21
30
 
22
31
  [tool.setuptools.packages.find]
23
32
  where = ["src"]
24
33
 
25
34
  [tool.setuptools.package-data]
26
- "squarenet" = ["data/*.wkb"]
35
+ "squarenet" = ["data/*.wkb"]
@@ -0,0 +1,4 @@
1
+ from .squarenet import SquareNet
2
+ from .utils import Potential, breakpoint
3
+ __all__ = ["squarenet"]
4
+ __version__ = "0.1.0"
@@ -0,0 +1,71 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ from mpl_toolkits.mplot3d.art3d import Poly3DCollection
4
+
5
+ def checkerboard(grid, scale):
6
+ """Split a grid into two sets of blocks following a checkerboard pattern."""
7
+ ni, nj = grid.shape[:2]
8
+ d = grid.shape[-1]
9
+ hi, hj = ni // scale, nj // scale
10
+
11
+ # Crop to ensure clean divisibility
12
+ grid = grid[:hi * scale, :hj * scale]
13
+
14
+ # Reshape and transpose into a block structure
15
+ blocks = grid.reshape(scale, hi, scale, hj, d)
16
+ blocks = blocks.transpose(0, 2, 1, 3, 4)
17
+
18
+ # Flatten into blocks for easy masking
19
+ flat_blocks = blocks.reshape(scale, scale, hi * hj, d)
20
+
21
+ # Generate checkerboard mask
22
+ ii, jj = np.indices((scale, scale))
23
+ mask = (ii % 2) == (jj % 2)
24
+
25
+ return flat_blocks[mask], flat_blocks[~mask]
26
+
27
+ def checkerboard2D(grid, scale=[2, 4, 8, 16], ax=None, colors=("lightgrey", "blue"), s=1, alpha=0.7):
28
+ """Visualize different checkerboard scales on a 2D/3D point grid."""
29
+ # 1. Handle multiple scales
30
+ if isinstance(scale, list):
31
+ ns = int(np.sqrt(len(scale)))
32
+ fig, axes = plt.subplots(ns, ns, figsize=(10, 10))
33
+ for a, sc in zip(axes.flat, scale):
34
+ checkerboard2D(grid, scale=sc, ax=a, colors=colors, s=s, alpha=alpha)
35
+ return axes
36
+
37
+ # 2. Base case: single scale
38
+ if ax is None:
39
+ fig, ax = plt.subplots(figsize=(6, 6))
40
+
41
+ # Use the checkerboard function to get the two sets of points
42
+ set1, set2 = checkerboard(grid, scale)
43
+
44
+ # Plot both sets (set1 are all 'blue' blocks, set2 are all 'lightgrey' blocks)
45
+ for points, color in zip([set1, set2], colors):
46
+ # points shape: (num_blocks, points_per_block, dims) -> flat for scatter
47
+ pts_flat = points.reshape(-1, grid.shape[-1])
48
+ ax.scatter(*(pts_flat[:, i] for i in range(pts_flat.shape[-1])),
49
+ c=color, s=s, alpha=alpha, edgecolors='none')
50
+
51
+ ax.set_title(f"Scale: {scale}")
52
+ ax.axis("off")
53
+ return ax
54
+
55
+ def checkerboard3D(grid_3d, scale=8, ax = None, colors=("lightgrey", "blue"), s=3, alpha=1):
56
+ """Visualize different checkerboard scales on the surface of a 3D point grid."""
57
+ if ax is None:
58
+ fig = plt.figure(figsize=(10, 10))
59
+ ax = fig.add_subplot(111, projection='3d')
60
+
61
+ faces = [
62
+ grid_3d[-1, :, :], #grid_3d[0, :, :],
63
+ grid_3d[:, 0, :], #grid_3d[:, -1, :],
64
+ grid_3d[:, :, -1], #grid_3d[:, :, 0]
65
+ ]
66
+
67
+ for face in faces:
68
+ checkerboard2D(face, scale=scale, ax=ax, colors = colors, s=s, alpha=alpha)
69
+
70
+ ax.set_box_aspect([1, 1, 1])
71
+ plt.show()
@@ -0,0 +1,154 @@
1
+ import numpy as np
2
+
3
+ """"
4
+ =============================================
5
+ =============================================
6
+ PSEUDO-CODE: convert unstructured point cloud (N, D)
7
+ to a grid (N1, ..., ND, D) by iteratively sorting
8
+ the d-est heuristic along the d-est axis
9
+
10
+ We can see the grid as D iterators on the points
11
+ such that axis-d iterator maps the point
12
+ P(n ~ n1...nd...nD) to the "next" point
13
+ Pnext(n ~ n1...nd+1...nD). Let call it Pnext(n, d)
14
+
15
+ We can select D euclidian heuristics
16
+ H(d): (x, y, z,...) -> value which we want to
17
+ be increasing along the d-est axis of the grid
18
+ It turns out that H(0) = x, H(1) = y,.... is
19
+ already a pretty good heuristic.
20
+
21
+ So the goal is simply to ensure that all
22
+ heuristics are sorted along the grid, in the
23
+ sense that for all point P(n) and axis d,
24
+ H(d)(P(n)) <= H(d)(Pnext(n, d))
25
+
26
+ We can compute a grid disorder parameter which is
27
+ just the counts of all P, Pnext which breaks this
28
+ inequality
29
+ =============================================
30
+ Sort_increasing is then pretty simple:
31
+ For learning step in (1, Max_iter = 100)
32
+ For d in (1, D):
33
+ sort heuristic d along axis d.
34
+ Check disorder
35
+ If disorder = 0, we are done !
36
+ =============================================
37
+ =============================================
38
+ """
39
+
40
+ def sort_increasing(gridmap, heuristics, max_iter=100):
41
+ """
42
+ Args:
43
+ -gridmap (np.array of ints):
44
+ an initial gridmap such that cloud_features[gridmap]
45
+ write any feature (N, *C) of the point-cloud (N, D)
46
+ on a grid (N1, ..., ND, *C)
47
+
48
+ -max_iter (int):
49
+ last step after which algorithm shall stop
50
+ even if it hasn't converged yet
51
+ Returns:
52
+ -gridmap
53
+ sorted gridmap such that cloud_features[gridmap]
54
+ now write the feature on a spatially coherent grid
55
+ -learningcurve (list of values):
56
+ track the performance of the optimisation process.
57
+ should converge to 0
58
+ """
59
+ g = gridmap.copy()
60
+ learning_curve = []
61
+
62
+ #loop[0]: index to heuristic 0
63
+ #loop[d+1]: heuristic d to heuristic d+1
64
+ #back_to_id: heuristic D-1 to index
65
+ loop, back_to_id = loop_boost(heuristics)
66
+
67
+ for _ in range(max_iter):
68
+ # --- 1. Check for convergence ---
69
+ disorder = 0
70
+ for d, heuristic in enumerate(loop):
71
+ g = heuristic[g]
72
+
73
+ # Efficient disorder check: H(d)(P) > H(d)(Pnext)
74
+ diff = np.diff(g, axis=d)
75
+ disorder += np.sum(diff < 0)
76
+
77
+ g = back_to_id[g]
78
+
79
+ learning_curve.append(disorder)
80
+ if disorder == 0:
81
+ break
82
+
83
+ # --- 2. Sorting Phase ---
84
+ for d, heuristic in enumerate(loop):
85
+ g = heuristic[g]
86
+ g.sort(axis = d)
87
+
88
+ g = back_to_id[g]
89
+
90
+ # last cleanup:
91
+ gridmap = np.ascontiguousarray(g)
92
+ return gridmap, learning_curve
93
+
94
+ # ============================================
95
+ # ============================================
96
+ # Heuristics
97
+ # ============================================
98
+ # ============================================
99
+ def carthesian_heuristics(points):
100
+ return points
101
+
102
+ # ============================================
103
+ # ============================================
104
+ # Boosters
105
+ # ============================================
106
+ # ============================================
107
+ def integer_boost(heuristics):
108
+ """
109
+ Booster: Convert heuristics to integer
110
+ to boost sort_increasing function
111
+
112
+ Args:
113
+ - heuristics (np.ndarray) (N,D): heuristics computed on the point cloud
114
+
115
+ Returns:
116
+ - h_int (list of np.uint32 arrays): heuristics as integers
117
+ """
118
+ N, D = heuristics.shape
119
+ h_int = []
120
+ for d in range(D):
121
+ order = np.argsort(heuristics[:, d])
122
+ ranks = np.empty(N, dtype=np.int32)
123
+ ranks[order] = np.arange(N)
124
+ h_int.append(ranks)
125
+ return h_int
126
+
127
+ def loop_boost(heuristics):
128
+ """
129
+ Booster: make looping over heuristics a bit faster
130
+
131
+ Args:
132
+ - heuristics (np.ndarray) (N,D): heuristics computed on the point cloud
133
+ Return:
134
+ - loop (...): list of permutations such that
135
+ loop[d](h_int[d][n]) = h_int[d+1][n]
136
+ - back_to_id (...): permutation such that
137
+ back_to_id(h_int[-1][n]) = n
138
+ """
139
+ int_boost = integer_boost(heuristics)
140
+ N = len(int_boost[0])
141
+ identity = np.arange(N, dtype=np.int32)
142
+ #start the loop
143
+ h_int = [identity] + int_boost
144
+ #close the loop
145
+ h_int_plus = int_boost + [identity]
146
+ loop = []
147
+
148
+ for h, hplus in zip(h_int, h_int_plus):
149
+ sigma = np.zeros(N, dtype=np.int32)
150
+ sigma[h] = hplus
151
+ loop.append(np.ascontiguousarray(sigma))
152
+
153
+ loop, back_to_id = loop[:-1], loop[-1]
154
+ return loop, back_to_id
@@ -0,0 +1,230 @@
1
+ import numpy as np
2
+ from .core import sort_increasing, carthesian_heuristics
3
+ from .boards import checkerboard, checkerboard2D, checkerboard3D
4
+ from .views import localview, lazylocalview
5
+ from .utils import initpoint, dualgrid
6
+ from warnings import warn
7
+
8
+ class SquareNet:
9
+ """
10
+ An iterative grid-straightening algorithm that untangles a D-dimensional mesh
11
+ by sorting nodes along each spatial axis to enforce a structured ordering.
12
+
13
+ The grid is represented as an (I, J, ..., D) array, where D is the number of spatial dimensions.
14
+ Each iteration attempts to minimize disorder along each dimension independently.
15
+ """
16
+
17
+ def __init__(self, IJ_=(100, 100), max_iter=100, warnings_=True):
18
+ """
19
+ Initialize the SquareNet.
20
+
21
+ Args:
22
+ IJ_ (tuple): Grid dimensions (Rows, Cols, ...). Supports any number of dimensions.
23
+ max_iter (int): Maximum number of straightening iterations.
24
+ warnings_ (bool): Flag to shut up all warnings if asked
25
+ """
26
+ self.IJ_ = tuple(IJ_)
27
+ self.D = len(IJ_) # Spatial dimensions (x, y, z,...)
28
+ self.N = np.prod(IJ_)
29
+ self.max_iter = max_iter
30
+ self.learning_curve = []
31
+ self.warnings_ = warnings_
32
+
33
+ # Internal state
34
+ self.points = None # Points as given by the user
35
+ self.heuristics = None # Heuristics is computed on points
36
+ self.grid = np.arange(
37
+ self.N, dtype = np.int32
38
+ ).reshape(IJ_) #grid to sort heuritics
39
+ self.invert_grid = dualgrid(self.grid) #for invertibility
40
+ self.packed = False #Flag for faster computations if possible
41
+
42
+ def fit(self, points):
43
+ """
44
+ Fit the grid to a set of points in D dimensions.
45
+
46
+ Args:
47
+ points (np.ndarray or str): Array of shape (N, D) or a method name supported
48
+ by .utils.initpoint.
49
+ """
50
+ if isinstance(points, str):
51
+ points = initpoint(method=points, size=(self.N, self.D))
52
+
53
+ N, D = points.shape
54
+ assert N == self.N, f"Input points ({N}) must match grid size {self.N}"
55
+ assert D == self.D, f"Input points dimension ({D}) must match D={self.D}"
56
+
57
+ self.points = points
58
+ self.heuristics = carthesian_heuristics(points)
59
+
60
+ grid, learning_curve = sort_increasing(
61
+ self.grid, self.heuristics, self.max_iter
62
+ )
63
+
64
+ last_iter = len(learning_curve) -1
65
+ last_error = learning_curve[-1]
66
+
67
+ if last_iter == 0:
68
+ #just tacking advantage of situation
69
+ #to boost map and invertmap function
70
+ self.autopack()
71
+
72
+ elif last_error == 0:
73
+ print(f"succesfully sorted at iteration {last_iter}")
74
+
75
+ else:
76
+ if self.warnings_:
77
+ warn(
78
+ "Disorder didn't converge to 0. "
79
+ "Check the learning curve and consider increasing the max_iter parameter.",
80
+ ConvergenceWarning,
81
+ stacklevel=2
82
+ )
83
+
84
+ # Save results
85
+ self.grid = grid
86
+ self.invert_grid = dualgrid(grid)
87
+ self.learning_curve = learning_curve
88
+
89
+ def map(self, features):
90
+ """
91
+ Gather: cloud data (N, *C) -> grid data (N1, ..., ND, *C)
92
+ """
93
+ if not self.packed:
94
+ return features[self.grid]
95
+ C = features.shape[1:]
96
+ return features.reshape(*self.IJ_, *C)
97
+
98
+ def invert_map(self, features):
99
+ """
100
+ Restores: grid data (N1, ..., ND, *C) -> cloud data (N, *C)
101
+ """
102
+ if not self.packed:
103
+ return features.reshape(
104
+ -1, *features.shape[self.D:]
105
+ )[self.invert_grid.flatten()]
106
+
107
+ C = features.shape[self.D:]
108
+ return features.reshape(-1, *C)
109
+
110
+ def mapidx(self, index):
111
+ """
112
+ Convert: ONE cloud index -> ONE grid index
113
+ """
114
+ return self.invert_grid[index]
115
+
116
+ def invert_mapidx(self, index):
117
+ """
118
+ Convert: ONE grid index-> ONE cloud index
119
+ """
120
+ return self.grid[tuple(index)]
121
+
122
+ def checkerboard(self, scale = [2, 4, 8, 16], toplot = True, **kwargs):
123
+ """
124
+ alias for boards.checkerboard:
125
+
126
+ return the net as a checkerboard at required scale = number of cells per axe
127
+ make a nice plot if self.D = 2 or 3
128
+
129
+ **kwargs are extra visualization arguments (color, point size...)
130
+ """
131
+ gpoints = np.ascontiguousarray(self.map(self.points))
132
+ #nice plot if possible
133
+ if toplot and (self.D == 2):
134
+ checkerboard2D(gpoints, scale = scale, **kwargs)
135
+ elif toplot and (self.D == 3):
136
+ checkerboard3D(gpoints, scale = 8, **kwargs)
137
+ else:
138
+ return checkerboard(gpoints, scale)
139
+
140
+ def views(self, X, wr, map=True, invert_map=False, select_lazy=None, **kwargs):
141
+ """
142
+ Alias for views.localview
143
+
144
+ Compute local neighborhood views of the input array `X`
145
+ using rectangular windows of radius `wr`. The window size
146
+ is given by ``ws = 2 * wr + 1`` (per dimension).
147
+
148
+ Parameters
149
+ ----------
150
+ X : np.ndarray
151
+ Input data of shape (N, *C) or (*G, *C).
152
+ wr : int or tuple (per dimension)
153
+ Radius of the window.
154
+ map : bool, default True
155
+ If True, `X` is assumed to be in (N, *C) space.
156
+ Otherwise, it is assumed to be in (*G, *C) space.
157
+ invert_map : bool, default False
158
+ If True, the output will stay in (*G, *C) space
159
+ Otherwise, it will be converted back to (N, *C)
160
+ select_lazy : None or IndexLike, default None
161
+ Subset of indices where the view is computed.
162
+ Must be grid indexes
163
+ **kwargs :
164
+ Additional keyword arguments passed to `np.pad`
165
+ (e.g., boundary conditions). See NumPy documentation.
166
+
167
+ Returns
168
+ -------
169
+ Xview : np.ndarray or WindowCollector
170
+ - If dense output:
171
+ Array of shape (N, *C, *ws) or (*G, *C, *ws).
172
+ - If `select_lazy` is used:
173
+ A mapping (e.g. dict-like) from selected indices
174
+ to local views of shape (*G[sel], *C, *ws).
175
+ """
176
+ lazy = (select_lazy is not None)
177
+
178
+ Xmap = self.map(X) if map else X
179
+ Xmap = np.ascontiguousarray(Xmap)
180
+
181
+ args = (Xmap, wr, self.D)
182
+
183
+ Xview = (
184
+ localview(*args, **kwargs)
185
+ if not lazy
186
+ else lazylocalview(select_lazy, *args, **kwargs)
187
+ )
188
+
189
+ if invert_map:
190
+ if lazy:
191
+ Xview = {
192
+ self.invert_mapidx(key): value
193
+ for key, value in Xview.items()
194
+ }
195
+ else:
196
+ if self.warnings_:
197
+ warn(
198
+ "Using invert_map may be slow and memory-intensive. "
199
+ "Consider working with gridded data or enabling select_lazy "
200
+ "if you only need a subset of the views.",
201
+ PerformanceWarning,
202
+ stacklevel=2
203
+ )
204
+ Xview = self.invert_map(Xview)
205
+
206
+
207
+ return Xview
208
+
209
+ def autopack(self):
210
+ print(
211
+ "Successfully sorted at iteration 0...\n"
212
+ "...Which means data are allready packed\n"
213
+ "Performing an autopack to boost performance."
214
+ )
215
+ self.packed = True
216
+
217
+ if self.warnings_:
218
+ if not self.points.flags.c_contiguous:
219
+ warn(
220
+ "Consider calling np.ascontiguousarray on your input arrays "
221
+ "(e.g., points and covariates) to improve performance.",
222
+ PerformanceWarning,
223
+ stacklevel=2
224
+ )
225
+
226
+ class ConvergenceWarning(UserWarning):
227
+ pass
228
+
229
+ class PerformanceWarning(UserWarning):
230
+ pass