"""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", ]