squarenet 1.3.0__tar.gz → 1.3.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 (26) hide show
  1. {squarenet-1.3.0 → squarenet-1.3.2}/PKG-INFO +1 -1
  2. {squarenet-1.3.0 → squarenet-1.3.2}/pyproject.toml +1 -1
  3. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/__init__.py +1 -1
  4. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/neighbormap.py +7 -5
  5. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/squarenet.py +53 -18
  6. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/utils.py +59 -31
  7. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet.egg-info/PKG-INFO +1 -1
  8. {squarenet-1.3.0 → squarenet-1.3.2}/README.md +0 -0
  9. {squarenet-1.3.0 → squarenet-1.3.2}/setup.cfg +0 -0
  10. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/artist.py +0 -0
  11. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/core.py +0 -0
  12. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/data/__init__.py +0 -0
  13. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/optim/core_numpy.py +0 -0
  14. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/optim/core_torch.py +0 -0
  15. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/optim/numpy/booster.py +0 -0
  16. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/optim/numpy/hashtable.py +0 -0
  17. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/optim/numpy/subgrid.py +0 -0
  18. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/optim/torch/booster.py +0 -0
  19. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/optim/torch/hashtable.py +0 -0
  20. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/optim/torch/subgrid.py +0 -0
  21. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/sampler.py +0 -0
  22. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet/views.py +0 -0
  23. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet.egg-info/SOURCES.txt +0 -0
  24. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet.egg-info/dependency_links.txt +0 -0
  25. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet.egg-info/requires.txt +0 -0
  26. {squarenet-1.3.0 → squarenet-1.3.2}/src/squarenet.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: squarenet
3
- Version: 1.3.0
3
+ Version: 1.3.2
4
4
  Summary: Gridification for point clouds in any dimension (fast greedy optimal transport through Cartesian Sort).
5
5
  Author-email: ArmanddeCacqueray <armanddecacqueray@sfr.fr>
6
6
  Project-URL: Homepage, https://github.com/ArmanddeCacqueray/SquareNet
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "squarenet"
7
- version = "1.3.0"
7
+ version = "1.3.2"
8
8
  description = "Gridification for point clouds in any dimension (fast greedy optimal transport through Cartesian Sort)."
9
9
  authors = [{ name = "ArmanddeCacqueray", email = "armanddecacqueray@sfr.fr" }]
10
10
  readme = "README.md"
@@ -1,4 +1,4 @@
1
1
  from .squarenet import SquareNet
2
2
 
3
3
  __all__ = ["SquareNet", "samplepoints", "place_at", "plotpoints", "sqplot", "carthesian_sort"]
4
- __version__ = "1.3.0"
4
+ __version__ = "1.3.2"
@@ -14,14 +14,19 @@ def neighbormap(grided_points, mapidx, criterion = "rank", thresholdcut = 1,
14
14
  d = len(gpts.shape) - 1
15
15
  wr = windowradius
16
16
  ws = 2*wr+1
17
+ N = np.prod(gpts.shape[:-1])
18
+ D = gpts.shape[-1]
17
19
 
18
20
  selection = None
19
21
 
20
- if np.prod(gpts.shape)*(ws**2) >= sample_size:
21
- N = np.prod(gpts.shape[:-1])
22
+ if N*D*(ws**2) >= sample_size:
22
23
  sample_size_x = min(sample_size//(ws**2), N)
23
24
  selection = np.random.choice(N, replace = False, size = sample_size_x)
24
25
  selection = mapidx(selection)
26
+ else:
27
+ selection = mapidx(np.arange(N))
28
+ mask = np.isfinite(grided_points[selection]).all(axis=-1)
29
+ selection = tuple(dim_coords[mask] for dim_coords in selection)
25
30
 
26
31
  pad_width = [(0, 0)]*(d+1)
27
32
  for gax in projection:
@@ -39,9 +44,6 @@ def neighbormap(grided_points, mapidx, criterion = "rank", thresholdcut = 1,
39
44
  axis=projection
40
45
  )
41
46
 
42
-
43
-
44
-
45
47
  dists = kernel(gpts[selection], gview[selection])
46
48
 
47
49
  if criterion =="value":
@@ -58,7 +58,8 @@ class SquareNet:
58
58
  self.max_iter = max_iter
59
59
  self.verbose = verbose
60
60
  self.backend = backend
61
- self.torchdevice = "cpu"
61
+ self.device = "cpu"
62
+ self._torchdevice = "cpu"
62
63
 
63
64
  if backend not in {"numpy", "jax", "torch"}:
64
65
  raise ValueError(f"Unknown backend '{backend}' Should be 'numpy' 'torch' or 'jax'")
@@ -67,14 +68,29 @@ class SquareNet:
67
68
  self.xp = np
68
69
 
69
70
  elif backend == "jax":
71
+ import jax
70
72
  import jax.numpy as jnp
73
+ import shutil
71
74
  self.xp = jnp
75
+ self.device = jax.default_backend()
76
+ if shutil.which("nvidia-smi") is not None:
77
+ try:
78
+ import torch
79
+ if torch.cuda.is_available():
80
+ self._torchdevice = torch.device("cuda")
81
+ except ImportError:
82
+ if warnings_ == True:
83
+ warn(
84
+ "SquareNet tryed to import pytorch because a GPU was detected."\
85
+ "Pytorch was not found so SquareNet will default to cpu for the fit"
86
+ )
87
+
72
88
 
73
89
  elif backend == "torch":
74
90
  import torch
75
91
  self.xp = torch
76
92
  if torch.cuda.is_available():
77
- self.torchdevice = torch.device("cuda")
93
+ self.device = torch.device("cuda")
78
94
 
79
95
  self.warnings_ = warnings_
80
96
  if stencil is not None:
@@ -88,7 +104,7 @@ class SquareNet:
88
104
  # Internal utilities
89
105
  # ------------------------------------------------------------------
90
106
  def _to_backend(self, x):
91
- return to_backend(x, backend = self.backend, device = self.torchdevice, warnings_ = self.warnings_)
107
+ return to_backend(x, backend = self.backend, device = self.device, warnings_ = self.warnings_)
92
108
 
93
109
  def _reset_state(self):
94
110
  """Reset internal state to initial configuration."""
@@ -96,7 +112,7 @@ class SquareNet:
96
112
  self.grid = self.xp.arange(
97
113
  self.N,
98
114
  dtype=self.xp.int32,
99
- device=self.torchdevice,
115
+ device=self.device,
100
116
  ).reshape(self.gridshape)
101
117
 
102
118
  else:
@@ -118,6 +134,7 @@ class SquareNet:
118
134
  if isinstance(points, str):
119
135
  points = samplepoints(method=points, size=(self.N, self.D))
120
136
 
137
+ points = points.clone() if hasattr(points, "clone") else points.copy()
121
138
  points = self._to_backend(points)
122
139
  N, D = points.shape
123
140
 
@@ -133,7 +150,7 @@ class SquareNet:
133
150
 
134
151
  return points
135
152
 
136
- def _make_stencil(self, n_target, dtype=float, max_iter=1000):
153
+ def _make_stencil(self, n_target, dtype=float, max_iter=100):
137
154
  """Create a grid stencil based on a p-holder norm boundary. see utils.make_stencil
138
155
  stencil allow to fit n_target points in a grid with shape bigger than n_target"""
139
156
  stencil = make_stencil(self.gridshape, n_target, dtype, max_iter)
@@ -164,6 +181,7 @@ class SquareNet:
164
181
  Padding is performed such that points in the middle of the grid are true points
165
182
  and points in the corner are fictive points with +- inf coordinates, using the stencil.
166
183
  """
184
+ n_target = len(points)
167
185
  if self.stencil is None:
168
186
  if self.verbose + verbose >= 2:
169
187
  print(
@@ -174,8 +192,14 @@ class SquareNet:
174
192
  " >>> saved_stencil = sn.stencil\n"
175
193
  " >>> sn = SquareNet(..., stencil=saved_stencil)\n"
176
194
  )
177
- self._make_stencil(n_target=len(points), dtype=points.dtype)
178
-
195
+ self._make_stencil(n_target=n_target, dtype=points.dtype)
196
+ elif self.xp.isfinite(self.stencil).sum() != self.D*n_target:
197
+ if self.verbose + verbose >= 2:
198
+ print(
199
+ "\n[SquareNet] Update the stencil to match new number of points...\n"
200
+ )
201
+ self._make_stencil(n_target=n_target, dtype=points.dtype)
202
+
179
203
  return fill_in(points, self.stencil, self.xp)
180
204
  # ------------------------------------------------------------------
181
205
  # Core API
@@ -204,13 +228,24 @@ class SquareNet:
204
228
  see ``squarenet.core``
205
229
  """
206
230
  old_backend = self.backend
207
- if self.backend == "torch" and self.torchdevice != "cpu":
208
- fit_with_numpy = False
231
+ old_device = self.device
232
+
233
+ if self._torchdevice != "cpu":
234
+ #just for the fit, will be updated after
235
+ self.backend = "torch"
236
+ self.device = self._torchdevice
209
237
  else:
210
- fit_with_numpy = True
211
238
  #just for the fit, will be updated after
212
239
  self.backend = "numpy"
213
-
240
+ self.device = "cpu"
241
+
242
+ true_g = np.array(self.gridshape)
243
+ true_g = true_g[true_g > 1]
244
+ D = len(true_g)
245
+ if D >= 5:
246
+ #method "fast" is as good as the other methods in 5D and more,
247
+ #robust and ultimate are too slow anyway in those cases
248
+ method = "fast"
214
249
  max_iter = self.max_iter
215
250
  verbose = self.verbose
216
251
  def _log(msg):
@@ -222,7 +257,7 @@ class SquareNet:
222
257
  print(f"\n=== {title} ===")
223
258
 
224
259
  points = self._validate_points(points)
225
- self.points = points.clone() if hasattr(points, "clone") else points.copy()
260
+ grid = self._to_backend(self.grid)
226
261
 
227
262
  _log("Starting gridification... available method [fast, robust, ultimate]")
228
263
  _log(f"selected {method}")
@@ -236,8 +271,8 @@ class SquareNet:
236
271
  if method == "ultimate":
237
272
  _log(f"(max iter: 2 x {mi} + 4 x {mi} + {mi})")
238
273
 
239
- self.grid, lc = carthesian_sort(self.grid, points, max_iter=max_iter, method = method,
240
- backend = self.backend, verbose = self.verbose)
274
+ grid, lc = carthesian_sort(grid, points, max_iter=max_iter, method = method,
275
+ backend = self.backend, verbose = self.verbose)
241
276
 
242
277
  # --------------------------------------------------
243
278
  # Finalization
@@ -267,10 +302,10 @@ class SquareNet:
267
302
  stacklevel=2,
268
303
  )
269
304
 
270
- if fit_with_numpy:
271
- self.backend = old_backend
272
- self.grid = to_backend(self.grid, backend = self.backend, device = self.torchdevice)
273
- self.points = to_backend(self.points, backend = self.backend, device = self.torchdevice)
305
+ self.backend = old_backend
306
+ self.device = old_device
307
+ self.grid = self._to_backend(grid)
308
+ self.points = self._to_backend(points)
274
309
  # --------------------------------------------------
275
310
  # Update mappings
276
311
  # --------------------------------------------------
@@ -8,7 +8,7 @@ def index_identity(shape):
8
8
  """index_identy[i, j, k, ...] = [i, j, k, ...]"""
9
9
  return np.moveaxis(np.indices(shape), 0, -1)
10
10
 
11
- def make_stencil(gridshape, n_target, dtype=float, max_iter = 1000):
11
+ def make_stencil(gridshape, n_target, dtype=float, max_iter = 100):
12
12
  """
13
13
  Create a grid stencil based on a p-holder norm boundary.
14
14
  Uses binary search to find a smooth convexe subdomain of the hyperrectangular
@@ -52,7 +52,7 @@ def make_stencil(gridshape, n_target, dtype=float, max_iter = 1000):
52
52
 
53
53
  log_x = np.log(x_abs)
54
54
 
55
- low_p, high_p = -1.0, 1000.0
55
+ low_p, high_p = 2.0, 1000.0
56
56
 
57
57
  for it in range(max_iter):
58
58
  p = (low_p + high_p) / 2.0
@@ -216,50 +216,78 @@ def project(gridpoints, feature_axes=(0, 1), index=0):
216
216
 
217
217
  def from_backend(x):
218
218
  """
219
- Convert torch/jax/numpy array to numpy safely.
219
+ Safely convert Torch, JAX, or array-like objects back to Numpy.
220
220
  """
221
+ # 1. Already a Numpy array
221
222
  if isinstance(x, np.ndarray):
222
223
  return x
223
224
 
224
- # torch
225
+ # 2. PyTorch tensor
225
226
  if hasattr(x, "detach"):
226
227
  return x.detach().cpu().numpy()
227
228
 
228
- # jax
229
- if hasattr(x, "__array__"):
230
- return np.asarray(x)
231
-
229
+ # 3. JAX arrays or any object with __array__ protocol (Pandas, etc.)
232
230
  return np.asarray(x)
233
231
 
234
- def to_backend(x, backend="numpy", device = "cpu", warnings_ = True):
232
+ def to_backend(x, backend="numpy", device="cpu", warnings_=True):
235
233
  """
236
- Convert numpy array to target backend.
234
+ Convert arrays/tensors between Numpy, Torch, and JAX.
235
+ Utilizes DLPack for zero-copy transfers across frameworks when possible.
237
236
  """
237
+ # Identify input framework without heavy imports
238
+ in_type = type(x).__module__.split('.')[0]
239
+
240
+ # ---------------------------------------------------------
241
+ # 1. Target: NUMPY
242
+ # ---------------------------------------------------------
238
243
  if backend == "numpy":
239
- return np.asarray(x)
244
+ return from_backend(x)
240
245
 
241
- if backend == "torch":
246
+ # ---------------------------------------------------------
247
+ # 2. Target: TORCH
248
+ # ---------------------------------------------------------
249
+ elif backend == "torch":
242
250
  import torch
243
- device = torch.device(device)
244
- x_device = getattr(x, "device", None)
245
- if (
246
- warnings_
247
- and x_device is not None
248
- and hasattr(x_device, "type")
249
- and x_device.type == "cpu"
250
- and device.type != "cpu"
251
- ):
252
- warn(
253
- "Downgrading tensor as initial device was GPU but asked device is CPU"
254
- )
255
-
256
- return torch.as_tensor(x, device=device)
257
-
258
- if backend == "jax":
251
+ tgt_dev = torch.device(device)
252
+
253
+ if in_type == "torch":
254
+ if warnings_ and x.device.type != "cpu" and tgt_dev.type == "cpu":
255
+ warn("Downgrading tensor: initial device was GPU but asked for CPU.")
256
+ return x.to(tgt_dev)
257
+
258
+ elif in_type in ["jax", "jaxlib"]:
259
+ # Zero-copy JAX to Torch via DLPack
260
+ return torch.from_dlpack(x).to(tgt_dev)
261
+
262
+ return torch.as_tensor(x, device=tgt_dev)
263
+
264
+ # ---------------------------------------------------------
265
+ # 3. Target: JAX
266
+ # ---------------------------------------------------------
267
+ elif backend == "jax":
268
+ import jax
259
269
  import jax.numpy as jnp
260
- return jnp.asarray(x)
261
-
262
- raise ValueError(f"Unknown backend: {backend}")
270
+
271
+ # JAX uses "gpu" instead of "cuda"
272
+ device = "cpu" if "cpu" in str(device) else "gpu"
273
+ try:
274
+ tgt_dev = jax.devices(device)[0]
275
+ except RuntimeError:
276
+ tgt_dev = None
277
+
278
+ if in_type == "torch":
279
+ import torch.utils.dlpack
280
+ # DLPack requires contiguous memory
281
+ x = x if x.is_contiguous() else x.contiguous()
282
+ j_arr = jax.dlpack.from_dlpack(torch.utils.dlpack.to_dlpack(x))
283
+ return jax.device_put(j_arr, tgt_dev) if tgt_dev else j_arr
284
+
285
+ elif in_type in ["jax", "jaxlib"]:
286
+ return jax.device_put(x, tgt_dev) if tgt_dev else x
287
+
288
+ return jax.device_put(x, tgt_dev) if tgt_dev else jnp.asarray(x)
289
+
290
+ raise ValueError(f"Unknown backend: '{backend}'. Expected 'numpy', 'torch', or 'jax'.")
263
291
 
264
292
  def progress_bar(it, total, bar_length=30):
265
293
  progress = it / total
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: squarenet
3
- Version: 1.3.0
3
+ Version: 1.3.2
4
4
  Summary: Gridification for point clouds in any dimension (fast greedy optimal transport through Cartesian Sort).
5
5
  Author-email: ArmanddeCacqueray <armanddecacqueray@sfr.fr>
6
6
  Project-URL: Homepage, https://github.com/ArmanddeCacqueray/SquareNet
File without changes
File without changes