// 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)
}