from stdint import * import stdlib import string import vipermath import stdio import t, c import memhub # ============================================================ # Constants # ============================================================ MAX_NDIM: t.CDefine = 4 float64: t.CTypedef = t.CDouble float32: t.CTypedef = t.CFloat int64: t.CTypedef = t.CLong int32: t.CTypedef = t.CInt uint8: t.CTypedef = t.CUnsignedChar pi: t.CDefine = 3.14159265358979323846 e: t.CDefine = 2.71828182845904523536 # ============================================================ # ndarray - generic n-dimensional array (PEP 695) # # Element type T is a numeric type (t.CDouble / t.CFloat / t.CInt / ...). # T is erased to i8* at the IR level but each ndarray[T] instantiation # generates specialized code (data access uses T's load/store width). # # Default usage: ndarray[t.CDouble] for floating point. # ============================================================ @t.Object class ndarray[T]: data: T | t.CPtr shape: t.CArray[t.CSizeT, MAX_NDIM] strides: t.CArray[t.CSizeT, MAX_NDIM] ndim: t.CInt size: t.CSizeT owns_data: t.CInt pool: memhub.MemManager | t.CPtr def __new__(self, pool: memhub.MemManager | t.CPtr, n: t.CSizeT): self.pool = pool self.data = pool.alloc(n * T.__sizeof__()) if self.data: memset(self.data, 0, n * T.__sizeof__()) self.ndim = 1 self.size = n self.shape[0] = n self.strides[0] = 1 self.owns_data = 1 def __add__(self, other: ndarray[T] | t.CPtr) -> ndarray[T] | t.CPtr: result: ndarray[T] | t.CPtr = _empty_like(self) if not result: return None i: t.CSizeT = 0 while i < self.size: result.data[i] = self.data[i] + other.data[i] i += 1 return result def __sub__(self, other: ndarray[T] | t.CPtr) -> ndarray[T] | t.CPtr: result: ndarray[T] | t.CPtr = _empty_like(self) if not result: return None i: t.CSizeT = 0 while i < self.size: result.data[i] = self.data[i] - other.data[i] i += 1 return result def __mul__(self, other: ndarray[T] | t.CPtr) -> ndarray[T] | t.CPtr: result: ndarray[T] | t.CPtr = _empty_like(self) if not result: return None i: t.CSizeT = 0 while i < self.size: result.data[i] = self.data[i] * other.data[i] i += 1 return result def __truediv__(self, other: ndarray[T] | t.CPtr) -> ndarray[T] | t.CPtr: result: ndarray[T] | t.CPtr = _empty_like(self) if not result: return None i: t.CSizeT = 0 while i < self.size: if other.data[i] != T(0): result.data[i] = self.data[i] / other.data[i] else: result.data[i] = T(0) i += 1 return result def __floordiv__(self, other: ndarray[T] | t.CPtr) -> ndarray[T] | t.CPtr: result: ndarray[T] | t.CPtr = _empty_like(self) if not result: return None i: t.CSizeT = 0 while i < self.size: if other.data[i] != T(0): result.data[i] = vipermath.floor(self.data[i] / other.data[i]) else: result.data[i] = T(0) i += 1 return result def __mod__(self, other: ndarray[T] | t.CPtr) -> ndarray[T] | t.CPtr: result: ndarray[T] | t.CPtr = _empty_like(self) if not result: return None i: t.CSizeT = 0 while i < self.size: if other.data[i] != T(0): result.data[i] = vipermath.fmod(self.data[i], other.data[i]) else: result.data[i] = T(0) i += 1 return result def __neg__(self) -> ndarray[T] | t.CPtr: result: ndarray[T] | t.CPtr = _empty_like(self) if not result: return None i: t.CSizeT = 0 while i < self.size: result.data[i] = -self.data[i] i += 1 return result def __len__(self) -> t.CInt: if self.ndim > 0: return t.CInt(self.shape[0]) return 0 def at2d(self, row: t.CSizeT, col: t.CSizeT) -> T: return self.data[row * self.shape[1] + col] def set2d(self, row: t.CSizeT, col: t.CSizeT, val: T): self.data[row * self.shape[1] + col] = val def delete(self): if self.owns_data and self.data: self.pool.free(self.data) self.data = None self.pool.free(self) def fill(self, val: T): i: t.CSizeT = 0 while i < self.size: self.data[i] = val i += 1 def copy(self) -> ndarray[T] | t.CPtr: result: ndarray[T] | t.CPtr = _empty_like(self) if not result: return None memcpy(result.data, self.data, self.size * T.__sizeof__()) return result def reshape(self, new_shape: INTPTR, new_ndim: t.CInt) -> ndarray[T] | t.CPtr: new_size: t.CSizeT = 1 i: t.CInt = 0 for i in range(new_ndim): new_size *= t.CSizeT(new_shape[i]) if new_size != self.size: return None result: ndarray[T] | t.CPtr = _alloc_ndarray(self.pool) if not result: return None result.data = self.data result.pool = self.pool result.ndim = new_ndim result.size = self.size result.owns_data = 0 for i in range(new_ndim): result.shape[i] = t.CSizeT(new_shape[i]) _compute_strides(result) return result def sum(self) -> T: s: T = T(0) i: t.CSizeT = 0 while i < self.size: s += self.data[i] i += 1 return s def mean(self) -> T: if self.size == 0: return T(0) return self.sum() / T(self.size) def min(self) -> T: if self.size == 0: return T(0) m: T = self.data[0] i: t.CSizeT = 1 while i < self.size: if self.data[i] < m: m = self.data[i] i += 1 return m def max(self) -> T: if self.size == 0: return T(0) m: T = self.data[0] i: t.CSizeT = 1 while i < self.size: if self.data[i] > m: m = self.data[i] i += 1 return m def argmax(self) -> t.CInt: if self.size == 0: return -1 m: T = self.data[0] idx: t.CInt = 0 i: t.CSizeT = 1 while i < self.size: if self.data[i] > m: m = self.data[i] idx = t.CInt(i) i += 1 return idx def argmin(self) -> t.CInt: if self.size == 0: return -1 m: T = self.data[0] idx: t.CInt = 0 i: t.CSizeT = 1 while i < self.size: if self.data[i] < m: m = self.data[i] idx = t.CInt(i) i += 1 return idx def dot(self, other: ndarray[T] | t.CPtr) -> T: s: T = T(0) n: t.CSizeT = self.size if (self.size < other.size) else other.size i: t.CSizeT = 0 while i < n: s += self.data[i] * other.data[i] i += 1 return s def T(self) -> ndarray[T] | t.CPtr: if self.ndim != 2: return None rows: t.CSizeT = self.shape[0] cols: t.CSizeT = self.shape[1] result: ndarray[T] | t.CPtr = empty2d(self.pool, rows, cols) if not result: return None # Swap shape for transpose result.shape[0] = cols result.shape[1] = rows _compute_strides(result) i: t.CSizeT = 0 while i < rows: j: t.CSizeT = 0 while j < cols: result.data[j * rows + i] = self.data[i * cols + j] j += 1 i += 1 return result def print_arr(self): if self.ndim == 1: printf("[") i: t.CSizeT = 0 while i < self.size: if i > 0: printf(", ") printf("%.4f", self.data[i]) i += 1 printf("]\n") elif self.ndim == 2: rows: t.CSizeT = self.shape[0] cols: t.CSizeT = self.shape[1] printf("[") i: t.CSizeT = 0 while i < rows: if i > 0: printf(" ") printf("[") j: t.CSizeT = 0 while j < cols: if j > 0: printf(", ") printf("%.4f", self.data[i * cols + j]) j += 1 if i < rows - 1: printf("]\n") else: printf("]]\n") i += 1 # ============================================================ # Convenience aliases: typed ndarray instantiations # (must be after class ndarray[T] definition to avoid forward reference) # ============================================================ Float64Array: t.CTypedef = ndarray[t.CDouble] Float32Array: t.CTypedef = ndarray[t.CFloat] Int64Array: t.CTypedef = ndarray[t.CLong] Int32Array: t.CTypedef = ndarray[t.CInt] Uint8Array: t.CTypedef = ndarray[t.CUnsignedChar] # ============================================================ # Internal helpers (specialized for float64 by default) # Use _alloc_ndarray_t[T] / _empty_like_t[T] for other types # ============================================================ def _alloc_ndarray(pool: memhub.MemManager | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: a: ndarray[t.CDouble] | t.CPtr = pool.alloc(ndarray[t.CDouble].__sizeof__()) if a: memset(a, 0, ndarray[t.CDouble].__sizeof__()) return a def _compute_strides(a: ndarray[t.CDouble] | t.CPtr): if a.ndim <= 0: return a.strides[a.ndim - 1] = 1 i: t.CInt = a.ndim - 2 while i >= 0: a.strides[i] = a.strides[i + 1] * a.shape[i + 1] i -= 1 def _empty_like(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _alloc_ndarray(a.pool) if not result: return None result.data = a.pool.alloc(a.size * t.CDouble.__sizeof__()) if not result.data: a.pool.free(result) return None result.pool = a.pool result.ndim = a.ndim result.size = a.size result.owns_data = 1 i: t.CInt = 0 for i in range(a.ndim): result.shape[i] = a.shape[i] _compute_strides(result) return result # ============================================================ # Array creation functions (float64 default) # ============================================================ def array(pool: memhub.MemManager | t.CPtr, data: t.CDouble | t.CPtr, n: t.CSizeT) -> ndarray[t.CDouble] | t.CPtr: a: ndarray[t.CDouble] | t.CPtr = _alloc_ndarray(pool) if not a: return None a.data = pool.alloc(n * t.CDouble.__sizeof__()) if not a.data: pool.free(a) return None memcpy(a.data, data, n * t.CDouble.__sizeof__()) a.pool = pool a.ndim = 1 a.size = n a.shape[0] = n a.strides[0] = 1 a.owns_data = 1 return a def zeros(pool: memhub.MemManager | t.CPtr, n: t.CSizeT) -> ndarray[t.CDouble] | t.CPtr: a: ndarray[t.CDouble] | t.CPtr = _alloc_ndarray(pool) if not a: return None a.data = pool.alloc(n * t.CDouble.__sizeof__()) if not a.data: pool.free(a) return None memset(a.data, 0, n * t.CDouble.__sizeof__()) a.pool = pool a.ndim = 1 a.size = n a.shape[0] = n a.strides[0] = 1 a.owns_data = 1 return a def ones(pool: memhub.MemManager | t.CPtr, n: t.CSizeT) -> ndarray[t.CDouble] | t.CPtr: a: ndarray[t.CDouble] | t.CPtr = zeros(pool, n) if not a: return None a.fill(t.CDouble(1.0)) return a def full(pool: memhub.MemManager | t.CPtr, n: t.CSizeT, val: t.CDouble) -> ndarray[t.CDouble] | t.CPtr: a: ndarray[t.CDouble] | t.CPtr = zeros(pool, n) if not a: return None a.fill(val) return a def arange(pool: memhub.MemManager | t.CPtr, start: t.CDouble, stop: t.CDouble, step: t.CDouble) -> ndarray[t.CDouble] | t.CPtr: if step == t.CDouble(0.0): return None n: t.CSizeT = 0 if step > t.CDouble(0.0): v: t.CDouble = start while v < stop: n += 1 v += step else: v: t.CDouble = start while v > stop: n += 1 v += step if n == 0: return None a: ndarray[t.CDouble] | t.CPtr = zeros(pool, n) if not a: return None i: t.CSizeT = 0 v = start while i < n: a.data[i] = v v += step i += 1 return a def linspace(pool: memhub.MemManager | t.CPtr, start: t.CDouble, stop: t.CDouble, num: t.CSizeT) -> ndarray[t.CDouble] | t.CPtr: if num == 0: return None a: ndarray[t.CDouble] | t.CPtr = zeros(pool, num) if not a: return None if num == 1: a.data[0] = start return a step: t.CDouble = (stop - start) / t.CDouble(num - 1) i: t.CSizeT = 0 while i < num: a.data[i] = start + t.CDouble(i) * step i += 1 return a def empty2d(pool: memhub.MemManager | t.CPtr, rows: t.CSizeT, cols: t.CSizeT) -> ndarray[t.CDouble] | t.CPtr: a: ndarray[t.CDouble] | t.CPtr = _alloc_ndarray(pool) if not a: return None total: t.CSizeT = rows * cols a.data = pool.alloc(total * t.CDouble.__sizeof__()) if not a.data: pool.free(a) return None memset(a.data, 0, total * t.CDouble.__sizeof__()) a.pool = pool a.ndim = 2 a.size = total a.shape[0] = rows a.shape[1] = cols a.owns_data = 1 _compute_strides(a) return a def zeros2d(pool: memhub.MemManager | t.CPtr, rows: t.CSizeT, cols: t.CSizeT) -> ndarray[t.CDouble] | t.CPtr: return empty2d(pool, rows, cols) def ones2d(pool: memhub.MemManager | t.CPtr, rows: t.CSizeT, cols: t.CSizeT) -> ndarray[t.CDouble] | t.CPtr: a: ndarray[t.CDouble] | t.CPtr = empty2d(pool, rows, cols) if not a: return None a.fill(t.CDouble(1.0)) return a def eye(pool: memhub.MemManager | t.CPtr, n: t.CSizeT) -> ndarray[t.CDouble] | t.CPtr: a: ndarray[t.CDouble] | t.CPtr = empty2d(pool, n, n) if not a: return None i: t.CSizeT = 0 while i < n: a.data[i * n + i] = t.CDouble(1.0) i += 1 return a def diag(pool: memhub.MemManager | t.CPtr, vals: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: n: t.CSizeT = vals.size a: ndarray[t.CDouble] | t.CPtr = empty2d(pool, n, n) if not a: return None i: t.CSizeT = 0 while i < n: a.data[i * n + i] = vals.data[i] i += 1 return a # ============================================================ # Mathematical functions (element-wise, float64 default) # ============================================================ def np_abs(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: v: t.CDouble = a.data[i] result.data[i] = v if v >= t.CDouble(0.0) else -v i += 1 return result def np_sqrt(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.sqrt(a.data[i]) i += 1 return result def np_exp(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.exp(a.data[i]) i += 1 return result def np_log(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.log(a.data[i]) i += 1 return result def np_sin(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.sin(a.data[i]) i += 1 return result def np_cos(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.cos(a.data[i]) i += 1 return result def np_tan(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.tan(a.data[i]) i += 1 return result def np_pow(a: ndarray[t.CDouble] | t.CPtr, p: t.CDouble) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.pow(a.data[i], p) i += 1 return result # ============================================================ # Scalar operations (float64 default) # ============================================================ def add_scalar(a: ndarray[t.CDouble] | t.CPtr, s: t.CDouble) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = a.data[i] + s i += 1 return result def mul_scalar(a: ndarray[t.CDouble] | t.CPtr, s: t.CDouble) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = a.data[i] * s i += 1 return result def sub_scalar(a: ndarray[t.CDouble] | t.CPtr, s: t.CDouble) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = a.data[i] - s i += 1 return result def div_scalar(a: ndarray[t.CDouble] | t.CPtr, s: t.CDouble) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: if s != t.CDouble(0.0): result.data[i] = a.data[i] / s else: result.data[i] = t.CDouble(0.0) i += 1 return result # ============================================================ # Matrix operations (float64 default) # ============================================================ def matmul(a: ndarray[t.CDouble] | t.CPtr, b: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: # 2D matrix multiply: a(m,k) x b(k,n) = c(m,n) if a.ndim != 2 or b.ndim != 2: return None m: t.CSizeT = a.shape[0] k: t.CSizeT = a.shape[1] k2: t.CSizeT = b.shape[0] n: t.CSizeT = b.shape[1] if k != k2: return None c: ndarray[t.CDouble] | t.CPtr = empty2d(a.pool, m, n) if not c: return None i: t.CSizeT = 0 while i < m: j: t.CSizeT = 0 while j < n: s: t.CDouble = t.CDouble(0.0) p: t.CSizeT = 0 while p < k: s += a.data[i * k + p] * b.data[p * n + j] p += 1 c.data[i * n + j] = s j += 1 i += 1 return c def dot_product(a: ndarray[t.CDouble] | t.CPtr, b: ndarray[t.CDouble] | t.CPtr) -> t.CDouble: return a.dot(b) # ============================================================ # Reduction operations (float64 default) # ============================================================ def np_sum(a: ndarray[t.CDouble] | t.CPtr) -> t.CDouble: return a.sum() def np_mean(a: ndarray[t.CDouble] | t.CPtr) -> t.CDouble: return a.mean() def np_min(a: ndarray[t.CDouble] | t.CPtr) -> t.CDouble: return a.min() def np_max(a: ndarray[t.CDouble] | t.CPtr) -> t.CDouble: return a.max() def np_argmax(a: ndarray[t.CDouble] | t.CPtr) -> t.CInt: return a.argmax() def np_argmin(a: ndarray[t.CDouble] | t.CPtr) -> t.CInt: return a.argmin() # ============================================================ # Utility (float64 default) # ============================================================ def var(a: ndarray[t.CDouble] | t.CPtr) -> t.CDouble: if a.size == 0: return t.CDouble(0.0) m: t.CDouble = a.mean() s: t.CDouble = t.CDouble(0.0) i: t.CSizeT = 0 while i < a.size: d: t.CDouble = a.data[i] - m s += d * d i += 1 return s / t.CDouble(a.size) def std(a: ndarray[t.CDouble] | t.CPtr) -> t.CDouble: return vipermath.sqrt(var(a)) def norm(a: ndarray[t.CDouble] | t.CPtr) -> t.CDouble: return vipermath.sqrt(a.dot(a)) def clip(a: ndarray[t.CDouble] | t.CPtr, lo: t.CDouble, hi: t.CDouble) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: v: t.CDouble = a.data[i] if v < lo: v = lo if v > hi: v = hi result.data[i] = v i += 1 return result def concatenate(a: ndarray[t.CDouble] | t.CPtr, b: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: if a.ndim != 1 or b.ndim != 1: return None total: t.CSizeT = a.size + b.size result: ndarray[t.CDouble] | t.CPtr = zeros(a.pool, total) if not result: return None memcpy(result.data, a.data, a.size * t.CDouble.__sizeof__()) memcpy(result.data + a.size, b.data, b.size * t.CDouble.__sizeof__()) return result def sort_arr(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = a.copy() if not result: return None # Simple insertion sort i: t.CSizeT = 1 while i < result.size: key: t.CDouble = result.data[i] j: t.CSizeT = i - 1 while j < result.size and result.data[j] > key: result.data[j + 1] = result.data[j] if j == 0: break j -= 1 if j > 0 or result.data[0] <= key: result.data[j + 1] = key else: result.data[1] = result.data[0] result.data[0] = key i += 1 return result def reverse(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = a.data[a.size - 1 - i] i += 1 return result # ============================================================ # Additional math functions (element-wise, float64 default) # ============================================================ def np_log10(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.log10(a.data[i]) i += 1 return result def np_log2(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.log2(a.data[i]) i += 1 return result def np_floor(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.floor(a.data[i]) i += 1 return result def np_ceil(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.ceil(a.data[i]) i += 1 return result def np_round(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.round(a.data[i]) i += 1 return result def np_sign(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: if a.data[i] > t.CDouble(0.0): result.data[i] = t.CDouble(1.0) elif a.data[i] < t.CDouble(0.0): result.data[i] = t.CDouble(-1.0) else: result.data[i] = t.CDouble(0.0) i += 1 return result def np_tanh(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.tanh(a.data[i]) i += 1 return result def np_sinh(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.sinh(a.data[i]) i += 1 return result def np_cosh(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.cosh(a.data[i]) i += 1 return result def np_arcsin(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.asin(a.data[i]) i += 1 return result def np_arccos(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.acos(a.data[i]) i += 1 return result def np_arctan(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.atan(a.data[i]) i += 1 return result def np_arctan2(y: ndarray[t.CDouble] | t.CPtr, x: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(y) if not result: return None i: t.CSizeT = 0 while i < y.size: result.data[i] = vipermath.atan2(y.data[i], x.data[i]) i += 1 return result def np_degrees(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.degrees(a.data[i]) i += 1 return result def np_radians(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = vipermath.radians(a.data[i]) i += 1 return result def np_isnan(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = t.CDouble(vipermath.isnan(a.data[i])) i += 1 return result def np_isinf(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = t.CDouble(vipermath.isinf(a.data[i])) i += 1 return result def np_maximum(a: ndarray[t.CDouble] | t.CPtr, b: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = a.data[i] if a.data[i] > b.data[i] else b.data[i] i += 1 return result def np_minimum(a: ndarray[t.CDouble] | t.CPtr, b: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = a.data[i] if a.data[i] < b.data[i] else b.data[i] i += 1 return result # ============================================================ # Additional array operations (float64 default) # ============================================================ def cumsum(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None s: t.CDouble = t.CDouble(0.0) i: t.CSizeT = 0 while i < a.size: s += a.data[i] result.data[i] = s i += 1 return result def diff(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: if a.size < 2: return None result: ndarray[t.CDouble] | t.CPtr = zeros(a.pool, a.size - 1) if not result: return None i: t.CSizeT = 0 while i < a.size - 1: result.data[i] = a.data[i + 1] - a.data[i] i += 1 return result def flatten(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None result.ndim = 1 result.shape[0] = a.size result.strides[0] = 1 memcpy(result.data, a.data, a.size * t.CDouble.__sizeof__()) return result def trace(a: ndarray[t.CDouble] | t.CPtr) -> t.CDouble: if a.ndim != 2: return t.CDouble(0.0) n: t.CSizeT = a.shape[0] if a.shape[0] < a.shape[1] else a.shape[1] s: t.CDouble = t.CDouble(0.0) i: t.CSizeT = 0 while i < n: s += a.data[i * a.shape[1] + i] i += 1 return s def outer(a: ndarray[t.CDouble] | t.CPtr, b: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = empty2d(a.pool, a.size, b.size) if not result: return None i: t.CSizeT = 0 while i < a.size: j: t.CSizeT = 0 while j < b.size: result.data[i * b.size + j] = a.data[i] * b.data[j] j += 1 i += 1 return result def np_where(condition: ndarray[t.CDouble] | t.CPtr, x: ndarray[t.CDouble] | t.CPtr, y: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(x) if not result: return None i: t.CSizeT = 0 while i < x.size: if condition.data[i] != t.CDouble(0.0): result.data[i] = x.data[i] else: result.data[i] = y.data[i] i += 1 return result def np_count_nonzero(a: ndarray[t.CDouble] | t.CPtr) -> t.CInt: count: t.CInt = 0 i: t.CSizeT = 0 while i < a.size: if a.data[i] != t.CDouble(0.0): count += 1 i += 1 return count def np_all(a: ndarray[t.CDouble] | t.CPtr) -> t.CInt: i: t.CSizeT = 0 while i < a.size: if a.data[i] == t.CDouble(0.0): return 0 i += 1 return 1 def np_any(a: ndarray[t.CDouble] | t.CPtr) -> t.CInt: i: t.CSizeT = 0 while i < a.size: if a.data[i] != t.CDouble(0.0): return 1 i += 1 return 0 def np_equal(a: ndarray[t.CDouble] | t.CPtr, b: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = t.CDouble(1.0) if a.data[i] == b.data[i] else t.CDouble(0.0) i += 1 return result def np_not_equal(a: ndarray[t.CDouble] | t.CPtr, b: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = t.CDouble(1.0) if a.data[i] != b.data[i] else t.CDouble(0.0) i += 1 return result def np_less(a: ndarray[t.CDouble] | t.CPtr, b: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = t.CDouble(1.0) if a.data[i] < b.data[i] else t.CDouble(0.0) i += 1 return result def np_greater(a: ndarray[t.CDouble] | t.CPtr, b: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = t.CDouble(1.0) if a.data[i] > b.data[i] else t.CDouble(0.0) i += 1 return result def np_less_equal(a: ndarray[t.CDouble] | t.CPtr, b: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = t.CDouble(1.0) if a.data[i] <= b.data[i] else t.CDouble(0.0) i += 1 return result def np_greater_equal(a: ndarray[t.CDouble] | t.CPtr, b: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None i: t.CSizeT = 0 while i < a.size: result.data[i] = t.CDouble(1.0) if a.data[i] >= b.data[i] else t.CDouble(0.0) i += 1 return result # ============================================================ # Additional creation functions (float64 default) # ============================================================ def empty(pool: memhub.MemManager | t.CPtr, n: t.CSizeT) -> ndarray[t.CDouble] | t.CPtr: return zeros(pool, n) def full2d(pool: memhub.MemManager | t.CPtr, rows: t.CSizeT, cols: t.CSizeT, val: t.CDouble) -> ndarray[t.CDouble] | t.CPtr: a: ndarray[t.CDouble] | t.CPtr = empty2d(pool, rows, cols) if not a: return None a.fill(val) return a def zeros_like(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _alloc_ndarray(a.pool) if not result: return None result.data = a.pool.alloc(a.size * t.CDouble.__sizeof__()) if not result.data: a.pool.free(result) return None memset(result.data, 0, a.size * t.CDouble.__sizeof__()) result.pool = a.pool result.ndim = a.ndim result.size = a.size result.owns_data = 1 i: t.CInt = 0 for i in range(a.ndim): result.shape[i] = a.shape[i] _compute_strides(result) return result def ones_like(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: result: ndarray[t.CDouble] | t.CPtr = _empty_like(a) if not result: return None result.fill(t.CDouble(1.0)) return result def arange1(pool: memhub.MemManager | t.CPtr, stop: t.CDouble) -> ndarray[t.CDouble] | t.CPtr: return arange(pool, t.CDouble(0.0), stop, t.CDouble(1.0)) def linspace2(pool: memhub.MemManager | t.CPtr, start: t.CDouble, stop: t.CDouble) -> ndarray[t.CDouble] | t.CPtr: return linspace(pool, start, stop, 50) def meshgrid(x: ndarray[t.CDouble] | t.CPtr, y: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: # Returns flattened grid pairs - simplified version # Returns X grid as 2D array (each row is x) result: ndarray[t.CDouble] | t.CPtr = empty2d(x.pool, y.size, x.size) if not result: return None i: t.CSizeT = 0 while i < y.size: j: t.CSizeT = 0 while j < x.size: result.data[i * x.size + j] = x.data[j] j += 1 i += 1 return result # ============================================================ # Linear algebra helpers (float64 default) # ============================================================ def det2x2(a: ndarray[t.CDouble] | t.CPtr) -> t.CDouble: # Determinant of 2x2 matrix if a.ndim != 2: return t.CDouble(0.0) if a.shape[0] != 2 or a.shape[1] != 2: return t.CDouble(0.0) return a.data[0] * a.data[3] - a.data[1] * a.data[2] def inv2x2(a: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: # Inverse of 2x2 matrix d: t.CDouble = det2x2(a) if d == t.CDouble(0.0): return None result: ndarray[t.CDouble] | t.CPtr = empty2d(a.pool, 2, 2) if not result: return None result.data[0] = a.data[3] / d result.data[1] = -a.data[1] / d result.data[2] = -a.data[2] / d result.data[3] = a.data[0] / d return result def cross3(a: ndarray[t.CDouble] | t.CPtr, b: ndarray[t.CDouble] | t.CPtr) -> ndarray[t.CDouble] | t.CPtr: # Cross product of 3D vectors result: ndarray[t.CDouble] | t.CPtr = zeros(a.pool, 3) if not result: return None result.data[0] = a.data[1] * b.data[2] - a.data[2] * b.data[1] result.data[1] = a.data[2] * b.data[0] - a.data[0] * b.data[2] result.data[2] = a.data[0] * b.data[1] - a.data[1] * b.data[0] return result def np_interp(x: t.CDouble, xp: ndarray[t.CDouble] | t.CPtr, fp: ndarray[t.CDouble] | t.CPtr) -> t.CDouble: # 1D linear interpolation if xp.size == 0: return t.CDouble(0.0) if x <= xp.data[0]: return fp.data[0] if x >= xp.data[xp.size - 1]: return fp.data[xp.size - 1] i: t.CSizeT = 0 while i < xp.size - 1: if x >= xp.data[i] and x <= xp.data[i + 1]: t_val: t.CDouble = (x - xp.data[i]) / (xp.data[i + 1] - xp.data[i]) return fp.data[i] + t_val * (fp.data[i + 1] - fp.data[i]) i += 1 return fp.data[xp.size - 1] def prod(a: ndarray[t.CDouble] | t.CPtr) -> t.CDouble: p: t.CDouble = t.CDouble(1.0) i: t.CSizeT = 0 while i < a.size: p *= a.data[i] i += 1 return p def median(a: ndarray[t.CDouble] | t.CPtr) -> t.CDouble: if a.size == 0: return t.CDouble(0.0) s: ndarray[t.CDouble] | t.CPtr = sort_arr(a) if not s: return t.CDouble(0.0) mid: t.CSizeT = a.size / 2 if a.size % 2 == 1: result: t.CDouble = s.data[mid] else: result = (s.data[mid - 1] + s.data[mid]) / t.CDouble(2.0) s.delete() return result def percentile(a: ndarray[t.CDouble] | t.CPtr, q: t.CDouble) -> t.CDouble: if a.size == 0: return t.CDouble(0.0) s: ndarray[t.CDouble] | t.CPtr = sort_arr(a) if not s: return t.CDouble(0.0) idx: t.CDouble = q / t.CDouble(100.0) * t.CDouble(a.size - 1) lo: t.CSizeT = t.CSizeT(vipermath.floor(idx)) hi: t.CSizeT = t.CSizeT(vipermath.ceil(idx)) frac: t.CDouble = idx - vipermath.floor(idx) result: t.CDouble = s.data[lo] + frac * (s.data[hi] - s.data[lo]) s.delete() return result