import math import site import sys from pathlib import Path _VENDOR_ROOT = Path(__file__).resolve().parent.parent / ".vendor" for _vendor_path in (_VENDOR_ROOT / "python", _VENDOR_ROOT / "sitepkgs"): if _vendor_path.exists(): vendor_text = str(_vendor_path) if vendor_text not in sys.path: sys.path.insert(0, vendor_text) try: import numpy as np except ModuleNotFoundError: user_site = site.getusersitepackages() if user_site and user_site not in sys.path: sys.path.append(user_site) try: import numpy as np except ModuleNotFoundError: np = None if np is not None and not hasattr(np, "asarray"): np = None Matrix = list[list[float]] Vector = list[float] SUMPROD = getattr(math, "sumprod", None) def zeros(rows: int, cols: int) -> Matrix: return [[0.0 for _ in range(cols)] for _ in range(rows)] def zeros_vector(size: int) -> Vector: return [0.0 for _ in range(size)] def identity(size: int) -> Matrix: matrix = zeros(size, size) for index in range(size): matrix[index][index] = 1.0 return matrix def copy_matrix(matrix: Matrix) -> Matrix: return [row[:] for row in matrix] def transpose(matrix: Matrix) -> Matrix: if not matrix: return [] if np is not None: return np.asarray(matrix, dtype=np.float64).T.tolist() return [list(column) for column in zip(*matrix)] def matvec(matrix: Matrix, vector: Vector) -> Vector: if np is not None: return (np.asarray(matrix, dtype=np.float64) @ np.asarray(vector, dtype=np.float64)).tolist() if SUMPROD is not None: return [SUMPROD(row, vector) for row in matrix] return [sum(value * vector[idx] for idx, value in enumerate(row)) for row in matrix] def matmul(left: Matrix, right: Matrix) -> Matrix: if not left or not right: return [] if np is not None: return (np.asarray(left, dtype=np.float64) @ np.asarray(right, dtype=np.float64)).tolist() right_t = transpose(right) if SUMPROD is not None: return [[SUMPROD(row, column) for column in right_t] for row in left] return [ [sum(a * b for a, b in zip(row, column)) for column in right_t] for row in left ] def add_matrices(left: Matrix, right: Matrix) -> Matrix: return [ [left[row][col] + right[row][col] for col in range(len(left[row]))] for row in range(len(left)) ] def subtract_matrices(left: Matrix, right: Matrix) -> Matrix: return [ [left[row][col] - right[row][col] for col in range(len(left[row]))] for row in range(len(left)) ] def scale_matrix(matrix: Matrix, scalar: float) -> Matrix: return [[scalar * value for value in row] for row in matrix] def dot(left: Vector, right: Vector) -> float: if np is not None: return float(np.dot(np.asarray(left, dtype=np.float64), np.asarray(right, dtype=np.float64))) if SUMPROD is not None: return SUMPROD(left, right) return sum(a * b for a, b in zip(left, right)) def norm(vector: Vector) -> float: return math.sqrt(dot(vector, vector)) def outer(left: Vector, right: Vector) -> Matrix: if np is not None: return np.outer(np.asarray(left, dtype=np.float64), np.asarray(right, dtype=np.float64)).tolist() return [[a * b for b in right] for a in left] def mean(values: Vector) -> float: return sum(values) / len(values) if values else 0.0 def trace(matrix: Matrix) -> float: return sum(matrix[index][index] for index in range(min(len(matrix), len(matrix[0])))) def covariance_matrix(samples: list[Vector]) -> Matrix: if not samples: return [] if np is not None: sample_array = np.asarray(samples, dtype=np.float64) centered = sample_array - sample_array.mean(axis=0, keepdims=True) denominator = max(len(samples) - 1, 1) return ((centered.T @ centered) / denominator).tolist() feature_count = len(samples[0]) sample_count = len(samples) means = [ sum(sample[feature] for sample in samples) / sample_count for feature in range(feature_count) ] covariance = zeros(feature_count, feature_count) for sample in samples: centered = [sample[index] - means[index] for index in range(feature_count)] for row in range(feature_count): for col in range(feature_count): covariance[row][col] += centered[row] * centered[col] denominator = max(sample_count - 1, 1) return scale_matrix(covariance, 1.0 / denominator) def solve_linear_system(matrix: Matrix, vector: Vector) -> Vector: if np is not None: return np.linalg.solve( np.asarray(matrix, dtype=np.float64), np.asarray(vector, dtype=np.float64), ).tolist() size = len(matrix) augmented = [matrix[row][:] + [vector[row]] for row in range(size)] for pivot_index in range(size): pivot_row = max( range(pivot_index, size), key=lambda row_index: abs(augmented[row_index][pivot_index]), ) augmented[pivot_index], augmented[pivot_row] = augmented[pivot_row], augmented[pivot_index] pivot_value = augmented[pivot_index][pivot_index] if abs(pivot_value) < 1e-12: raise ValueError("Singular matrix encountered while solving linear system.") inverse_pivot = 1.0 / pivot_value augmented[pivot_index] = [value * inverse_pivot for value in augmented[pivot_index]] for row_index in range(size): if row_index == pivot_index: continue factor = augmented[row_index][pivot_index] augmented[row_index] = [ augmented[row_index][col] - factor * augmented[pivot_index][col] for col in range(size + 1) ] return [augmented[row][-1] for row in range(size)] def invert_matrix(matrix: Matrix) -> Matrix: if np is not None: return np.linalg.inv(np.asarray(matrix, dtype=np.float64)).tolist() size = len(matrix) inverse_columns = [] for basis_index in range(size): basis_vector = [0.0 for _ in range(size)] basis_vector[basis_index] = 1.0 inverse_columns.append(solve_linear_system(matrix, basis_vector)) return transpose(inverse_columns) def dominant_eigenpair_symmetric( matrix: Matrix, max_iterations: int = 64, tolerance: float = 1e-10, ) -> tuple[float, Vector]: size = len(matrix) if size == 0: return 0.0, [] if np is not None: values, vectors = np.linalg.eigh(np.asarray(matrix, dtype=np.float64)) index = int(np.argmax(values)) eigenvalue = float(values[index]) if eigenvalue <= tolerance: return 0.0, zeros_vector(size) return eigenvalue, vectors[:, index].astype(float).tolist() vector = [1.0 / math.sqrt(size) for _ in range(size)] for _ in range(max_iterations): next_vector = matvec(matrix, vector) next_norm = norm(next_vector) if next_norm < tolerance: return 0.0, zeros_vector(size) next_vector = [value / next_norm for value in next_vector] delta = max(abs(a - b) for a, b in zip(vector, next_vector)) vector = next_vector if delta < tolerance: break eigenvalue = dot(vector, matvec(matrix, vector)) return eigenvalue, vector def top_k_eigenpairs_symmetric(matrix: Matrix, k: int) -> list[tuple[float, Vector]]: if np is not None and matrix: values, vectors = np.linalg.eigh(np.asarray(matrix, dtype=np.float64)) ranked = sorted( ( (float(values[index]), vectors[:, index].astype(float).tolist()) for index in range(len(values)) if float(values[index]) > 1e-9 ), key=lambda item: item[0], reverse=True, ) return ranked[: min(k, len(ranked))] working = copy_matrix(matrix) eigenpairs: list[tuple[float, Vector]] = [] for _ in range(min(k, len(working))): eigenvalue, eigenvector = dominant_eigenpair_symmetric(working) if eigenvalue <= 1e-9 or not eigenvector: break eigenpairs.append((eigenvalue, eigenvector)) deflation = scale_matrix(outer(eigenvector, eigenvector), eigenvalue) working = subtract_matrices(working, deflation) return eigenpairs def softmax(logits: Vector) -> Vector: if not logits: return [] if np is not None: values = np.asarray(logits, dtype=np.float64) shifted = np.exp(values - values.max()) total = float(shifted.sum()) if total == 0.0: return [1.0 / len(logits) for _ in logits] return (shifted / total).tolist() max_logit = max(logits) shifted = [math.exp(logit - max_logit) for logit in logits] total = sum(shifted) if total == 0.0: return [1.0 / len(logits) for _ in logits] return [value / total for value in shifted]