// tensor_ops.mbt 鈥?matmul + softmax + log_softmax (v0.13.1).
//
// All ops are forward-only and operate on Tensor structs (or
// elementwise float arrays for softmax).

///|
/// 2D matrix multiply: `c[m, n] = sum_k a[m, k] * b[k, n]`.
/// Inputs `a` is shape `[m, k]`, `b` is shape `[k, n]`. Returns a
/// fresh `Tensor` of shape `[m, n]`. Naive O(m*n*k) loop.
pub fn tensor_matmul(a : Tensor, b : Tensor) -> Tensor {
  if a.shape.length() != 2 || b.shape.length() != 2 {
    abort("tensor_matmul: both inputs must be 2D")
  }
  let m = a.shape[0]
  let k = a.shape[1]
  let k2 = b.shape[0]
  let n = b.shape[1]
  if k != k2 {
    abort("tensor_matmul: shape mismatch a[\{m},\{k}] x b[\{k2},\{n}]")
  }
  let out_data : Array[Float] = Array::make(m * n, 0.0F)
  for i in 0.. Tensor {
  if x.shape.length() != 2 {
    abort("tensor_softmax: input must be 2D")
  }
  let m = x.shape[0]
  let n = x.shape[1]
  let out : Array[Float] = Array::make(m * n, 0.0F)
  for i in 0.. mx { mx = x.data[row_off + j] }
    }
    // Compute exp(x - max) and sum.
    let mut sum : Float = 0.0F
    for j in 0.. Tensor {
  if x.shape.length() != 2 {
    abort("tensor_log_softmax: input must be 2D")
  }
  let m = x.shape[0]
  let n = x.shape[1]
  let out : Array[Float] = Array::make(m * n, 0.0F)
  for i in 0.. mx { mx = x.data[row_off + j] }
    }
    // Compute log-sum-exp = max + log(sum exp(x - max)).
    let mut sum : Float = 0.0F
    for j in 0.. Float {
  expf(x)
}

///|
/// Float32 log via libm FFI (matches Julia's `log(Float32, x)`).
pub fn math_log_f32(x : Float) -> Float {
  logf(x)
}