Spaces:
Paused
Paused
File size: 17,962 Bytes
5e0b58b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 | """5D spatial indexing for Brain-5D neural networks.
This module provides coordinate manipulation and spatial indexing for
the 5-dimensional neuron space used in Brain-5D. It supports:
- Packing/unpacking 5D coordinates into 64-bit integers
- Conversion between linear indices and 5D coordinates
- Euclidean and weighted distance calculations
- Neighbor iteration within a radius
- Boundary coordinate generation
- LRU-cached offset computation for performance
The 5D space is organized as (x, y, z, d4, d5) where:
- x, y, z: spatial dimensions (0-255)
- d4, d5: functional dimensions (0-255)
"""
from __future__ import annotations
import math
from collections.abc import Iterable, Iterator
from functools import lru_cache
from itertools import product
# ============================================================================
# Type Aliases
# ============================================================================
Coord5D = tuple[int, int, int, int, int]
"""Type alias for a 5D coordinate."""
Dim5D = tuple[int, int, int, int, int]
"""Type alias for 5D dimensions."""
# ============================================================================
# Constants
# ============================================================================
BITS_PER_DIM: int = 8
"""Number of bits allocated per dimension in packed representation."""
MASK: int = 0xFF
"""Bitmask for extracting a single dimension (8 bits)."""
SHIFTS: tuple[int, int, int, int, int] = (0, 8, 16, 24, 32)
"""Bit shifts for each dimension in packed representation."""
DIM_NAMES: dict[str, int] = {
"x": 0,
"y": 1,
"z": 2,
"d4": 3,
"d5": 4,
}
"""Mapping from dimension names to indices."""
DIM_INDICES: dict[int, str] = {v: k for k, v in DIM_NAMES.items()}
"""Mapping from dimension indices to names."""
MAX_COORD: int = 255
"""Maximum coordinate value (8-bit range)."""
# ============================================================================
# Coordinate Validation
# ============================================================================
def validate_coord(coord: Coord5D) -> None:
"""Validate that all coordinates are within the 0-255 range.
Args:
coord: 5D coordinate to validate.
Raises:
ValueError: If any coordinate is outside the valid range.
"""
for i, c in enumerate(coord):
if c < 0 or c > MAX_COORD:
raise ValueError(
f"Coordinate {c} at dimension {i} must be between 0 and {MAX_COORD}"
)
def validate_dims(dims: Dim5D) -> None:
"""Validate that all dimensions are positive.
Args:
dims: 5D dimensions to validate.
Raises:
ValueError: If any dimension is <= 0.
"""
for i, d in enumerate(dims):
if d <= 0:
raise ValueError(f"Dimension {i} must be positive, got {d}")
def validate_coord_in_dims(coord: Coord5D, dims: Dim5D) -> None:
"""Validate that a coordinate is within the given dimensions.
Args:
coord: 5D coordinate to validate.
dims: 5D dimensions.
Raises:
ValueError: If the coordinate is outside the dimensions.
"""
validate_dims(dims)
for i, (c, d) in enumerate(zip(coord, dims)):
if c < 0 or c >= d:
raise ValueError(f"Coordinate {c} at dimension {i} outside dimension {d}")
def is_valid_coord(coord: Coord5D) -> bool:
"""Check if a coordinate is valid (all coordinates in 0-255 range).
Args:
coord: 5D coordinate to check.
Returns:
True if valid, False otherwise.
"""
try:
validate_coord(coord)
return True
except ValueError:
return False
def is_valid_coord_in_dims(coord: Coord5D, dims: Dim5D) -> bool:
"""Check if a coordinate is valid within the given dimensions.
Args:
coord: 5D coordinate to check.
dims: 5D dimensions.
Returns:
True if valid, False otherwise.
"""
try:
validate_coord_in_dims(coord, dims)
return True
except ValueError:
return False
# ============================================================================
# Packing / Unpacking
# ============================================================================
def pack_coords(x: int, y: int, z: int, d4: int, d5: int) -> int:
"""Pack five 8-bit coordinates into a single 64-bit integer.
This is useful for storing coordinates as keys in dictionaries
or for efficient hashing.
Args:
x: X coordinate (0-255).
y: Y coordinate (0-255).
z: Z coordinate (0-255).
d4: Fourth dimension coordinate (0-255).
d5: Fifth dimension coordinate (0-255).
Returns:
A 64-bit integer with packed coordinates.
Raises:
ValueError: If any coordinate is outside the 0-255 range.
Example:
>>> packed = pack_coords(1, 2, 3, 4, 5)
>>> unpack_coords(packed)
(1, 2, 3, 4, 5)
"""
coords = (x, y, z, d4, d5)
for i, c in enumerate(coords):
if c < 0 or c > MAX_COORD:
raise ValueError(
f"Coordinate {c} at dimension {i} must be between 0 and {MAX_COORD}"
)
return (
(x << SHIFTS[0])
| (y << SHIFTS[1])
| (z << SHIFTS[2])
| (d4 << SHIFTS[3])
| (d5 << SHIFTS[4])
)
def unpack_coords(index: int) -> Coord5D:
"""Unpack a 64-bit integer into five 8-bit coordinates.
Args:
index: Packed 64-bit integer containing 5D coordinates.
Returns:
A 5D coordinate tuple (x, y, z, d4, d5).
Example:
>>> pack = pack_coords(1, 2, 3, 4, 5)
>>> unpack_coords(pack)
(1, 2, 3, 4, 5)
"""
return (
(index >> SHIFTS[0]) & MASK,
(index >> SHIFTS[1]) & MASK,
(index >> SHIFTS[2]) & MASK,
(index >> SHIFTS[3]) & MASK,
(index >> SHIFTS[4]) & MASK,
)
# ============================================================================
# Linear Index Conversion
# ============================================================================
def coords_to_linear(coord: Coord5D, dims: Dim5D) -> int:
"""Convert 5D coordinates to a linear index (row-major order).
The linear index is computed as:
(((x * dims[1] + y) * dims[2] + z) * dims[3] + d4) * dims[4] + d5
Args:
coord: 5D coordinate to convert.
dims: 5D dimensions (must all be positive).
Returns:
Linear index in [0, product(dims)).
Raises:
ValueError: If dimensions are not positive or coordinate is out of bounds.
Example:
>>> dims = (10, 10, 10, 10, 10)
>>> coords_to_linear((1, 2, 3, 4, 5), dims)
# Returns a unique index for that coordinate
"""
validate_dims(dims)
validate_coord_in_dims(coord, dims)
x, y, z, d4, d5 = coord
return (((x * dims[1] + y) * dims[2] + z) * dims[3] + d4) * dims[4] + d5
def linear_to_5d(index: int, dims: Dim5D) -> Coord5D:
"""Convert a linear index back to 5D coordinates (row-major order).
This is the inverse of coords_to_linear.
Args:
index: Linear index in [0, product(dims)).
dims: 5D dimensions (must all be positive).
Returns:
The corresponding 5D coordinate.
Raises:
ValueError: If dimensions are not positive or index is out of bounds.
Example:
>>> dims = (10, 10, 10, 10, 10)
>>> linear_to_5d(12345, dims)
# Returns the original coordinate
"""
validate_dims(dims)
total = 1
for d in dims:
total *= d
if index < 0 or index >= total:
raise ValueError(f"Linear index {index} outside [0, {total})")
# Decode in reverse order (row-major)
idx = index
d5 = idx % dims[4]
idx //= dims[4]
d4 = idx % dims[3]
idx //= dims[3]
z = idx % dims[2]
idx //= dims[2]
y = idx % dims[1]
idx //= dims[1]
x = idx % dims[0]
return (x, y, z, d4, d5)
def linear_to_coord(index: int, dims: Dim5D) -> Coord5D:
"""Alias for linear_to_5d."""
return linear_to_5d(index, dims)
# ============================================================================
# Distance Functions
# ============================================================================
def euclidean_distance_5d(a: Coord5D, b: Coord5D) -> float:
"""Calculate the Euclidean distance between two 5D coordinates.
Args:
a: First 5D coordinate.
b: Second 5D coordinate.
Returns:
The Euclidean distance (sqrt of sum of squared differences).
"""
return math.sqrt(sum((ai - bi) ** 2 for ai, bi in zip(a, b)))
def distance_5d(a: Coord5D, b: Coord5D) -> float:
"""Alias for euclidean_distance_5d."""
return euclidean_distance_5d(a, b)
def weighted_distance_5d(
a: Coord5D,
b: Coord5D,
weights: tuple[float, float, float, float, float],
) -> float:
"""Calculate weighted Euclidean distance between two 5D coordinates.
This allows different importance to be assigned to each dimension.
Args:
a: First 5D coordinate.
b: Second 5D coordinate.
weights: Weight for each dimension (x, y, z, d4, d5).
Returns:
The weighted Euclidean distance.
Example:
>>> a = (1, 2, 3, 4, 5)
>>> b = (2, 3, 4, 5, 6)
>>> weights = (1.0, 1.0, 1.0, 0.5, 0.5)
>>> weighted_distance_5d(a, b, weights)
"""
return math.sqrt(
sum(weights[i] * (ai - bi) ** 2 for i, (ai, bi) in enumerate(zip(a, b)))
)
def chebyshev_distance_5d(a: Coord5D, b: Coord5D) -> float:
"""Calculate the Chebyshev distance between two 5D coordinates.
Chebyshev distance is the maximum absolute difference along any dimension.
Args:
a: First 5D coordinate.
b: Second 5D coordinate.
Returns:
The Chebyshev distance.
"""
return float(max(abs(ai - bi) for ai, bi in zip(a, b)))
def manhattan_distance_5d(a: Coord5D, b: Coord5D) -> float:
"""Calculate the Manhattan distance between two 5D coordinates.
Manhattan distance is the sum of absolute differences along each dimension.
Args:
a: First 5D coordinate.
b: Second 5D coordinate.
Returns:
The Manhattan distance.
"""
return float(sum(abs(ai - bi) for ai, bi in zip(a, b)))
# ============================================================================
# Neighbor Generation
# ============================================================================
@lru_cache(maxsize=32)
def neighbour_offsets(radius: float) -> tuple[Coord5D, ...]:
"""Generate all 5D offset vectors within a given radius.
Results are cached for performance.
Args:
radius: Maximum distance from origin (inclusive).
Returns:
A tuple of 5D offset vectors (excluding the zero vector).
Note:
Only integer offsets are considered. The radius is effectively
the Chebyshev radius (max coordinate difference).
"""
if radius <= 0:
return ()
r = int(math.ceil(radius))
offsets: list[Coord5D] = []
for off in product(range(-r, r + 1), repeat=5):
if off == (0, 0, 0, 0, 0):
continue
# Calculate squared distance directly from the offset
sq_dist = sum(v * v for v in off)
if math.sqrt(sq_dist) <= radius:
offsets.append((off[0], off[1], off[2], off[3], off[4]))
return tuple(offsets)
def iter_neighbour_coords(
coord: Coord5D,
dimensions: Dim5D,
radius: float,
include_self: bool = False,
) -> Iterator[Coord5D]:
"""Iterate over all neighboring coordinates within a radius.
Args:
coord: Center coordinate.
dimensions: 5D dimensions for bounds checking.
radius: Search radius.
include_self: Whether to include the center coordinate.
Yields:
Valid neighboring coordinates within the radius and dimensions.
Example:
>>> center = (5, 5, 5, 5, 5)
>>> dims = (10, 10, 10, 10, 10)
>>> for neighbor in iter_neighbour_coords(center, dims, 2.0):
... print(neighbor)
"""
validate_dims(dimensions)
validate_coord_in_dims(coord, dimensions)
for offset in neighbour_offsets(radius):
candidate = tuple(c + oc for c, oc in zip(coord, offset))
if all(0 <= candidate[i] < dimensions[i] for i in range(5)):
yield (candidate[0], candidate[1], candidate[2], candidate[3], candidate[4])
if include_self:
yield coord
def iter_linear_neighbours(
center_idx: int,
dims: Dim5D,
radius: float,
include_self: bool = False,
) -> Iterator[int]:
"""Iterate over linear indices of neighbors within a radius.
Args:
center_idx: Linear index of the center coordinate.
dims: 5D dimensions.
radius: Search radius.
include_self: Whether to include the center index.
Yields:
Linear indices of neighboring coordinates.
Example:
>>> for idx in iter_linear_neighbours(12345, (10,10,10,10,10), 2.0):
... print(idx)
"""
center = linear_to_5d(center_idx, dims)
for coord in iter_neighbour_coords(center, dims, radius, include_self):
yield coords_to_linear(coord, dims)
def neighbour_count(dims: Dim5D, radius: float) -> int:
"""Count the number of neighbors within a radius for a given dimension.
Note: This is an upper bound estimate, as it doesn't account for boundary
effects (coordinates at the edges have fewer neighbors).
Args:
dims: 5D dimensions.
radius: Search radius.
Returns:
Maximum number of neighbors within the radius.
"""
offsets = neighbour_offsets(radius)
return len(offsets)
# ============================================================================
# Boundary Coordinates
# ============================================================================
def make_boundary_coord(
dims: Dim5D,
dimension: str,
value: int,
) -> Coord5D:
"""Create a coordinate on a specific dimension boundary.
This is used for defining input and output layers on specific
dimensions of the 5D space.
Args:
dims: 5D dimensions.
dimension: Name of the dimension ('x', 'y', 'z', 'd4', 'd5').
value: Coordinate value on that dimension.
Returns:
A 5D coordinate with all other dimensions set to 0.
Raises:
ValueError: If the dimension name is unknown or the value is out of bounds.
Example:
>>> dims = (10, 10, 10, 10, 10)
>>> make_boundary_coord(dims, 'x', 5)
(5, 0, 0, 0, 0)
"""
if dimension not in DIM_NAMES:
raise ValueError(
f"Unknown dimension: {dimension}. "
f"Valid dimensions: {', '.join(DIM_NAMES.keys())}"
)
idx = DIM_NAMES[dimension]
if value < 0 or value >= dims[idx]:
raise ValueError(
f"Value {value} outside dimension {dimension} (0-{dims[idx]-1})"
)
coord = [0, 0, 0, 0, 0]
coord[idx] = value
return (coord[0], coord[1], coord[2], coord[3], coord[4])
def get_dimension_name(index: int) -> str | None:
"""Get the name of a dimension by its index.
Args:
index: Dimension index (0-4).
Returns:
Dimension name, or None if index is invalid.
"""
return DIM_INDICES.get(index)
def get_dimension_index(name: str) -> int | None:
"""Get the index of a dimension by its name.
Args:
name: Dimension name ('x', 'y', 'z', 'd4', 'd5').
Returns:
Dimension index, or None if name is invalid.
"""
return DIM_NAMES.get(name)
# ============================================================================
# Utility Functions
# ============================================================================
def total_cells(dims: Dim5D) -> int:
"""Calculate the total number of cells in the 5D grid.
Args:
dims: 5D dimensions.
Returns:
Product of all dimensions.
"""
total = 1
for d in dims:
total *= d
return total
def linear_to_coords_batch(
indices: Iterable[int],
dims: Dim5D,
) -> Iterator[Coord5D]:
"""Convert multiple linear indices to coordinates.
Args:
indices: Iterable of linear indices.
dims: 5D dimensions.
Yields:
5D coordinates for each index.
"""
for idx in indices:
yield linear_to_5d(idx, dims)
def coords_to_linear_batch(
coords: Iterable[Coord5D],
dims: Dim5D,
) -> Iterator[int]:
"""Convert multiple coordinates to linear indices.
Args:
coords: Iterable of 5D coordinates.
dims: 5D dimensions.
Yields:
Linear indices for each coordinate.
"""
for coord in coords:
yield coords_to_linear(coord, dims)
# ============================================================================
# Module Exports
# ============================================================================
__all__ = [
# Type aliases
"Coord5D",
"Dim5D",
# Constants
"BITS_PER_DIM",
"MASK",
"SHIFTS",
"DIM_NAMES",
"DIM_INDICES",
"MAX_COORD",
# Validation
"validate_coord",
"validate_dims",
"validate_coord_in_dims",
"is_valid_coord",
"is_valid_coord_in_dims",
# Packing
"pack_coords",
"unpack_coords",
# Linear conversion
"coords_to_linear",
"linear_to_5d",
"linear_to_coord",
"linear_to_coords_batch",
"coords_to_linear_batch",
# Distance
"euclidean_distance_5d",
"distance_5d",
"weighted_distance_5d",
"chebyshev_distance_5d",
"manhattan_distance_5d",
# Neighbors
"neighbour_offsets",
"iter_neighbour_coords",
"iter_linear_neighbours",
"neighbour_count",
# Boundary
"make_boundary_coord",
"get_dimension_name",
"get_dimension_index",
# Utilities
"total_cells",
]
|