anywidget-vector 0.2.5__tar.gz → 0.2.6__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 (49) hide show
  1. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/PKG-INFO +6 -1
  2. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/README.md +2 -0
  3. anywidget_vector-0.2.6/assets/demo.png +0 -0
  4. anywidget_vector-0.2.6/examples/test_add_numpy.py +98 -0
  5. anywidget_vector-0.2.6/examples/test_projection.py +98 -0
  6. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/pyproject.toml +2 -1
  7. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/ui/__init__.py +1 -1
  8. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/ui/canvas.js +6 -2
  9. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/ui/styles.css +2 -2
  10. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/widget.py +186 -0
  11. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/uv.lock +207 -1
  12. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/.github/workflows/ci.yml +0 -0
  13. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/.github/workflows/pypi.yml +0 -0
  14. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/.gitignore +0 -0
  15. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/.python-version +0 -0
  16. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/LICENSE +0 -0
  17. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/examples/demo.py +0 -0
  18. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/__init__.py +0 -0
  19. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/__init__.py +0 -0
  20. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/chroma/__init__.py +0 -0
  21. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/chroma/client.py +0 -0
  22. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/chroma/converter.py +0 -0
  23. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/grafeo/__init__.py +0 -0
  24. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/grafeo/browser.js +0 -0
  25. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/grafeo/client.py +0 -0
  26. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/grafeo/converter.py +0 -0
  27. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/grafeo/embed.js +0 -0
  28. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/lancedb/__init__.py +0 -0
  29. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/lancedb/client.py +0 -0
  30. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/lancedb/converter.py +0 -0
  31. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/pinecone/__init__.py +0 -0
  32. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/pinecone/client.js +0 -0
  33. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/pinecone/converter.py +0 -0
  34. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/qdrant/__init__.py +0 -0
  35. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/qdrant/client.js +0 -0
  36. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/qdrant/converter.py +0 -0
  37. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/weaviate/__init__.py +0 -0
  38. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/weaviate/client.js +0 -0
  39. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/backends/weaviate/converter.py +0 -0
  40. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/demo.py +0 -0
  41. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/py.typed +0 -0
  42. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/static/icons.js +0 -0
  43. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/ui/constants.js +0 -0
  44. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/ui/properties.js +0 -0
  45. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/ui/settings.js +0 -0
  46. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/ui/sidebar.js +0 -0
  47. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/src/anywidget_vector/ui/toolbar.js +0 -0
  48. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/tests/__init__.py +0 -0
  49. {anywidget_vector-0.2.5 → anywidget_vector-0.2.6}/tests/test_widget.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anywidget-vector
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: Interactive 3D vector visualization with query UI for vector databases
5
5
  Project-URL: Homepage, https://grafeo.dev/
6
6
  Project-URL: Repository, https://github.com/GrafeoDB/anywidget-vector
@@ -48,6 +48,9 @@ Provides-Extra: pandas
48
48
  Requires-Dist: pandas>=2.0; extra == 'pandas'
49
49
  Provides-Extra: pinecone
50
50
  Requires-Dist: pinecone-client>=3.0; extra == 'pinecone'
51
+ Provides-Extra: projection
52
+ Requires-Dist: scikit-learn>=1.3; extra == 'projection'
53
+ Requires-Dist: umap-learn>=0.5; extra == 'projection'
51
54
  Provides-Extra: qdrant
52
55
  Requires-Dist: qdrant-client>=1.0; extra == 'qdrant'
53
56
  Provides-Extra: weaviate
@@ -60,6 +63,8 @@ Interactive 3D vector visualization for Python notebooks.
60
63
 
61
64
  Works with Marimo, Jupyter, VS Code, Colab, anywhere [anywidget](https://anywidget.dev/) runs.
62
65
 
66
+ ![anywidget-vector demo](assets/demo.png)
67
+
63
68
  ## Features
64
69
 
65
70
  - **Universal**: One widget, every notebook environment
@@ -4,6 +4,8 @@ Interactive 3D vector visualization for Python notebooks.
4
4
 
5
5
  Works with Marimo, Jupyter, VS Code, Colab, anywhere [anywidget](https://anywidget.dev/) runs.
6
6
 
7
+ ![anywidget-vector demo](assets/demo.png)
8
+
7
9
  ## Features
8
10
 
9
11
  - **Universal**: One widget, every notebook environment
Binary file
@@ -0,0 +1,98 @@
1
+ # /// script
2
+ # requires-python = ">=3.12"
3
+ # dependencies = [
4
+ # "anywidget-vector==0.2.5",
5
+ # "marimo",
6
+ # "numpy==2.4.3",
7
+ # ]
8
+ # ///
9
+
10
+ import marimo
11
+
12
+ __generated_with = "0.18.4"
13
+ app = marimo.App(width="medium")
14
+
15
+
16
+ @app.cell
17
+ def _():
18
+ import marimo as mo
19
+
20
+ return (mo,)
21
+
22
+
23
+ @app.cell
24
+ def _():
25
+ import numpy as np
26
+
27
+ from anywidget_vector import VectorSpace
28
+
29
+ return VectorSpace, np
30
+
31
+
32
+ @app.cell(hide_code=True)
33
+ def _(mo):
34
+ mo.md("""
35
+ # Test: `add_numpy`
36
+ """)
37
+ return
38
+
39
+
40
+ @app.cell
41
+ def _(VectorSpace):
42
+ # Start with an empty widget
43
+ w = VectorSpace(
44
+ width=900,
45
+ height=500,
46
+ background="#0f0f1a",
47
+ color_field="group",
48
+ )
49
+ w
50
+ return (w,)
51
+
52
+
53
+ @app.cell
54
+ def _(mo, np, w):
55
+ # Add two numpy clusters
56
+ rng = np.random.default_rng(42)
57
+
58
+ cluster_a = rng.normal(loc=[0.3, 0.3, 0.3], scale=0.1, size=(50, 3))
59
+ cluster_b = rng.normal(loc=[0.7, 0.7, 0.7], scale=0.1, size=(50, 3))
60
+
61
+ w.add_numpy(
62
+ cluster_a,
63
+ labels=[f"A-{i}" for i in range(len(cluster_a))],
64
+ metadata={"group": ["alpha"] * len(cluster_a)},
65
+ ).add_numpy(
66
+ cluster_b,
67
+ labels=[f"B-{i}" for i in range(len(cluster_b))],
68
+ metadata={"group": ["beta"] * len(cluster_b)},
69
+ )
70
+
71
+ mo.md(f"Added **{len(w.points)}** points total")
72
+ return
73
+
74
+
75
+ @app.cell
76
+ def _(mo, np, w):
77
+ # Add a third cluster with 2D data (z defaults to 0)
78
+ rng2 = np.random.default_rng(99)
79
+ flat = rng2.normal(loc=[0.5, 0.5], scale=0.08, size=(30, 2))
80
+
81
+ w.add_numpy(flat, metadata={"group": ["flat"] * len(flat)})
82
+
83
+ mo.md(f"Now **{len(w.points)}** points (added 30 flat 2D points)")
84
+ return
85
+
86
+
87
+ @app.cell
88
+ def _(mo, w):
89
+ # Verify point IDs are unique
90
+ ids = [p["id"] for p in w.points]
91
+ unique = len(set(ids))
92
+ status = "All IDs unique" if unique == len(ids) else f"DUPLICATE IDS: {len(ids) - unique} collisions"
93
+ mo.md(f"Points: {len(ids)}, Unique IDs: {unique}: **{status}**")
94
+ return
95
+
96
+
97
+ if __name__ == "__main__":
98
+ app.run()
@@ -0,0 +1,98 @@
1
+ # /// script
2
+ # requires-python = ">=3.12"
3
+ # dependencies = [
4
+ # "anywidget-vector",
5
+ # "marimo",
6
+ # "numpy",
7
+ # "scikit-learn",
8
+ # ]
9
+ # ///
10
+
11
+ import marimo
12
+
13
+ __generated_with = "0.20.0"
14
+ app = marimo.App(width="medium")
15
+
16
+
17
+ @app.cell
18
+ def _():
19
+ import marimo as mo
20
+
21
+ return (mo,)
22
+
23
+
24
+ @app.cell
25
+ def _():
26
+ import numpy as np
27
+
28
+ from anywidget_vector import VectorSpace
29
+
30
+ return VectorSpace, np
31
+
32
+
33
+ @app.cell(hide_code=True)
34
+ def _(mo):
35
+ mo.md("""# Interactive Projection Demo
36
+
37
+ High-dimensional data projected to 3D. Switch projections live with the dropdown.
38
+ """)
39
+ return
40
+
41
+
42
+ @app.cell
43
+ def _(np):
44
+ # Generate 50-dimensional clustered data
45
+ rng = np.random.default_rng(42)
46
+
47
+ def make_cluster(center, n=80, noise=0.3):
48
+ return rng.normal(loc=center, scale=noise, size=(n, len(center)))
49
+
50
+ n_dims = 50
51
+ centers = [rng.uniform(-2, 2, size=n_dims) for _ in range(5)]
52
+ cluster_names = ["alpha", "beta", "gamma", "delta", "epsilon"]
53
+
54
+ vectors = np.vstack([make_cluster(c) for c in centers])
55
+ groups = [name for name, c in zip(cluster_names, centers, strict=True) for _ in range(80)]
56
+ labels = [f"{g}-{i}" for i, g in enumerate(groups)]
57
+
58
+ f"Generated {len(vectors)} points in {n_dims}D across 5 clusters"
59
+ return vectors, groups, labels
60
+
61
+
62
+ @app.cell
63
+ def _(mo):
64
+ method = mo.ui.dropdown(
65
+ options=["pca", "tsne"],
66
+ value="pca",
67
+ label="Projection method",
68
+ )
69
+ method
70
+ return (method,)
71
+
72
+
73
+ @app.cell
74
+ def _(VectorSpace, groups, labels, method, vectors):
75
+ w = VectorSpace(
76
+ width=900,
77
+ height=500,
78
+ background="#0f0f1a",
79
+ color_field="group",
80
+ )
81
+ w.add_numpy(
82
+ vectors,
83
+ labels=labels,
84
+ metadata={"group": groups},
85
+ )
86
+ w.project(method.value)
87
+ w
88
+ return (w,)
89
+
90
+
91
+ @app.cell
92
+ def _(mo, method, w):
93
+ mo.md(f"Showing **{method.value.upper()}** projection of {len(w.points)} points")
94
+ return
95
+
96
+
97
+ if __name__ == "__main__":
98
+ app.run()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "anywidget-vector"
3
- version = "0.2.5"
3
+ version = "0.2.6"
4
4
  description = "Interactive 3D vector visualization with query UI for vector databases"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -40,6 +40,7 @@ pinecone = ["pinecone-client>=3.0"]
40
40
  weaviate = ["weaviate-client>=4.0"]
41
41
  lancedb = ["lancedb>=0.1"]
42
42
  grafeo = ["grafeo>=0.5"]
43
+ projection = ["scikit-learn>=1.3", "umap-learn>=0.5"]
43
44
  embed = [] # Marker extra — WASM loads from CDN on demand
44
45
  demo = [] # Marker extra — enables demo mode with pre-populated dataset
45
46
  all = [
@@ -123,6 +123,7 @@ function render({{ model, el }}) {{
123
123
  const wrapper = document.createElement("div");
124
124
  wrapper.className = "avs-wrapper";
125
125
  wrapper.style.width = model.get("width") + "px";
126
+ wrapper.style.height = model.get("height") + "px";
126
127
  el.appendChild(wrapper);
127
128
 
128
129
  let sidebar = null;
@@ -154,7 +155,6 @@ function render({{ model, el }}) {{
154
155
 
155
156
  const main = document.createElement("div");
156
157
  main.className = "avs-main";
157
- main.style.height = model.get("height") + "px";
158
158
  wrapper.appendChild(main);
159
159
 
160
160
  // Sidebar (left, explorer panel)
@@ -50,15 +50,19 @@ export function createCanvas(model, container, callbacks) {
50
50
  scene = new THREE.Scene();
51
51
  scene.background = new THREE.Color(model.get("background"));
52
52
 
53
+ // Use actual container dimensions when available, fall back to model values
54
+ const initW = container.clientWidth || model.get("width");
55
+ const initH = container.clientHeight || model.get("height");
56
+
53
57
  // Camera
54
- const aspect = model.get("width") / model.get("height");
58
+ const aspect = initW / initH;
55
59
  camera = new THREE.PerspectiveCamera(60, aspect, 0.01, 1000);
56
60
  const camPos = model.get("camera_position") || [2, 2, 2];
57
61
  camera.position.set(camPos[0], camPos[1], camPos[2]);
58
62
 
59
63
  // Renderer
60
64
  renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
61
- renderer.setSize(model.get("width"), model.get("height"));
65
+ renderer.setSize(initW, initH);
62
66
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
63
67
  container.appendChild(renderer.domElement);
64
68
 
@@ -273,11 +273,11 @@
273
273
  width: 280px;
274
274
  }
275
275
 
276
- .avs-panel-left {
276
+ .avs-panel-left.avs-open {
277
277
  border-right: 1px solid var(--avs-border);
278
278
  }
279
279
 
280
- .avs-panel-right {
280
+ .avs-panel-right.avs-open {
281
281
  border-left: 1px solid var(--avs-border);
282
282
  }
283
283
 
@@ -109,6 +109,7 @@ class VectorSpace(anywidget.AnyWidget):
109
109
  def __init__(self, points: list[dict[str, Any]] | None = None, **kwargs: Any) -> None:
110
110
  super().__init__(points=points or [], **kwargs)
111
111
  self._backend_client: Any = None
112
+ self._vectors: Any = None # High-dim vectors for projection (numpy array, not synced to JS)
112
113
  self.observe(self._on_execute_query, names=["_execute_query"])
113
114
 
114
115
  # === Backend Configuration ===
@@ -209,6 +210,158 @@ class VectorSpace(anywidget.AnyWidget):
209
210
  self.observe(_handler, names=["selected_points"])
210
211
  return callback
211
212
 
213
+ # === Add Points ===
214
+
215
+ def add_points(self, data: list[dict[str, Any]]) -> VectorSpace:
216
+ """Append point dicts to the existing points.
217
+
218
+ Args:
219
+ data: List of point dicts with at least x, y keys.
220
+
221
+ Returns:
222
+ Self for chaining.
223
+ """
224
+ self.points = [*self.points, *_normalize_points(data)]
225
+ return self
226
+
227
+ def add_numpy(
228
+ self,
229
+ positions: Any,
230
+ *,
231
+ ids: list[str] | None = None,
232
+ labels: list[str] | None = None,
233
+ metadata: dict[str, list[Any]] | None = None,
234
+ ) -> VectorSpace:
235
+ """Append points from a NumPy array.
236
+
237
+ For arrays with D > 3 columns, the full vectors are stored internally
238
+ and PCA is used for the initial 3D coordinates. Call ``project()`` to
239
+ switch projection methods interactively.
240
+
241
+ Args:
242
+ positions: Array of shape (N, 2), (N, 3), or (N, D) for high-dim data.
243
+ ids: Optional list of point IDs.
244
+ labels: Optional list of labels.
245
+ metadata: Optional dict mapping field names to per-point value lists.
246
+
247
+ Returns:
248
+ Self for chaining.
249
+ """
250
+ import numpy as np
251
+
252
+ arr = np.asarray(positions, dtype=np.float64)
253
+ n_samples, n_dims = arr.shape
254
+
255
+ # High-dimensional: store vectors and project to 3D
256
+ if n_dims > 3:
257
+ if self._vectors is not None:
258
+ self._vectors = np.vstack([self._vectors, arr])
259
+ else:
260
+ self._vectors = arr
261
+ coords = _pca(arr, n_components=3)
262
+ else:
263
+ coords = arr
264
+
265
+ offset = len(self.points)
266
+ new_points = []
267
+ for i in range(n_samples):
268
+ point: dict[str, Any] = {
269
+ "id": ids[i] if ids else f"point_{offset + i}",
270
+ "x": float(coords[i, 0]),
271
+ "y": float(coords[i, 1]),
272
+ "z": float(coords[i, 2]) if coords.shape[1] > 2 else 0.0,
273
+ }
274
+ if labels:
275
+ point["label"] = labels[i]
276
+ if metadata:
277
+ for key, values in metadata.items():
278
+ point[key] = values[i]
279
+ new_points.append(point)
280
+ self.points = [*self.points, *new_points]
281
+ return self
282
+
283
+ # === Projection ===
284
+
285
+ def set_vectors(self, vectors: Any) -> VectorSpace:
286
+ """Store high-dimensional vectors for use with ``project()``.
287
+
288
+ Must have the same number of rows as ``self.points``.
289
+
290
+ Args:
291
+ vectors: Array-like of shape (N, D).
292
+
293
+ Returns:
294
+ Self for chaining.
295
+ """
296
+ import numpy as np
297
+
298
+ self._vectors = np.asarray(vectors, dtype=np.float64)
299
+ return self
300
+
301
+ def project(self, method: str = "pca", *, n_components: int = 3, **kwargs: Any) -> VectorSpace:
302
+ """Reproject point coordinates using dimensionality reduction.
303
+
304
+ Reads vectors from ``set_vectors()`` / ``add_numpy()`` (for D > 3), or
305
+ falls back to extracting the ``vector`` field from each point dict.
306
+
307
+ Supported methods: ``pca``, ``tsne``, ``umap``.
308
+
309
+ Args:
310
+ method: Projection algorithm name.
311
+ n_components: Target dimensions (2 or 3).
312
+ **kwargs: Passed to the underlying algorithm.
313
+
314
+ Returns:
315
+ Self for chaining.
316
+ """
317
+ vectors = self._resolve_vectors()
318
+ n_components = min(n_components, vectors.shape[1])
319
+
320
+ if method == "pca":
321
+ coords = _pca(vectors, n_components=n_components)
322
+ elif method == "tsne":
323
+ coords = _tsne(vectors, n_components=n_components, **kwargs)
324
+ elif method == "umap":
325
+ coords = _umap(vectors, n_components=n_components, **kwargs)
326
+ else:
327
+ raise ValueError(f"Unknown projection method: {method!r}. Use 'pca', 'tsne', or 'umap'.")
328
+
329
+ # Normalize to roughly [-1, 1] so the scene stays well-framed
330
+ for col in range(coords.shape[1]):
331
+ c = coords[:, col]
332
+ lo, hi = c.min(), c.max()
333
+ if hi - lo > 0:
334
+ coords[:, col] = 2 * (c - lo) / (hi - lo) - 1
335
+
336
+ points = [dict(p) for p in self.points]
337
+ for i, p in enumerate(points):
338
+ p["x"] = float(coords[i, 0])
339
+ p["y"] = float(coords[i, 1])
340
+ p["z"] = float(coords[i, 2]) if n_components >= 3 else 0.0
341
+ self.points = points
342
+ return self
343
+
344
+ def _resolve_vectors(self) -> Any:
345
+ """Get the vector matrix for projection."""
346
+ import numpy as np
347
+
348
+ if self._vectors is not None:
349
+ if len(self._vectors) != len(self.points):
350
+ raise ValueError(
351
+ f"Stored vectors ({len(self._vectors)}) don't match point count ({len(self.points)}). "
352
+ "Call set_vectors() again after modifying points."
353
+ )
354
+ return self._vectors
355
+
356
+ # Fall back to point["vector"] fields
357
+ vecs = [p.get("vector") for p in self.points]
358
+ if all(v is not None for v in vecs):
359
+ return np.array(vecs, dtype=np.float64)
360
+
361
+ raise ValueError(
362
+ "No vectors available. Use set_vectors(), add_numpy() with D > 3, or ensure points have a 'vector' field."
363
+ )
364
+
212
365
  # === Factory Methods ===
213
366
 
214
367
  @classmethod
@@ -923,3 +1076,36 @@ def _to_list(obj: Any) -> list[Any]:
923
1076
  if hasattr(obj, "tolist"):
924
1077
  return obj.tolist()
925
1078
  return list(obj)
1079
+
1080
+
1081
+ # === Projection Helpers ===
1082
+
1083
+
1084
+ def _pca(vectors: Any, n_components: int = 3) -> Any:
1085
+ """PCA via SVD (numpy only, no sklearn needed)."""
1086
+ import numpy as np
1087
+
1088
+ x = np.asarray(vectors, dtype=np.float64)
1089
+ x_centered = x - x.mean(axis=0)
1090
+ _u, _s, vt = np.linalg.svd(x_centered, full_matrices=False)
1091
+ return x_centered @ vt[:n_components].T
1092
+
1093
+
1094
+ def _tsne(vectors: Any, n_components: int = 3, **kwargs: Any) -> Any:
1095
+ """t-SNE projection (requires scikit-learn)."""
1096
+ try:
1097
+ from sklearn.manifold import TSNE
1098
+ except ImportError:
1099
+ raise ImportError("t-SNE requires scikit-learn: uv add scikit-learn") from None
1100
+ kwargs.setdefault("perplexity", min(30.0, len(vectors) - 1))
1101
+ return TSNE(n_components=n_components, **kwargs).fit_transform(vectors)
1102
+
1103
+
1104
+ def _umap(vectors: Any, n_components: int = 3, **kwargs: Any) -> Any:
1105
+ """UMAP projection (requires umap-learn)."""
1106
+ try:
1107
+ from umap import UMAP
1108
+ except ImportError:
1109
+ raise ImportError("UMAP requires umap-learn: uv add umap-learn") from None
1110
+ kwargs.setdefault("n_neighbors", min(15, len(vectors) - 1))
1111
+ return UMAP(n_components=n_components, **kwargs).fit_transform(vectors)
@@ -50,7 +50,7 @@ wheels = [
50
50
 
51
51
  [[package]]
52
52
  name = "anywidget-vector"
53
- version = "0.2.5"
53
+ version = "0.2.6"
54
54
  source = { editable = "." }
55
55
  dependencies = [
56
56
  { name = "anywidget" },
@@ -91,6 +91,10 @@ pandas = [
91
91
  pinecone = [
92
92
  { name = "pinecone-client" },
93
93
  ]
94
+ projection = [
95
+ { name = "scikit-learn" },
96
+ { name = "umap-learn" },
97
+ ]
94
98
  qdrant = [
95
99
  { name = "qdrant-client" },
96
100
  ]
@@ -124,7 +128,9 @@ requires-dist = [
124
128
  { name = "qdrant-client", marker = "extra == 'all'", specifier = ">=1" },
125
129
  { name = "qdrant-client", marker = "extra == 'qdrant'", specifier = ">=1.0" },
126
130
  { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15" },
131
+ { name = "scikit-learn", marker = "extra == 'projection'", specifier = ">=1.3" },
127
132
  { name = "ty", marker = "extra == 'dev'", specifier = ">=0.0.20" },
133
+ { name = "umap-learn", marker = "extra == 'projection'", specifier = ">=0.5" },
128
134
  { name = "weaviate-client", marker = "extra == 'all'", specifier = ">=4.20" },
129
135
  { name = "weaviate-client", marker = "extra == 'weaviate'", specifier = ">=4.0" },
130
136
  ]
@@ -956,6 +962,15 @@ wheels = [
956
962
  { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 },
957
963
  ]
958
964
 
965
+ [[package]]
966
+ name = "joblib"
967
+ version = "1.5.3"
968
+ source = { registry = "https://pypi.org/simple" }
969
+ sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603 }
970
+ wheels = [
971
+ { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071 },
972
+ ]
973
+
959
974
  [[package]]
960
975
  name = "jsonschema"
961
976
  version = "4.26.0"
@@ -1061,6 +1076,26 @@ wheels = [
1061
1076
  { url = "https://files.pythonhosted.org/packages/66/50/bd47bca59a87a88a4ca291a0718291422440750d84b34318048c70a537c2/lancedb-0.29.2-cp39-abi3-win_amd64.whl", hash = "sha256:101eb0ac018bb0b643dd9ea22065f6f2102e9d44c9ac58a197477ccbfbc0b9fa", size = 52028768 },
1062
1077
  ]
1063
1078
 
1079
+ [[package]]
1080
+ name = "llvmlite"
1081
+ version = "0.46.0"
1082
+ source = { registry = "https://pypi.org/simple" }
1083
+ sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456 }
1084
+ wheels = [
1085
+ { url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767 },
1086
+ { url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176 },
1087
+ { url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630 },
1088
+ { url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940 },
1089
+ { url = "https://files.pythonhosted.org/packages/e6/ff/3eba7eb0aed4b6fca37125387cd417e8c458e750621fce56d2c541f67fa8/llvmlite-0.46.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:30b60892d034bc560e0ec6654737aaa74e5ca327bd8114d82136aa071d611172", size = 37232767 },
1090
+ { url = "https://files.pythonhosted.org/packages/0e/54/737755c0a91558364b9200702c3c9c15d70ed63f9b98a2c32f1c2aa1f3ba/llvmlite-0.46.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cc19b051753368a9c9f31dc041299059ee91aceec81bd57b0e385e5d5bf1a54", size = 56275176 },
1091
+ { url = "https://files.pythonhosted.org/packages/e6/91/14f32e1d70905c1c0aa4e6609ab5d705c3183116ca02ac6df2091868413a/llvmlite-0.46.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bca185892908f9ede48c0acd547fe4dc1bafefb8a4967d47db6cf664f9332d12", size = 55128629 },
1092
+ { url = "https://files.pythonhosted.org/packages/4a/a7/d526ae86708cea531935ae777b6dbcabe7db52718e6401e0fb9c5edea80e/llvmlite-0.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:67438fd30e12349ebb054d86a5a1a57fd5e87d264d2451bcfafbbbaa25b82a35", size = 38138941 },
1093
+ { url = "https://files.pythonhosted.org/packages/95/ae/af0ffb724814cc2ea64445acad05f71cff5f799bb7efb22e47ee99340dbc/llvmlite-0.46.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:d252edfb9f4ac1fcf20652258e3f102b26b03eef738dc8a6ffdab7d7d341d547", size = 37232768 },
1094
+ { url = "https://files.pythonhosted.org/packages/c9/19/5018e5352019be753b7b07f7759cdabb69ca5779fea2494be8839270df4c/llvmlite-0.46.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379fdd1c59badeff8982cb47e4694a6143bec3bb49aa10a466e095410522064d", size = 56275173 },
1095
+ { url = "https://files.pythonhosted.org/packages/9f/c9/d57877759d707e84c082163c543853245f91b70c804115a5010532890f18/llvmlite-0.46.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e8cbfff7f6db0fa2c771ad24154e2a7e457c2444d7673e6de06b8b698c3b269", size = 55128628 },
1096
+ { url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056 },
1097
+ ]
1098
+
1064
1099
  [[package]]
1065
1100
  name = "loro"
1066
1101
  version = "1.10.3"
@@ -1335,6 +1370,30 @@ wheels = [
1335
1370
  { url = "https://files.pythonhosted.org/packages/03/cc/7cb74758e6df95e0c4e1253f203b6dd7f348bf2f29cf89e9210a2416d535/narwhals-2.16.0-py3-none-any.whl", hash = "sha256:846f1fd7093ac69d63526e50732033e86c30ea0026a44d9b23991010c7d1485d", size = 443951 },
1336
1371
  ]
1337
1372
 
1373
+ [[package]]
1374
+ name = "numba"
1375
+ version = "0.64.0"
1376
+ source = { registry = "https://pypi.org/simple" }
1377
+ dependencies = [
1378
+ { name = "llvmlite" },
1379
+ { name = "numpy" },
1380
+ ]
1381
+ sdist = { url = "https://files.pythonhosted.org/packages/23/c9/a0fb41787d01d621046138da30f6c2100d80857bf34b3390dd68040f27a3/numba-0.64.0.tar.gz", hash = "sha256:95e7300af648baa3308127b1955b52ce6d11889d16e8cfe637b4f85d2fca52b1", size = 2765679 }
1382
+ wheels = [
1383
+ { url = "https://files.pythonhosted.org/packages/70/a6/9fc52cb4f0d5e6d8b5f4d81615bc01012e3cf24e1052a60f17a68deb8092/numba-0.64.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69440a8e8bc1a81028446f06b363e28635aa67bd51b1e498023f03b812e0ce68", size = 2683418 },
1384
+ { url = "https://files.pythonhosted.org/packages/9b/89/1a74ea99b180b7a5587b0301ed1b183a2937c4b4b67f7994689b5d36fc34/numba-0.64.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13721011f693ba558b8dd4e4db7f2640462bba1b855bdc804be45bbeb55031a", size = 3804087 },
1385
+ { url = "https://files.pythonhosted.org/packages/91/e1/583c647404b15f807410510fec1eb9b80cb8474165940b7749f026f21cbc/numba-0.64.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0b180b1133f2b5d8b3f09d96b6d7a9e51a7da5dda3c09e998b5bcfac85d222c", size = 3504309 },
1386
+ { url = "https://files.pythonhosted.org/packages/85/23/0fce5789b8a5035e7ace21216a468143f3144e02013252116616c58339aa/numba-0.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:e63dc94023b47894849b8b106db28ccb98b49d5498b98878fac1a38f83ac007a", size = 2752740 },
1387
+ { url = "https://files.pythonhosted.org/packages/52/80/2734de90f9300a6e2503b35ee50d9599926b90cbb7ac54f9e40074cd07f1/numba-0.64.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3bab2c872194dcd985f1153b70782ec0fbbe348fffef340264eacd3a76d59fd6", size = 2683392 },
1388
+ { url = "https://files.pythonhosted.org/packages/42/e8/14b5853ebefd5b37723ef365c5318a30ce0702d39057eaa8d7d76392859d/numba-0.64.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:703a246c60832cad231d2e73c1182f25bf3cc8b699759ec8fe58a2dbc689a70c", size = 3812245 },
1389
+ { url = "https://files.pythonhosted.org/packages/8a/a2/f60dc6c96d19b7185144265a5fbf01c14993d37ff4cd324b09d0212aa7ce/numba-0.64.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e2e49a7900ee971d32af7609adc0cfe6aa7477c6f6cccdf6d8138538cf7756f", size = 3511328 },
1390
+ { url = "https://files.pythonhosted.org/packages/9c/2a/fe7003ea7e7237ee7014f8eaeeb7b0d228a2db22572ca85bab2648cf52cb/numba-0.64.0-cp313-cp313-win_amd64.whl", hash = "sha256:396f43c3f77e78d7ec84cdfc6b04969c78f8f169351b3c4db814b97e7acf4245", size = 2752668 },
1391
+ { url = "https://files.pythonhosted.org/packages/3d/8a/77d26afe0988c592dd97cb8d4e80bfb3dfc7dbdacfca7d74a7c5c81dd8c2/numba-0.64.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f565d55eaeff382cbc86c63c8c610347453af3d1e7afb2b6569aac1c9b5c93ce", size = 2683590 },
1392
+ { url = "https://files.pythonhosted.org/packages/8e/4b/600b8b7cdbc7f9cebee9ea3d13bb70052a79baf28944024ffcb59f0712e3/numba-0.64.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9b55169b18892c783f85e9ad9e6f5297a6d12967e4414e6b71361086025ff0bb", size = 3781163 },
1393
+ { url = "https://files.pythonhosted.org/packages/ff/73/53f2d32bfa45b7175e9944f6b816d8c32840178c3eee9325033db5bf838e/numba-0.64.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:196bcafa02c9dd1707e068434f6d5cedde0feb787e3432f7f1f0e993cc336c4c", size = 3481172 },
1394
+ { url = "https://files.pythonhosted.org/packages/b5/00/aebd2f7f1e11e38814bb96e95a27580817a7b340608d3ac085fdbab83174/numba-0.64.0-cp314-cp314-win_amd64.whl", hash = "sha256:213e9acbe7f1c05090592e79020315c1749dd52517b90e94c517dca3f014d4a1", size = 2754700 },
1395
+ ]
1396
+
1338
1397
  [[package]]
1339
1398
  name = "numpy"
1340
1399
  version = "2.4.2"
@@ -2128,6 +2187,22 @@ wheels = [
2128
2187
  { url = "https://files.pythonhosted.org/packages/40/6d/b6ee155462a0156b94312bdd82d2b92ea56e909740045a87ccb98bf52405/pymdown_extensions-10.20.1-py3-none-any.whl", hash = "sha256:24af7feacbca56504b313b7b418c4f5e1317bb5fea60f03d57be7fcc40912aa0", size = 268768 },
2129
2188
  ]
2130
2189
 
2190
+ [[package]]
2191
+ name = "pynndescent"
2192
+ version = "0.6.0"
2193
+ source = { registry = "https://pypi.org/simple" }
2194
+ dependencies = [
2195
+ { name = "joblib" },
2196
+ { name = "llvmlite" },
2197
+ { name = "numba" },
2198
+ { name = "scikit-learn" },
2199
+ { name = "scipy" },
2200
+ ]
2201
+ sdist = { url = "https://files.pythonhosted.org/packages/4a/fb/7f58c397fb31666756457ee2ac4c0289ef2daad57f4ae4be8dec12f80b03/pynndescent-0.6.0.tar.gz", hash = "sha256:7ffde0fb5b400741e055a9f7d377e3702e02250616834231f6c209e39aac24f5", size = 2992987 }
2202
+ wheels = [
2203
+ { url = "https://files.pythonhosted.org/packages/b2/e6/94145d714402fd5ade00b5661f2d0ab981219e07f7db9bfa16786cdb9c04/pynndescent-0.6.0-py3-none-any.whl", hash = "sha256:dc8c74844e4c7f5cbd1e0cd6909da86fdc789e6ff4997336e344779c3d5538ef", size = 73511 },
2204
+ ]
2205
+
2131
2206
  [[package]]
2132
2207
  name = "pypika"
2133
2208
  version = "0.51.0"
@@ -2433,6 +2508,111 @@ wheels = [
2433
2508
  { url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753 },
2434
2509
  ]
2435
2510
 
2511
+ [[package]]
2512
+ name = "scikit-learn"
2513
+ version = "1.8.0"
2514
+ source = { registry = "https://pypi.org/simple" }
2515
+ dependencies = [
2516
+ { name = "joblib" },
2517
+ { name = "numpy" },
2518
+ { name = "scipy" },
2519
+ { name = "threadpoolctl" },
2520
+ ]
2521
+ sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585 }
2522
+ wheels = [
2523
+ { url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242 },
2524
+ { url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075 },
2525
+ { url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492 },
2526
+ { url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904 },
2527
+ { url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359 },
2528
+ { url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898 },
2529
+ { url = "https://files.pythonhosted.org/packages/03/aa/e22e0768512ce9255eba34775be2e85c2048da73da1193e841707f8f039c/scikit_learn-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d6ae97234d5d7079dc0040990a6f7aeb97cb7fa7e8945f1999a429b23569e0a", size = 8513770 },
2530
+ { url = "https://files.pythonhosted.org/packages/58/37/31b83b2594105f61a381fc74ca19e8780ee923be2d496fcd8d2e1147bd99/scikit_learn-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:edec98c5e7c128328124a029bceb09eda2d526997780fef8d65e9a69eead963e", size = 8044458 },
2531
+ { url = "https://files.pythonhosted.org/packages/2d/5a/3f1caed8765f33eabb723596666da4ebbf43d11e96550fb18bdec42b467b/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74b66d8689d52ed04c271e1329f0c61635bcaf5b926db9b12d58914cdc01fe57", size = 8610341 },
2532
+ { url = "https://files.pythonhosted.org/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fdf95767f989b0cfedb85f7ed8ca215d4be728031f56ff5a519ee1e3276dc2e", size = 8900022 },
2533
+ { url = "https://files.pythonhosted.org/packages/1c/f9/9b7563caf3ec8873e17a31401858efab6b39a882daf6c1bfa88879c0aa11/scikit_learn-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:2de443b9373b3b615aec1bb57f9baa6bb3a9bd093f1269ba95c17d870422b271", size = 7989409 },
2534
+ { url = "https://files.pythonhosted.org/packages/49/bd/1f4001503650e72c4f6009ac0c4413cb17d2d601cef6f71c0453da2732fc/scikit_learn-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:eddde82a035681427cbedded4e6eff5e57fa59216c2e3e90b10b19ab1d0a65c3", size = 7619760 },
2535
+ { url = "https://files.pythonhosted.org/packages/d2/7d/a630359fc9dcc95496588c8d8e3245cc8fd81980251079bc09c70d41d951/scikit_learn-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7cc267b6108f0a1499a734167282c00c4ebf61328566b55ef262d48e9849c735", size = 8826045 },
2536
+ { url = "https://files.pythonhosted.org/packages/cc/56/a0c86f6930cfcd1c7054a2bc417e26960bb88d32444fe7f71d5c2cfae891/scikit_learn-1.8.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:fe1c011a640a9f0791146011dfd3c7d9669785f9fed2b2a5f9e207536cf5c2fd", size = 8420324 },
2537
+ { url = "https://files.pythonhosted.org/packages/46/1e/05962ea1cebc1cf3876667ecb14c283ef755bf409993c5946ade3b77e303/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72358cce49465d140cc4e7792015bb1f0296a9742d5622c67e31399b75468b9e", size = 8680651 },
2538
+ { url = "https://files.pythonhosted.org/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045 },
2539
+ { url = "https://files.pythonhosted.org/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994 },
2540
+ { url = "https://files.pythonhosted.org/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518 },
2541
+ { url = "https://files.pythonhosted.org/packages/24/05/1af2c186174cc92dcab2233f327336058c077d38f6fe2aceb08e6ab4d509/scikit_learn-1.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c22a2da7a198c28dd1a6e1136f19c830beab7fdca5b3e5c8bba8394f8a5c45b3", size = 8528667 },
2542
+ { url = "https://files.pythonhosted.org/packages/a8/25/01c0af38fe969473fb292bba9dc2b8f9b451f3112ff242c647fee3d0dfe7/scikit_learn-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:6b595b07a03069a2b1740dc08c2299993850ea81cce4fe19b2421e0c970de6b7", size = 8066524 },
2543
+ { url = "https://files.pythonhosted.org/packages/be/ce/a0623350aa0b68647333940ee46fe45086c6060ec604874e38e9ab7d8e6c/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29ffc74089f3d5e87dfca4c2c8450f88bdc61b0fc6ed5d267f3988f19a1309f6", size = 8657133 },
2544
+ { url = "https://files.pythonhosted.org/packages/b8/cb/861b41341d6f1245e6ca80b1c1a8c4dfce43255b03df034429089ca2a2c5/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb65db5d7531bccf3a4f6bec3462223bea71384e2cda41da0f10b7c292b9e7c4", size = 8923223 },
2545
+ { url = "https://files.pythonhosted.org/packages/76/18/a8def8f91b18cd1ba6e05dbe02540168cb24d47e8dcf69e8d00b7da42a08/scikit_learn-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:56079a99c20d230e873ea40753102102734c5953366972a71d5cb39a32bc40c6", size = 8096518 },
2546
+ { url = "https://files.pythonhosted.org/packages/d1/77/482076a678458307f0deb44e29891d6022617b2a64c840c725495bee343f/scikit_learn-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3bad7565bc9cf37ce19a7c0d107742b320c1285df7aab1a6e2d28780df167242", size = 7754546 },
2547
+ { url = "https://files.pythonhosted.org/packages/2d/d1/ef294ca754826daa043b2a104e59960abfab4cf653891037d19dd5b6f3cf/scikit_learn-1.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4511be56637e46c25721e83d1a9cea9614e7badc7040c4d573d75fbe257d6fd7", size = 8848305 },
2548
+ { url = "https://files.pythonhosted.org/packages/5b/e2/b1f8b05138ee813b8e1a4149f2f0d289547e60851fd1bb268886915adbda/scikit_learn-1.8.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:a69525355a641bf8ef136a7fa447672fb54fe8d60cab5538d9eb7c6438543fb9", size = 8432257 },
2549
+ { url = "https://files.pythonhosted.org/packages/26/11/c32b2138a85dcb0c99f6afd13a70a951bfdff8a6ab42d8160522542fb647/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2656924ec73e5939c76ac4c8b026fc203b83d8900362eb2599d8aee80e4880f", size = 8678673 },
2550
+ { url = "https://files.pythonhosted.org/packages/c7/57/51f2384575bdec454f4fe4e7a919d696c9ebce914590abf3e52d47607ab8/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15fc3b5d19cc2be65404786857f2e13c70c83dd4782676dd6814e3b89dc8f5b9", size = 8922467 },
2551
+ { url = "https://files.pythonhosted.org/packages/35/4d/748c9e2872637a57981a04adc038dacaa16ba8ca887b23e34953f0b3f742/scikit_learn-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:00d6f1d66fbcf4eba6e356e1420d33cc06c70a45bb1363cd6f6a8e4ebbbdece2", size = 8774395 },
2552
+ { url = "https://files.pythonhosted.org/packages/60/22/d7b2ebe4704a5e50790ba089d5c2ae308ab6bb852719e6c3bd4f04c3a363/scikit_learn-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f28dd15c6bb0b66ba09728cf09fd8736c304be29409bd8445a080c1280619e8c", size = 8002647 },
2553
+ ]
2554
+
2555
+ [[package]]
2556
+ name = "scipy"
2557
+ version = "1.17.1"
2558
+ source = { registry = "https://pypi.org/simple" }
2559
+ dependencies = [
2560
+ { name = "numpy" },
2561
+ ]
2562
+ sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822 }
2563
+ wheels = [
2564
+ { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954 },
2565
+ { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662 },
2566
+ { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366 },
2567
+ { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017 },
2568
+ { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842 },
2569
+ { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890 },
2570
+ { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557 },
2571
+ { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856 },
2572
+ { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682 },
2573
+ { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340 },
2574
+ { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199 },
2575
+ { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001 },
2576
+ { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719 },
2577
+ { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595 },
2578
+ { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429 },
2579
+ { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952 },
2580
+ { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063 },
2581
+ { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449 },
2582
+ { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943 },
2583
+ { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621 },
2584
+ { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708 },
2585
+ { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135 },
2586
+ { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977 },
2587
+ { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601 },
2588
+ { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667 },
2589
+ { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159 },
2590
+ { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771 },
2591
+ { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910 },
2592
+ { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980 },
2593
+ { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543 },
2594
+ { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510 },
2595
+ { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131 },
2596
+ { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032 },
2597
+ { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766 },
2598
+ { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007 },
2599
+ { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333 },
2600
+ { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066 },
2601
+ { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763 },
2602
+ { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984 },
2603
+ { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877 },
2604
+ { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750 },
2605
+ { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858 },
2606
+ { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723 },
2607
+ { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098 },
2608
+ { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397 },
2609
+ { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163 },
2610
+ { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291 },
2611
+ { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317 },
2612
+ { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327 },
2613
+ { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165 },
2614
+ ]
2615
+
2436
2616
  [[package]]
2437
2617
  name = "shellingham"
2438
2618
  version = "1.5.4"
@@ -2534,6 +2714,15 @@ wheels = [
2534
2714
  { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248 },
2535
2715
  ]
2536
2716
 
2717
+ [[package]]
2718
+ name = "threadpoolctl"
2719
+ version = "3.6.0"
2720
+ source = { registry = "https://pypi.org/simple" }
2721
+ sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274 }
2722
+ wheels = [
2723
+ { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638 },
2724
+ ]
2725
+
2537
2726
  [[package]]
2538
2727
  name = "tokenizers"
2539
2728
  version = "0.22.2"
@@ -2672,6 +2861,23 @@ wheels = [
2672
2861
  { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521 },
2673
2862
  ]
2674
2863
 
2864
+ [[package]]
2865
+ name = "umap-learn"
2866
+ version = "0.5.11"
2867
+ source = { registry = "https://pypi.org/simple" }
2868
+ dependencies = [
2869
+ { name = "numba" },
2870
+ { name = "numpy" },
2871
+ { name = "pynndescent" },
2872
+ { name = "scikit-learn" },
2873
+ { name = "scipy" },
2874
+ { name = "tqdm" },
2875
+ ]
2876
+ sdist = { url = "https://files.pythonhosted.org/packages/94/9a/a1e4a257a9aa979dac4f6d5781dac929cbb0949959e2003ed82657d10b0f/umap_learn-0.5.11.tar.gz", hash = "sha256:31566ffd495fbf05d7ab3efcba703861c0f5e6fc6998a838d0e2becdd00e54f5", size = 96409 }
2877
+ wheels = [
2878
+ { url = "https://files.pythonhosted.org/packages/43/d2/fcf7192dd1cd8c090b6cfd53fa223c4fb2887a17c47e06bc356d44f40dfb/umap_learn-0.5.11-py3-none-any.whl", hash = "sha256:cb17adbde9d544ba79481b3ab4d81ac222e940f3d9219307bea6044f869af3cc", size = 90890 },
2879
+ ]
2880
+
2675
2881
  [[package]]
2676
2882
  name = "urllib3"
2677
2883
  version = "2.6.3"