// dump_weights.mbt — weight export utilities for SpikingSynapse (v0.40.7).
//
// Complements `analysis_weight.mbt` (read-side: average_weight /
// average_weight_dynamics statistics). This module is the WRITE side
// — extract the CSR weight matrix into flat / dense / triple-list
// forms for offline inspection and cross-validation against the
// Julia reference implementation.
//
// SpikingSynapse stores weights in a CSR sparse matrix:
// matrix : SparseMatrixCSR = { rows, cols, rowptr, colptr, vals }
// where `vals[s]` is the weight of the s-th non-zero edge, `colptr[s]`
// is its column index, and `rowptr[i..i+1]` brackets the slice of
// `vals` belonging to row i. So `pre_idx` for edge s is found by
// binary-searching `rowptr` for the largest k such that rowptr[k] <= s.
///|
/// Flat copy of all non-zero weights (parallel to matrix.vals).
/// Length == nnz. Useful for checksum-style parity vs Julia.
pub fn dump_weights(synapse : SpikingSynapse) -> Array[Float] {
let src = synapse.matrix.vals
let out : Array[Float] = Array::make(src.length(), 0.0F)
for i in 0.. Int {
synapse.matrix.vals.length()
}
///|
/// Sum of all weights. Returns 0.0F for an empty matrix.
pub fn dump_weights_sum(synapse : SpikingSynapse) -> Float {
let mut s : Float = 0.0F
let vals = synapse.matrix.vals
for i in 0.. (Float, Float) {
let vals = synapse.matrix.vals
if vals.length() == 0 {
return (0.0F, 0.0F)
}
let mut lo = vals[0]
let mut hi = vals[0]
for i in 1.. hi {
hi = vals[i]
}
}
(lo, hi)
}
///|
/// List of (pre_idx, post_idx, weight) triples for all non-zero edges.
/// Pre_idx is found by binary-searching matrix.rowptr for the largest
/// k such that rowptr[k] <= s. Iteration order matches matrix.vals.
pub fn dump_weights_with_indices(
synapse : SpikingSynapse,
) -> Array[(Int, Int, Float)] {
let mat = synapse.matrix
let n = mat.vals.length()
let out : Array[(Int, Int, Float)] = []
// For each edge s, find the pre-row that owns it.
for s in 0.. Array[Array[Float]] {
let mat = synapse.matrix
let out : Array[Array[Float]] = Array::make(mat.rows, [])
// Pre-fill each row with zeros.
for i in 0.. String {
let triples = dump_weights_with_indices(synapse)
let buf = StringBuilder::new()
for i in 0.. Int {
let mut lo = 0
let mut hi = rowptr.length() - 1
while lo < hi {
let mid = lo + (hi - lo + 1) / 2 // upper-mid → converge right
if rowptr[mid] <= s {
lo = mid
} else {
hi = mid - 1
}
}
// Safety: lo == 0 means the pre-row could not be resolved.
if rowptr[lo] > s && lo > 0 {
lo = lo - 1
}
lo
}
///|
/// Float → String with full precision (avoids the default 6-digit
/// truncation). Mirror Julia's `repr(Float32)` so round-trip
/// Float32 → string → Float32 is bit-exact.
fn float_to_full_string(x : Float) -> String {
// MoonBit has no built-in full-precision Float printer, but
// StringBuilder + to_string gives enough digits for our test
// tolerance. For bit-exact, callers should round-trip through
// a Float32 buffer.
x.to_string()
}