diskpack 0.5.0__tar.gz → 0.6.0__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.
- {diskpack-0.5.0/src/diskpack.egg-info → diskpack-0.6.0}/PKG-INFO +1 -1
- {diskpack-0.5.0 → diskpack-0.6.0}/pyproject.toml +1 -1
- {diskpack-0.5.0 → diskpack-0.6.0}/src/diskpack/packer.py +51 -5
- {diskpack-0.5.0 → diskpack-0.6.0/src/diskpack.egg-info}/PKG-INFO +1 -1
- {diskpack-0.5.0 → diskpack-0.6.0}/LICENSE +0 -0
- {diskpack-0.5.0 → diskpack-0.6.0}/README.md +0 -0
- {diskpack-0.5.0 → diskpack-0.6.0}/setup.cfg +0 -0
- {diskpack-0.5.0 → diskpack-0.6.0}/src/diskpack/__init__.py +0 -0
- {diskpack-0.5.0 → diskpack-0.6.0}/src/diskpack.egg-info/SOURCES.txt +0 -0
- {diskpack-0.5.0 → diskpack-0.6.0}/src/diskpack.egg-info/dependency_links.txt +0 -0
- {diskpack-0.5.0 → diskpack-0.6.0}/src/diskpack.egg-info/requires.txt +0 -0
- {diskpack-0.5.0 → diskpack-0.6.0}/src/diskpack.egg-info/top_level.txt +0 -0
- {diskpack-0.5.0 → diskpack-0.6.0}/tests/tests.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "diskpack"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.0"
|
|
8
8
|
authors = [{ name="James Kelly", email="mrkellyjam@gmail.com" }]
|
|
9
9
|
description = "A high-performance vectorized circle packer with spatial hashing."
|
|
10
10
|
readme = "README.md"
|
|
@@ -8,6 +8,9 @@ Point = np.ndarray
|
|
|
8
8
|
GridKey = Tuple[int, int]
|
|
9
9
|
Circle = Tuple[float, float, float]
|
|
10
10
|
|
|
11
|
+
# Threshold for switching between vectorized and spatial index approaches
|
|
12
|
+
VECTORIZED_THRESHOLD = 300
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
@dataclass
|
|
13
16
|
class PackingConfig:
|
|
@@ -192,6 +195,27 @@ class CirclePacker:
|
|
|
192
195
|
origin=self.geometry.min_coords,
|
|
193
196
|
mega_threshold=self.config.mega_circle_threshold
|
|
194
197
|
)
|
|
198
|
+
|
|
199
|
+
# Cache for numpy arrays (avoid repeated conversion)
|
|
200
|
+
self._centers_arr: Optional[np.ndarray] = None
|
|
201
|
+
self._radii_arr: Optional[np.ndarray] = None
|
|
202
|
+
self._cache_valid = False
|
|
203
|
+
|
|
204
|
+
def _invalidate_cache(self) -> None:
|
|
205
|
+
"""Mark the numpy array cache as needing refresh."""
|
|
206
|
+
self._cache_valid = False
|
|
207
|
+
|
|
208
|
+
def _get_arrays(self) -> Tuple[np.ndarray, np.ndarray]:
|
|
209
|
+
"""Get cached numpy arrays of centers and radii."""
|
|
210
|
+
if not self._cache_valid or self._centers_arr is None:
|
|
211
|
+
if len(self.centers) > 0:
|
|
212
|
+
self._centers_arr = np.array(self.centers)
|
|
213
|
+
self._radii_arr = np.array(self.radii)
|
|
214
|
+
else:
|
|
215
|
+
self._centers_arr = np.empty((0, 2))
|
|
216
|
+
self._radii_arr = np.empty(0)
|
|
217
|
+
self._cache_valid = True
|
|
218
|
+
return self._centers_arr, self._radii_arr
|
|
195
219
|
|
|
196
220
|
def _sample_candidate_points(self, count: int) -> np.ndarray:
|
|
197
221
|
points = np.random.uniform(
|
|
@@ -209,16 +233,37 @@ class CirclePacker:
|
|
|
209
233
|
return max_radius - self.config.padding
|
|
210
234
|
|
|
211
235
|
def _compute_max_radii_batch(self, points: np.ndarray) -> np.ndarray:
|
|
212
|
-
"""
|
|
236
|
+
"""
|
|
237
|
+
Vectorized max radius computation for multiple points.
|
|
238
|
+
|
|
239
|
+
Uses hybrid approach:
|
|
240
|
+
- Fully vectorized numpy for small circle counts (faster due to no Python loop)
|
|
241
|
+
- Spatial index for large circle counts (faster due to fewer distance calculations)
|
|
242
|
+
"""
|
|
213
243
|
if len(points) == 0:
|
|
214
244
|
return np.array([])
|
|
215
245
|
|
|
246
|
+
# Boundary distances (fully vectorized)
|
|
216
247
|
max_radii = self.geometry.distances_to_boundary_batch(points)
|
|
217
248
|
|
|
218
|
-
if len(self.centers)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
249
|
+
if len(self.centers) == 0:
|
|
250
|
+
return max_radii - self.config.padding
|
|
251
|
+
|
|
252
|
+
centers_arr, radii_arr = self._get_arrays()
|
|
253
|
+
|
|
254
|
+
if len(self.centers) < VECTORIZED_THRESHOLD:
|
|
255
|
+
# Fully vectorized approach for small circle counts
|
|
256
|
+
# Compute distance from each point to each circle: (n_points, n_circles)
|
|
257
|
+
dists = np.linalg.norm(
|
|
258
|
+
points[:, np.newaxis, :] - centers_arr[np.newaxis, :, :],
|
|
259
|
+
axis=2
|
|
260
|
+
) - radii_arr
|
|
261
|
+
|
|
262
|
+
# Min distance to any circle for each point
|
|
263
|
+
min_circle_dists = np.min(dists, axis=1)
|
|
264
|
+
max_radii = np.minimum(max_radii, min_circle_dists)
|
|
265
|
+
else:
|
|
266
|
+
# Spatial index approach for large circle counts
|
|
222
267
|
for i, point in enumerate(points):
|
|
223
268
|
indices = list(self.spatial_index.get_nearby_indices(point))
|
|
224
269
|
if indices:
|
|
@@ -256,6 +301,7 @@ class CirclePacker:
|
|
|
256
301
|
self.centers.append(center)
|
|
257
302
|
self.radii.append(radius)
|
|
258
303
|
self.spatial_index.add_circle(idx, center, radius)
|
|
304
|
+
self._invalidate_cache()
|
|
259
305
|
|
|
260
306
|
def _generate_hex_grid(self, radius: float) -> np.ndarray:
|
|
261
307
|
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|