Skip to main content

cjc_runtime/
builtins.rs

1//! Shared builtin function implementations for CJC eval and MIR-exec.
2//!
3//! These are pure (or nearly pure) dispatchers that return
4//! `Result<Value, String>`. Each caller wraps the `String` into its own
5//! error type (e.g. `EvalError::Runtime` or `MirExecError::Runtime`).
6//!
7//! **Contract:** Functions in this module must NOT depend on interpreter
8//! state. Anything that needs `&mut self` on an interpreter (print, gc_*,
9//! clock, Tensor.randn) stays in each executor.
10
11use std::cell::RefCell;
12use std::rc::Rc;
13
14use crate::accumulator::BinnedAccumulatorF64;
15use crate::complex::ComplexF64;
16use crate::scratchpad::Scratchpad;
17use crate::paged_kv::PagedKvCache;
18use crate::tensor::Tensor;
19use crate::tensor_simd::UnaryOp;
20use crate::value::{Bf16, Value};
21
22// ---------------------------------------------------------------------------
23// Value conversion helpers
24// ---------------------------------------------------------------------------
25
26/// Convert a `Value::Array` of ints into a `Vec<usize>` (shape).
27pub fn value_to_shape(val: &Value) -> Result<Vec<usize>, String> {
28    match val {
29        Value::Array(arr) => {
30            let mut shape = Vec::with_capacity(arr.len());
31            for v in arr.iter() {
32                shape.push(value_to_usize(v)?);
33            }
34            Ok(shape)
35        }
36        _ => Err(format!("expected Array for shape, got {}", val.type_name())),
37    }
38}
39
40/// Convert a `Value::Int` to `usize`, rejecting negatives.
41pub fn value_to_usize(val: &Value) -> Result<usize, String> {
42    match val {
43        Value::Int(i) => {
44            if *i < 0 {
45                Err(format!("expected non-negative integer, got {i}"))
46            } else {
47                Ok(*i as usize)
48            }
49        }
50        _ => Err(format!("expected Int, got {}", val.type_name())),
51    }
52}
53
54/// Convert a `Value::Array` of floats/ints to `Vec<f64>`.
55pub fn value_to_f64_vec(val: &Value) -> Result<Vec<f64>, String> {
56    match val {
57        Value::Array(arr) => {
58            let mut data = Vec::with_capacity(arr.len());
59            for v in arr.iter() {
60                match v {
61                    Value::Float(f) => data.push(*f),
62                    Value::Int(i) => data.push(*i as f64),
63                    // NA values are silently skipped in aggregations (na_rm=true default)
64                    Value::Na => {}
65                    _ => {
66                        return Err(format!(
67                            "expected numeric values in array, got {}",
68                            v.type_name()
69                        ));
70                    }
71                }
72            }
73            Ok(data)
74        }
75        _ => Err(format!("expected Array, got {}", val.type_name())),
76    }
77}
78
79/// Convert a `Value::Array` of complex tuples (re, im) to `Vec<(f64, f64)>`.
80pub fn value_to_complex_vec(val: &Value) -> Result<Vec<(f64, f64)>, String> {
81    match val {
82        Value::Array(arr) => {
83            let mut data = Vec::with_capacity(arr.len());
84            for v in arr.iter() {
85                match v {
86                    Value::Tuple(t) if t.len() == 2 => {
87                        let re = match &t[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("complex tuple element must be numeric".into()) };
88                        let im = match &t[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("complex tuple element must be numeric".into()) };
89                        data.push((re, im));
90                    }
91                    _ => return Err("expected array of (re, im) tuples".into()),
92                }
93            }
94            Ok(data)
95        }
96        _ => Err(format!("expected Array of complex tuples, got {}", val.type_name())),
97    }
98}
99
100/// Convert a `Value::Array` of ints to `Vec<usize>`.
101pub fn value_to_usize_vec(val: &Value) -> Result<Vec<usize>, String> {
102    match val {
103        Value::Array(arr) => {
104            let mut indices = Vec::with_capacity(arr.len());
105            for v in arr.iter() {
106                indices.push(value_to_usize(v)?);
107            }
108            Ok(indices)
109        }
110        _ => Err(format!("expected Array for indices, got {}", val.type_name())),
111    }
112}
113
114/// Extract a `&Tensor` from a `Value::Tensor`.
115pub fn value_to_tensor(val: &Value) -> Result<&Tensor, String> {
116    match val {
117        Value::Tensor(t) => Ok(t),
118        _ => Err(format!("expected Tensor, got {}", val.type_name())),
119    }
120}
121
122/// Convert a `Value::Float` or `Value::Int` to `f64`.
123pub fn value_to_f64(val: &Value) -> Result<f64, String> {
124    match val {
125        Value::Float(v) => Ok(*v),
126        Value::Int(v) => Ok(*v as f64),
127        _ => Err(format!("expected Float or Int, got {}", val.type_name())),
128    }
129}
130
131/// Extract bytes from a `ByteSlice`, `Bytes`, or `String` value.
132pub fn value_to_bytes(val: &Value) -> Result<Vec<u8>, String> {
133    match val {
134        Value::ByteSlice(b) => Ok(b.as_ref().clone()),
135        Value::Bytes(b) => Ok(b.borrow().clone()),
136        Value::String(s) => Ok(s.as_bytes().to_vec()),
137        _ => Err(format!("expected ByteSlice or Bytes, got {}", val.type_name())),
138    }
139}
140
141/// Structural equality comparison for assertion builtins.
142pub fn values_equal(a: &Value, b: &Value) -> bool {
143    match (a, b) {
144        (Value::Int(a), Value::Int(b)) => a == b,
145        (Value::Float(a), Value::Float(b)) => a == b,
146        (Value::Bool(a), Value::Bool(b)) => a == b,
147        (Value::String(a), Value::String(b)) => a == b,
148        (Value::Void, Value::Void) => true,
149        _ => false,
150    }
151}
152
153// ---------------------------------------------------------------------------
154// Deterministic categorical sampling (needs external RNG)
155// ---------------------------------------------------------------------------
156
157/// Sample an index from a 1-D probability tensor using the given uniform
158/// random value `u` in [0, 1). Returns the selected index (0-based).
159/// This is a general-purpose RL primitive, not domain-specific.
160pub fn categorical_sample_with_u(probs: &Tensor, u: f64) -> Result<i64, String> {
161    if probs.ndim() == 0 {
162        return Err("categorical_sample requires at least a 1-D tensor".into());
163    }
164    let data = probs.to_vec();
165    if data.is_empty() {
166        return Err("categorical_sample: empty probability tensor".into());
167    }
168    let mut cumsum = 0.0;
169    for (i, &p) in data.iter().enumerate() {
170        cumsum += p;
171        if u < cumsum {
172            return Ok(i as i64);
173        }
174    }
175    // Numerical safety: return last valid index
176    Ok((data.len() - 1) as i64)
177}
178
179// ---------------------------------------------------------------------------
180// Stateless builtin functions
181// ---------------------------------------------------------------------------
182
183/// Dispatch a stateless builtin function by name.
184/// Returns `Ok(Some(value))` if handled, `Ok(None)` if not a known builtin.
185pub fn dispatch_builtin(name: &str, args: &[Value]) -> Result<Option<Value>, String> {
186    match name {
187        "Complex" => {
188            let re = match args.get(0) {
189                Some(Value::Float(v)) => *v,
190                Some(Value::Int(v)) => *v as f64,
191                _ => return Err("Complex() requires numeric re argument".into()),
192            };
193            let im = match args.get(1) {
194                Some(Value::Float(v)) => *v,
195                Some(Value::Int(v)) => *v as f64,
196                None => 0.0,
197                _ => return Err("Complex() requires numeric im argument".into()),
198            };
199            Ok(Some(Value::Complex(ComplexF64::new(re, im))))
200        }
201        // f16 conversion builtins
202        "f16_to_f64" => match &args[0] {
203            Value::F16(v) => Ok(Some(Value::Float(v.to_f64()))),
204            _ => Err("f16_to_f64 expects f16".into()),
205        },
206        "f64_to_f16" => match &args[0] {
207            Value::Float(v) => Ok(Some(Value::F16(crate::f16::F16::from_f64(*v)))),
208            Value::Int(v) => Ok(Some(Value::F16(crate::f16::F16::from_f64(*v as f64)))),
209            _ => Err("f64_to_f16 expects f64".into()),
210        },
211        "f16_to_f32" => match &args[0] {
212            Value::F16(v) => Ok(Some(Value::Float(v.to_f32() as f64))),
213            _ => Err("f16_to_f32 expects f16".into()),
214        },
215        "f32_to_f16" => match &args[0] {
216            Value::Float(v) => Ok(Some(Value::F16(crate::f16::F16::from_f32(*v as f32)))),
217            _ => Err("f32_to_f16 expects f32".into()),
218        },
219        // bf16 conversion builtins
220        "bf16_to_f32" => match &args[0] {
221            Value::Bf16(v) => Ok(Some(Value::Float(v.to_f32() as f64))),
222            _ => Err("bf16_to_f32 expects bf16".into()),
223        },
224        "f32_to_bf16" => match &args[0] {
225            Value::Float(v) => Ok(Some(Value::Bf16(Bf16::from_f32(*v as f32)))),
226            _ => Err("f32_to_bf16 expects f32".into()),
227        },
228        // Tensor constructors (stateless ones)
229        "Tensor.zeros" => {
230            let shape = value_to_shape(&args[0])?;
231            Ok(Some(Value::Tensor(Tensor::zeros(&shape))))
232        }
233        "Tensor.ones" => {
234            let shape = value_to_shape(&args[0])?;
235            Ok(Some(Value::Tensor(Tensor::ones(&shape))))
236        }
237        "Tensor.from_vec" => {
238            if args.len() != 2 {
239                return Err("Tensor.from_vec requires 2 arguments: data and shape".into());
240            }
241            let data = value_to_f64_vec(&args[0])?;
242            let shape = value_to_shape(&args[1])?;
243            let t = Tensor::from_vec(data, &shape).map_err(|e| format!("{e}"))?;
244            Ok(Some(Value::Tensor(t)))
245        }
246        "matmul" => {
247            if args.len() != 2 {
248                return Err("matmul requires 2 Tensor arguments".into());
249            }
250            let a = value_to_tensor(&args[0])?;
251            let b = value_to_tensor(&args[1])?;
252            Ok(Some(Value::Tensor(a.matmul(b).map_err(|e| format!("{e}"))?)))
253        }
254        "attention" => {
255            if args.len() != 3 {
256                return Err("attention requires 3 Tensor arguments: queries, keys, values".into());
257            }
258            let q = value_to_tensor(&args[0])?;
259            let k = value_to_tensor(&args[1])?;
260            let v = value_to_tensor(&args[2])?;
261            Ok(Some(Value::Tensor(
262                Tensor::scaled_dot_product_attention(q, k, v).map_err(|e| format!("{e}"))?,
263            )))
264        }
265        "Buffer.alloc" => {
266            if args.is_empty() {
267                return Err("Buffer.alloc requires a length argument".into());
268            }
269            let len = value_to_usize(&args[0])?;
270            Ok(Some(Value::Tensor(Tensor::zeros(&[len]))))
271        }
272        "Tensor.from_bytes" => {
273            if args.len() < 2 || args.len() > 3 {
274                return Err(
275                    "Tensor.from_bytes requires 2-3 arguments: bytes, shape, [dtype='f64']".into(),
276                );
277            }
278            let bytes = match &args[0] {
279                Value::ByteSlice(b) => b.clone(),
280                Value::Bytes(b) => Rc::new(b.borrow().clone()),
281                _ => {
282                    return Err(
283                        "Tensor.from_bytes: first argument must be ByteSlice or Bytes".into(),
284                    )
285                }
286            };
287            let shape = value_to_shape(&args[1])?;
288            let dtype = if args.len() == 3 {
289                match &args[2] {
290                    Value::String(s) => s.as_str().to_string(),
291                    _ => return Err("Tensor.from_bytes: dtype must be a string".into()),
292                }
293            } else {
294                "f64".to_string()
295            };
296            Ok(Some(Value::Tensor(
297                Tensor::from_bytes(&bytes, &shape, &dtype).map_err(|e| format!("{e}"))?,
298            )))
299        }
300        "Scratchpad.new" => {
301            if args.len() != 2 {
302                return Err("Scratchpad.new requires 2 arguments: max_seq_len, dim".into());
303            }
304            let max_seq_len = value_to_usize(&args[0])?;
305            let dim = value_to_usize(&args[1])?;
306            Ok(Some(Value::Scratchpad(Rc::new(RefCell::new(
307                Scratchpad::new(max_seq_len, dim),
308            )))))
309        }
310        "PagedKvCache.new" => {
311            if args.len() != 2 {
312                return Err("PagedKvCache.new requires 2 arguments: max_tokens, dim".into());
313            }
314            let max_tokens = value_to_usize(&args[0])?;
315            let dim = value_to_usize(&args[1])?;
316            Ok(Some(Value::PagedKvCache(Rc::new(RefCell::new(
317                PagedKvCache::new(max_tokens, dim),
318            )))))
319        }
320        "AlignedByteSlice.from_bytes" => {
321            if args.len() != 1 {
322                return Err("AlignedByteSlice.from_bytes requires 1 argument: bytes".into());
323            }
324            let bytes = match &args[0] {
325                Value::ByteSlice(b) => b.clone(),
326                Value::Bytes(b) => Rc::new(b.borrow().clone()),
327                _ => {
328                    return Err(
329                        "AlignedByteSlice.from_bytes: argument must be ByteSlice or Bytes".into(),
330                    )
331                }
332            };
333            Ok(Some(Value::AlignedBytes(
334                crate::aligned_pool::AlignedByteSlice::from_bytes(bytes),
335            )))
336        }
337        "to_string" => {
338            if args.len() != 1 {
339                return Err("to_string requires exactly 1 argument".into());
340            }
341            Ok(Some(Value::String(Rc::new(format!("{}", args[0])))))
342        }
343
344        // ── String manipulation builtins ────────────────────────────────
345        "str_upper" => {
346            if args.len() != 1 { return Err("str_upper requires 1 argument".into()); }
347            match &args[0] {
348                Value::String(s) => Ok(Some(Value::String(Rc::new(s.to_uppercase())))),
349                _ => Err("str_upper: argument must be a string".into()),
350            }
351        }
352        "str_lower" => {
353            if args.len() != 1 { return Err("str_lower requires 1 argument".into()); }
354            match &args[0] {
355                Value::String(s) => Ok(Some(Value::String(Rc::new(s.to_lowercase())))),
356                _ => Err("str_lower: argument must be a string".into()),
357            }
358        }
359        "str_trim" => {
360            if args.len() != 1 { return Err("str_trim requires 1 argument".into()); }
361            match &args[0] {
362                Value::String(s) => Ok(Some(Value::String(Rc::new(s.trim().to_string())))),
363                _ => Err("str_trim: argument must be a string".into()),
364            }
365        }
366        "str_contains" => {
367            if args.len() != 2 { return Err("str_contains requires 2 arguments".into()); }
368            match (&args[0], &args[1]) {
369                (Value::String(haystack), Value::String(needle)) => {
370                    Ok(Some(Value::Bool(haystack.contains(needle.as_str()))))
371                }
372                _ => Err("str_contains: both arguments must be strings".into()),
373            }
374        }
375        "str_replace" => {
376            if args.len() != 3 { return Err("str_replace requires 3 arguments (str, from, to)".into()); }
377            match (&args[0], &args[1], &args[2]) {
378                (Value::String(s), Value::String(from), Value::String(to)) => {
379                    // Replace first occurrence only (matches tidy/stringr semantics).
380                    // Use str_replace_all for global replacement.
381                    Ok(Some(Value::String(Rc::new(s.replacen(from.as_str(), to.as_str(), 1)))))
382                }
383                _ => Err("str_replace: all arguments must be strings".into()),
384            }
385        }
386        "str_split" => {
387            if args.len() != 2 { return Err("str_split requires 2 arguments (str, delimiter)".into()); }
388            match (&args[0], &args[1]) {
389                (Value::String(s), Value::String(delim)) => {
390                    let parts: Vec<Value> = s.split(delim.as_str())
391                        .map(|p| Value::String(Rc::new(p.to_string())))
392                        .collect();
393                    Ok(Some(Value::Array(Rc::new(parts))))
394                }
395                _ => Err("str_split: both arguments must be strings".into()),
396            }
397        }
398        "str_join" => {
399            if args.len() != 2 { return Err("str_join requires 2 arguments (array, delimiter)".into()); }
400            match (&args[0], &args[1]) {
401                (Value::Array(arr), Value::String(delim)) => {
402                    let parts: Vec<String> = arr.iter()
403                        .map(|v| format!("{}", v))
404                        .collect();
405                    Ok(Some(Value::String(Rc::new(parts.join(delim.as_str())))))
406                }
407                _ => Err("str_join: first arg must be array, second must be string".into()),
408            }
409        }
410        "str_starts_with" => {
411            if args.len() != 2 { return Err("str_starts_with requires 2 arguments".into()); }
412            match (&args[0], &args[1]) {
413                (Value::String(s), Value::String(prefix)) => {
414                    Ok(Some(Value::Bool(s.starts_with(prefix.as_str()))))
415                }
416                _ => Err("str_starts_with: both arguments must be strings".into()),
417            }
418        }
419        "str_ends_with" => {
420            if args.len() != 2 { return Err("str_ends_with requires 2 arguments".into()); }
421            match (&args[0], &args[1]) {
422                (Value::String(s), Value::String(suffix)) => {
423                    Ok(Some(Value::Bool(s.ends_with(suffix.as_str()))))
424                }
425                _ => Err("str_ends_with: both arguments must be strings".into()),
426            }
427        }
428        "str_repeat" => {
429            if args.len() != 2 { return Err("str_repeat requires 2 arguments (str, count)".into()); }
430            match (&args[0], &args[1]) {
431                (Value::String(s), Value::Int(n)) => {
432                    if *n < 0 { return Err("str_repeat: count must be non-negative".into()); }
433                    Ok(Some(Value::String(Rc::new(s.repeat(*n as usize)))))
434                }
435                _ => Err("str_repeat: first arg must be string, second must be integer".into()),
436            }
437        }
438        "str_chars" => {
439            if args.len() != 1 { return Err("str_chars requires 1 argument".into()); }
440            match &args[0] {
441                Value::String(s) => {
442                    let chars: Vec<Value> = s.chars()
443                        .map(|c| Value::String(Rc::new(c.to_string())))
444                        .collect();
445                    Ok(Some(Value::Array(Rc::new(chars))))
446                }
447                _ => Err("str_chars: argument must be a string".into()),
448            }
449        }
450        "str_substr" => {
451            if args.len() != 3 { return Err("str_substr requires 3 arguments (str, start, len)".into()); }
452            match (&args[0], &args[1], &args[2]) {
453                (Value::String(s), Value::Int(start), Value::Int(len)) => {
454                    let start = (*start).max(0) as usize;
455                    let len = (*len).max(0) as usize;
456                    let result: String = s.chars().skip(start).take(len).collect();
457                    Ok(Some(Value::String(Rc::new(result))))
458                }
459                _ => Err("str_substr: (str, int, int) expected".into()),
460            }
461        }
462
463        "len" => {
464            if args.len() != 1 {
465                return Err("len requires exactly 1 argument".into());
466            }
467            match &args[0] {
468                Value::Array(arr) => Ok(Some(Value::Int(arr.len() as i64))),
469                Value::String(s) => Ok(Some(Value::Int(s.len() as i64))),
470                Value::Tensor(t) => Ok(Some(Value::Int(t.len() as i64))),
471                Value::Tuple(t) => Ok(Some(Value::Int(t.len() as i64))),
472                other => Err(format!("len not supported for {}", other.type_name())),
473            }
474        }
475        "push" => {
476            if args.len() != 2 {
477                return Err("push requires 2 arguments: array and value".into());
478            }
479            match (&args[0], &args[1]) {
480                (Value::Array(a), val) => {
481                    let mut new_arr = (**a).clone();
482                    new_arr.push(val.clone());
483                    Ok(Some(Value::Array(Rc::new(new_arr))))
484                }
485                _ => Err("push requires an Array as first argument".into()),
486            }
487        }
488        "sort" => {
489            if args.len() != 1 {
490                return Err("sort requires exactly 1 argument".into());
491            }
492            match &args[0] {
493                Value::Array(arr) => {
494                    let mut sorted: Vec<Value> = (**arr).clone();
495                    sorted.sort_by(|a, b| {
496                        let fa = match a {
497                            Value::Float(f) => *f,
498                            Value::Int(i) => *i as f64,
499                            _ => f64::NAN,
500                        };
501                        let fb = match b {
502                            Value::Float(f) => *f,
503                            Value::Int(i) => *i as f64,
504                            _ => f64::NAN,
505                        };
506                        fa.partial_cmp(&fb).unwrap_or(std::cmp::Ordering::Equal)
507                    });
508                    Ok(Some(Value::Array(Rc::new(sorted))))
509                }
510                _ => Err(format!("sort requires an Array, got {}", args[0].type_name())),
511            }
512        }
513        "sqrt" => {
514            if args.len() != 1 {
515                return Err("sqrt requires exactly 1 argument".into());
516            }
517            match &args[0] {
518                Value::Float(f) => Ok(Some(Value::Float(f.sqrt()))),
519                Value::Int(i) => Ok(Some(Value::Float((*i as f64).sqrt()))),
520                _ => Err(format!("sqrt requires a number, got {}", args[0].type_name())),
521            }
522        }
523        "log" => {
524            if args.len() != 1 {
525                return Err("log requires exactly 1 argument".into());
526            }
527            match &args[0] {
528                Value::Float(f) => Ok(Some(Value::Float(f.ln()))),
529                Value::Int(i) => Ok(Some(Value::Float((*i as f64).ln()))),
530                _ => Err(format!("log requires a number, got {}", args[0].type_name())),
531            }
532        }
533        "exp" => {
534            if args.len() != 1 {
535                return Err("exp requires exactly 1 argument".into());
536            }
537            match &args[0] {
538                Value::Float(f) => Ok(Some(Value::Float(f.exp()))),
539                Value::Int(i) => Ok(Some(Value::Float((*i as f64).exp()))),
540                _ => Err(format!("exp requires a number, got {}", args[0].type_name())),
541            }
542        }
543        // ---- Mathematics Hardening Phase: Trigonometric ----
544        "sin" => {
545            if args.len() != 1 { return Err("sin requires exactly 1 argument".into()); }
546            match &args[0] {
547                Value::Float(f) => Ok(Some(Value::Float(f.sin()))),
548                Value::Int(i) => Ok(Some(Value::Float((*i as f64).sin()))),
549                _ => Err(format!("sin requires a number, got {}", args[0].type_name())),
550            }
551        }
552        "cos" => {
553            if args.len() != 1 { return Err("cos requires exactly 1 argument".into()); }
554            match &args[0] {
555                Value::Float(f) => Ok(Some(Value::Float(f.cos()))),
556                Value::Int(i) => Ok(Some(Value::Float((*i as f64).cos()))),
557                _ => Err(format!("cos requires a number, got {}", args[0].type_name())),
558            }
559        }
560        "tan" => {
561            if args.len() != 1 { return Err("tan requires exactly 1 argument".into()); }
562            match &args[0] {
563                Value::Float(f) => Ok(Some(Value::Float(f.tan()))),
564                Value::Int(i) => Ok(Some(Value::Float((*i as f64).tan()))),
565                _ => Err(format!("tan requires a number, got {}", args[0].type_name())),
566            }
567        }
568        "asin" => {
569            if args.len() != 1 { return Err("asin requires exactly 1 argument".into()); }
570            match &args[0] {
571                Value::Float(f) => Ok(Some(Value::Float(f.asin()))),
572                Value::Int(i) => Ok(Some(Value::Float((*i as f64).asin()))),
573                _ => Err(format!("asin requires a number, got {}", args[0].type_name())),
574            }
575        }
576        "acos" => {
577            if args.len() != 1 { return Err("acos requires exactly 1 argument".into()); }
578            match &args[0] {
579                Value::Float(f) => Ok(Some(Value::Float(f.acos()))),
580                Value::Int(i) => Ok(Some(Value::Float((*i as f64).acos()))),
581                _ => Err(format!("acos requires a number, got {}", args[0].type_name())),
582            }
583        }
584        "atan" => {
585            if args.len() != 1 { return Err("atan requires exactly 1 argument".into()); }
586            match &args[0] {
587                Value::Float(f) => Ok(Some(Value::Float(f.atan()))),
588                Value::Int(i) => Ok(Some(Value::Float((*i as f64).atan()))),
589                _ => Err(format!("atan requires a number, got {}", args[0].type_name())),
590            }
591        }
592        "atan2" => {
593            if args.len() != 2 { return Err("atan2 requires exactly 2 arguments".into()); }
594            let y = match &args[0] {
595                Value::Float(f) => *f,
596                Value::Int(i) => *i as f64,
597                _ => return Err(format!("atan2 requires numbers, got {}", args[0].type_name())),
598            };
599            let x = match &args[1] {
600                Value::Float(f) => *f,
601                Value::Int(i) => *i as f64,
602                _ => return Err(format!("atan2 requires numbers, got {}", args[1].type_name())),
603            };
604            Ok(Some(Value::Float(y.atan2(x))))
605        }
606        // ---- Mathematics Hardening Phase: Hyperbolic ----
607        "sinh" => {
608            if args.len() != 1 { return Err("sinh requires exactly 1 argument".into()); }
609            match &args[0] {
610                Value::Float(f) => Ok(Some(Value::Float(f.sinh()))),
611                Value::Int(i) => Ok(Some(Value::Float((*i as f64).sinh()))),
612                _ => Err(format!("sinh requires a number, got {}", args[0].type_name())),
613            }
614        }
615        "cosh" => {
616            if args.len() != 1 { return Err("cosh requires exactly 1 argument".into()); }
617            match &args[0] {
618                Value::Float(f) => Ok(Some(Value::Float(f.cosh()))),
619                Value::Int(i) => Ok(Some(Value::Float((*i as f64).cosh()))),
620                _ => Err(format!("cosh requires a number, got {}", args[0].type_name())),
621            }
622        }
623        "tanh" | "tanh_scalar" => {
624            if args.len() != 1 { return Err("tanh requires exactly 1 argument".into()); }
625            match &args[0] {
626                Value::Float(f) => Ok(Some(Value::Float(f.tanh()))),
627                Value::Int(i) => Ok(Some(Value::Float((*i as f64).tanh()))),
628                Value::Tensor(t) => Ok(Some(Value::Tensor(t.tanh_activation()))),
629                _ => Err(format!("tanh requires a number or Tensor, got {}", args[0].type_name())),
630            }
631        }
632        // ---- Mathematics Hardening Phase: Exponentiation & Logarithms ----
633        "pow" => {
634            if args.len() != 2 { return Err("pow requires exactly 2 arguments".into()); }
635            let base = match &args[0] {
636                Value::Float(f) => *f,
637                Value::Int(i) => *i as f64,
638                _ => return Err(format!("pow requires numbers, got {}", args[0].type_name())),
639            };
640            let exp = match &args[1] {
641                Value::Float(f) => *f,
642                Value::Int(i) => *i as f64,
643                _ => return Err(format!("pow requires numbers, got {}", args[1].type_name())),
644            };
645            Ok(Some(Value::Float(base.powf(exp))))
646        }
647        "log2" => {
648            if args.len() != 1 { return Err("log2 requires exactly 1 argument".into()); }
649            match &args[0] {
650                Value::Float(f) => Ok(Some(Value::Float(f.log2()))),
651                Value::Int(i) => Ok(Some(Value::Float((*i as f64).log2()))),
652                _ => Err(format!("log2 requires a number, got {}", args[0].type_name())),
653            }
654        }
655        "log10" => {
656            if args.len() != 1 { return Err("log10 requires exactly 1 argument".into()); }
657            match &args[0] {
658                Value::Float(f) => Ok(Some(Value::Float(f.log10()))),
659                Value::Int(i) => Ok(Some(Value::Float((*i as f64).log10()))),
660                _ => Err(format!("log10 requires a number, got {}", args[0].type_name())),
661            }
662        }
663        "log1p" => {
664            if args.len() != 1 { return Err("log1p requires exactly 1 argument".into()); }
665            match &args[0] {
666                Value::Float(f) => Ok(Some(Value::Float(f.ln_1p()))),
667                Value::Int(i) => Ok(Some(Value::Float((*i as f64).ln_1p()))),
668                _ => Err(format!("log1p requires a number, got {}", args[0].type_name())),
669            }
670        }
671        "expm1" => {
672            if args.len() != 1 { return Err("expm1 requires exactly 1 argument".into()); }
673            match &args[0] {
674                Value::Float(f) => Ok(Some(Value::Float(f.exp_m1()))),
675                Value::Int(i) => Ok(Some(Value::Float((*i as f64).exp_m1()))),
676                _ => Err(format!("expm1 requires a number, got {}", args[0].type_name())),
677            }
678        }
679        // ---- Mathematics Hardening Phase: Rounding ----
680        "ceil" => {
681            if args.len() != 1 { return Err("ceil requires exactly 1 argument".into()); }
682            match &args[0] {
683                Value::Float(f) => Ok(Some(Value::Float(f.ceil()))),
684                Value::Int(i) => Ok(Some(Value::Int(*i))),
685                _ => Err(format!("ceil requires a number, got {}", args[0].type_name())),
686            }
687        }
688        "round" => {
689            if args.len() != 1 { return Err("round requires exactly 1 argument".into()); }
690            match &args[0] {
691                Value::Float(f) => Ok(Some(Value::Float(f.round()))),
692                Value::Int(i) => Ok(Some(Value::Int(*i))),
693                _ => Err(format!("round requires a number, got {}", args[0].type_name())),
694            }
695        }
696        "floor" => {
697            if args.len() != 1 {
698                return Err("floor requires exactly 1 argument".into());
699            }
700            match &args[0] {
701                Value::Float(f) => Ok(Some(Value::Float(f.floor()))),
702                Value::Int(i) => Ok(Some(Value::Int(*i))),
703                _ => Err(format!("floor requires a number, got {}", args[0].type_name())),
704            }
705        }
706        "int" => {
707            if args.len() != 1 {
708                return Err("int requires exactly 1 argument".into());
709            }
710            match &args[0] {
711                Value::Float(f) => Ok(Some(Value::Int(*f as i64))),
712                Value::Int(i) => Ok(Some(Value::Int(*i))),
713                Value::Bool(b) => Ok(Some(Value::Int(if *b { 1 } else { 0 }))),
714                _ => Err(format!("int requires a number, got {}", args[0].type_name())),
715            }
716        }
717        "float" => {
718            if args.len() != 1 {
719                return Err("float requires exactly 1 argument".into());
720            }
721            match &args[0] {
722                Value::Int(i) => Ok(Some(Value::Float(*i as f64))),
723                Value::Float(f) => Ok(Some(Value::Float(*f))),
724                Value::Bool(b) => Ok(Some(Value::Float(if *b { 1.0 } else { 0.0 }))),
725                _ => Err(format!("float requires a number, got {}", args[0].type_name())),
726            }
727        }
728        "isnan" => {
729            if args.len() != 1 {
730                return Err("isnan requires exactly 1 argument".into());
731            }
732            match &args[0] {
733                Value::Float(f) => Ok(Some(Value::Bool(f.is_nan()))),
734                Value::Int(_) => Ok(Some(Value::Bool(false))),
735                _ => Err(format!("isnan requires a number, got {}", args[0].type_name())),
736            }
737        }
738        "isinf" => {
739            if args.len() != 1 {
740                return Err("isinf requires exactly 1 argument".into());
741            }
742            match &args[0] {
743                Value::Float(f) => Ok(Some(Value::Bool(f.is_infinite()))),
744                Value::Int(_) => Ok(Some(Value::Bool(false))),
745                _ => Err(format!("isinf requires a number, got {}", args[0].type_name())),
746            }
747        }
748        "abs" => {
749            if args.len() != 1 {
750                return Err("abs requires exactly 1 argument".into());
751            }
752            match &args[0] {
753                Value::Float(f) => Ok(Some(Value::Float(f.abs()))),
754                Value::Int(i) => Ok(Some(Value::Int(i.abs()))),
755                _ => Err(format!("abs requires a number, got {}", args[0].type_name())),
756            }
757        }
758        // ---- Mathematics Hardening Phase: Comparison & Sign ----
759        "min" => {
760            if args.len() != 2 { return Err("min requires exactly 2 arguments".into()); }
761            let a = match &args[0] {
762                Value::Float(f) => *f,
763                Value::Int(i) => *i as f64,
764                _ => return Err(format!("min requires numbers, got {}", args[0].type_name())),
765            };
766            let b = match &args[1] {
767                Value::Float(f) => *f,
768                Value::Int(i) => *i as f64,
769                _ => return Err(format!("min requires numbers, got {}", args[1].type_name())),
770            };
771            Ok(Some(Value::Float(a.min(b))))
772        }
773        "max" => {
774            if args.len() != 2 { return Err("max requires exactly 2 arguments".into()); }
775            let a = match &args[0] {
776                Value::Float(f) => *f,
777                Value::Int(i) => *i as f64,
778                _ => return Err(format!("max requires numbers, got {}", args[0].type_name())),
779            };
780            let b = match &args[1] {
781                Value::Float(f) => *f,
782                Value::Int(i) => *i as f64,
783                _ => return Err(format!("max requires numbers, got {}", args[1].type_name())),
784            };
785            Ok(Some(Value::Float(a.max(b))))
786        }
787        "sign" => {
788            if args.len() != 1 { return Err("sign requires exactly 1 argument".into()); }
789            match &args[0] {
790                Value::Float(f) => Ok(Some(Value::Float(f.signum()))),
791                Value::Int(i) => Ok(Some(Value::Float((*i as f64).signum()))),
792                _ => Err(format!("sign requires a number, got {}", args[0].type_name())),
793            }
794        }
795        // ---- Mathematics Hardening Phase: Precision Helpers ----
796        "hypot" => {
797            if args.len() != 2 { return Err("hypot requires exactly 2 arguments".into()); }
798            let x = match &args[0] {
799                Value::Float(f) => *f,
800                Value::Int(i) => *i as f64,
801                _ => return Err(format!("hypot requires numbers, got {}", args[0].type_name())),
802            };
803            let y = match &args[1] {
804                Value::Float(f) => *f,
805                Value::Int(i) => *i as f64,
806                _ => return Err(format!("hypot requires numbers, got {}", args[1].type_name())),
807            };
808            Ok(Some(Value::Float(x.hypot(y))))
809        }
810        // ---- Mathematics Hardening Phase: Constants ----
811        "PI" => {
812            if !args.is_empty() { return Err("PI takes no arguments".into()); }
813            Ok(Some(Value::Float(std::f64::consts::PI)))
814        }
815        "E" => {
816            if !args.is_empty() { return Err("E takes no arguments".into()); }
817            Ok(Some(Value::Float(std::f64::consts::E)))
818        }
819        "TAU" => {
820            if !args.is_empty() { return Err("TAU takes no arguments".into()); }
821            Ok(Some(Value::Float(std::f64::consts::TAU)))
822        }
823        "INF" => {
824            if !args.is_empty() { return Err("INF takes no arguments".into()); }
825            Ok(Some(Value::Float(f64::INFINITY)))
826        }
827        "NAN_VAL" => {
828            if !args.is_empty() { return Err("NAN_VAL takes no arguments".into()); }
829            Ok(Some(Value::Float(f64::NAN)))
830        }
831        // ---- Mathematics Hardening Phase: Vector Operations ----
832        "dot" => {
833            if args.len() != 2 { return Err("dot requires exactly 2 arguments".into()); }
834            let a = match &args[0] {
835                Value::Tensor(t) => t,
836                _ => return Err(format!("dot requires tensors, got {}", args[0].type_name())),
837            };
838            let b = match &args[1] {
839                Value::Tensor(t) => t,
840                _ => return Err(format!("dot requires tensors, got {}", args[1].type_name())),
841            };
842            if a.ndim() != 1 || b.ndim() != 1 {
843                return Err("dot requires 1D tensors".into());
844            }
845            if a.len() != b.len() {
846                return Err(format!("dot: length mismatch ({} vs {})", a.len(), b.len()));
847            }
848            let av = a.to_vec();
849            let bv = b.to_vec();
850            let products: Vec<f64> = av.iter().zip(bv.iter()).map(|(x, y)| x * y).collect();
851            let sum = crate::accumulator::binned_sum_f64(&products);
852            Ok(Some(Value::Float(sum)))
853        }
854        "outer" => {
855            if args.len() != 2 { return Err("outer requires exactly 2 arguments".into()); }
856            let a = match &args[0] {
857                Value::Tensor(t) => t,
858                _ => return Err(format!("outer requires tensors, got {}", args[0].type_name())),
859            };
860            let b = match &args[1] {
861                Value::Tensor(t) => t,
862                _ => return Err(format!("outer requires tensors, got {}", args[1].type_name())),
863            };
864            if a.ndim() != 1 || b.ndim() != 1 {
865                return Err("outer requires 1D tensors".into());
866            }
867            let av = a.to_vec();
868            let bv = b.to_vec();
869            let m = av.len();
870            let n = bv.len();
871            let mut data = Vec::with_capacity(m * n);
872            for ai in &av {
873                for bj in &bv {
874                    data.push(ai * bj);
875                }
876            }
877            Ok(Some(Value::Tensor(Tensor::from_vec(data, &[m, n]).map_err(|e| format!("{e}"))?)))
878        }
879        "cross" => {
880            if args.len() != 2 { return Err("cross requires exactly 2 arguments".into()); }
881            let a = match &args[0] {
882                Value::Tensor(t) => t,
883                _ => return Err(format!("cross requires tensors, got {}", args[0].type_name())),
884            };
885            let b = match &args[1] {
886                Value::Tensor(t) => t,
887                _ => return Err(format!("cross requires tensors, got {}", args[1].type_name())),
888            };
889            if a.ndim() != 1 || b.ndim() != 1 || a.len() != 3 || b.len() != 3 {
890                return Err("cross requires two 3-element 1D tensors".into());
891            }
892            let av = a.to_vec();
893            let bv = b.to_vec();
894            let result = vec![
895                av[1] * bv[2] - av[2] * bv[1],
896                av[2] * bv[0] - av[0] * bv[2],
897                av[0] * bv[1] - av[1] * bv[0],
898            ];
899            Ok(Some(Value::Tensor(Tensor::from_vec(result, &[3]).map_err(|e| format!("{e}"))?)))
900        }
901        "norm" => {
902            if args.len() < 1 || args.len() > 2 { return Err("norm requires 1-2 arguments".into()); }
903            let t = match &args[0] {
904                Value::Tensor(t) => t,
905                _ => return Err(format!("norm requires a tensor, got {}", args[0].type_name())),
906            };
907            let ord = if args.len() == 2 {
908                match &args[1] {
909                    Value::Int(i) => *i,
910                    Value::Float(f) => *f as i64,
911                    _ => return Err("norm: ord must be an integer".into()),
912                }
913            } else {
914                2 // default: L2 norm
915            };
916            let data = t.to_vec();
917            let result = match ord {
918                1 => {
919                    let abs_vals: Vec<f64> = data.iter().map(|x| x.abs()).collect();
920                    crate::accumulator::binned_sum_f64(&abs_vals)
921                }
922                2 => {
923                    let sq_vals: Vec<f64> = data.iter().map(|x| x * x).collect();
924                    crate::accumulator::binned_sum_f64(&sq_vals).sqrt()
925                }
926                _ => {
927                    let p = ord as f64;
928                    let pow_vals: Vec<f64> = data.iter().map(|x| x.abs().powf(p)).collect();
929                    crate::accumulator::binned_sum_f64(&pow_vals).powf(1.0 / p)
930                }
931            };
932            Ok(Some(Value::Float(result)))
933        }
934        // ---- Mathematics Hardening Phase: Tensor Constructors ----
935        "Tensor.linspace" => {
936            if args.len() != 3 { return Err("Tensor.linspace requires 3 arguments (start, end, n)".into()); }
937            let start = match &args[0] {
938                Value::Float(f) => *f,
939                Value::Int(i) => *i as f64,
940                _ => return Err("Tensor.linspace: start must be a number".into()),
941            };
942            let end = match &args[1] {
943                Value::Float(f) => *f,
944                Value::Int(i) => *i as f64,
945                _ => return Err("Tensor.linspace: end must be a number".into()),
946            };
947            let n = match &args[2] {
948                Value::Int(i) => *i as usize,
949                _ => return Err("Tensor.linspace: n must be an integer".into()),
950            };
951            if n == 0 {
952                return Ok(Some(Value::Tensor(Tensor::from_vec(vec![], &[0]).map_err(|e| format!("{e}"))?)));
953            }
954            if n == 1 {
955                return Ok(Some(Value::Tensor(Tensor::from_vec(vec![start], &[1]).map_err(|e| format!("{e}"))?)));
956            }
957            let step = (end - start) / (n as f64 - 1.0);
958            let data: Vec<f64> = (0..n).map(|i| start + step * i as f64).collect();
959            Ok(Some(Value::Tensor(Tensor::from_vec(data, &[n]).map_err(|e| format!("{e}"))?)))
960        }
961        "Tensor.arange" => {
962            if args.len() < 2 || args.len() > 3 { return Err("Tensor.arange requires 2-3 arguments (start, end, step?)".into()); }
963            let start = match &args[0] {
964                Value::Float(f) => *f,
965                Value::Int(i) => *i as f64,
966                _ => return Err("Tensor.arange: start must be a number".into()),
967            };
968            let end = match &args[1] {
969                Value::Float(f) => *f,
970                Value::Int(i) => *i as f64,
971                _ => return Err("Tensor.arange: end must be a number".into()),
972            };
973            let step = if args.len() == 3 {
974                match &args[2] {
975                    Value::Float(f) => *f,
976                    Value::Int(i) => *i as f64,
977                    _ => return Err("Tensor.arange: step must be a number".into()),
978                }
979            } else {
980                1.0
981            };
982            if step == 0.0 { return Err("Tensor.arange: step cannot be zero".into()); }
983            let mut data = Vec::new();
984            let mut val = start;
985            if step > 0.0 {
986                while val < end { data.push(val); val += step; }
987            } else {
988                while val > end { data.push(val); val += step; }
989            }
990            let n = data.len();
991            Ok(Some(Value::Tensor(Tensor::from_vec(data, &[n]).map_err(|e| format!("{e}"))?)))
992        }
993        "Tensor.eye" => {
994            if args.len() != 1 { return Err("Tensor.eye requires 1 argument (n)".into()); }
995            let n = match &args[0] {
996                Value::Int(i) => *i as usize,
997                _ => return Err("Tensor.eye: n must be an integer".into()),
998            };
999            let mut data = vec![0.0; n * n];
1000            for i in 0..n {
1001                data[i * n + i] = 1.0;
1002            }
1003            Ok(Some(Value::Tensor(Tensor::from_vec(data, &[n, n]).map_err(|e| format!("{e}"))?)))
1004        }
1005        "Tensor.full" => {
1006            if args.len() != 2 { return Err("Tensor.full requires 2 arguments (shape, value)".into()); }
1007            let shape = match &args[0] {
1008                Value::Array(arr) => {
1009                    let mut s = Vec::new();
1010                    for v in arr.iter() {
1011                        match v {
1012                            Value::Int(i) => s.push(*i as usize),
1013                            _ => return Err("Tensor.full: shape must be an array of ints".into()),
1014                        }
1015                    }
1016                    s
1017                }
1018                _ => return Err("Tensor.full: shape must be an array".into()),
1019            };
1020            let fill_val = match &args[1] {
1021                Value::Float(f) => *f,
1022                Value::Int(i) => *i as f64,
1023                _ => return Err("Tensor.full: value must be a number".into()),
1024            };
1025            let total: usize = shape.iter().product();
1026            let data = vec![fill_val; total];
1027            Ok(Some(Value::Tensor(Tensor::from_vec(data, &shape).map_err(|e| format!("{e}"))?)))
1028        }
1029        "Tensor.diag" => {
1030            if args.len() != 1 { return Err("Tensor.diag requires 1 argument".into()); }
1031            let t = match &args[0] {
1032                Value::Tensor(t) => t,
1033                _ => return Err("Tensor.diag requires a tensor".into()),
1034            };
1035            match t.ndim() {
1036                1 => {
1037                    // 1D -> 2D diagonal matrix
1038                    let data = t.to_vec();
1039                    let n = data.len();
1040                    let mut out = vec![0.0; n * n];
1041                    for i in 0..n {
1042                        out[i * n + i] = data[i];
1043                    }
1044                    Ok(Some(Value::Tensor(Tensor::from_vec(out, &[n, n]).map_err(|e| format!("{e}"))?)))
1045                }
1046                2 => {
1047                    // 2D -> 1D diagonal extraction
1048                    let rows = t.shape()[0];
1049                    let cols = t.shape()[1];
1050                    let n = rows.min(cols);
1051                    let mut data = Vec::with_capacity(n);
1052                    for i in 0..n {
1053                        data.push(t.get(&[i, i]).map_err(|e| format!("{e}"))?);
1054                    }
1055                    Ok(Some(Value::Tensor(Tensor::from_vec(data, &[n]).map_err(|e| format!("{e}"))?)))
1056                }
1057                _ => Err("Tensor.diag requires a 1D or 2D tensor".into()),
1058            }
1059        }
1060        "assert" => {
1061            if args.len() != 1 {
1062                return Err("assert requires exactly 1 argument".into());
1063            }
1064            match &args[0] {
1065                Value::Bool(true) => Ok(Some(Value::Void)),
1066                Value::Bool(false) => Err("assertion failed".into()),
1067                other => Err(format!("assert requires Bool, got {}", other.type_name())),
1068            }
1069        }
1070        "assert_eq" => {
1071            if args.len() != 2 {
1072                return Err("assert_eq requires exactly 2 arguments".into());
1073            }
1074            if values_equal(&args[0], &args[1]) {
1075                Ok(Some(Value::Void))
1076            } else {
1077                Err(format!("assertion failed: `{}` != `{}`", args[0], args[1]))
1078            }
1079        }
1080        // ── JSON builtins ──────────────────────────────────────────
1081        "json_parse" => {
1082            if args.len() != 1 {
1083                return Err("json_parse requires exactly 1 argument".into());
1084            }
1085            let s = match &args[0] {
1086                Value::String(s) => s.as_str(),
1087                _ => return Err(format!("json_parse requires String, got {}", args[0].type_name())),
1088            };
1089            crate::json::json_parse(s).map(Some)
1090        }
1091        "json_stringify" => {
1092            if args.len() != 1 {
1093                return Err("json_stringify requires exactly 1 argument".into());
1094            }
1095            crate::json::json_stringify(&args[0]).map(|s| Some(Value::String(Rc::new(s))))
1096        }
1097
1098        // ── DateTime builtins (pure arithmetic, except datetime_now) ──
1099        "datetime_from_epoch" => {
1100            if args.len() != 1 {
1101                return Err("datetime_from_epoch requires exactly 1 argument".into());
1102            }
1103            match &args[0] {
1104                Value::Int(n) => Ok(Some(Value::Int(crate::datetime::datetime_from_epoch(*n)))),
1105                _ => Err(format!("datetime_from_epoch requires Int, got {}", args[0].type_name())),
1106            }
1107        }
1108        "datetime_from_parts" => {
1109            if args.len() != 6 {
1110                return Err("datetime_from_parts requires 6 arguments (year, month, day, hour, min, sec)".into());
1111            }
1112            let mut vals = [0i64; 6];
1113            for (i, arg) in args.iter().enumerate() {
1114                match arg {
1115                    Value::Int(n) => vals[i] = *n,
1116                    _ => return Err(format!("datetime_from_parts arg {} must be Int", i)),
1117                }
1118            }
1119            Ok(Some(Value::Int(crate::datetime::datetime_from_parts(
1120                vals[0], vals[1], vals[2], vals[3], vals[4], vals[5],
1121            ))))
1122        }
1123        "datetime_year" => {
1124            if args.len() != 1 { return Err("datetime_year requires 1 argument".into()); }
1125            match &args[0] {
1126                Value::Int(n) => Ok(Some(Value::Int(crate::datetime::datetime_year(*n)))),
1127                _ => Err(format!("datetime_year requires Int, got {}", args[0].type_name())),
1128            }
1129        }
1130        "datetime_month" => {
1131            if args.len() != 1 { return Err("datetime_month requires 1 argument".into()); }
1132            match &args[0] {
1133                Value::Int(n) => Ok(Some(Value::Int(crate::datetime::datetime_month(*n)))),
1134                _ => Err(format!("datetime_month requires Int, got {}", args[0].type_name())),
1135            }
1136        }
1137        "datetime_day" => {
1138            if args.len() != 1 { return Err("datetime_day requires 1 argument".into()); }
1139            match &args[0] {
1140                Value::Int(n) => Ok(Some(Value::Int(crate::datetime::datetime_day(*n)))),
1141                _ => Err(format!("datetime_day requires Int, got {}", args[0].type_name())),
1142            }
1143        }
1144        "datetime_hour" => {
1145            if args.len() != 1 { return Err("datetime_hour requires 1 argument".into()); }
1146            match &args[0] {
1147                Value::Int(n) => Ok(Some(Value::Int(crate::datetime::datetime_hour(*n)))),
1148                _ => Err(format!("datetime_hour requires Int, got {}", args[0].type_name())),
1149            }
1150        }
1151        "datetime_minute" => {
1152            if args.len() != 1 { return Err("datetime_minute requires 1 argument".into()); }
1153            match &args[0] {
1154                Value::Int(n) => Ok(Some(Value::Int(crate::datetime::datetime_minute(*n)))),
1155                _ => Err(format!("datetime_minute requires Int, got {}", args[0].type_name())),
1156            }
1157        }
1158        "datetime_second" => {
1159            if args.len() != 1 { return Err("datetime_second requires 1 argument".into()); }
1160            match &args[0] {
1161                Value::Int(n) => Ok(Some(Value::Int(crate::datetime::datetime_second(*n)))),
1162                _ => Err(format!("datetime_second requires Int, got {}", args[0].type_name())),
1163            }
1164        }
1165        "datetime_diff" => {
1166            if args.len() != 2 { return Err("datetime_diff requires 2 arguments".into()); }
1167            match (&args[0], &args[1]) {
1168                (Value::Int(a), Value::Int(b)) => Ok(Some(Value::Int(crate::datetime::datetime_diff(*a, *b)))),
1169                _ => Err("datetime_diff requires two Int arguments".into()),
1170            }
1171        }
1172        "datetime_add_millis" => {
1173            if args.len() != 2 { return Err("datetime_add_millis requires 2 arguments".into()); }
1174            match (&args[0], &args[1]) {
1175                (Value::Int(a), Value::Int(b)) => Ok(Some(Value::Int(crate::datetime::datetime_add_millis(*a, *b)))),
1176                _ => Err("datetime_add_millis requires two Int arguments".into()),
1177            }
1178        }
1179        "datetime_format" => {
1180            if args.len() != 1 { return Err("datetime_format requires 1 argument".into()); }
1181            match &args[0] {
1182                Value::Int(n) => Ok(Some(Value::String(Rc::new(crate::datetime::datetime_format(*n))))),
1183                _ => Err(format!("datetime_format requires Int, got {}", args[0].type_name())),
1184            }
1185        }
1186
1187        // ── File I/O builtins ─────────────────────────────────────────
1188        "file_read" => {
1189            if args.len() != 1 { return Err("file_read requires 1 argument".into()); }
1190            match &args[0] {
1191                Value::String(path) => {
1192                    let content = std::fs::read_to_string(path.as_str())
1193                        .map_err(|e| format!("file_read error: {}", e))?;
1194                    Ok(Some(Value::String(Rc::new(content))))
1195                }
1196                _ => Err(format!("file_read requires String path, got {}", args[0].type_name())),
1197            }
1198        }
1199        "file_write" => {
1200            if args.len() != 2 { return Err("file_write requires 2 arguments (path, content)".into()); }
1201            match (&args[0], &args[1]) {
1202                (Value::String(path), Value::String(content)) => {
1203                    std::fs::write(path.as_str(), content.as_str())
1204                        .map_err(|e| format!("file_write error: {}", e))?;
1205                    Ok(Some(Value::Void))
1206                }
1207                _ => Err("file_write requires (String, String) arguments".into()),
1208            }
1209        }
1210        "file_exists" => {
1211            if args.len() != 1 { return Err("file_exists requires 1 argument".into()); }
1212            match &args[0] {
1213                Value::String(path) => Ok(Some(Value::Bool(std::path::Path::new(path.as_str()).exists()))),
1214                _ => Err(format!("file_exists requires String path, got {}", args[0].type_name())),
1215            }
1216        }
1217        "file_lines" => {
1218            if args.len() != 1 { return Err("file_lines requires 1 argument".into()); }
1219            match &args[0] {
1220                Value::String(path) => {
1221                    let content = std::fs::read_to_string(path.as_str())
1222                        .map_err(|e| format!("file_lines error: {}", e))?;
1223                    let lines: Vec<Value> = content
1224                        .lines()
1225                        .map(|l| Value::String(Rc::new(l.to_string())))
1226                        .collect();
1227                    Ok(Some(Value::Array(Rc::new(lines))))
1228                }
1229                _ => Err(format!("file_lines requires String path, got {}", args[0].type_name())),
1230            }
1231        }
1232        // ── TidyView Phase 1: Data I/O builtins ──────────────────────
1233        "dir_list" => {
1234            if args.len() != 1 { return Err("dir_list requires 1 argument (path)".into()); }
1235            match &args[0] {
1236                Value::String(path) => {
1237                    let entries = std::fs::read_dir(path.as_str())
1238                        .map_err(|e| format!("dir_list error: {}", e))?;
1239                    // Collect into BTreeSet for deterministic ordering
1240                    let mut sorted = std::collections::BTreeSet::new();
1241                    for entry in entries {
1242                        let entry = entry.map_err(|e| format!("dir_list error: {}", e))?;
1243                        let name = entry.file_name().to_string_lossy().to_string();
1244                        sorted.insert(name);
1245                    }
1246                    let values: Vec<Value> = sorted
1247                        .into_iter()
1248                        .map(|s| Value::String(Rc::new(s)))
1249                        .collect();
1250                    Ok(Some(Value::Array(Rc::new(values))))
1251                }
1252                _ => Err(format!("dir_list requires String path, got {}", args[0].type_name())),
1253            }
1254        }
1255        "path_join" => {
1256            if args.len() != 2 { return Err("path_join requires 2 arguments (base, segment)".into()); }
1257            match (&args[0], &args[1]) {
1258                (Value::String(a), Value::String(b)) => {
1259                    let joined = std::path::Path::new(a.as_str())
1260                        .join(b.as_str())
1261                        .to_string_lossy()
1262                        .to_string();
1263                    Ok(Some(Value::String(Rc::new(joined))))
1264                }
1265                _ => Err(format!(
1266                    "path_join requires (String, String) arguments, got ({}, {})",
1267                    args[0].type_name(), args[1].type_name()
1268                )),
1269            }
1270        }
1271
1272        // ── Window function builtins ──────────────────────────────────
1273        "window_sum" | "window_mean" | "window_min" | "window_max" => {
1274            if args.len() != 2 {
1275                return Err(format!("{name} requires 2 arguments (array, window_size)"));
1276            }
1277            let data = value_to_f64_vec(&args[0])?;
1278            let ws = match &args[1] {
1279                Value::Int(i) => {
1280                    if *i < 0 {
1281                        return Err(format!("{name}: window_size must be non-negative, got {i}"));
1282                    }
1283                    *i as usize
1284                }
1285                _ => return Err(format!("{name} requires Int window_size, got {}", args[1].type_name())),
1286            };
1287            let result = match name {
1288                "window_sum" => crate::window::window_sum(&data, ws),
1289                "window_mean" => crate::window::window_mean(&data, ws),
1290                "window_min" => crate::window::window_min(&data, ws),
1291                "window_max" => crate::window::window_max(&data, ws),
1292                _ => unreachable!(),
1293            };
1294            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1295            Ok(Some(Value::Array(Rc::new(values))))
1296        }
1297
1298        // ── Stats builtins ───────────────────────────────────────────
1299        "mean" => {
1300            if args.len() != 1 { return Err("mean requires 1 argument".into()); }
1301            let data = value_to_f64_vec(&args[0])?;
1302            if data.is_empty() { return Err("mean: empty data".into()); }
1303            Ok(Some(Value::Float(cjc_repro::kahan_sum_f64(&data) / data.len() as f64)))
1304        }
1305        "variance" => {
1306            if args.len() != 1 { return Err("variance requires 1 argument".into()); }
1307            let data = value_to_f64_vec(&args[0])?;
1308            Ok(Some(Value::Float(crate::stats::variance(&data)?)))
1309        }
1310        "sd" => {
1311            if args.len() != 1 { return Err("sd requires 1 argument".into()); }
1312            let data = value_to_f64_vec(&args[0])?;
1313            Ok(Some(Value::Float(crate::stats::sd(&data)?)))
1314        }
1315        "se" => {
1316            if args.len() != 1 { return Err("se requires 1 argument".into()); }
1317            let data = value_to_f64_vec(&args[0])?;
1318            Ok(Some(Value::Float(crate::stats::se(&data)?)))
1319        }
1320        "median" => {
1321            if args.len() != 1 { return Err("median requires 1 argument".into()); }
1322            let data = value_to_f64_vec(&args[0])?;
1323            Ok(Some(Value::Float(crate::stats::median(&data)?)))
1324        }
1325        "quantile" => {
1326            if args.len() != 2 { return Err("quantile requires 2 arguments".into()); }
1327            let data = value_to_f64_vec(&args[0])?;
1328            let p = match &args[1] {
1329                Value::Float(f) => *f,
1330                Value::Int(i) => *i as f64,
1331                _ => return Err("quantile: p must be a number".into()),
1332            };
1333            Ok(Some(Value::Float(crate::stats::quantile(&data, p)?)))
1334        }
1335        // ── Bastion primitives ──────────────────────────────────────
1336        "nth_element" => {
1337            if args.len() != 2 { return Err("nth_element requires 2 arguments: data, k".into()); }
1338            let data = value_to_f64_vec(&args[0])?;
1339            let k = value_to_usize(&args[1])?;
1340            Ok(Some(Value::Float(crate::stats::nth_element_copy(&data, k)?)))
1341        }
1342        "median_fast" => {
1343            if args.len() != 1 { return Err("median_fast requires 1 argument".into()); }
1344            let data = value_to_f64_vec(&args[0])?;
1345            Ok(Some(Value::Float(crate::stats::median_fast(&data)?)))
1346        }
1347        "quantile_fast" => {
1348            if args.len() != 2 { return Err("quantile_fast requires 2 arguments: data, p".into()); }
1349            let data = value_to_f64_vec(&args[0])?;
1350            let p = match &args[1] {
1351                Value::Float(f) => *f,
1352                Value::Int(i) => *i as f64,
1353                _ => return Err("quantile_fast: p must be a number".into()),
1354            };
1355            Ok(Some(Value::Float(crate::stats::quantile_fast(&data, p)?)))
1356        }
1357        "filter_mask" => {
1358            if args.len() != 2 { return Err("filter_mask requires 2 arguments: data, mask".into()); }
1359            let data = value_to_f64_vec(&args[0])?;
1360            let mask: Vec<bool> = match &args[1] {
1361                Value::Array(arr) => arr.iter().map(|v| match v {
1362                    Value::Bool(b) => Ok(*b),
1363                    Value::Int(i) => Ok(*i != 0),
1364                    _ => Err("filter_mask: mask must be array of bools".to_string()),
1365                }).collect::<Result<Vec<_>, _>>()?,
1366                _ => return Err("filter_mask: mask must be an array".into()),
1367            };
1368            let result = crate::stats::filter_mask(&data, &mask)?;
1369            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1370            Ok(Some(Value::Array(Rc::new(values))))
1371        }
1372        "erf" => {
1373            if args.len() != 1 { return Err("erf requires 1 argument".into()); }
1374            let x = match &args[0] {
1375                Value::Float(f) => *f,
1376                Value::Int(i) => *i as f64,
1377                _ => return Err("erf requires a number".into()),
1378            };
1379            Ok(Some(Value::Float(crate::distributions::erf(x))))
1380        }
1381        "erfc" => {
1382            if args.len() != 1 { return Err("erfc requires 1 argument".into()); }
1383            let x = match &args[0] {
1384                Value::Float(f) => *f,
1385                Value::Int(i) => *i as f64,
1386                _ => return Err("erfc requires a number".into()),
1387            };
1388            Ok(Some(Value::Float(crate::distributions::erfc(x))))
1389        }
1390        "iqr" => {
1391            if args.len() != 1 { return Err("iqr requires 1 argument".into()); }
1392            let data = value_to_f64_vec(&args[0])?;
1393            Ok(Some(Value::Float(crate::stats::iqr(&data)?)))
1394        }
1395        "skewness" => {
1396            if args.len() != 1 { return Err("skewness requires 1 argument".into()); }
1397            let data = value_to_f64_vec(&args[0])?;
1398            Ok(Some(Value::Float(crate::stats::skewness(&data)?)))
1399        }
1400        "kurtosis" => {
1401            if args.len() != 1 { return Err("kurtosis requires 1 argument".into()); }
1402            let data = value_to_f64_vec(&args[0])?;
1403            Ok(Some(Value::Float(crate::stats::kurtosis(&data)?)))
1404        }
1405        "z_score" => {
1406            if args.len() != 1 { return Err("z_score requires 1 argument".into()); }
1407            let data = value_to_f64_vec(&args[0])?;
1408            let result = crate::stats::z_score(&data)?;
1409            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1410            Ok(Some(Value::Array(Rc::new(values))))
1411        }
1412        "standardize" => {
1413            if args.len() != 1 { return Err("standardize requires 1 argument".into()); }
1414            let data = value_to_f64_vec(&args[0])?;
1415            let result = crate::stats::standardize(&data)?;
1416            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1417            Ok(Some(Value::Array(Rc::new(values))))
1418        }
1419        "n_distinct" => {
1420            if args.len() != 1 { return Err("n_distinct requires 1 argument".into()); }
1421            let data = value_to_f64_vec(&args[0])?;
1422            Ok(Some(Value::Int(crate::stats::n_distinct(&data) as i64)))
1423        }
1424        // ── Correlation builtins ─────────────────────────────────────
1425        "cor" => {
1426            if args.len() != 2 { return Err("cor requires 2 arguments".into()); }
1427            let x = value_to_f64_vec(&args[0])?;
1428            let y = value_to_f64_vec(&args[1])?;
1429            Ok(Some(Value::Float(crate::stats::cor(&x, &y)?)))
1430        }
1431        "cov" => {
1432            if args.len() != 2 { return Err("cov requires 2 arguments".into()); }
1433            let x = value_to_f64_vec(&args[0])?;
1434            let y = value_to_f64_vec(&args[1])?;
1435            Ok(Some(Value::Float(crate::stats::cov(&x, &y)?)))
1436        }
1437        // ── Distribution builtins ────────────────────────────────────
1438        "normal_cdf" => {
1439            if args.len() != 1 { return Err("normal_cdf requires 1 argument".into()); }
1440            let x = match &args[0] {
1441                Value::Float(f) => *f,
1442                Value::Int(i) => *i as f64,
1443                _ => return Err("normal_cdf requires a number".into()),
1444            };
1445            Ok(Some(Value::Float(crate::distributions::normal_cdf(x))))
1446        }
1447        "normal_pdf" => {
1448            if args.len() != 1 { return Err("normal_pdf requires 1 argument".into()); }
1449            let x = match &args[0] {
1450                Value::Float(f) => *f,
1451                Value::Int(i) => *i as f64,
1452                _ => return Err("normal_pdf requires a number".into()),
1453            };
1454            Ok(Some(Value::Float(crate::distributions::normal_pdf(x))))
1455        }
1456        "normal_ppf" => {
1457            if args.len() != 1 { return Err("normal_ppf requires 1 argument".into()); }
1458            let p = match &args[0] {
1459                Value::Float(f) => *f,
1460                Value::Int(i) => *i as f64,
1461                _ => return Err("normal_ppf requires a number".into()),
1462            };
1463            Ok(Some(Value::Float(crate::distributions::normal_ppf(p)?)))
1464        }
1465        "t_cdf" => {
1466            if args.len() != 2 { return Err("t_cdf requires 2 arguments (x, df)".into()); }
1467            let x = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("t_cdf: x must be a number".into()) };
1468            let df = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("t_cdf: df must be a number".into()) };
1469            Ok(Some(Value::Float(crate::distributions::t_cdf(x, df))))
1470        }
1471        "chi2_cdf" => {
1472            if args.len() != 2 { return Err("chi2_cdf requires 2 arguments (x, df)".into()); }
1473            let x = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("chi2_cdf: x must be a number".into()) };
1474            let df = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("chi2_cdf: df must be a number".into()) };
1475            Ok(Some(Value::Float(crate::distributions::chi2_cdf(x, df))))
1476        }
1477        "f_cdf" => {
1478            if args.len() != 3 { return Err("f_cdf requires 3 arguments (x, df1, df2)".into()); }
1479            let x = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("f_cdf: x must be a number".into()) };
1480            let df1 = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("f_cdf: df1 must be a number".into()) };
1481            let df2 = match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("f_cdf: df2 must be a number".into()) };
1482            Ok(Some(Value::Float(crate::distributions::f_cdf(x, df1, df2))))
1483        }
1484        // ── Hypothesis test builtins ─────────────────────────────────
1485        "t_test" => {
1486            if args.len() != 2 { return Err("t_test requires 2 arguments (data, mu)".into()); }
1487            let data = value_to_f64_vec(&args[0])?;
1488            let mu = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("t_test: mu must be a number".into()) };
1489            let r = crate::hypothesis::t_test(&data, mu)?;
1490            let mut fields = std::collections::BTreeMap::new();
1491            fields.insert("t_statistic".into(), Value::Float(r.t_statistic));
1492            fields.insert("p_value".into(), Value::Float(r.p_value));
1493            fields.insert("df".into(), Value::Float(r.df));
1494            fields.insert("mean".into(), Value::Float(r.mean));
1495            fields.insert("se".into(), Value::Float(r.se));
1496            Ok(Some(Value::Struct { name: "TTestResult".into(), fields }))
1497        }
1498        "t_test_two_sample" => {
1499            if args.len() != 2 { return Err("t_test_two_sample requires 2 arguments".into()); }
1500            let x = value_to_f64_vec(&args[0])?;
1501            let y = value_to_f64_vec(&args[1])?;
1502            let r = crate::hypothesis::t_test_two_sample(&x, &y)?;
1503            let mut fields = std::collections::BTreeMap::new();
1504            fields.insert("t_statistic".into(), Value::Float(r.t_statistic));
1505            fields.insert("p_value".into(), Value::Float(r.p_value));
1506            fields.insert("df".into(), Value::Float(r.df));
1507            Ok(Some(Value::Struct { name: "TTestResult".into(), fields }))
1508        }
1509        "chi_squared_test" => {
1510            if args.len() != 2 { return Err("chi_squared_test requires 2 arguments".into()); }
1511            let obs = value_to_f64_vec(&args[0])?;
1512            let exp = value_to_f64_vec(&args[1])?;
1513            let r = crate::hypothesis::chi_squared_test(&obs, &exp)?;
1514            let mut fields = std::collections::BTreeMap::new();
1515            fields.insert("chi2".into(), Value::Float(r.chi2));
1516            fields.insert("p_value".into(), Value::Float(r.p_value));
1517            fields.insert("df".into(), Value::Float(r.df));
1518            Ok(Some(Value::Struct { name: "ChiSquaredResult".into(), fields }))
1519        }
1520        // ── Linalg builtins ──────────────────────────────────────────
1521        "det" => {
1522            if args.len() != 1 { return Err("det requires 1 Tensor argument".into()); }
1523            let t = value_to_tensor(&args[0])?;
1524            Ok(Some(Value::Float(t.det().map_err(|e| format!("{e}"))?)))
1525        }
1526        "solve" => {
1527            if args.len() != 2 { return Err("solve requires 2 Tensor arguments".into()); }
1528            let a = value_to_tensor(&args[0])?;
1529            let b = value_to_tensor(&args[1])?;
1530            Ok(Some(Value::Tensor(a.solve(b).map_err(|e| format!("{e}"))?)))
1531        }
1532        "lstsq" => {
1533            if args.len() != 2 { return Err("lstsq requires 2 Tensor arguments".into()); }
1534            let a = value_to_tensor(&args[0])?;
1535            let b = value_to_tensor(&args[1])?;
1536            Ok(Some(Value::Tensor(a.lstsq(b).map_err(|e| format!("{e}"))?)))
1537        }
1538        "trace" => {
1539            if args.len() != 1 { return Err("trace requires 1 Tensor argument".into()); }
1540            let t = value_to_tensor(&args[0])?;
1541            Ok(Some(Value::Float(t.trace().map_err(|e| format!("{e}"))?)))
1542        }
1543        "norm_frobenius" => {
1544            if args.len() != 1 { return Err("norm_frobenius requires 1 Tensor argument".into()); }
1545            let t = value_to_tensor(&args[0])?;
1546            Ok(Some(Value::Float(t.norm_frobenius().map_err(|e| format!("{e}"))?)))
1547        }
1548        "eigh" => {
1549            if args.len() != 1 { return Err("eigh requires 1 Tensor argument".into()); }
1550            let t = value_to_tensor(&args[0])?;
1551            let (vals, vecs) = t.eigh().map_err(|e| format!("{e}"))?;
1552            let val_values: Vec<Value> = vals.into_iter().map(Value::Float).collect();
1553            Ok(Some(Value::Tuple(Rc::new(vec![
1554                Value::Array(Rc::new(val_values)),
1555                Value::Tensor(vecs),
1556            ]))))
1557        }
1558        "matrix_rank" => {
1559            if args.len() != 1 { return Err("matrix_rank requires 1 Tensor argument".into()); }
1560            let t = value_to_tensor(&args[0])?;
1561            Ok(Some(Value::Int(t.matrix_rank().map_err(|e| format!("{e}"))? as i64)))
1562        }
1563        "kron" => {
1564            if args.len() != 2 { return Err("kron requires 2 Tensor arguments".into()); }
1565            let a = value_to_tensor(&args[0])?;
1566            let b = value_to_tensor(&args[1])?;
1567            Ok(Some(Value::Tensor(a.kron(b).map_err(|e| format!("{e}"))?)))
1568        }
1569        // ── ML builtins ──────────────────────────────────────────────
1570        "mse_loss" => {
1571            if args.len() != 2 { return Err("mse_loss requires 2 arguments".into()); }
1572            let pred = value_to_f64_vec(&args[0])?;
1573            let target = value_to_f64_vec(&args[1])?;
1574            Ok(Some(Value::Float(crate::ml::mse_loss(&pred, &target)?)))
1575        }
1576        "cross_entropy_loss" => {
1577            if args.len() != 2 { return Err("cross_entropy_loss requires 2 arguments".into()); }
1578            let pred = value_to_f64_vec(&args[0])?;
1579            let target = value_to_f64_vec(&args[1])?;
1580            Ok(Some(Value::Float(crate::ml::cross_entropy_loss(&pred, &target)?)))
1581        }
1582        "huber_loss" => {
1583            if args.len() != 3 { return Err("huber_loss requires 3 arguments".into()); }
1584            let pred = value_to_f64_vec(&args[0])?;
1585            let target = value_to_f64_vec(&args[1])?;
1586            let delta = match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("huber_loss: delta must be a number".into()) };
1587            Ok(Some(Value::Float(crate::ml::huber_loss(&pred, &target, delta)?)))
1588        }
1589        // ── Cumulative builtins ──────────────────────────────────────
1590        "cumsum" => {
1591            if args.len() != 1 { return Err("cumsum requires 1 argument".into()); }
1592            let data = value_to_f64_vec(&args[0])?;
1593            let result = crate::stats::cumsum(&data);
1594            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1595            Ok(Some(Value::Array(Rc::new(values))))
1596        }
1597        "cumprod" => {
1598            if args.len() != 1 { return Err("cumprod requires 1 argument".into()); }
1599            let data = value_to_f64_vec(&args[0])?;
1600            let result = crate::stats::cumprod(&data);
1601            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1602            Ok(Some(Value::Array(Rc::new(values))))
1603        }
1604        "cummax" => {
1605            if args.len() != 1 { return Err("cummax requires 1 argument".into()); }
1606            let data = value_to_f64_vec(&args[0])?;
1607            let result = crate::stats::cummax(&data);
1608            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1609            Ok(Some(Value::Array(Rc::new(values))))
1610        }
1611        "cummin" => {
1612            if args.len() != 1 { return Err("cummin requires 1 argument".into()); }
1613            let data = value_to_f64_vec(&args[0])?;
1614            let result = crate::stats::cummin(&data);
1615            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1616            Ok(Some(Value::Array(Rc::new(values))))
1617        }
1618        "lag" => {
1619            if args.len() != 2 { return Err("lag requires 2 arguments".into()); }
1620            let data = value_to_f64_vec(&args[0])?;
1621            let n = value_to_usize(&args[1])?;
1622            let result = crate::stats::lag(&data, n);
1623            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1624            Ok(Some(Value::Array(Rc::new(values))))
1625        }
1626        "lead" => {
1627            if args.len() != 2 { return Err("lead requires 2 arguments".into()); }
1628            let data = value_to_f64_vec(&args[0])?;
1629            let n = value_to_usize(&args[1])?;
1630            let result = crate::stats::lead(&data, n);
1631            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1632            Ok(Some(Value::Array(Rc::new(values))))
1633        }
1634        "rank" => {
1635            if args.len() != 1 { return Err("rank requires 1 argument".into()); }
1636            let data = value_to_f64_vec(&args[0])?;
1637            let result = crate::stats::rank(&data);
1638            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1639            Ok(Some(Value::Array(Rc::new(values))))
1640        }
1641        "dense_rank" => {
1642            if args.len() != 1 { return Err("dense_rank requires 1 argument".into()); }
1643            let data = value_to_f64_vec(&args[0])?;
1644            let result = crate::stats::dense_rank(&data);
1645            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1646            Ok(Some(Value::Array(Rc::new(values))))
1647        }
1648        "histogram" => {
1649            if args.len() != 2 { return Err("histogram requires 2 arguments".into()); }
1650            let data = value_to_f64_vec(&args[0])?;
1651            let n_bins = value_to_usize(&args[1])?;
1652            let (edges, counts) = crate::stats::histogram(&data, n_bins)?;
1653            let edge_values: Vec<Value> = edges.into_iter().map(Value::Float).collect();
1654            let count_values: Vec<Value> = counts.into_iter().map(|c| Value::Int(c as i64)).collect();
1655            Ok(Some(Value::Tuple(Rc::new(vec![
1656                Value::Array(Rc::new(edge_values)),
1657                Value::Array(Rc::new(count_values)),
1658            ]))))
1659        }
1660        // ── Additional stats builtins ───────────────────────────────
1661        "sample_variance" => {
1662            if args.len() != 1 { return Err("sample_variance requires 1 argument".into()); }
1663            let data = value_to_f64_vec(&args[0])?;
1664            Ok(Some(Value::Float(crate::stats::sample_variance(&data)?)))
1665        }
1666        "sample_sd" => {
1667            if args.len() != 1 { return Err("sample_sd requires 1 argument".into()); }
1668            let data = value_to_f64_vec(&args[0])?;
1669            Ok(Some(Value::Float(crate::stats::sample_sd(&data)?)))
1670        }
1671        "sample_cov" => {
1672            if args.len() != 2 { return Err("sample_cov requires 2 arguments".into()); }
1673            let x = value_to_f64_vec(&args[0])?;
1674            let y = value_to_f64_vec(&args[1])?;
1675            Ok(Some(Value::Float(crate::stats::sample_cov(&x, &y)?)))
1676        }
1677        "row_number" => {
1678            if args.len() != 1 { return Err("row_number requires 1 argument".into()); }
1679            let data = value_to_f64_vec(&args[0])?;
1680            let result = crate::stats::row_number(&data);
1681            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1682            Ok(Some(Value::Array(Rc::new(values))))
1683        }
1684        // ── Distribution PPF builtins ───────────────────────────────
1685        "t_ppf" => {
1686            if args.len() != 2 { return Err("t_ppf requires 2 arguments (p, df)".into()); }
1687            let p = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("t_ppf: p must be a number".into()) };
1688            let df = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("t_ppf: df must be a number".into()) };
1689            Ok(Some(Value::Float(crate::distributions::t_ppf(p, df)?)))
1690        }
1691        "chi2_ppf" => {
1692            if args.len() != 2 { return Err("chi2_ppf requires 2 arguments (p, df)".into()); }
1693            let p = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("chi2_ppf: p must be a number".into()) };
1694            let df = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("chi2_ppf: df must be a number".into()) };
1695            Ok(Some(Value::Float(crate::distributions::chi2_ppf(p, df)?)))
1696        }
1697        "f_ppf" => {
1698            if args.len() != 3 { return Err("f_ppf requires 3 arguments (p, df1, df2)".into()); }
1699            let p = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("f_ppf: p must be a number".into()) };
1700            let df1 = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("f_ppf: df1 must be a number".into()) };
1701            let df2 = match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("f_ppf: df2 must be a number".into()) };
1702            Ok(Some(Value::Float(crate::distributions::f_ppf(p, df1, df2)?)))
1703        }
1704        // ── Discrete distribution builtins ──────────────────────────
1705        "binomial_pmf" => {
1706            if args.len() != 3 { return Err("binomial_pmf requires 3 arguments (k, n, p)".into()); }
1707            let k = value_to_usize(&args[0])? as u64;
1708            let n = value_to_usize(&args[1])? as u64;
1709            let p = match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("binomial_pmf: p must be a number".into()) };
1710            Ok(Some(Value::Float(crate::distributions::binomial_pmf(k, n, p))))
1711        }
1712        "binomial_cdf" => {
1713            if args.len() != 3 { return Err("binomial_cdf requires 3 arguments (k, n, p)".into()); }
1714            let k = value_to_usize(&args[0])? as u64;
1715            let n = value_to_usize(&args[1])? as u64;
1716            let p = match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("binomial_cdf: p must be a number".into()) };
1717            Ok(Some(Value::Float(crate::distributions::binomial_cdf(k, n, p))))
1718        }
1719        "poisson_pmf" => {
1720            if args.len() != 2 { return Err("poisson_pmf requires 2 arguments (k, lambda)".into()); }
1721            let k = value_to_usize(&args[0])? as u64;
1722            let lambda = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("poisson_pmf: lambda must be a number".into()) };
1723            Ok(Some(Value::Float(crate::distributions::poisson_pmf(k, lambda))))
1724        }
1725        "poisson_cdf" => {
1726            if args.len() != 2 { return Err("poisson_cdf requires 2 arguments (k, lambda)".into()); }
1727            let k = value_to_usize(&args[0])? as u64;
1728            let lambda = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("poisson_cdf: lambda must be a number".into()) };
1729            Ok(Some(Value::Float(crate::distributions::poisson_cdf(k, lambda))))
1730        }
1731        // ── Hypothesis test builtins (additional) ───────────────────
1732        "t_test_paired" => {
1733            if args.len() != 2 { return Err("t_test_paired requires 2 arguments".into()); }
1734            let x = value_to_f64_vec(&args[0])?;
1735            let y = value_to_f64_vec(&args[1])?;
1736            let r = crate::hypothesis::t_test_paired(&x, &y)?;
1737            let mut fields = std::collections::BTreeMap::new();
1738            fields.insert("t_statistic".into(), Value::Float(r.t_statistic));
1739            fields.insert("p_value".into(), Value::Float(r.p_value));
1740            fields.insert("df".into(), Value::Float(r.df));
1741            Ok(Some(Value::Struct { name: "TTestResult".into(), fields }))
1742        }
1743        "anova_oneway" => {
1744            if args.len() < 2 { return Err("anova_oneway requires at least 2 group arguments".into()); }
1745            let mut groups = Vec::new();
1746            let mut group_vecs = Vec::new();
1747            for a in args.iter() {
1748                group_vecs.push(value_to_f64_vec(a)?);
1749            }
1750            for gv in &group_vecs {
1751                groups.push(gv.as_slice());
1752            }
1753            let r = crate::hypothesis::anova_oneway(&groups)?;
1754            let mut fields = std::collections::BTreeMap::new();
1755            fields.insert("f_statistic".into(), Value::Float(r.f_statistic));
1756            fields.insert("p_value".into(), Value::Float(r.p_value));
1757            fields.insert("df_between".into(), Value::Float(r.df_between));
1758            fields.insert("df_within".into(), Value::Float(r.df_within));
1759            fields.insert("ss_between".into(), Value::Float(r.ss_between));
1760            fields.insert("ss_within".into(), Value::Float(r.ss_within));
1761            Ok(Some(Value::Struct { name: "AnovaResult".into(), fields }))
1762        }
1763        "f_test" => {
1764            if args.len() != 2 { return Err("f_test requires 2 arguments".into()); }
1765            let x = value_to_f64_vec(&args[0])?;
1766            let y = value_to_f64_vec(&args[1])?;
1767            let (f_stat, p_val) = crate::hypothesis::f_test(&x, &y)?;
1768            let mut fields = std::collections::BTreeMap::new();
1769            fields.insert("f_statistic".into(), Value::Float(f_stat));
1770            fields.insert("p_value".into(), Value::Float(p_val));
1771            Ok(Some(Value::Struct { name: "FTestResult".into(), fields }))
1772        }
1773        "lm" => {
1774            // lm(X, y, n, p) — linear model
1775            if args.len() != 4 { return Err("lm requires 4 arguments (X_flat, y, n, p)".into()); }
1776            let x_flat = value_to_f64_vec(&args[0])?;
1777            let y = value_to_f64_vec(&args[1])?;
1778            let n = value_to_usize(&args[2])?;
1779            let p = value_to_usize(&args[3])?;
1780            let r = crate::hypothesis::lm(&x_flat, &y, n, p)?;
1781            let coef_values: Vec<Value> = r.coefficients.into_iter().map(Value::Float).collect();
1782            let se_values: Vec<Value> = r.std_errors.into_iter().map(Value::Float).collect();
1783            let t_values: Vec<Value> = r.t_values.into_iter().map(Value::Float).collect();
1784            let p_values: Vec<Value> = r.p_values.into_iter().map(Value::Float).collect();
1785            let resid_values: Vec<Value> = r.residuals.into_iter().map(Value::Float).collect();
1786            let mut fields = std::collections::BTreeMap::new();
1787            fields.insert("coefficients".into(), Value::Array(Rc::new(coef_values)));
1788            fields.insert("std_errors".into(), Value::Array(Rc::new(se_values)));
1789            fields.insert("t_values".into(), Value::Array(Rc::new(t_values)));
1790            fields.insert("p_values".into(), Value::Array(Rc::new(p_values)));
1791            fields.insert("r_squared".into(), Value::Float(r.r_squared));
1792            fields.insert("adj_r_squared".into(), Value::Float(r.adj_r_squared));
1793            fields.insert("residuals".into(), Value::Array(Rc::new(resid_values)));
1794            fields.insert("f_statistic".into(), Value::Float(r.f_statistic));
1795            Ok(Some(Value::Struct { name: "LmResult".into(), fields }))
1796        }
1797        // ── ML builtins (additional) ────────────────────────────────
1798        "binary_cross_entropy" => {
1799            if args.len() != 2 { return Err("binary_cross_entropy requires 2 arguments".into()); }
1800            let pred = value_to_f64_vec(&args[0])?;
1801            let target = value_to_f64_vec(&args[1])?;
1802            Ok(Some(Value::Float(crate::ml::binary_cross_entropy(&pred, &target)?)))
1803        }
1804        "hinge_loss" => {
1805            if args.len() != 2 { return Err("hinge_loss requires 2 arguments".into()); }
1806            let pred = value_to_f64_vec(&args[0])?;
1807            let target = value_to_f64_vec(&args[1])?;
1808            Ok(Some(Value::Float(crate::ml::hinge_loss(&pred, &target)?)))
1809        }
1810        "confusion_matrix" => {
1811            if args.len() != 2 { return Err("confusion_matrix requires 2 arguments".into()); }
1812            let pred = value_to_f64_vec(&args[0])?;
1813            let actual = value_to_f64_vec(&args[1])?;
1814            let pred_bool: Vec<bool> = pred.iter().map(|&x| x > 0.5).collect();
1815            let actual_bool: Vec<bool> = actual.iter().map(|&x| x > 0.5).collect();
1816            let cm = crate::ml::confusion_matrix(&pred_bool, &actual_bool);
1817            let mut fields = std::collections::BTreeMap::new();
1818            fields.insert("tp".into(), Value::Int(cm.tp as i64));
1819            fields.insert("fp".into(), Value::Int(cm.fp as i64));
1820            fields.insert("tn".into(), Value::Int(cm.tn as i64));
1821            fields.insert("fn_count".into(), Value::Int(cm.fn_count as i64));
1822            fields.insert("precision".into(), Value::Float(crate::ml::precision(&cm)));
1823            fields.insert("recall".into(), Value::Float(crate::ml::recall(&cm)));
1824            fields.insert("f1_score".into(), Value::Float(crate::ml::f1_score(&cm)));
1825            fields.insert("accuracy".into(), Value::Float(crate::ml::accuracy(&cm)));
1826            Ok(Some(Value::Struct { name: "ConfusionMatrix".into(), fields }))
1827        }
1828        "auc_roc" => {
1829            if args.len() != 2 { return Err("auc_roc requires 2 arguments (scores, labels)".into()); }
1830            let scores = value_to_f64_vec(&args[0])?;
1831            let labels_f = value_to_f64_vec(&args[1])?;
1832            let labels: Vec<bool> = labels_f.iter().map(|&x| x > 0.5).collect();
1833            Ok(Some(Value::Float(crate::ml::auc_roc(&scores, &labels)?)))
1834        }
1835        // ── Embedding layer ─────────────────────────────────────────
1836        "embedding" => {
1837            if args.len() != 2 { return Err("embedding requires 2 arguments (weight, indices)".into()); }
1838            let weight = value_to_tensor(&args[0])?;
1839            let indices: Vec<i64> = match &args[1] {
1840                Value::Array(arr) => arr.iter().map(|v| match v {
1841                    Value::Int(i) => Ok(*i),
1842                    _ => Err("embedding: indices must be integers".to_string()),
1843                }).collect::<Result<Vec<_>, _>>()?,
1844                Value::Tensor(t) => t.to_vec().iter().map(|f| Ok::<i64, String>(*f as i64)).collect::<Result<Vec<_>, _>>()?,
1845                _ => return Err("embedding: indices must be an array or tensor".into()),
1846            };
1847            let result = crate::ml::embedding(weight, &indices)?;
1848            Ok(Some(Value::Tensor(result)))
1849        }
1850        // ── Deterministic batch indices ─────────────────────────────
1851        "batch_indices" => {
1852            if args.len() != 3 { return Err("batch_indices requires 3 arguments (dataset_size, batch_size, seed)".into()); }
1853            let ds = match &args[0] { Value::Int(i) => *i as usize, _ => return Err("batch_indices: dataset_size must be int".into()) };
1854            let bs = match &args[1] { Value::Int(i) => *i as usize, _ => return Err("batch_indices: batch_size must be int".into()) };
1855            let seed = match &args[2] { Value::Int(i) => *i as u64, _ => return Err("batch_indices: seed must be int".into()) };
1856            let result = crate::ml::batch_indices(ds, bs, seed);
1857            let arr: Vec<Value> = result.into_iter().map(|(s, e)| {
1858                Value::Array(Rc::new(vec![Value::Int(s as i64), Value::Int(e as i64)]))
1859            }).collect();
1860            Ok(Some(Value::Array(Rc::new(arr))))
1861        }
1862        // ── Tensor activation builtins ──────────────────────────────
1863        "sigmoid" => {
1864            if args.len() != 1 { return Err("sigmoid requires 1 argument".into()); }
1865            let t = value_to_tensor(&args[0])?;
1866            Ok(Some(Value::Tensor(t.sigmoid())))
1867        }
1868        "tanh_activation" => {
1869            if args.len() != 1 { return Err("tanh_activation requires 1 argument".into()); }
1870            let t = value_to_tensor(&args[0])?;
1871            Ok(Some(Value::Tensor(t.tanh_activation())))
1872        }
1873        "leaky_relu" => {
1874            if args.len() != 2 { return Err("leaky_relu requires 2 arguments (tensor, alpha)".into()); }
1875            let t = value_to_tensor(&args[0])?;
1876            let alpha = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("leaky_relu: alpha must be a number".into()) };
1877            Ok(Some(Value::Tensor(t.leaky_relu(alpha))))
1878        }
1879        "silu" => {
1880            if args.len() != 1 { return Err("silu requires 1 argument".into()); }
1881            let t = value_to_tensor(&args[0])?;
1882            Ok(Some(Value::Tensor(t.silu())))
1883        }
1884        "mish" => {
1885            if args.len() != 1 { return Err("mish requires 1 argument".into()); }
1886            let t = value_to_tensor(&args[0])?;
1887            Ok(Some(Value::Tensor(t.mish())))
1888        }
1889        // ── relu: scalar + tensor unified ──────────────────────────────
1890        "relu" => {
1891            if args.len() != 1 { return Err("relu requires 1 argument".into()); }
1892            match &args[0] {
1893                Value::Float(f) => Ok(Some(Value::Float(f.max(0.0)))),
1894                Value::Int(i) => Ok(Some(Value::Int((*i).max(0)))),
1895                Value::Tensor(t) => Ok(Some(Value::Tensor(t.relu()))),
1896                _ => Err(format!("relu requires a number or Tensor, got {}", args[0].type_name())),
1897            }
1898        }
1899        // ── reshape: reshape a tensor ──────────────────────────────────
1900        "reshape" => {
1901            if args.len() != 2 { return Err("reshape requires 2 arguments (tensor, shape)".into()); }
1902            let t = value_to_tensor(&args[0])?;
1903            let shape = value_to_shape(&args[1])?;
1904            let result = t.reshape(&shape).map_err(|e| format!("{e}"))?;
1905            Ok(Some(Value::Tensor(result)))
1906        }
1907        // ── tensor_slice: slice a tensor along all dims ────────────────
1908        "tensor_slice" => {
1909            if args.len() != 3 { return Err("tensor_slice requires 3 arguments (tensor, starts, ends)".into()); }
1910            let t = value_to_tensor(&args[0])?;
1911            let starts = value_to_usize_vec(&args[1])?;
1912            let ends = value_to_usize_vec(&args[2])?;
1913            if starts.len() != ends.len() {
1914                return Err("tensor_slice: starts and ends must have same length".into());
1915            }
1916            let ranges: Vec<(usize, usize)> = starts.into_iter().zip(ends).collect();
1917            let result = t.slice(&ranges).map_err(|e| format!("{e}"))?;
1918            Ok(Some(Value::Tensor(result)))
1919        }
1920        // ── slice: slice tensor along one dim ──────────────────────────
1921        "slice" => {
1922            if args.len() != 4 { return Err("slice requires 4 arguments (tensor, dim, start, end)".into()); }
1923            let t = value_to_tensor(&args[0])?;
1924            let dim = match &args[1] { Value::Int(i) => *i as usize, _ => return Err("slice: dim must be an integer".into()) };
1925            let start = match &args[2] { Value::Int(i) => *i as usize, _ => return Err("slice: start must be an integer".into()) };
1926            let end = match &args[3] { Value::Int(i) => *i as usize, _ => return Err("slice: end must be an integer".into()) };
1927            if dim >= t.ndim() {
1928                return Err(format!("slice: dim {} out of bounds for tensor with {} dimensions", dim, t.ndim()));
1929            }
1930            let mut ranges: Vec<(usize, usize)> = t.shape().iter().map(|&s| (0, s)).collect();
1931            ranges[dim] = (start, end);
1932            let result = t.slice(&ranges).map_err(|e| format!("{e}"))?;
1933            Ok(Some(Value::Tensor(result)))
1934        }
1935        "argmax" => {
1936            if args.len() != 1 { return Err("argmax requires 1 argument".into()); }
1937            let t = value_to_tensor(&args[0])?;
1938            Ok(Some(Value::Int(t.argmax() as i64)))
1939        }
1940        "argmin" => {
1941            if args.len() != 1 { return Err("argmin requires 1 argument".into()); }
1942            let t = value_to_tensor(&args[0])?;
1943            Ok(Some(Value::Int(t.argmin() as i64)))
1944        }
1945        "clamp" => {
1946            if args.len() != 3 { return Err("clamp requires 3 arguments (tensor, min, max)".into()); }
1947            let t = value_to_tensor(&args[0])?;
1948            let min_v = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("clamp: min must be a number".into()) };
1949            let max_v = match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("clamp: max must be a number".into()) };
1950            Ok(Some(Value::Tensor(t.clamp(min_v, max_v))))
1951        }
1952        "one_hot" => {
1953            if args.len() != 2 { return Err("one_hot requires 2 arguments (indices, depth)".into()); }
1954            let indices = value_to_usize_vec(&args[0])?;
1955            let depth = value_to_usize(&args[1])?;
1956            Ok(Some(Value::Tensor(Tensor::one_hot(&indices, depth).map_err(|e| format!("{e}"))?)))
1957        }
1958        // ── FFT builtins ────────────────────────────────────────────
1959        "rfft" => {
1960            if args.len() != 1 { return Err("rfft requires 1 argument".into()); }
1961            let data = value_to_f64_vec(&args[0])?;
1962            let result = crate::fft::rfft(&data);
1963            let pairs: Vec<Value> = result.iter().map(|&(re, im)| {
1964                Value::Tuple(Rc::new(vec![Value::Float(re), Value::Float(im)]))
1965            }).collect();
1966            Ok(Some(Value::Array(Rc::new(pairs))))
1967        }
1968        "psd" => {
1969            if args.len() != 1 { return Err("psd requires 1 argument".into()); }
1970            let data = value_to_f64_vec(&args[0])?;
1971            let result = crate::fft::psd(&data);
1972            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1973            Ok(Some(Value::Array(Rc::new(values))))
1974        }
1975
1976        // ── B1: Weighted & robust statistics ──────────────────────────
1977        "weighted_mean" => {
1978            if args.len() != 2 { return Err("weighted_mean requires 2 arguments".into()); }
1979            let data = value_to_f64_vec(&args[0])?;
1980            let weights = value_to_f64_vec(&args[1])?;
1981            Ok(Some(Value::Float(crate::stats::weighted_mean(&data, &weights)?)))
1982        }
1983        "weighted_var" => {
1984            if args.len() != 2 { return Err("weighted_var requires 2 arguments".into()); }
1985            let data = value_to_f64_vec(&args[0])?;
1986            let weights = value_to_f64_vec(&args[1])?;
1987            Ok(Some(Value::Float(crate::stats::weighted_var(&data, &weights)?)))
1988        }
1989        "trimmed_mean" => {
1990            if args.len() != 2 { return Err("trimmed_mean requires 2 arguments".into()); }
1991            let data = value_to_f64_vec(&args[0])?;
1992            let prop = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("trimmed_mean: proportion must be a number".into()) };
1993            Ok(Some(Value::Float(crate::stats::trimmed_mean(&data, prop)?)))
1994        }
1995        "winsorize" => {
1996            if args.len() != 2 { return Err("winsorize requires 2 arguments".into()); }
1997            let data = value_to_f64_vec(&args[0])?;
1998            let prop = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("winsorize: proportion must be a number".into()) };
1999            let result = crate::stats::winsorize(&data, prop)?;
2000            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2001            Ok(Some(Value::Array(Rc::new(values))))
2002        }
2003        "mad" => {
2004            if args.len() != 1 { return Err("mad requires 1 argument".into()); }
2005            let data = value_to_f64_vec(&args[0])?;
2006            Ok(Some(Value::Float(crate::stats::mad(&data)?)))
2007        }
2008        "mode" => {
2009            if args.len() != 1 { return Err("mode requires 1 argument".into()); }
2010            let data = value_to_f64_vec(&args[0])?;
2011            Ok(Some(Value::Float(crate::stats::mode(&data)?)))
2012        }
2013        "percentile_rank" => {
2014            if args.len() != 2 { return Err("percentile_rank requires 2 arguments".into()); }
2015            let data = value_to_f64_vec(&args[0])?;
2016            let value = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("percentile_rank: value must be a number".into()) };
2017            Ok(Some(Value::Float(crate::stats::percentile_rank(&data, value)?)))
2018        }
2019
2020        // ── B4: ML training extensions ─────────────────────────────────
2021        "cat" => {
2022            if args.len() != 2 { return Err("cat requires 2 arguments (array of tensors, axis)".into()); }
2023            let tensors_arr = match &args[0] {
2024                Value::Array(arr) => arr.iter().map(|v| match v {
2025                    Value::Tensor(t) => Ok(t),
2026                    _ => Err("cat: first argument must be array of tensors".to_string()),
2027                }).collect::<Result<Vec<&Tensor>, String>>()?,
2028                _ => return Err("cat: first argument must be array of tensors".into()),
2029            };
2030            let axis = value_to_usize(&args[1])?;
2031            let refs: Vec<&Tensor> = tensors_arr;
2032            Ok(Some(Value::Tensor(Tensor::cat(&refs, axis).map_err(|e| format!("{e}"))?)))
2033        }
2034        "stack" => {
2035            if args.len() != 2 { return Err("stack requires 2 arguments (array of tensors, axis)".into()); }
2036            let tensors_arr = match &args[0] {
2037                Value::Array(arr) => arr.iter().map(|v| match v {
2038                    Value::Tensor(t) => Ok(t),
2039                    _ => Err("stack: first argument must be array of tensors".to_string()),
2040                }).collect::<Result<Vec<&Tensor>, String>>()?,
2041                _ => return Err("stack: first argument must be array of tensors".into()),
2042            };
2043            let axis = value_to_usize(&args[1])?;
2044            Ok(Some(Value::Tensor(Tensor::stack(&tensors_arr, axis).map_err(|e| format!("{e}"))?)))
2045        }
2046        "topk" => {
2047            if args.len() != 2 { return Err("topk requires 2 arguments (tensor, k)".into()); }
2048            let t = value_to_tensor(&args[0])?;
2049            let k = value_to_usize(&args[1])?;
2050            let (vals, idxs) = t.topk(k).map_err(|e| format!("{e}"))?;
2051            let idx_values: Vec<Value> = idxs.into_iter().map(|i| Value::Int(i as i64)).collect();
2052            Ok(Some(Value::Tuple(Rc::new(vec![Value::Tensor(vals), Value::Array(Rc::new(idx_values))]))))
2053        }
2054        "batch_norm" => {
2055            if args.len() != 6 { return Err("batch_norm requires 6 arguments".into()); }
2056            let x = value_to_f64_vec(&args[0])?;
2057            let mean = value_to_f64_vec(&args[1])?;
2058            let var = value_to_f64_vec(&args[2])?;
2059            let gamma = value_to_f64_vec(&args[3])?;
2060            let beta = value_to_f64_vec(&args[4])?;
2061            let eps = match &args[5] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("batch_norm: eps must be a number".into()) };
2062            let result = crate::ml::batch_norm(&x, &mean, &var, &gamma, &beta, eps)?;
2063            let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2064            Ok(Some(Value::Array(Rc::new(values))))
2065        }
2066        "dropout_mask" => {
2067            if args.len() != 3 { return Err("dropout_mask requires 3 arguments (n, prob, seed)".into()); }
2068            let n = value_to_usize(&args[0])?;
2069            let prob = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("dropout_mask: prob must be a number".into()) };
2070            let seed = match &args[2] { Value::Int(i) => *i as u64, _ => return Err("dropout_mask: seed must be an integer".into()) };
2071            let mask = crate::ml::dropout_mask(n, prob, seed);
2072            let values: Vec<Value> = mask.into_iter().map(Value::Float).collect();
2073            Ok(Some(Value::Array(Rc::new(values))))
2074        }
2075        "lr_step_decay" => {
2076            if args.len() != 4 { return Err("lr_step_decay requires 4 arguments".into()); }
2077            let lr = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("lr_step_decay: lr must be a number".into()) };
2078            let rate = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("lr_step_decay: rate must be a number".into()) };
2079            let epoch = value_to_usize(&args[2])?;
2080            let step = value_to_usize(&args[3])?;
2081            Ok(Some(Value::Float(crate::ml::lr_step_decay(lr, rate, epoch, step))))
2082        }
2083        "lr_cosine" => {
2084            if args.len() != 4 { return Err("lr_cosine requires 4 arguments".into()); }
2085            let max_lr = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("lr_cosine: max_lr must be a number".into()) };
2086            let min_lr = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("lr_cosine: min_lr must be a number".into()) };
2087            let epoch = value_to_usize(&args[2])?;
2088            let total = value_to_usize(&args[3])?;
2089            Ok(Some(Value::Float(crate::ml::lr_cosine(max_lr, min_lr, epoch, total))))
2090        }
2091        "lr_linear_warmup" => {
2092            if args.len() != 3 { return Err("lr_linear_warmup requires 3 arguments".into()); }
2093            let lr = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("lr_linear_warmup: lr must be a number".into()) };
2094            let epoch = value_to_usize(&args[1])?;
2095            let warmup = value_to_usize(&args[2])?;
2096            Ok(Some(Value::Float(crate::ml::lr_linear_warmup(lr, epoch, warmup))))
2097        }
2098        "l1_penalty" => {
2099            if args.len() != 2 { return Err("l1_penalty requires 2 arguments".into()); }
2100            let params = value_to_f64_vec(&args[0])?;
2101            let lambda = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("l1_penalty: lambda must be a number".into()) };
2102            Ok(Some(Value::Float(crate::ml::l1_penalty(&params, lambda))))
2103        }
2104        "l2_penalty" => {
2105            if args.len() != 2 { return Err("l2_penalty requires 2 arguments".into()); }
2106            let params = value_to_f64_vec(&args[0])?;
2107            let lambda = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("l2_penalty: lambda must be a number".into()) };
2108            Ok(Some(Value::Float(crate::ml::l2_penalty(&params, lambda))))
2109        }
2110
2111        // ── B3: Linear algebra extensions ─────────────────────────────
2112        "cond" => {
2113            if args.len() != 1 { return Err("cond requires 1 Tensor argument".into()); }
2114            let t = value_to_tensor(&args[0])?;
2115            Ok(Some(Value::Float(t.cond().map_err(|e| format!("{e}"))?)))
2116        }
2117        "norm_1" => {
2118            if args.len() != 1 { return Err("norm_1 requires 1 Tensor argument".into()); }
2119            let t = value_to_tensor(&args[0])?;
2120            Ok(Some(Value::Float(t.norm_1().map_err(|e| format!("{e}"))?)))
2121        }
2122        "norm_inf" => {
2123            if args.len() != 1 { return Err("norm_inf requires 1 Tensor argument".into()); }
2124            let t = value_to_tensor(&args[0])?;
2125            Ok(Some(Value::Float(t.norm_inf().map_err(|e| format!("{e}"))?)))
2126        }
2127        "schur" => {
2128            if args.len() != 1 { return Err("schur requires 1 Tensor argument".into()); }
2129            let t = value_to_tensor(&args[0])?;
2130            let (q, t_mat) = t.schur().map_err(|e| format!("{e}"))?;
2131            Ok(Some(Value::Tuple(Rc::new(vec![Value::Tensor(q), Value::Tensor(t_mat)]))))
2132        }
2133        "matrix_exp" => {
2134            if args.len() != 1 { return Err("matrix_exp requires 1 Tensor argument".into()); }
2135            let t = value_to_tensor(&args[0])?;
2136            Ok(Some(Value::Tensor(t.matrix_exp().map_err(|e| format!("{e}"))?)))
2137        }
2138
2139        // ── B2: Rank correlations & partial correlation ────────────────
2140        "spearman_cor" => {
2141            if args.len() != 2 { return Err("spearman_cor requires 2 arguments".into()); }
2142            let x = value_to_f64_vec(&args[0])?;
2143            let y = value_to_f64_vec(&args[1])?;
2144            Ok(Some(Value::Float(crate::stats::spearman_cor(&x, &y)?)))
2145        }
2146        "kendall_cor" => {
2147            if args.len() != 2 { return Err("kendall_cor requires 2 arguments".into()); }
2148            let x = value_to_f64_vec(&args[0])?;
2149            let y = value_to_f64_vec(&args[1])?;
2150            Ok(Some(Value::Float(crate::stats::kendall_cor(&x, &y)?)))
2151        }
2152        "partial_cor" => {
2153            if args.len() != 3 { return Err("partial_cor requires 3 arguments".into()); }
2154            let x = value_to_f64_vec(&args[0])?;
2155            let y = value_to_f64_vec(&args[1])?;
2156            let z = value_to_f64_vec(&args[2])?;
2157            Ok(Some(Value::Float(crate::stats::partial_cor(&x, &y, &z)?)))
2158        }
2159        "cor_ci" => {
2160            if args.len() != 3 { return Err("cor_ci requires 3 arguments".into()); }
2161            let x = value_to_f64_vec(&args[0])?;
2162            let y = value_to_f64_vec(&args[1])?;
2163            let alpha = match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("cor_ci: alpha must be a number".into()) };
2164            let (lo, hi) = crate::stats::cor_ci(&x, &y, alpha)?;
2165            Ok(Some(Value::Tuple(Rc::new(vec![Value::Float(lo), Value::Float(hi)]))))
2166        }
2167
2168        // ── B6: Advanced FFT & Distributions ─────────────────────────
2169        "hann" => {
2170            if args.len() != 1 { return Err("hann requires 1 argument (n)".into()); }
2171            let n = match &args[0] { Value::Int(i) => *i as usize, _ => return Err("hann: n must be an integer".into()) };
2172            let w = crate::fft::hann_window(n);
2173            Ok(Some(Value::Array(Rc::new(w.into_iter().map(Value::Float).collect()))))
2174        }
2175        "hamming" => {
2176            if args.len() != 1 { return Err("hamming requires 1 argument (n)".into()); }
2177            let n = match &args[0] { Value::Int(i) => *i as usize, _ => return Err("hamming: n must be an integer".into()) };
2178            let w = crate::fft::hamming_window(n);
2179            Ok(Some(Value::Array(Rc::new(w.into_iter().map(Value::Float).collect()))))
2180        }
2181        "blackman" => {
2182            if args.len() != 1 { return Err("blackman requires 1 argument (n)".into()); }
2183            let n = match &args[0] { Value::Int(i) => *i as usize, _ => return Err("blackman: n must be an integer".into()) };
2184            let w = crate::fft::blackman_window(n);
2185            Ok(Some(Value::Array(Rc::new(w.into_iter().map(Value::Float).collect()))))
2186        }
2187        "fft_arbitrary" => {
2188            if args.len() != 1 { return Err("fft_arbitrary requires 1 argument (complex array)".into()); }
2189            let data = value_to_complex_vec(&args[0])?;
2190            let result = crate::fft::fft_arbitrary(&data);
2191            let pairs: Vec<Value> = result.iter().map(|&(re, im)| {
2192                Value::Tuple(Rc::new(vec![Value::Float(re), Value::Float(im)]))
2193            }).collect();
2194            Ok(Some(Value::Array(Rc::new(pairs))))
2195        }
2196        "fft_2d" => {
2197            if args.len() != 3 { return Err("fft_2d requires 3 arguments (data, rows, cols)".into()); }
2198            let data = value_to_complex_vec(&args[0])?;
2199            let rows = match &args[1] { Value::Int(i) => *i as usize, _ => return Err("fft_2d: rows must be an integer".into()) };
2200            let cols = match &args[2] { Value::Int(i) => *i as usize, _ => return Err("fft_2d: cols must be an integer".into()) };
2201            let result = crate::fft::fft_2d(&data, rows, cols)?;
2202            let pairs: Vec<Value> = result.iter().map(|&(re, im)| {
2203                Value::Tuple(Rc::new(vec![Value::Float(re), Value::Float(im)]))
2204            }).collect();
2205            Ok(Some(Value::Array(Rc::new(pairs))))
2206        }
2207        "ifft_2d" => {
2208            if args.len() != 3 { return Err("ifft_2d requires 3 arguments (data, rows, cols)".into()); }
2209            let data = value_to_complex_vec(&args[0])?;
2210            let rows = match &args[1] { Value::Int(i) => *i as usize, _ => return Err("ifft_2d: rows must be an integer".into()) };
2211            let cols = match &args[2] { Value::Int(i) => *i as usize, _ => return Err("ifft_2d: cols must be an integer".into()) };
2212            let result = crate::fft::ifft_2d(&data, rows, cols)?;
2213            let pairs: Vec<Value> = result.iter().map(|&(re, im)| {
2214                Value::Tuple(Rc::new(vec![Value::Float(re), Value::Float(im)]))
2215            }).collect();
2216            Ok(Some(Value::Array(Rc::new(pairs))))
2217        }
2218        "beta_pdf" => {
2219            if args.len() != 3 { return Err("beta_pdf requires 3 arguments (x, a, b)".into()); }
2220            let x = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("beta_pdf: x must be a number".into()) };
2221            let a = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("beta_pdf: a must be a number".into()) };
2222            let b = match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("beta_pdf: b must be a number".into()) };
2223            Ok(Some(Value::Float(crate::distributions::beta_pdf(x, a, b))))
2224        }
2225        "beta_cdf" => {
2226            if args.len() != 3 { return Err("beta_cdf requires 3 arguments (x, a, b)".into()); }
2227            let x = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("beta_cdf: x must be a number".into()) };
2228            let a = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("beta_cdf: a must be a number".into()) };
2229            let b = match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("beta_cdf: b must be a number".into()) };
2230            Ok(Some(Value::Float(crate::distributions::beta_cdf(x, a, b))))
2231        }
2232        "gamma_pdf" => {
2233            if args.len() != 3 { return Err("gamma_pdf requires 3 arguments (x, k, theta)".into()); }
2234            let x = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("gamma_pdf: x must be a number".into()) };
2235            let k = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("gamma_pdf: k must be a number".into()) };
2236            let theta = match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("gamma_pdf: theta must be a number".into()) };
2237            Ok(Some(Value::Float(crate::distributions::gamma_pdf(x, k, theta))))
2238        }
2239        "gamma_cdf" => {
2240            if args.len() != 3 { return Err("gamma_cdf requires 3 arguments (x, k, theta)".into()); }
2241            let x = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("gamma_cdf: x must be a number".into()) };
2242            let k = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("gamma_cdf: k must be a number".into()) };
2243            let theta = match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("gamma_cdf: theta must be a number".into()) };
2244            Ok(Some(Value::Float(crate::distributions::gamma_cdf(x, k, theta))))
2245        }
2246        "exp_pdf" => {
2247            if args.len() != 2 { return Err("exp_pdf requires 2 arguments (x, lambda)".into()); }
2248            let x = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("exp_pdf: x must be a number".into()) };
2249            let lambda = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("exp_pdf: lambda must be a number".into()) };
2250            Ok(Some(Value::Float(crate::distributions::exp_pdf(x, lambda))))
2251        }
2252        "exp_cdf" => {
2253            if args.len() != 2 { return Err("exp_cdf requires 2 arguments (x, lambda)".into()); }
2254            let x = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("exp_cdf: x must be a number".into()) };
2255            let lambda = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("exp_cdf: lambda must be a number".into()) };
2256            Ok(Some(Value::Float(crate::distributions::exp_cdf(x, lambda))))
2257        }
2258        "weibull_pdf" => {
2259            if args.len() != 3 { return Err("weibull_pdf requires 3 arguments (x, k, lambda)".into()); }
2260            let x = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("weibull_pdf: x must be a number".into()) };
2261            let k = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("weibull_pdf: k must be a number".into()) };
2262            let lambda = match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("weibull_pdf: lambda must be a number".into()) };
2263            Ok(Some(Value::Float(crate::distributions::weibull_pdf(x, k, lambda))))
2264        }
2265        "weibull_cdf" => {
2266            if args.len() != 3 { return Err("weibull_cdf requires 3 arguments (x, k, lambda)".into()); }
2267            let x = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("weibull_cdf: x must be a number".into()) };
2268            let k = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("weibull_cdf: k must be a number".into()) };
2269            let lambda = match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("weibull_cdf: lambda must be a number".into()) };
2270            Ok(Some(Value::Float(crate::distributions::weibull_cdf(x, k, lambda))))
2271        }
2272
2273        // ── B5: Analyst QoL extensions ─────────────────────────────
2274        "case_when" => {
2275            if args.len() != 3 { return Err("case_when requires 3 arguments (conditions, values, default)".into()); }
2276            let conditions = match &args[0] {
2277                Value::Array(arr) => arr.iter().map(|v| match v {
2278                    Value::Bool(b) => Ok(*b),
2279                    _ => Err("case_when conditions must be booleans".into()),
2280                }).collect::<Result<Vec<bool>, String>>()?,
2281                _ => return Err("case_when conditions must be an array".into()),
2282            };
2283            let values = match &args[1] {
2284                Value::Array(arr) => arr.as_ref().clone(),
2285                _ => return Err("case_when values must be an array".into()),
2286            };
2287            if conditions.len() != values.len() {
2288                return Err("case_when conditions and values must have same length".into());
2289            }
2290            for (i, &cond) in conditions.iter().enumerate() {
2291                if cond { return Ok(Some(values[i].clone())); }
2292            }
2293            Ok(Some(args[2].clone())) // default
2294        }
2295        "ntile" => {
2296            if args.len() != 2 { return Err("ntile requires 2 arguments (data, n)".into()); }
2297            let data = value_to_f64_vec(&args[0])?;
2298            let n = match &args[1] { Value::Int(i) => *i as usize, _ => return Err("ntile: n must be an integer".into()) };
2299            let result = crate::stats::ntile(&data, n)?;
2300            Ok(Some(Value::Array(Rc::new(result.into_iter().map(Value::Float).collect()))))
2301        }
2302        "percent_rank" => {
2303            if args.len() != 1 { return Err("percent_rank requires 1 argument".into()); }
2304            let data = value_to_f64_vec(&args[0])?;
2305            let result = crate::stats::percent_rank_fn(&data)?;
2306            Ok(Some(Value::Array(Rc::new(result.into_iter().map(Value::Float).collect()))))
2307        }
2308        "cume_dist" => {
2309            if args.len() != 1 { return Err("cume_dist requires 1 argument".into()); }
2310            let data = value_to_f64_vec(&args[0])?;
2311            let result = crate::stats::cume_dist(&data)?;
2312            Ok(Some(Value::Array(Rc::new(result.into_iter().map(Value::Float).collect()))))
2313        }
2314        "wls" => {
2315            if args.len() != 5 { return Err("wls requires 5 arguments (X, y, weights, n, p)".into()); }
2316            let x = value_to_f64_vec(&args[0])?;
2317            let y = value_to_f64_vec(&args[1])?;
2318            let w = value_to_f64_vec(&args[2])?;
2319            let n = match &args[3] { Value::Int(i) => *i as usize, _ => return Err("wls: n must be an integer".into()) };
2320            let p = match &args[4] { Value::Int(i) => *i as usize, _ => return Err("wls: p must be an integer".into()) };
2321            let r = crate::hypothesis::wls(&x, &y, &w, n, p)?;
2322            let fields = std::collections::BTreeMap::from([
2323                ("coefficients".to_string(), Value::Array(Rc::new(r.coefficients.into_iter().map(Value::Float).collect()))),
2324                ("r_squared".to_string(), Value::Float(r.r_squared)),
2325                ("residuals".to_string(), Value::Array(Rc::new(r.residuals.into_iter().map(Value::Float).collect()))),
2326            ]);
2327            Ok(Some(Value::Struct { name: "LmResult".to_string(), fields }))
2328        }
2329
2330        // ── B7: Non-parametric tests & multiple comparisons ────────
2331        "tukey_hsd" => {
2332            let groups: Vec<Vec<f64>> = args.iter()
2333                .map(|a| value_to_f64_vec(a))
2334                .collect::<Result<Vec<_>, _>>()?;
2335            let group_refs: Vec<&[f64]> = groups.iter().map(|g| g.as_slice()).collect();
2336            let results = crate::hypothesis::tukey_hsd(&group_refs)?;
2337            let result_values: Vec<Value> = results.iter().map(|pair| {
2338                let mut fields = std::collections::BTreeMap::new();
2339                fields.insert("group_i".into(), Value::Int(pair.group_i as i64));
2340                fields.insert("group_j".into(), Value::Int(pair.group_j as i64));
2341                fields.insert("mean_diff".into(), Value::Float(pair.mean_diff));
2342                fields.insert("q_statistic".into(), Value::Float(pair.q_statistic));
2343                fields.insert("p_value".into(), Value::Float(pair.p_value));
2344                Value::Struct { name: "TukeyHsdPair".into(), fields }
2345            }).collect();
2346            Ok(Some(Value::Array(Rc::new(result_values))))
2347        }
2348        "mann_whitney" => {
2349            if args.len() != 2 { return Err("mann_whitney requires 2 arguments".into()); }
2350            let x = value_to_f64_vec(&args[0])?;
2351            let y = value_to_f64_vec(&args[1])?;
2352            let r = crate::hypothesis::mann_whitney(&x, &y)?;
2353            let mut fields = std::collections::BTreeMap::new();
2354            fields.insert("u_statistic".into(), Value::Float(r.u_statistic));
2355            fields.insert("z_score".into(), Value::Float(r.z_score));
2356            fields.insert("p_value".into(), Value::Float(r.p_value));
2357            Ok(Some(Value::Struct { name: "MannWhitneyResult".into(), fields }))
2358        }
2359        "kruskal_wallis" => {
2360            let groups: Vec<Vec<f64>> = args.iter()
2361                .map(|a| value_to_f64_vec(a))
2362                .collect::<Result<Vec<_>, _>>()?;
2363            let group_refs: Vec<&[f64]> = groups.iter().map(|g| g.as_slice()).collect();
2364            let r = crate::hypothesis::kruskal_wallis(&group_refs)?;
2365            let mut fields = std::collections::BTreeMap::new();
2366            fields.insert("h_statistic".into(), Value::Float(r.h_statistic));
2367            fields.insert("p_value".into(), Value::Float(r.p_value));
2368            fields.insert("df".into(), Value::Float(r.df));
2369            Ok(Some(Value::Struct { name: "KruskalWallisResult".into(), fields }))
2370        }
2371        "wilcoxon_signed_rank" => {
2372            if args.len() != 2 { return Err("wilcoxon_signed_rank requires 2 arguments".into()); }
2373            let x = value_to_f64_vec(&args[0])?;
2374            let y = value_to_f64_vec(&args[1])?;
2375            let r = crate::hypothesis::wilcoxon_signed_rank(&x, &y)?;
2376            let mut fields = std::collections::BTreeMap::new();
2377            fields.insert("w_statistic".into(), Value::Float(r.w_statistic));
2378            fields.insert("z_score".into(), Value::Float(r.z_score));
2379            fields.insert("p_value".into(), Value::Float(r.p_value));
2380            Ok(Some(Value::Struct { name: "WilcoxonResult".into(), fields }))
2381        }
2382        "bonferroni" => {
2383            if args.len() != 1 { return Err("bonferroni requires 1 argument (p_values array)".into()); }
2384            let pvals = value_to_f64_vec(&args[0])?;
2385            let adj = crate::hypothesis::bonferroni(&pvals);
2386            Ok(Some(Value::Array(Rc::new(adj.into_iter().map(Value::Float).collect()))))
2387        }
2388        "fdr_bh" => {
2389            if args.len() != 1 { return Err("fdr_bh requires 1 argument (p_values array)".into()); }
2390            let pvals = value_to_f64_vec(&args[0])?;
2391            let adj = crate::hypothesis::fdr_bh(&pvals);
2392            Ok(Some(Value::Array(Rc::new(adj.into_iter().map(Value::Float).collect()))))
2393        }
2394        "logistic_regression" => {
2395            if args.len() != 4 { return Err("logistic_regression requires 4 arguments (X, y, n, p)".into()); }
2396            let x = value_to_f64_vec(&args[0])?;
2397            let y = value_to_f64_vec(&args[1])?;
2398            let n = match &args[2] { Value::Int(i) => *i as usize, _ => return Err("logistic_regression: n must be an integer".into()) };
2399            let p = match &args[3] { Value::Int(i) => *i as usize, _ => return Err("logistic_regression: p must be an integer".into()) };
2400            let r = crate::hypothesis::logistic_regression(&x, &y, n, p)?;
2401            let mut fields = std::collections::BTreeMap::new();
2402            fields.insert("coefficients".into(), Value::Array(Rc::new(r.coefficients.into_iter().map(Value::Float).collect())));
2403            fields.insert("std_errors".into(), Value::Array(Rc::new(r.std_errors.into_iter().map(Value::Float).collect())));
2404            fields.insert("z_values".into(), Value::Array(Rc::new(r.z_values.into_iter().map(Value::Float).collect())));
2405            fields.insert("p_values".into(), Value::Array(Rc::new(r.p_values.into_iter().map(Value::Float).collect())));
2406            fields.insert("log_likelihood".into(), Value::Float(r.log_likelihood));
2407            fields.insert("aic".into(), Value::Float(r.aic));
2408            fields.insert("iterations".into(), Value::Int(r.iterations as i64));
2409            Ok(Some(Value::Struct { name: "LogisticResult".into(), fields }))
2410        }
2411
2412        // ── Stationarity tests ────────────────────────────────────────
2413        "adf_test" => {
2414            if args.len() != 1 { return Err("adf_test requires 1 argument: data".into()); }
2415            let data = value_to_f64_vec(&args[0])?;
2416            let (t_stat, p_val) = crate::stationarity::adf_test(&data)?;
2417            let mut fields = std::collections::BTreeMap::new();
2418            fields.insert("statistic".into(), Value::Float(t_stat));
2419            fields.insert("p_value".into(), Value::Float(p_val));
2420            Ok(Some(Value::Struct { name: "AdfResult".into(), fields }))
2421        }
2422        "kpss_test" => {
2423            if args.len() != 1 { return Err("kpss_test requires 1 argument: data".into()); }
2424            let data = value_to_f64_vec(&args[0])?;
2425            let (stat, p_val) = crate::stationarity::kpss_test(&data)?;
2426            let mut fields = std::collections::BTreeMap::new();
2427            fields.insert("statistic".into(), Value::Float(stat));
2428            fields.insert("p_value".into(), Value::Float(p_val));
2429            Ok(Some(Value::Struct { name: "KpssResult".into(), fields }))
2430        }
2431        "pp_test" => {
2432            if args.len() != 1 { return Err("pp_test requires 1 argument: data".into()); }
2433            let data = value_to_f64_vec(&args[0])?;
2434            let (z_t, p_val) = crate::stationarity::pp_test(&data)?;
2435            let mut fields = std::collections::BTreeMap::new();
2436            fields.insert("statistic".into(), Value::Float(z_t));
2437            fields.insert("p_value".into(), Value::Float(p_val));
2438            Ok(Some(Value::Struct { name: "PpResult".into(), fields }))
2439        }
2440
2441        // Phase C4: Sorting & Tensor Indexing
2442        "argsort" => {
2443            if args.len() != 1 { return Err("argsort requires 1 arg: Tensor".into()); }
2444            let t = value_to_tensor(&args[0])?;
2445            Ok(Some(Value::Tensor(t.argsort())))
2446        }
2447        "gather" => {
2448            if args.len() != 3 { return Err("gather requires 3 args: tensor, dim, indices".into()); }
2449            let t = value_to_tensor(&args[0])?;
2450            let dim = value_to_usize(&args[1])?;
2451            let indices = value_to_tensor(&args[2])?;
2452            Ok(Some(Value::Tensor(t.gather(dim, &indices).map_err(|e| format!("{e}"))?)))
2453        }
2454        "scatter" => {
2455            if args.len() != 4 { return Err("scatter requires 4 args: tensor, dim, indices, src".into()); }
2456            let t = value_to_tensor(&args[0])?;
2457            let dim = value_to_usize(&args[1])?;
2458            let indices = value_to_tensor(&args[2])?;
2459            let src = value_to_tensor(&args[3])?;
2460            Ok(Some(Value::Tensor(t.scatter(dim, &indices, &src).map_err(|e| format!("{e}"))?)))
2461        }
2462        "index_select" => {
2463            if args.len() != 3 { return Err("index_select requires 3 args: tensor, dim, indices".into()); }
2464            let t = value_to_tensor(&args[0])?;
2465            let dim = value_to_usize(&args[1])?;
2466            let indices = value_to_tensor(&args[2])?;
2467            Ok(Some(Value::Tensor(t.index_select(dim, &indices).map_err(|e| format!("{e}"))?)))
2468        }
2469
2470        // Phase C6: Collection utilities
2471        "array_push" => {
2472            if args.len() != 2 { return Err("array_push requires 2 args: array, value".into()); }
2473            let mut arr_rc = match &args[0] { Value::Array(a) => Rc::clone(a), _ => return Err("array_push: first arg must be Array".into()) };
2474            // COW: Rc::make_mut only clones if refcount > 1.
2475            // For `arr = array_push(arr, val)` where old binding is overwritten,
2476            // refcount is 1 → zero-copy push (amortized O(1) instead of O(n)).
2477            Rc::make_mut(&mut arr_rc).push(args[1].clone());
2478            Ok(Some(Value::Array(arr_rc)))
2479        }
2480        "array_pop" => {
2481            if args.len() != 1 { return Err("array_pop requires 1 arg: array".into()); }
2482            let arr = match &args[0] { Value::Array(a) => (**a).clone(), _ => return Err("array_pop: expected Array".into()) };
2483            if arr.is_empty() { return Err("array_pop: empty array".into()); }
2484            let mut new_arr = arr;
2485            let last = new_arr.pop().unwrap();
2486            Ok(Some(Value::Tuple(Rc::new(vec![last, Value::Array(Rc::new(new_arr))]))))
2487        }
2488        "array_contains" => {
2489            if args.len() != 2 { return Err("array_contains requires 2 args: array, value".into()); }
2490            let arr = match &args[0] { Value::Array(a) => a, _ => return Err("array_contains: first arg must be Array".into()) };
2491            let needle = &args[1];
2492            let found = arr.iter().any(|v| format!("{v}") == format!("{needle}"));
2493            Ok(Some(Value::Bool(found)))
2494        }
2495        "array_reverse" => {
2496            if args.len() != 1 { return Err("array_reverse requires 1 arg: array".into()); }
2497            let arr = match &args[0] { Value::Array(a) => (**a).clone(), _ => return Err("array_reverse: expected Array".into()) };
2498            let mut new_arr = arr;
2499            new_arr.reverse();
2500            Ok(Some(Value::Array(Rc::new(new_arr))))
2501        }
2502        "array_flatten" => {
2503            if args.len() != 1 { return Err("array_flatten requires 1 arg: array".into()); }
2504            let arr = match &args[0] { Value::Array(a) => a.clone(), _ => return Err("array_flatten: expected Array".into()) };
2505            let mut result = Vec::new();
2506            fn flatten_recursive(arr: &[Value], result: &mut Vec<Value>) {
2507                for v in arr {
2508                    match v {
2509                        Value::Array(inner) => flatten_recursive(inner, result),
2510                        _ => result.push(v.clone()),
2511                    }
2512                }
2513            }
2514            flatten_recursive(&arr, &mut result);
2515            Ok(Some(Value::Array(Rc::new(result))))
2516        }
2517        "array_len" => {
2518            if args.len() != 1 { return Err("array_len requires 1 arg: array".into()); }
2519            match &args[0] {
2520                Value::Array(a) => Ok(Some(Value::Int(a.len() as i64))),
2521                _ => Err("array_len: expected Array".into()),
2522            }
2523        }
2524        "array_slice" => {
2525            if args.len() != 3 { return Err("array_slice requires 3 args: array, start, end".into()); }
2526            let arr = match &args[0] { Value::Array(a) => a, _ => return Err("array_slice: expected Array".into()) };
2527            let start = match &args[1] { Value::Int(i) => *i as usize, _ => return Err("array_slice: start must be Int".into()) };
2528            let end = match &args[2] { Value::Int(i) => *i as usize, _ => return Err("array_slice: end must be Int".into()) };
2529            if start > end || end > arr.len() {
2530                return Err(format!("array_slice: bounds [{start}, {end}) out of range for len {}", arr.len()));
2531            }
2532            Ok(Some(Value::Array(Rc::new(arr[start..end].to_vec()))))
2533        }
2534
2535        // Phase C5: Map & Set constructors
2536        "Map.new" => {
2537            if !args.is_empty() { return Err("Map.new takes 0 arguments".into()); }
2538            Ok(Some(Value::Map(Rc::new(RefCell::new(crate::det_map::DetMap::new())))))
2539        }
2540        "Set.new" => {
2541            if !args.is_empty() { return Err("Set.new takes 0 arguments".into()); }
2542            Ok(Some(Value::Map(Rc::new(RefCell::new(crate::det_map::DetMap::new())))))
2543        }
2544
2545        // Phase C3: Bitwise operations
2546        "bit_and" => {
2547            if args.len() != 2 { return Err("bit_and requires 2 Int args".into()); }
2548            let a = match &args[0] { Value::Int(i) => *i, _ => return Err("bit_and: expected Int".into()) };
2549            let b = match &args[1] { Value::Int(i) => *i, _ => return Err("bit_and: expected Int".into()) };
2550            Ok(Some(Value::Int(a & b)))
2551        }
2552        "bit_or" => {
2553            if args.len() != 2 { return Err("bit_or requires 2 Int args".into()); }
2554            let a = match &args[0] { Value::Int(i) => *i, _ => return Err("bit_or: expected Int".into()) };
2555            let b = match &args[1] { Value::Int(i) => *i, _ => return Err("bit_or: expected Int".into()) };
2556            Ok(Some(Value::Int(a | b)))
2557        }
2558        "bit_xor" => {
2559            if args.len() != 2 { return Err("bit_xor requires 2 Int args".into()); }
2560            let a = match &args[0] { Value::Int(i) => *i, _ => return Err("bit_xor: expected Int".into()) };
2561            let b = match &args[1] { Value::Int(i) => *i, _ => return Err("bit_xor: expected Int".into()) };
2562            Ok(Some(Value::Int(a ^ b)))
2563        }
2564        "bit_not" => {
2565            if args.len() != 1 { return Err("bit_not requires 1 Int arg".into()); }
2566            let a = match &args[0] { Value::Int(i) => *i, _ => return Err("bit_not: expected Int".into()) };
2567            Ok(Some(Value::Int(!a)))
2568        }
2569        "bit_shl" => {
2570            if args.len() != 2 { return Err("bit_shl requires 2 Int args".into()); }
2571            let a = match &args[0] { Value::Int(i) => *i, _ => return Err("bit_shl: expected Int".into()) };
2572            let n = match &args[1] { Value::Int(i) => *i, _ => return Err("bit_shl: expected Int".into()) };
2573            if n < 0 || n > 63 { return Err("bit_shl: shift amount must be 0-63".into()); }
2574            Ok(Some(Value::Int(((a as u64) << n) as i64)))
2575        }
2576        "bit_shr" => {
2577            if args.len() != 2 { return Err("bit_shr requires 2 Int args".into()); }
2578            let a = match &args[0] { Value::Int(i) => *i, _ => return Err("bit_shr: expected Int".into()) };
2579            let n = match &args[1] { Value::Int(i) => *i, _ => return Err("bit_shr: expected Int".into()) };
2580            if n < 0 || n > 63 { return Err("bit_shr: shift amount must be 0-63".into()); }
2581            Ok(Some(Value::Int(((a as u64) >> n) as i64)))
2582        }
2583        "popcount" => {
2584            if args.len() != 1 { return Err("popcount requires 1 Int arg".into()); }
2585            let a = match &args[0] { Value::Int(i) => *i, _ => return Err("popcount: expected Int".into()) };
2586            Ok(Some(Value::Int((a as u64).count_ones() as i64)))
2587        }
2588
2589        // Phase C2: Optimizer constructors
2590        "Adam.new" => {
2591            if args.len() < 2 || args.len() > 4 {
2592                return Err("Adam.new requires 2-4 args: n_params, lr, [beta1], [beta2]".into());
2593            }
2594            let n = match &args[0] { Value::Int(i) => *i as usize, _ => return Err("Adam.new: n_params must be Int".into()) };
2595            let lr = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("Adam.new: lr must be Float".into()) };
2596            let beta1 = if args.len() > 2 {
2597                match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("Adam.new: beta1 must be Float".into()) }
2598            } else { 0.9 };
2599            let beta2 = if args.len() > 3 {
2600                match &args[3] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("Adam.new: beta2 must be Float".into()) }
2601            } else { 0.999 };
2602            let mut state = crate::ml::AdamState::new(n, lr);
2603            state.beta1 = beta1;
2604            state.beta2 = beta2;
2605            let erased: Rc<RefCell<dyn std::any::Any>> = Rc::new(RefCell::new(state));
2606            Ok(Some(Value::OptimizerState(erased)))
2607        }
2608        "Sgd.new" => {
2609            if args.len() < 2 || args.len() > 3 {
2610                return Err("Sgd.new requires 2-3 args: n_params, lr, [momentum]".into());
2611            }
2612            let n = match &args[0] { Value::Int(i) => *i as usize, _ => return Err("Sgd.new: n_params must be Int".into()) };
2613            let lr = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("Sgd.new: lr must be Float".into()) };
2614            let momentum = if args.len() > 2 {
2615                match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("Sgd.new: momentum must be Float".into()) }
2616            } else { 0.0 };
2617            let state = crate::ml::SgdState::new(n, lr, momentum);
2618            let erased: Rc<RefCell<dyn std::any::Any>> = Rc::new(RefCell::new(state));
2619            Ok(Some(Value::OptimizerState(erased)))
2620        }
2621
2622        // ---- ML Autodiff Builtins ----
2623        // stop_gradient: returns x unchanged; in AD context, gradients don't flow through
2624        "stop_gradient" => {
2625            if args.len() != 1 { return Err("stop_gradient requires exactly 1 argument".into()); }
2626            Ok(Some(args[0].clone()))
2627        }
2628        // grad_checkpoint: returns x unchanged; semantic marker for memory checkpointing
2629        "grad_checkpoint" => {
2630            if args.len() != 1 { return Err("grad_checkpoint requires exactly 1 argument".into()); }
2631            Ok(Some(args[0].clone()))
2632        }
2633        // clip_grad: clips a gradient value to [min_val, max_val] range
2634        "clip_grad" => {
2635            if args.len() != 3 { return Err("clip_grad requires 3 arguments (value, min, max)".into()); }
2636            let val = match &args[0] {
2637                Value::Float(f) => *f,
2638                Value::Int(i) => *i as f64,
2639                _ => return Err("clip_grad requires numeric arguments".into()),
2640            };
2641            let min_val = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("clip_grad min must be numeric".into()) };
2642            let max_val = match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("clip_grad max must be numeric".into()) };
2643            Ok(Some(Value::Float(val.max(min_val).min(max_val))))
2644        }
2645        // grad_scale: scales a gradient value by a scalar factor
2646        "grad_scale" => {
2647            if args.len() != 2 { return Err("grad_scale requires 2 arguments (value, scale)".into()); }
2648            let val = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("grad_scale requires numeric first arg".into()) };
2649            let scale = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("grad_scale requires numeric scale".into()) };
2650            Ok(Some(Value::Float(val * scale)))
2651        }
2652
2653        // ── v0.1 Broadcasting builtins ──────────────────────────────
2654
2655        "broadcast" => {
2656            if args.len() != 2 {
2657                return Err("broadcast requires 2 arguments (fn_name, tensor)".into());
2658            }
2659            let fn_name = match &args[0] {
2660                Value::String(s) => s.clone(),
2661                _ => return Err("broadcast: first argument must be a function name string".into()),
2662            };
2663            let t = value_to_tensor(&args[1])?;
2664
2665            // SIMD-accelerated path for known unary operations that can be
2666            // vectorized with AVX2 (bit-identical to scalar).
2667            match fn_name.as_str() {
2668                "sqrt" => return Ok(Some(Value::Tensor(t.map_simd(UnaryOp::Sqrt)))),
2669                "abs"  => return Ok(Some(Value::Tensor(t.map_simd(UnaryOp::Abs)))),
2670                "neg"  => return Ok(Some(Value::Tensor(t.map_simd(UnaryOp::Neg)))),
2671                "relu" => return Ok(Some(Value::Tensor(t.map_simd(UnaryOp::Relu)))),
2672                _ => {} // fall through to scalar path
2673            }
2674
2675            // Scalar path for transcendental functions (sin, cos, exp, etc.)
2676            // These cannot be trivially SIMD-vectorized while preserving
2677            // bit-identical results with libm scalar implementations.
2678            let f: Box<dyn Fn(f64) -> f64> = match fn_name.as_str() {
2679                "sin"     => Box::new(|x: f64| x.sin()),
2680                "cos"     => Box::new(|x: f64| x.cos()),
2681                "tan"     => Box::new(|x: f64| x.tan()),
2682                "asin"    => Box::new(|x: f64| x.asin()),
2683                "acos"    => Box::new(|x: f64| x.acos()),
2684                "atan"    => Box::new(|x: f64| x.atan()),
2685                "exp"     => Box::new(|x: f64| x.exp()),
2686                "ln"      => Box::new(|x: f64| x.ln()),
2687                "log"     => Box::new(|x: f64| x.ln()),
2688                "log2"    => Box::new(|x: f64| x.log2()),
2689                "log10"   => Box::new(|x: f64| x.log10()),
2690                "log1p"   => Box::new(|x: f64| x.ln_1p()),
2691                "expm1"   => Box::new(|x: f64| x.exp_m1()),
2692                "floor"   => Box::new(|x: f64| x.floor()),
2693                "ceil"    => Box::new(|x: f64| x.ceil()),
2694                "round"   => Box::new(|x: f64| x.round()),
2695                "sigmoid" => Box::new(|x: f64| 1.0 / (1.0 + (-x).exp())),
2696                "tanh"    => Box::new(|x: f64| x.tanh()),
2697                "sign"    => Box::new(|x: f64| {
2698                    if x > 0.0 { 1.0 } else if x < 0.0 { -1.0 } else { 0.0 }
2699                }),
2700                _ => return Err(format!("broadcast: unknown unary function '{fn_name}'")),
2701            };
2702            Ok(Some(Value::Tensor(t.map(f))))
2703        }
2704
2705        "broadcast2" => {
2706            if args.len() != 3 {
2707                return Err("broadcast2 requires 3 arguments (fn_name, tensor1, tensor2)".into());
2708            }
2709            let fn_name = match &args[0] {
2710                Value::String(s) => s.clone(),
2711                _ => return Err("broadcast2: first argument must be a function name string".into()),
2712            };
2713            let t1 = value_to_tensor(&args[1])?;
2714            let t2 = value_to_tensor(&args[2])?;
2715            let result = match fn_name.as_str() {
2716                "add"   => t1.add(&t2),
2717                "sub"   => t1.sub(&t2),
2718                "mul"   => t1.mul_elem(&t2),
2719                "div"   => t1.div_elem(&t2),
2720                "pow"   => t1.elem_pow(&t2),
2721                "min"   => t1.elem_min(&t2),
2722                "max"   => t1.elem_max(&t2),
2723                "atan2" => t1.elem_atan2(&t2),
2724                "hypot" => t1.elem_hypot(&t2),
2725                _ => return Err(format!("broadcast2: unknown binary function '{fn_name}'")),
2726            };
2727            match result {
2728                Ok(t) => Ok(Some(Value::Tensor(t))),
2729                Err(e) => Err(format!("broadcast2: {e}")),
2730            }
2731        }
2732
2733        // ── Peak RSS memory tracking ─────────────────────────────────
2734        "peak_rss" => {
2735            Ok(Some(Value::Int(peak_rss_kb() as i64)))
2736        }
2737
2738        // ── Fused broadcast operations (eliminate intermediate tensors) ──
2739        "broadcast_fma" => {
2740            // broadcast_fma(a, b, c) = a * b + c element-wise in one pass.
2741            // Eliminates the intermediate tensor that broadcast2("mul") would create.
2742            if args.len() != 3 {
2743                return Err("broadcast_fma requires 3 arguments (a, b, c)".into());
2744            }
2745            let a = value_to_tensor(&args[0])?;
2746            let b = value_to_tensor(&args[1])?;
2747            let c = value_to_tensor(&args[2])?;
2748            let result = a.fused_mul_add(&b, &c)
2749                .map_err(|e| format!("broadcast_fma: {e}"))?;
2750            Ok(Some(Value::Tensor(result)))
2751        }
2752
2753        // -- Phase 2: Tensor boolean/masking ops --------------------------------
2754        "tensor_where" => {
2755            let a = value_to_tensor(&args[0])?;
2756            let cond = value_to_tensor(&args[1])?;
2757            let other = value_to_tensor(&args[2])?;
2758            Ok(Some(Value::Tensor(a.tensor_where(cond, other).map_err(|e| format!("{e}"))?)))
2759        }
2760        "tensor_any" => {
2761            let t = value_to_tensor(&args[0])?;
2762            Ok(Some(Value::Bool(t.any())))
2763        }
2764        "tensor_all" => {
2765            let t = value_to_tensor(&args[0])?;
2766            Ok(Some(Value::Bool(t.all())))
2767        }
2768        "tensor_nonzero" => {
2769            let t = value_to_tensor(&args[0])?;
2770            Ok(Some(Value::Tensor(t.nonzero())))
2771        }
2772        "tensor_masked_fill" => {
2773            let t = value_to_tensor(&args[0])?;
2774            let mask = value_to_tensor(&args[1])?;
2775            let val = value_to_f64(&args[2])?;
2776            Ok(Some(Value::Tensor(t.masked_fill(mask, val).map_err(|e| format!("{e}"))?)))
2777        }
2778        // -- Phase 2: Axis reductions ------------------------------------------
2779        "tensor_mean_axis" => {
2780            let t = value_to_tensor(&args[0])?;
2781            let axis = value_to_usize(&args[1])?;
2782            let keepdim = matches!(args.get(2), Some(Value::Bool(true)));
2783            Ok(Some(Value::Tensor(t.mean_axis(axis, keepdim).map_err(|e| format!("{e}"))?)))
2784        }
2785        "tensor_var_axis" => {
2786            let t = value_to_tensor(&args[0])?;
2787            let axis = value_to_usize(&args[1])?;
2788            let keepdim = matches!(args.get(2), Some(Value::Bool(true)));
2789            Ok(Some(Value::Tensor(t.var_axis(axis, keepdim).map_err(|e| format!("{e}"))?)))
2790        }
2791        "tensor_std_axis" => {
2792            let t = value_to_tensor(&args[0])?;
2793            let axis = value_to_usize(&args[1])?;
2794            let keepdim = matches!(args.get(2), Some(Value::Bool(true)));
2795            Ok(Some(Value::Tensor(t.std_axis(axis, keepdim).map_err(|e| format!("{e}"))?)))
2796        }
2797        "tensor_prod_axis" => {
2798            let t = value_to_tensor(&args[0])?;
2799            let axis = value_to_usize(&args[1])?;
2800            let keepdim = matches!(args.get(2), Some(Value::Bool(true)));
2801            Ok(Some(Value::Tensor(t.prod_axis(axis, keepdim).map_err(|e| format!("{e}"))?)))
2802        }
2803        // -- Phase 2: Sort ops -------------------------------------------------
2804        "tensor_sort" => {
2805            let t = value_to_tensor(&args[0])?;
2806            let axis = value_to_usize(&args[1])?;
2807            let desc = matches!(args.get(2), Some(Value::Bool(true)));
2808            Ok(Some(Value::Tensor(t.sort_axis(axis, desc).map_err(|e| format!("{e}"))?)))
2809        }
2810        "tensor_argsort_axis" => {
2811            let t = value_to_tensor(&args[0])?;
2812            let axis = value_to_usize(&args[1])?;
2813            let desc = matches!(args.get(2), Some(Value::Bool(true)));
2814            Ok(Some(Value::Tensor(t.argsort_axis(axis, desc).map_err(|e| format!("{e}"))?)))
2815        }
2816        // -- Phase 2: Einsum ---------------------------------------------------
2817        "einsum" => {
2818            let notation = match &args[0] {
2819                Value::String(s) => s.as_str().to_string(),
2820                _ => return Err("einsum: first arg must be notation string".into()),
2821            };
2822            let tensors: Vec<&Tensor> = args[1..].iter()
2823                .map(value_to_tensor)
2824                .collect::<Result<_, _>>()?;
2825            let result = Tensor::einsum(&notation, &tensors).map_err(|e| format!("{e}"))?;
2826            Ok(Some(Value::Tensor(result)))
2827        }
2828        // -- Phase 2: Reshape enhancements -------------------------------------
2829        "tensor_unsqueeze" => {
2830            let t = value_to_tensor(&args[0])?;
2831            let dim = value_to_usize(&args[1])?;
2832            Ok(Some(Value::Tensor(t.unsqueeze(dim).map_err(|e| format!("{e}"))?)))
2833        }
2834        "tensor_squeeze" => {
2835            let t = value_to_tensor(&args[0])?;
2836            let dim = args.get(1).map(|v| value_to_usize(v)).transpose()?;
2837            Ok(Some(Value::Tensor(t.squeeze(dim).map_err(|e| format!("{e}"))?)))
2838        }
2839        "tensor_flatten" => {
2840            let t = value_to_tensor(&args[0])?;
2841            let start = value_to_usize(&args[1])?;
2842            let end = value_to_usize(&args[2])?;
2843            Ok(Some(Value::Tensor(t.flatten(start, end).map_err(|e| format!("{e}"))?)))
2844        }
2845        "tensor_chunk" => {
2846            let t = value_to_tensor(&args[0])?;
2847            let n = value_to_usize(&args[1])?;
2848            let dim = value_to_usize(&args[2])?;
2849            let chunks = t.chunk(n, dim).map_err(|e| format!("{e}"))?;
2850            Ok(Some(Value::Array(Rc::new(chunks.into_iter().map(Value::Tensor).collect()))))
2851        }
2852        // -- Phase 3: SVD, PCA, Pseudoinverse ----------------------------------
2853        "svd" => {
2854            let t = value_to_tensor(&args[0])?;
2855            let (u, s, vt) = t.svd().map_err(|e| format!("{e}"))?;
2856            let s_tensor = Tensor::from_vec(s, &[u.shape()[1]]).map_err(|e| format!("{e}"))?;
2857            Ok(Some(Value::Tuple(Rc::new(vec![
2858                Value::Tensor(u),
2859                Value::Tensor(s_tensor),
2860                Value::Tensor(vt),
2861            ]))))
2862        }
2863        "pinv" => {
2864            let t = value_to_tensor(&args[0])?;
2865            Ok(Some(Value::Tensor(t.pinv().map_err(|e| format!("{e}"))?)))
2866        }
2867        "pca" => {
2868            let t = value_to_tensor(&args[0])?;
2869            let n_components = value_to_usize(&args[1])?;
2870            let (transformed, components, variance) = crate::ml::pca(&t, n_components).map_err(|e| format!("{e}"))?;
2871            let vlen = variance.len();
2872            let var_tensor = Tensor::from_vec(variance, &[vlen]).map_err(|e| format!("{e}"))?;
2873            Ok(Some(Value::Tuple(Rc::new(vec![
2874                Value::Tensor(transformed),
2875                Value::Tensor(components),
2876                Value::Tensor(var_tensor),
2877            ]))))
2878        }
2879        // -- Phase 7: Sparse operations ----------------------------------------
2880        "sparse_add" => {
2881            let a = value_to_sparse(&args[0])?;
2882            let b = value_to_sparse(&args[1])?;
2883            Ok(Some(Value::SparseTensor(crate::sparse::sparse_add(a, b).map_err(|e| e)?)))
2884        }
2885        "sparse_sub" => {
2886            let a = value_to_sparse(&args[0])?;
2887            let b = value_to_sparse(&args[1])?;
2888            Ok(Some(Value::SparseTensor(crate::sparse::sparse_sub(a, b).map_err(|e| e)?)))
2889        }
2890        "sparse_matmul" => {
2891            let a = value_to_sparse(&args[0])?;
2892            let b = value_to_sparse(&args[1])?;
2893            Ok(Some(Value::SparseTensor(crate::sparse::sparse_matmul(a, b).map_err(|e| e)?)))
2894        }
2895        "sparse_transpose" => {
2896            let a = value_to_sparse(&args[0])?;
2897            Ok(Some(Value::SparseTensor(crate::sparse::sparse_transpose(a))))
2898        }
2899        // -- Phase 9: Clustering -----------------------------------------------
2900        "kmeans" => {
2901            let data = value_to_f64_vec(&args[0])?;
2902            let n_samples = value_to_usize(&args[1])?;
2903            let n_features = value_to_usize(&args[2])?;
2904            let k = value_to_usize(&args[3])?;
2905            let max_iter = value_to_usize(&args[4])?;
2906            let seed = match &args[5] { Value::Int(v) => *v as u64, _ => 42 };
2907            let (centroids, labels, inertia) = crate::clustering::kmeans(&data, n_samples, n_features, k, max_iter, seed);
2908            let label_vals: Vec<Value> = labels.iter().map(|&l| Value::Int(l as i64)).collect();
2909            let centroid_t = Tensor::from_vec(centroids, &[k, n_features]).map_err(|e| format!("{e}"))?;
2910            Ok(Some(Value::Tuple(Rc::new(vec![
2911                Value::Tensor(centroid_t),
2912                Value::Array(Rc::new(label_vals)),
2913                Value::Float(inertia),
2914            ]))))
2915        }
2916        "dbscan" => {
2917            let data = value_to_f64_vec(&args[0])?;
2918            let n_samples = value_to_usize(&args[1])?;
2919            let n_features = value_to_usize(&args[2])?;
2920            let eps = value_to_f64(&args[3])?;
2921            let min_samples = value_to_usize(&args[4])?;
2922            let labels = crate::clustering::dbscan(&data, n_samples, n_features, eps, min_samples);
2923            let label_vals: Vec<Value> = labels.iter().map(|&l| Value::Int(l)).collect();
2924            Ok(Some(Value::Array(Rc::new(label_vals))))
2925        }
2926        // -- Phase 10: Categorical encoding ------------------------------------
2927        "label_encode" => {
2928            // label_encode: convert array of strings to (sorted_levels, codes)
2929            // Uses BTreeSet for deterministic sorted order
2930            let strs: Vec<String> = match &args[0] {
2931                Value::Array(arr) => arr.iter().map(|v| match v {
2932                    Value::String(s) => Ok(s.as_str().to_string()),
2933                    _ => Err("label_encode: expected array of strings".to_string()),
2934                }).collect::<Result<_, _>>()?,
2935                _ => return Err("label_encode: expected array".into()),
2936            };
2937            let mut level_set = std::collections::BTreeSet::new();
2938            for s in &strs { level_set.insert(s.clone()); }
2939            let levels: Vec<String> = level_set.into_iter().collect();
2940            let level_map: std::collections::BTreeMap<&str, u32> = levels.iter().enumerate()
2941                .map(|(i, s)| (s.as_str(), i as u32)).collect();
2942            let codes: Vec<u32> = strs.iter().map(|s| level_map[s.as_str()]).collect();
2943            let level_vals: Vec<Value> = levels.iter().map(|s| Value::String(Rc::new(s.clone()))).collect();
2944            let code_vals: Vec<Value> = codes.iter().map(|&c| Value::Int(c as i64)).collect();
2945            Ok(Some(Value::Tuple(Rc::new(vec![
2946                Value::Array(Rc::new(level_vals)),
2947                Value::Array(Rc::new(code_vals)),
2948            ]))))
2949        }
2950        // -- Phase 11: Time series ---------------------------------------------
2951        "acf" => {
2952            let data = value_to_f64_vec(&args[0])?;
2953            let max_lag = value_to_usize(&args[1])?;
2954            let result = crate::timeseries::acf(&data, max_lag);
2955            Ok(Some(Value::Array(Rc::new(result.into_iter().map(Value::Float).collect()))))
2956        }
2957        "ewma" => {
2958            let data = value_to_f64_vec(&args[0])?;
2959            let alpha = value_to_f64(&args[1])?;
2960            let result = crate::timeseries::ewma(&data, alpha);
2961            Ok(Some(Value::Array(Rc::new(result.into_iter().map(Value::Float).collect()))))
2962        }
2963        "diff" => {
2964            let data = value_to_f64_vec(&args[0])?;
2965            let periods = value_to_usize(&args[1])?;
2966            let result = crate::timeseries::diff(&data, periods);
2967            Ok(Some(Value::Array(Rc::new(result.into_iter().map(Value::Float).collect()))))
2968        }
2969        // -- Phase 5: Optimization root finding --------------------------------
2970        "bisect" => {
2971            // bisect expects a closure as first arg — handled by executor, not here
2972            Ok(None)
2973        }
2974        // -- Phase 6: Interpolation --------------------------------------------
2975        "polyfit" => {
2976            let x = value_to_f64_vec(&args[0])?;
2977            let y = value_to_f64_vec(&args[1])?;
2978            let degree = value_to_usize(&args[2])?;
2979            let coeffs = crate::interpolate::polyfit(&x, &y, degree).map_err(|e| e)?;
2980            Ok(Some(Value::Array(Rc::new(coeffs.into_iter().map(Value::Float).collect()))))
2981        }
2982        "polyval" => {
2983            let coeffs = value_to_f64_vec(&args[0])?;
2984            let x = value_to_f64_vec(&args[1])?;
2985            let result = crate::interpolate::polyval(&coeffs, &x);
2986            Ok(Some(Value::Array(Rc::new(result.into_iter().map(Value::Float).collect()))))
2987        }
2988
2989        // ── Phase 2 Beta Hardening: getenv ──────────────────────────────
2990        "getenv" => {
2991            if args.len() != 1 { return Err("getenv requires 1 argument (name)".into()); }
2992            let name = match &args[0] {
2993                Value::String(s) => s.as_str().to_string(),
2994                _ => return Err("getenv: argument must be String".into()),
2995            };
2996            let val = std::env::var(&name).unwrap_or_default();
2997            Ok(Some(Value::String(Rc::new(val))))
2998        }
2999
3000        // ── Phase 2 Beta Hardening: Functional map builtins ─────────────
3001        "map_new" => {
3002            if !args.is_empty() { return Err("map_new takes 0 arguments".into()); }
3003            Ok(Some(Value::Map(Rc::new(RefCell::new(crate::det_map::DetMap::new())))))
3004        }
3005        "map_set" => {
3006            if args.len() != 3 { return Err("map_set requires 3 args: map, key, value".into()); }
3007            let m = match &args[0] {
3008                Value::Map(m) => m,
3009                _ => return Err("map_set: first argument must be Map".into()),
3010            };
3011            let mut new_map = m.borrow().clone();
3012            new_map.insert(args[1].clone(), args[2].clone());
3013            Ok(Some(Value::Map(Rc::new(RefCell::new(new_map)))))
3014        }
3015        "map_get" => {
3016            if args.len() != 2 { return Err("map_get requires 2 args: map, key".into()); }
3017            let m = match &args[0] {
3018                Value::Map(m) => m,
3019                _ => return Err("map_get: first argument must be Map".into()),
3020            };
3021            match m.borrow().get(&args[1]) {
3022                Some(v) => Ok(Some(v.clone())),
3023                None => Ok(Some(Value::Void)),
3024            }
3025        }
3026        "map_keys" => {
3027            if args.len() != 1 { return Err("map_keys requires 1 arg: map".into()); }
3028            let m = match &args[0] {
3029                Value::Map(m) => m,
3030                _ => return Err("map_keys: argument must be Map".into()),
3031            };
3032            Ok(Some(Value::Array(Rc::new(m.borrow().keys()))))
3033        }
3034        "map_values" => {
3035            if args.len() != 1 { return Err("map_values requires 1 arg: map".into()); }
3036            let m = match &args[0] {
3037                Value::Map(m) => m,
3038                _ => return Err("map_values: argument must be Map".into()),
3039            };
3040            Ok(Some(Value::Array(Rc::new(m.borrow().values_vec()))))
3041        }
3042        "map_contains" => {
3043            if args.len() != 2 { return Err("map_contains requires 2 args: map, key".into()); }
3044            let m = match &args[0] {
3045                Value::Map(m) => m,
3046                _ => return Err("map_contains: first argument must be Map".into()),
3047            };
3048            Ok(Some(Value::Bool(m.borrow().contains_key(&args[1]))))
3049        }
3050
3051        // -- Phase 3: Numerical integration ------------------------------------
3052        "trapezoid" | "trapz" => {
3053            if args.len() != 2 {
3054                return Err("trapezoid requires 2 arguments (xs, ys)".into());
3055            }
3056            let xs = value_to_f64_vec(&args[0])?;
3057            let ys = value_to_f64_vec(&args[1])?;
3058            let result = crate::integrate::trapezoid(&xs, &ys)?;
3059            Ok(Some(Value::Float(result)))
3060        }
3061        "simpson" | "simps" => {
3062            if args.len() != 2 {
3063                return Err("simpson requires 2 arguments (xs, ys)".into());
3064            }
3065            let xs = value_to_f64_vec(&args[0])?;
3066            let ys = value_to_f64_vec(&args[1])?;
3067            let result = crate::integrate::simpson(&xs, &ys)?;
3068            Ok(Some(Value::Float(result)))
3069        }
3070        "cumtrapz" => {
3071            if args.len() != 2 {
3072                return Err("cumtrapz requires 2 arguments (xs, ys)".into());
3073            }
3074            let xs = value_to_f64_vec(&args[0])?;
3075            let ys = value_to_f64_vec(&args[1])?;
3076            let result = crate::integrate::cumtrapz(&xs, &ys)?;
3077            Ok(Some(Value::Array(Rc::new(
3078                result.into_iter().map(Value::Float).collect(),
3079            ))))
3080        }
3081        // -- Phase 3: Numerical differentiation --------------------------------
3082        "diff_central" => {
3083            if args.len() != 2 {
3084                return Err("diff_central requires 2 arguments (xs, ys)".into());
3085            }
3086            let xs = value_to_f64_vec(&args[0])?;
3087            let ys = value_to_f64_vec(&args[1])?;
3088            let result = crate::differentiate::diff_central(&xs, &ys)?;
3089            Ok(Some(Value::Array(Rc::new(
3090                result.into_iter().map(Value::Float).collect(),
3091            ))))
3092        }
3093        "diff_forward" => {
3094            if args.len() != 2 {
3095                return Err("diff_forward requires 2 arguments (xs, ys)".into());
3096            }
3097            let xs = value_to_f64_vec(&args[0])?;
3098            let ys = value_to_f64_vec(&args[1])?;
3099            let result = crate::differentiate::diff_forward(&xs, &ys)?;
3100            Ok(Some(Value::Array(Rc::new(
3101                result.into_iter().map(Value::Float).collect(),
3102            ))))
3103        }
3104        "gradient_1d" => {
3105            if args.len() != 2 {
3106                return Err("gradient_1d requires 2 arguments (ys, dx)".into());
3107            }
3108            let ys = value_to_f64_vec(&args[0])?;
3109            let dx = value_to_f64(&args[1])?;
3110            let result = crate::differentiate::gradient_1d(&ys, dx);
3111            Ok(Some(Value::Array(Rc::new(
3112                result.into_iter().map(Value::Float).collect(),
3113            ))))
3114        }
3115        // -- Phase 3: Constrained optimization ---------------------------------
3116        "penalty_objective" => {
3117            if args.len() != 3 {
3118                return Err(
3119                    "penalty_objective requires 3 arguments (f_val, constraint_violations, penalty)"
3120                        .into(),
3121                );
3122            }
3123            let f_val = value_to_f64(&args[0])?;
3124            let violations = value_to_f64_vec(&args[1])?;
3125            let penalty = value_to_f64(&args[2])?;
3126            let result = crate::optimize::penalty_objective(f_val, &violations, penalty);
3127            Ok(Some(Value::Float(result)))
3128        }
3129        "project_box" => {
3130            if args.len() != 3 {
3131                return Err("project_box requires 3 arguments (x, lower, upper)".into());
3132            }
3133            let x = value_to_f64_vec(&args[0])?;
3134            let lower = value_to_f64_vec(&args[1])?;
3135            let upper = value_to_f64_vec(&args[2])?;
3136            let result = crate::optimize::project_box(&x, &lower, &upper)?;
3137            Ok(Some(Value::Array(Rc::new(
3138                result.into_iter().map(Value::Float).collect(),
3139            ))))
3140        }
3141        "projected_gd_step" => {
3142            if args.len() != 5 {
3143                return Err(
3144                    "projected_gd_step requires 5 arguments (x, grad, lr, lower, upper)".into(),
3145                );
3146            }
3147            let x = value_to_f64_vec(&args[0])?;
3148            let grad = value_to_f64_vec(&args[1])?;
3149            let lr = value_to_f64(&args[2])?;
3150            let lower = value_to_f64_vec(&args[3])?;
3151            let upper = value_to_f64_vec(&args[4])?;
3152            let result = crate::optimize::projected_gd_step(&x, &grad, lr, &lower, &upper)?;
3153            Ok(Some(Value::Array(Rc::new(
3154                result.into_iter().map(Value::Float).collect(),
3155            ))))
3156        }
3157
3158        // ── LSTM cell ───────────────────────────────────────────────
3159        "lstm_cell" => {
3160            if args.len() != 7 {
3161                return Err("lstm_cell requires 7 Tensor arguments: x, h_prev, c_prev, w_ih, w_hh, b_ih, b_hh".into());
3162            }
3163            let x = value_to_tensor(&args[0])?;
3164            let h_prev = value_to_tensor(&args[1])?;
3165            let c_prev = value_to_tensor(&args[2])?;
3166            let w_ih = value_to_tensor(&args[3])?;
3167            let w_hh = value_to_tensor(&args[4])?;
3168            let b_ih = value_to_tensor(&args[5])?;
3169            let b_hh = value_to_tensor(&args[6])?;
3170            let (h_new, c_new) = crate::ml::lstm_cell(x, h_prev, c_prev, w_ih, w_hh, b_ih, b_hh)?;
3171            Ok(Some(Value::Tuple(Rc::new(vec![
3172                Value::Tensor(h_new),
3173                Value::Tensor(c_new),
3174            ]))))
3175        }
3176
3177        // ── GRU cell ────────────────────────────────────────────────
3178        "gru_cell" => {
3179            if args.len() != 6 {
3180                return Err("gru_cell requires 6 Tensor arguments: x, h_prev, w_ih, w_hh, b_ih, b_hh".into());
3181            }
3182            let x = value_to_tensor(&args[0])?;
3183            let h_prev = value_to_tensor(&args[1])?;
3184            let w_ih = value_to_tensor(&args[2])?;
3185            let w_hh = value_to_tensor(&args[3])?;
3186            let b_ih = value_to_tensor(&args[4])?;
3187            let b_hh = value_to_tensor(&args[5])?;
3188            let h_new = crate::ml::gru_cell(x, h_prev, w_ih, w_hh, b_ih, b_hh)?;
3189            Ok(Some(Value::Tensor(h_new)))
3190        }
3191
3192        // ── Multi-Head Attention ────────────────────────────────────
3193        "multi_head_attention" => {
3194            if args.len() != 12 {
3195                return Err("multi_head_attention requires 12 arguments: q, k, v, w_q, w_k, w_v, w_o, b_q, b_k, b_v, b_o, num_heads".into());
3196            }
3197            let q = value_to_tensor(&args[0])?;
3198            let k = value_to_tensor(&args[1])?;
3199            let v = value_to_tensor(&args[2])?;
3200            let w_q = value_to_tensor(&args[3])?;
3201            let w_k = value_to_tensor(&args[4])?;
3202            let w_v = value_to_tensor(&args[5])?;
3203            let w_o = value_to_tensor(&args[6])?;
3204            let b_q = value_to_tensor(&args[7])?;
3205            let b_k = value_to_tensor(&args[8])?;
3206            let b_v = value_to_tensor(&args[9])?;
3207            let b_o = value_to_tensor(&args[10])?;
3208            let num_heads = value_to_usize(&args[11])?;
3209            let out = crate::ml::multi_head_attention(
3210                q, k, v, w_q, w_k, w_v, w_o, b_q, b_k, b_v, b_o, num_heads,
3211            )?;
3212            Ok(Some(Value::Tensor(out)))
3213        }
3214
3215        // ── AR fit (Yule-Walker) ────────────────────────────────────
3216        "ar_fit" => {
3217            if args.len() != 2 {
3218                return Err("ar_fit requires 2 arguments: data (array), p (int)".into());
3219            }
3220            let data = value_to_f64_vec(&args[0])?;
3221            let p = value_to_usize(&args[1])?;
3222            let coeffs = crate::timeseries::ar_fit(&data, p)?;
3223            Ok(Some(Value::Array(Rc::new(
3224                coeffs.into_iter().map(Value::Float).collect(),
3225            ))))
3226        }
3227
3228        // ── ARIMA differencing ──────────────────────────────────────
3229        "arima_diff" => {
3230            if args.len() != 2 {
3231                return Err("arima_diff requires 2 arguments: data (array), d (int)".into());
3232            }
3233            let data = value_to_f64_vec(&args[0])?;
3234            let d = value_to_usize(&args[1])?;
3235            let result = crate::timeseries::arima_diff(&data, d);
3236            Ok(Some(Value::Array(Rc::new(
3237                result.into_iter().map(Value::Float).collect(),
3238            ))))
3239        }
3240
3241        // ── AR forecast ─────────────────────────────────────────────
3242        "ar_forecast" => {
3243            if args.len() != 3 {
3244                return Err("ar_forecast requires 3 arguments: coeffs (array), history (array), steps (int)".into());
3245            }
3246            let coeffs = value_to_f64_vec(&args[0])?;
3247            let history = value_to_f64_vec(&args[1])?;
3248            let steps = value_to_usize(&args[2])?;
3249            let result = crate::timeseries::ar_forecast(&coeffs, &history, steps)?;
3250            Ok(Some(Value::Array(Rc::new(
3251                result.into_iter().map(Value::Float).collect(),
3252            ))))
3253        }
3254
3255        // ── Phase 5: Preprocessing builtins ─────────────────────────────
3256        "fillna" => {
3257            if args.len() != 2 { return Err("fillna requires 2 arguments (array, fill_value)".into()); }
3258            let arr = match &args[0] {
3259                Value::Array(a) => a.as_ref().clone(),
3260                _ => return Err(format!("fillna: first argument must be Array, got {}", args[0].type_name())),
3261            };
3262            let fill = &args[1];
3263            let result: Vec<Value> = arr.iter().map(|v| {
3264                match v {
3265                    Value::Na => fill.clone(),
3266                    Value::Void => fill.clone(),
3267                    Value::Float(f) if f.is_nan() => fill.clone(),
3268                    other => other.clone(),
3269                }
3270            }).collect();
3271            Ok(Some(Value::Array(Rc::new(result))))
3272        }
3273
3274        "is_na" => {
3275            if args.len() != 1 { return Err("is_na requires 1 argument".into()); }
3276            let result = matches!(&args[0], Value::Na);
3277            Ok(Some(Value::Bool(result)))
3278        }
3279
3280        "is_not_null" => {
3281            if args.len() != 1 { return Err("is_not_null requires 1 argument".into()); }
3282            let result = match &args[0] {
3283                Value::Na => false,
3284                Value::Void => false,
3285                Value::Float(f) => !f.is_nan(),
3286                _ => true,
3287            };
3288            Ok(Some(Value::Bool(result)))
3289        }
3290
3291        "drop_na" => {
3292            if args.len() != 1 { return Err("drop_na requires 1 argument (array)".into()); }
3293            let arr = match &args[0] {
3294                Value::Array(a) => a.as_ref().clone(),
3295                _ => return Err(format!("drop_na: first argument must be Array, got {}", args[0].type_name())),
3296            };
3297            let result: Vec<Value> = arr.into_iter().filter(|v| !matches!(v, Value::Na)).collect();
3298            Ok(Some(Value::Array(Rc::new(result))))
3299        }
3300
3301        "interpolate_linear" => {
3302            if args.len() != 1 { return Err("interpolate_linear requires 1 argument (array of f64)".into()); }
3303            let data = value_to_f64_vec(&args[0])?;
3304            let n = data.len();
3305            if n == 0 {
3306                return Ok(Some(Value::Array(Rc::new(vec![]))));
3307            }
3308            let mut result = data.clone();
3309
3310            // Find first and last non-NaN for edge fill
3311            let first_valid = result.iter().position(|x| !x.is_nan());
3312            let last_valid = result.iter().rposition(|x| !x.is_nan());
3313
3314            if let (Some(fv), Some(lv)) = (first_valid, last_valid) {
3315                // Backward-fill leading NaNs
3316                for i in 0..fv {
3317                    result[i] = result[fv];
3318                }
3319                // Forward-fill trailing NaNs
3320                for i in (lv + 1)..n {
3321                    result[i] = result[lv];
3322                }
3323                // Linearly interpolate interior NaNs
3324                let mut i = fv + 1;
3325                while i < lv {
3326                    if result[i].is_nan() {
3327                        // Find the next non-NaN
3328                        let start = i - 1;
3329                        let mut end = i + 1;
3330                        while end < n && result[end].is_nan() {
3331                            end += 1;
3332                        }
3333                        let v0 = result[start];
3334                        let v1 = result[end];
3335                        let span = (end - start) as f64;
3336                        for j in (start + 1)..end {
3337                            let t = (j - start) as f64 / span;
3338                            // Linear interpolation: v0 + t * (v1 - v0)
3339                            // Using Kahan-style: compute as v0*(1-t) + v1*t
3340                            use cjc_repro::KahanAccumulatorF64;
3341                            let mut acc = KahanAccumulatorF64::new();
3342                            acc.add(v0 * (1.0 - t));
3343                            acc.add(v1 * t);
3344                            result[j] = acc.finalize();
3345                        }
3346                        i = end + 1;
3347                    } else {
3348                        i += 1;
3349                    }
3350                }
3351            }
3352            // If no valid values, result stays all NaN
3353
3354            Ok(Some(Value::Array(Rc::new(
3355                result.into_iter().map(Value::Float).collect(),
3356            ))))
3357        }
3358
3359        "coalesce" => {
3360            if args.is_empty() { return Err("coalesce requires at least 1 argument".into()); }
3361            // Scalar mode: coalesce(val1, val2, ...) → first non-NA/non-Void
3362            let first_is_array = matches!(&args[0], Value::Array(_));
3363            if args.len() == 2 && first_is_array {
3364                // Array mode: coalesce(array_a, array_b) → element-wise
3365                let a = match &args[0] {
3366                    Value::Array(a) => a.as_ref().clone(),
3367                    _ => unreachable!(),
3368                };
3369                let b = match &args[1] {
3370                    Value::Array(b) => b.as_ref().clone(),
3371                    _ => return Err(format!("coalesce: second argument must be Array, got {}", args[1].type_name())),
3372                };
3373                if a.len() != b.len() {
3374                    return Err(format!("coalesce: arrays must have equal length, got {} and {}", a.len(), b.len()));
3375                }
3376                let result: Vec<Value> = a.iter().zip(b.iter()).map(|(va, vb)| {
3377                    let is_null_a = matches!(va, Value::Na) || matches!(va, Value::Void) || matches!(va, Value::Float(f) if f.is_nan());
3378                    if is_null_a { vb.clone() } else { va.clone() }
3379                }).collect();
3380                Ok(Some(Value::Array(Rc::new(result))))
3381            } else {
3382                // Scalar mode: return first non-NA, non-Void value
3383                for arg in args {
3384                    let is_null = matches!(arg, Value::Na) || matches!(arg, Value::Void) || matches!(arg, Value::Float(f) if f.is_nan());
3385                    if !is_null { return Ok(Some(arg.clone())); }
3386                }
3387                // All null → return last arg (or NA)
3388                Ok(Some(args.last().cloned().unwrap_or(Value::Na)))
3389            }
3390        }
3391
3392        "cut" => {
3393            if args.len() != 2 { return Err("cut requires 2 arguments (array, breaks)".into()); }
3394            let data = value_to_f64_vec(&args[0])?;
3395            let breaks = value_to_f64_vec(&args[1])?;
3396            if breaks.is_empty() {
3397                return Err("cut: breaks array must not be empty".into());
3398            }
3399            let mut sorted_breaks = breaks.clone();
3400            sorted_breaks.sort_by(f64::total_cmp);
3401            let labels: Vec<Value> = data.iter().map(|&x| {
3402                // Find the bin
3403                let label = if x <= sorted_breaks[0] {
3404                    format!("(-inf,{}]", sorted_breaks[0])
3405                } else if x > sorted_breaks[sorted_breaks.len() - 1] {
3406                    format!("({},inf)", sorted_breaks[sorted_breaks.len() - 1])
3407                } else {
3408                    let mut found = String::new();
3409                    for i in 1..sorted_breaks.len() {
3410                        if x <= sorted_breaks[i] {
3411                            found = format!("({},{}]", sorted_breaks[i - 1], sorted_breaks[i]);
3412                            break;
3413                        }
3414                    }
3415                    if found.is_empty() {
3416                        // x is exactly at the last break
3417                        format!("({},inf)", sorted_breaks[sorted_breaks.len() - 1])
3418                    } else {
3419                        found
3420                    }
3421                };
3422                Value::String(Rc::new(label))
3423            }).collect();
3424            Ok(Some(Value::Array(Rc::new(labels))))
3425        }
3426
3427        "qcut" => {
3428            if args.len() != 2 { return Err("qcut requires 2 arguments (array, n_bins)".into()); }
3429            let data = value_to_f64_vec(&args[0])?;
3430            let n = value_to_usize(&args[1])?;
3431            if n == 0 {
3432                return Err("qcut: n_bins must be > 0".into());
3433            }
3434            // Compute quantile break points
3435            let mut breaks = Vec::with_capacity(n - 1);
3436            for i in 1..n {
3437                let p = i as f64 / n as f64;
3438                let q = crate::stats::quantile(&data, p)?;
3439                breaks.push(q);
3440            }
3441            // Deduplicate breaks (can happen with repeated values)
3442            breaks.dedup_by(|a, b| *a == *b);
3443
3444            // Re-dispatch to cut logic
3445            let mut sorted_breaks = breaks.clone();
3446            sorted_breaks.sort_by(f64::total_cmp);
3447            let labels: Vec<Value> = data.iter().map(|&x| {
3448                let label = if sorted_breaks.is_empty() || x <= sorted_breaks[0] {
3449                    format!("(-inf,{}]", sorted_breaks.first().copied().unwrap_or(x))
3450                } else if x > sorted_breaks[sorted_breaks.len() - 1] {
3451                    format!("({},inf)", sorted_breaks[sorted_breaks.len() - 1])
3452                } else {
3453                    let mut found = String::new();
3454                    for i in 1..sorted_breaks.len() {
3455                        if x <= sorted_breaks[i] {
3456                            found = format!("({},{}]", sorted_breaks[i - 1], sorted_breaks[i]);
3457                            break;
3458                        }
3459                    }
3460                    if found.is_empty() {
3461                        format!("({},inf)", sorted_breaks[sorted_breaks.len() - 1])
3462                    } else {
3463                        found
3464                    }
3465                };
3466                Value::String(Rc::new(label))
3467            }).collect();
3468            Ok(Some(Value::Array(Rc::new(labels))))
3469        }
3470
3471        "min_max_scale" => {
3472            if args.len() != 3 { return Err("min_max_scale requires 3 arguments (array, low, high)".into()); }
3473            let data = value_to_f64_vec(&args[0])?;
3474            let low = value_to_f64(&args[1])?;
3475            let high = value_to_f64(&args[2])?;
3476            if data.is_empty() {
3477                return Ok(Some(Value::Array(Rc::new(vec![]))));
3478            }
3479            let mut min_val = f64::INFINITY;
3480            let mut max_val = f64::NEG_INFINITY;
3481            for &x in &data {
3482                if x < min_val { min_val = x; }
3483                if x > max_val { max_val = x; }
3484            }
3485            let range = max_val - min_val;
3486            let result: Vec<Value> = if range == 0.0 {
3487                // All values are the same — map to midpoint
3488                let mid = (low + high) / 2.0;
3489                data.iter().map(|_| Value::Float(mid)).collect()
3490            } else {
3491                data.iter().map(|&x| {
3492                    use cjc_repro::KahanAccumulatorF64;
3493                    let t = (x - min_val) / range;
3494                    let mut acc = KahanAccumulatorF64::new();
3495                    acc.add(low * (1.0 - t));
3496                    acc.add(high * t);
3497                    Value::Float(acc.finalize())
3498                }).collect()
3499            };
3500            Ok(Some(Value::Array(Rc::new(result))))
3501        }
3502
3503        "robust_scale" => {
3504            if args.len() != 1 { return Err("robust_scale requires 1 argument (array)".into()); }
3505            let data = value_to_f64_vec(&args[0])?;
3506            if data.is_empty() {
3507                return Ok(Some(Value::Array(Rc::new(vec![]))));
3508            }
3509            let med = crate::stats::median(&data)?;
3510            let iqr_val = crate::stats::iqr(&data)?;
3511            let result: Vec<Value> = if iqr_val == 0.0 {
3512                data.iter().map(|&x| Value::Float(x - med)).collect()
3513            } else {
3514                data.iter().map(|&x| Value::Float((x - med) / iqr_val)).collect()
3515            };
3516            Ok(Some(Value::Array(Rc::new(result))))
3517        }
3518
3519        // ── Categorical / Factor builtins ───────────────────────────────────
3520        "as_factor" => {
3521            // Convert a string array to a Factor struct: { levels: [String], codes: [Int] }
3522            if args.len() != 1 { return Err("as_factor requires 1 argument (string array)".into()); }
3523            let arr = match &args[0] {
3524                Value::Array(a) => a.as_ref().clone(),
3525                _ => return Err("as_factor: argument must be Array of strings".into()),
3526            };
3527            // Build levels in order of first appearance (deterministic)
3528            let mut levels: Vec<String> = Vec::new();
3529            let mut level_index = std::collections::BTreeMap::new();
3530            let mut codes: Vec<Value> = Vec::with_capacity(arr.len());
3531            for item in &arr {
3532                let s = match item {
3533                    Value::String(s) => s.as_str().to_string(),
3534                    _ => format!("{}", item),
3535                };
3536                let idx = if let Some(&idx) = level_index.get(&s) {
3537                    idx
3538                } else {
3539                    let idx = levels.len() as i64;
3540                    level_index.insert(s.clone(), idx);
3541                    levels.push(s);
3542                    idx
3543                };
3544                codes.push(Value::Int(idx));
3545            }
3546            let level_values: Vec<Value> = levels.into_iter()
3547                .map(|s| Value::String(Rc::new(s)))
3548                .collect();
3549            let mut fields = std::collections::BTreeMap::new();
3550            fields.insert("__type".to_string(), Value::String(Rc::new("Factor".to_string())));
3551            fields.insert("levels".to_string(), Value::Array(Rc::new(level_values)));
3552            fields.insert("codes".to_string(), Value::Array(Rc::new(codes)));
3553            Ok(Some(Value::Struct { name: "Factor".to_string(), fields }))
3554        }
3555
3556        "factor_levels" => {
3557            // Extract the levels array from a Factor struct
3558            if args.len() != 1 { return Err("factor_levels requires 1 argument (Factor)".into()); }
3559            match &args[0] {
3560                Value::Struct { name, fields } if name == "Factor" => {
3561                    match fields.get("levels") {
3562                        Some(v) => Ok(Some(v.clone())),
3563                        None => Err("factor_levels: Factor missing 'levels' field".into()),
3564                    }
3565                }
3566                _ => Err("factor_levels: argument must be a Factor".into()),
3567            }
3568        }
3569
3570        "factor_codes" => {
3571            // Extract the codes array from a Factor struct
3572            if args.len() != 1 { return Err("factor_codes requires 1 argument (Factor)".into()); }
3573            match &args[0] {
3574                Value::Struct { name, fields } if name == "Factor" => {
3575                    match fields.get("codes") {
3576                        Some(v) => Ok(Some(v.clone())),
3577                        None => Err("factor_codes: Factor missing 'codes' field".into()),
3578                    }
3579                }
3580                _ => Err("factor_codes: argument must be a Factor".into()),
3581            }
3582        }
3583
3584        "fct_relevel" => {
3585            // Reorder factor levels: fct_relevel(factor, new_order_array)
3586            if args.len() != 2 { return Err("fct_relevel requires 2 arguments (Factor, new_level_order)".into()); }
3587            let (old_levels, old_codes) = match &args[0] {
3588                Value::Struct { name, fields } if name == "Factor" => {
3589                    let levels = match fields.get("levels") {
3590                        Some(Value::Array(a)) => a.as_ref().clone(),
3591                        _ => return Err("fct_relevel: Factor missing 'levels'".into()),
3592                    };
3593                    let codes = match fields.get("codes") {
3594                        Some(Value::Array(a)) => a.as_ref().clone(),
3595                        _ => return Err("fct_relevel: Factor missing 'codes'".into()),
3596                    };
3597                    (levels, codes)
3598                }
3599                _ => return Err("fct_relevel: first argument must be a Factor".into()),
3600            };
3601            let new_order = match &args[1] {
3602                Value::Array(a) => a.as_ref().clone(),
3603                _ => return Err("fct_relevel: second argument must be array of level strings".into()),
3604            };
3605            // Build old level strings
3606            let old_strs: Vec<String> = old_levels.iter().map(|v| match v {
3607                Value::String(s) => s.as_str().to_string(),
3608                _ => format!("{}", v),
3609            }).collect();
3610            // Build new order strings
3611            let new_strs: Vec<String> = new_order.iter().map(|v| match v {
3612                Value::String(s) => s.as_str().to_string(),
3613                _ => format!("{}", v),
3614            }).collect();
3615            // Build mapping: old_index → new_index
3616            let mut remap = std::collections::BTreeMap::new();
3617            for (old_idx, s) in old_strs.iter().enumerate() {
3618                if let Some(new_idx) = new_strs.iter().position(|ns| ns == s) {
3619                    remap.insert(old_idx as i64, new_idx as i64);
3620                }
3621            }
3622            // Recode
3623            let new_codes: Vec<Value> = old_codes.iter().map(|v| {
3624                if let Value::Int(c) = v {
3625                    Value::Int(remap.get(c).copied().unwrap_or(*c))
3626                } else { v.clone() }
3627            }).collect();
3628            let new_level_values: Vec<Value> = new_strs.into_iter()
3629                .map(|s| Value::String(Rc::new(s)))
3630                .collect();
3631            let mut fields = std::collections::BTreeMap::new();
3632            fields.insert("__type".to_string(), Value::String(Rc::new("Factor".to_string())));
3633            fields.insert("levels".to_string(), Value::Array(Rc::new(new_level_values)));
3634            fields.insert("codes".to_string(), Value::Array(Rc::new(new_codes)));
3635            Ok(Some(Value::Struct { name: "Factor".to_string(), fields }))
3636        }
3637
3638        "fct_lump" => {
3639            // Lump rare factor levels into "Other": fct_lump(factor, n)
3640            // Keeps the top `n` most frequent levels, lumps rest into "Other"
3641            if args.len() != 2 { return Err("fct_lump requires 2 arguments (Factor, n)".into()); }
3642            let (old_levels, old_codes) = match &args[0] {
3643                Value::Struct { name, fields } if name == "Factor" => {
3644                    let levels = match fields.get("levels") {
3645                        Some(Value::Array(a)) => a.as_ref().clone(),
3646                        _ => return Err("fct_lump: Factor missing 'levels'".into()),
3647                    };
3648                    let codes = match fields.get("codes") {
3649                        Some(Value::Array(a)) => a.as_ref().clone(),
3650                        _ => return Err("fct_lump: Factor missing 'codes'".into()),
3651                    };
3652                    (levels, codes)
3653                }
3654                _ => return Err("fct_lump: first argument must be a Factor".into()),
3655            };
3656            let n = match &args[1] {
3657                Value::Int(n) => *n as usize,
3658                _ => return Err("fct_lump: second argument must be Int".into()),
3659            };
3660            // Count frequency of each code
3661            let mut freq = std::collections::BTreeMap::new();
3662            for v in &old_codes {
3663                if let Value::Int(c) = v { *freq.entry(*c).or_insert(0usize) += 1; }
3664            }
3665            // Sort by frequency descending (then by code for determinism)
3666            let mut freq_vec: Vec<(i64, usize)> = freq.into_iter().collect();
3667            freq_vec.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
3668            // Top n codes to keep
3669            let keep_codes: std::collections::BTreeSet<i64> = freq_vec.iter().take(n).map(|(c, _)| *c).collect();
3670            // Build new levels: kept levels + "Other"
3671            let old_strs: Vec<String> = old_levels.iter().map(|v| match v {
3672                Value::String(s) => s.as_str().to_string(),
3673                _ => format!("{}", v),
3674            }).collect();
3675            let mut new_levels: Vec<String> = Vec::new();
3676            let mut code_remap = std::collections::BTreeMap::new();
3677            for (old_idx, s) in old_strs.iter().enumerate() {
3678                if keep_codes.contains(&(old_idx as i64)) {
3679                    let new_idx = new_levels.len() as i64;
3680                    code_remap.insert(old_idx as i64, new_idx);
3681                    new_levels.push(s.clone());
3682                }
3683            }
3684            let other_idx = new_levels.len() as i64;
3685            new_levels.push("Other".to_string());
3686            // Recode
3687            let new_codes: Vec<Value> = old_codes.iter().map(|v| {
3688                if let Value::Int(c) = v {
3689                    Value::Int(*code_remap.get(c).unwrap_or(&other_idx))
3690                } else { v.clone() }
3691            }).collect();
3692            let new_level_values: Vec<Value> = new_levels.into_iter()
3693                .map(|s| Value::String(Rc::new(s)))
3694                .collect();
3695            let mut fields = std::collections::BTreeMap::new();
3696            fields.insert("__type".to_string(), Value::String(Rc::new("Factor".to_string())));
3697            fields.insert("levels".to_string(), Value::Array(Rc::new(new_level_values)));
3698            fields.insert("codes".to_string(), Value::Array(Rc::new(new_codes)));
3699            Ok(Some(Value::Struct { name: "Factor".to_string(), fields }))
3700        }
3701
3702        "fct_count" => {
3703            // Count observations per level: returns array of (level, count) tuples
3704            if args.len() != 1 { return Err("fct_count requires 1 argument (Factor)".into()); }
3705            let (levels, codes) = match &args[0] {
3706                Value::Struct { name, fields } if name == "Factor" => {
3707                    let levels = match fields.get("levels") {
3708                        Some(Value::Array(a)) => a.as_ref().clone(),
3709                        _ => return Err("fct_count: Factor missing 'levels'".into()),
3710                    };
3711                    let codes = match fields.get("codes") {
3712                        Some(Value::Array(a)) => a.as_ref().clone(),
3713                        _ => return Err("fct_count: Factor missing 'codes'".into()),
3714                    };
3715                    (levels, codes)
3716                }
3717                _ => return Err("fct_count: argument must be a Factor".into()),
3718            };
3719            let mut freq = std::collections::BTreeMap::new();
3720            for v in &codes {
3721                if let Value::Int(c) = v { *freq.entry(*c).or_insert(0i64) += 1; }
3722            }
3723            let result: Vec<Value> = levels.iter().enumerate().map(|(i, lev)| {
3724                let count = freq.get(&(i as i64)).copied().unwrap_or(0);
3725                Value::Tuple(Rc::new(vec![lev.clone(), Value::Int(count)]))
3726            }).collect();
3727            Ok(Some(Value::Array(Rc::new(result))))
3728        }
3729
3730        // ── Normality Tests ──────────────────────────────────────────────────
3731        "jarque_bera" => {
3732            if args.len() != 1 { return Err("jarque_bera requires 1 argument (data array)".into()); }
3733            let data = value_to_f64_vec(&args[0])?;
3734            let r = crate::hypothesis::jarque_bera(&data)?;
3735            let mut fields = std::collections::BTreeMap::new();
3736            fields.insert("statistic".to_string(), Value::Float(r.statistic));
3737            fields.insert("p_value".to_string(), Value::Float(r.p_value));
3738            Ok(Some(Value::Struct { name: "JarqueBeraResult".to_string(), fields }))
3739        }
3740
3741        "anderson_darling" => {
3742            if args.len() != 1 { return Err("anderson_darling requires 1 argument (data array)".into()); }
3743            let data = value_to_f64_vec(&args[0])?;
3744            let r = crate::hypothesis::anderson_darling(&data)?;
3745            let mut fields = std::collections::BTreeMap::new();
3746            fields.insert("statistic".to_string(), Value::Float(r.statistic));
3747            fields.insert("p_value".to_string(), Value::Float(r.p_value));
3748            Ok(Some(Value::Struct { name: "AndersonDarlingResult".to_string(), fields }))
3749        }
3750
3751        "ks_test" => {
3752            if args.len() != 1 { return Err("ks_test requires 1 argument (data array)".into()); }
3753            let data = value_to_f64_vec(&args[0])?;
3754            let r = crate::hypothesis::ks_test_normal(&data)?;
3755            let mut fields = std::collections::BTreeMap::new();
3756            fields.insert("statistic".to_string(), Value::Float(r.statistic));
3757            fields.insert("p_value".to_string(), Value::Float(r.p_value));
3758            Ok(Some(Value::Struct { name: "KSResult".to_string(), fields }))
3759        }
3760
3761        // ── Effect Sizes ────────────────────────────────────────────────────
3762        "cohens_d" => {
3763            if args.len() != 2 { return Err("cohens_d requires 2 arguments (x, y)".into()); }
3764            let x = value_to_f64_vec(&args[0])?;
3765            let y = value_to_f64_vec(&args[1])?;
3766            let d = crate::hypothesis::cohens_d(&x, &y)?;
3767            Ok(Some(Value::Float(d)))
3768        }
3769
3770        "eta_squared" => {
3771            if args.len() < 2 { return Err("eta_squared requires at least 2 group arguments".into()); }
3772            let groups: Vec<Vec<f64>> = args.iter().map(|a| value_to_f64_vec(a)).collect::<Result<Vec<_>, _>>()?;
3773            let refs: Vec<&[f64]> = groups.iter().map(|g| g.as_slice()).collect();
3774            let es = crate::hypothesis::eta_squared(&refs)?;
3775            Ok(Some(Value::Float(es)))
3776        }
3777
3778        "cramers_v" => {
3779            // cramers_v(table_flat, nrows, ncols)
3780            if args.len() != 3 { return Err("cramers_v requires 3 arguments (table, nrows, ncols)".into()); }
3781            let table = value_to_f64_vec(&args[0])?;
3782            let nrows = match &args[1] { Value::Int(n) => *n as usize, _ => return Err("cramers_v: nrows must be Int".into()) };
3783            let ncols = match &args[2] { Value::Int(n) => *n as usize, _ => return Err("cramers_v: ncols must be Int".into()) };
3784            let v = crate::hypothesis::cramers_v(&table, nrows, ncols)?;
3785            Ok(Some(Value::Float(v)))
3786        }
3787
3788        // ── Variance Tests ──────────────────────────────────────────────────
3789        "levene_test" => {
3790            if args.len() < 2 { return Err("levene_test requires at least 2 group arguments".into()); }
3791            let groups: Vec<Vec<f64>> = args.iter().map(|a| value_to_f64_vec(a)).collect::<Result<Vec<_>, _>>()?;
3792            let refs: Vec<&[f64]> = groups.iter().map(|g| g.as_slice()).collect();
3793            let (w, p) = crate::hypothesis::levene_test(&refs)?;
3794            let mut fields = std::collections::BTreeMap::new();
3795            fields.insert("statistic".to_string(), Value::Float(w));
3796            fields.insert("p_value".to_string(), Value::Float(p));
3797            Ok(Some(Value::Struct { name: "LeveneResult".to_string(), fields }))
3798        }
3799
3800        "bartlett_test" => {
3801            if args.len() < 2 { return Err("bartlett_test requires at least 2 group arguments".into()); }
3802            let groups: Vec<Vec<f64>> = args.iter().map(|a| value_to_f64_vec(a)).collect::<Result<Vec<_>, _>>()?;
3803            let refs: Vec<&[f64]> = groups.iter().map(|g| g.as_slice()).collect();
3804            let (t, p) = crate::hypothesis::bartlett_test(&refs)?;
3805            let mut fields = std::collections::BTreeMap::new();
3806            fields.insert("statistic".to_string(), Value::Float(t));
3807            fields.insert("p_value".to_string(), Value::Float(p));
3808            Ok(Some(Value::Struct { name: "BartlettResult".to_string(), fields }))
3809        }
3810
3811        // ── Sampling & Cross-Validation builtins ────────────────────────────
3812        "latin_hypercube" => {
3813            // latin_hypercube(n, dims, seed) → Tensor [n, dims]
3814            if args.len() != 3 { return Err("latin_hypercube requires 3 arguments (n, dims, seed)".into()); }
3815            let n = match &args[0] { Value::Int(n) => *n as usize, _ => return Err("latin_hypercube: n must be Int".into()) };
3816            let dims = match &args[1] { Value::Int(d) => *d as usize, _ => return Err("latin_hypercube: dims must be Int".into()) };
3817            let seed = match &args[2] { Value::Int(s) => *s as u64, _ => return Err("latin_hypercube: seed must be Int".into()) };
3818            let t = crate::distributions::latin_hypercube_sample(n, dims, seed);
3819            Ok(Some(Value::Tensor(t)))
3820        }
3821
3822        "sobol_sequence" => {
3823            // sobol_sequence(n, dims) → Tensor [n, dims]
3824            if args.len() != 2 { return Err("sobol_sequence requires 2 arguments (n, dims)".into()); }
3825            let n = match &args[0] { Value::Int(n) => *n as usize, _ => return Err("sobol_sequence: n must be Int".into()) };
3826            let dims = match &args[1] { Value::Int(d) => *d as usize, _ => return Err("sobol_sequence: dims must be Int".into()) };
3827            let t = crate::distributions::sobol_sequence(n, dims);
3828            Ok(Some(Value::Tensor(t)))
3829        }
3830
3831        "train_test_split" => {
3832            // train_test_split(n, test_fraction, seed) → (train_indices, test_indices)
3833            if args.len() != 3 { return Err("train_test_split requires 3 arguments (n, test_fraction, seed)".into()); }
3834            let n = match &args[0] { Value::Int(n) => *n as usize, _ => return Err("train_test_split: n must be Int".into()) };
3835            let frac = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("train_test_split: test_fraction must be Float".into()) };
3836            let seed = match &args[2] { Value::Int(s) => *s as u64, _ => return Err("train_test_split: seed must be Int".into()) };
3837            let (train, test) = crate::ml::train_test_split(n, frac, seed);
3838            let train_vals: Vec<Value> = train.into_iter().map(|i| Value::Int(i as i64)).collect();
3839            let test_vals: Vec<Value> = test.into_iter().map(|i| Value::Int(i as i64)).collect();
3840            Ok(Some(Value::Tuple(Rc::new(vec![
3841                Value::Array(Rc::new(train_vals)),
3842                Value::Array(Rc::new(test_vals)),
3843            ]))))
3844        }
3845
3846        "kfold_indices" => {
3847            // kfold_indices(n, k, seed) → array of (train_indices, test_indices) tuples
3848            if args.len() != 3 { return Err("kfold_indices requires 3 arguments (n, k, seed)".into()); }
3849            let n = match &args[0] { Value::Int(n) => *n as usize, _ => return Err("kfold_indices: n must be Int".into()) };
3850            let k = match &args[1] { Value::Int(k) => *k as usize, _ => return Err("kfold_indices: k must be Int".into()) };
3851            let seed = match &args[2] { Value::Int(s) => *s as u64, _ => return Err("kfold_indices: seed must be Int".into()) };
3852            let folds = crate::ml::kfold_indices(n, k, seed);
3853            let result: Vec<Value> = folds.into_iter().map(|(train, test)| {
3854                let train_vals: Vec<Value> = train.into_iter().map(|i| Value::Int(i as i64)).collect();
3855                let test_vals: Vec<Value> = test.into_iter().map(|i| Value::Int(i as i64)).collect();
3856                Value::Tuple(Rc::new(vec![
3857                    Value::Array(Rc::new(train_vals)),
3858                    Value::Array(Rc::new(test_vals)),
3859                ]))
3860            }).collect();
3861            Ok(Some(Value::Array(Rc::new(result))))
3862        }
3863
3864        "bootstrap" => {
3865            // bootstrap(data, n_resamples, stat_fn, seed) → Struct { point, ci_lower, ci_upper, se }
3866            // stat_fn: 0=mean, 1=median
3867            if args.len() != 4 { return Err("bootstrap requires 4 arguments (data, n_resamples, stat_fn, seed)".into()); }
3868            let data = match &args[0] {
3869                Value::Array(a) => {
3870                    let mut v = Vec::with_capacity(a.len());
3871                    for val in a.iter() {
3872                        match val {
3873                            Value::Float(f) => v.push(*f),
3874                            Value::Int(i) => v.push(*i as f64),
3875                            _ => return Err("bootstrap: data elements must be numeric".into()),
3876                        }
3877                    }
3878                    v
3879                }
3880                _ => return Err("bootstrap: data must be an array".into()),
3881            };
3882            let n_resamples = match &args[1] { Value::Int(n) => *n as usize, _ => return Err("bootstrap: n_resamples must be Int".into()) };
3883            let stat_fn = match &args[2] { Value::Int(s) => *s as usize, _ => return Err("bootstrap: stat_fn must be Int (0=mean, 1=median)".into()) };
3884            let seed = match &args[3] { Value::Int(s) => *s as u64, _ => return Err("bootstrap: seed must be Int".into()) };
3885            let (point, ci_lower, ci_upper, se) = crate::ml::bootstrap(&data, n_resamples, stat_fn, seed)?;
3886            let mut fields = std::collections::BTreeMap::new();
3887            fields.insert("point".to_string(), Value::Float(point));
3888            fields.insert("ci_lower".to_string(), Value::Float(ci_lower));
3889            fields.insert("ci_upper".to_string(), Value::Float(ci_upper));
3890            fields.insert("se".to_string(), Value::Float(se));
3891            Ok(Some(Value::Struct {
3892                name: "BootstrapResult".into(),
3893                fields,
3894            }))
3895        }
3896        "permutation_test" => {
3897            // permutation_test(x, y, n_perms, seed) → Struct { observed_diff, p_value }
3898            if args.len() != 4 { return Err("permutation_test requires 4 arguments (x, y, n_perms, seed)".into()); }
3899            let extract_floats = |val: &Value, name: &str| -> Result<Vec<f64>, String> {
3900                match val {
3901                    Value::Array(a) => {
3902                        let mut v = Vec::with_capacity(a.len());
3903                        for el in a.iter() {
3904                            match el {
3905                                Value::Float(f) => v.push(*f),
3906                                Value::Int(i) => v.push(*i as f64),
3907                                _ => return Err(format!("permutation_test: {} elements must be numeric", name)),
3908                            }
3909                        }
3910                        Ok(v)
3911                    }
3912                    _ => Err(format!("permutation_test: {} must be an array", name)),
3913                }
3914            };
3915            let x = extract_floats(&args[0], "x")?;
3916            let y = extract_floats(&args[1], "y")?;
3917            let n_perms = match &args[2] { Value::Int(n) => *n as usize, _ => return Err("permutation_test: n_perms must be Int".into()) };
3918            let seed = match &args[3] { Value::Int(s) => *s as u64, _ => return Err("permutation_test: seed must be Int".into()) };
3919            let (observed, p_value) = crate::ml::permutation_test(&x, &y, n_perms, seed)?;
3920            let mut fields = std::collections::BTreeMap::new();
3921            fields.insert("observed_diff".to_string(), Value::Float(observed));
3922            fields.insert("p_value".to_string(), Value::Float(p_value));
3923            Ok(Some(Value::Struct {
3924                name: "PermutationResult".into(),
3925                fields,
3926            }))
3927        }
3928
3929        "stratified_split" => {
3930            // stratified_split(labels, test_fraction, seed) → (train_indices, test_indices)
3931            if args.len() != 3 { return Err("stratified_split requires 3 arguments (labels, test_fraction, seed)".into()); }
3932            let labels = match &args[0] {
3933                Value::Array(a) => {
3934                    let mut v = Vec::with_capacity(a.len());
3935                    for val in a.iter() {
3936                        match val {
3937                            Value::Int(i) => v.push(*i),
3938                            _ => return Err("stratified_split: labels must be integer array".into()),
3939                        }
3940                    }
3941                    v
3942                }
3943                _ => return Err("stratified_split: labels must be an array".into()),
3944            };
3945            let frac = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("stratified_split: test_fraction must be Float".into()) };
3946            let seed = match &args[2] { Value::Int(s) => *s as u64, _ => return Err("stratified_split: seed must be Int".into()) };
3947            let (train, test) = crate::ml::stratified_split(&labels, frac, seed);
3948            let train_vals: Vec<Value> = train.into_iter().map(|i| Value::Int(i as i64)).collect();
3949            let test_vals: Vec<Value> = test.into_iter().map(|i| Value::Int(i as i64)).collect();
3950            Ok(Some(Value::Tuple(Rc::new(vec![
3951                Value::Array(Rc::new(train_vals)),
3952                Value::Array(Rc::new(test_vals)),
3953            ]))))
3954        }
3955
3956        _ => Ok(None), // Not a shared builtin
3957    }
3958}
3959
3960/// Extract a SparseCsr reference from a Value.
3961fn value_to_sparse(val: &Value) -> Result<&crate::sparse::SparseCsr, String> {
3962    match val {
3963        Value::SparseTensor(s) => Ok(s),
3964        _ => Err(format!("expected SparseTensor, got {}", val.type_name())),
3965    }
3966}
3967
3968// ---------------------------------------------------------------------------
3969// Peak RSS memory tracking (platform-specific)
3970// ---------------------------------------------------------------------------
3971
3972/// Returns peak resident set size in kilobytes.
3973///
3974/// Platform support:
3975/// - **Windows**: `GetProcessMemoryInfo` → `PeakWorkingSetSize`
3976/// - **Linux**: Reads `/proc/self/status` → `VmHWM`
3977/// - **macOS**: `getrusage(RUSAGE_SELF)` → `ru_maxrss` (in bytes on macOS)
3978/// - **Other**: Returns 0
3979pub fn peak_rss_kb() -> u64 {
3980    #[cfg(target_os = "windows")]
3981    {
3982        peak_rss_windows()
3983    }
3984    #[cfg(target_os = "linux")]
3985    {
3986        peak_rss_linux()
3987    }
3988    #[cfg(target_os = "macos")]
3989    {
3990        peak_rss_macos()
3991    }
3992    #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
3993    {
3994        0
3995    }
3996}
3997
3998#[cfg(target_os = "windows")]
3999fn peak_rss_windows() -> u64 {
4000    use std::mem::{size_of, MaybeUninit};
4001
4002    #[repr(C)]
4003    #[allow(non_snake_case)]
4004    struct ProcessMemoryCounters {
4005        cb: u32,
4006        PageFaultCount: u32,
4007        PeakWorkingSetSize: usize,
4008        WorkingSetSize: usize,
4009        QuotaPeakPagedPoolUsage: usize,
4010        QuotaPagedPoolUsage: usize,
4011        QuotaPeakNonPagedPoolUsage: usize,
4012        QuotaNonPagedPoolUsage: usize,
4013        PagefileUsage: usize,
4014        PeakPagefileUsage: usize,
4015    }
4016
4017    extern "system" {
4018        fn GetCurrentProcess() -> isize;
4019        fn K32GetProcessMemoryInfo(
4020            hProcess: isize,
4021            ppsmemCounters: *mut ProcessMemoryCounters,
4022            cb: u32,
4023        ) -> i32;
4024    }
4025
4026    unsafe {
4027        let mut pmc = MaybeUninit::<ProcessMemoryCounters>::zeroed();
4028        let pmc_ref = pmc.as_mut_ptr();
4029        (*pmc_ref).cb = size_of::<ProcessMemoryCounters>() as u32;
4030        let handle = GetCurrentProcess();
4031        if K32GetProcessMemoryInfo(handle, pmc_ref, (*pmc_ref).cb) != 0 {
4032            let pmc = pmc.assume_init();
4033            (pmc.PeakWorkingSetSize / 1024) as u64
4034        } else {
4035            0
4036        }
4037    }
4038}
4039
4040#[cfg(target_os = "linux")]
4041fn peak_rss_linux() -> u64 {
4042    // Read VmHWM from /proc/self/status (peak resident set size in kB).
4043    if let Ok(contents) = std::fs::read_to_string("/proc/self/status") {
4044        for line in contents.lines() {
4045            if line.starts_with("VmHWM:") {
4046                let parts: Vec<&str> = line.split_whitespace().collect();
4047                if parts.len() >= 2 {
4048                    if let Ok(kb) = parts[1].parse::<u64>() {
4049                        return kb;
4050                    }
4051                }
4052            }
4053        }
4054    }
4055    0
4056}
4057
4058#[cfg(target_os = "macos")]
4059fn peak_rss_macos() -> u64 {
4060    #[repr(C)]
4061    struct Rusage {
4062        ru_utime: [i64; 2],  // timeval (tv_sec, tv_usec)
4063        ru_stime: [i64; 2],  // timeval
4064        ru_maxrss: i64,      // max resident set size (bytes on macOS)
4065        // ... remaining fields omitted (we only need maxrss)
4066        _padding: [i64; 11],
4067    }
4068
4069    extern "C" {
4070        fn getrusage(who: i32, usage: *mut Rusage) -> i32;
4071    }
4072
4073    unsafe {
4074        let mut usage = std::mem::MaybeUninit::<Rusage>::zeroed();
4075        if getrusage(0 /* RUSAGE_SELF */, usage.as_mut_ptr()) == 0 {
4076            let usage = usage.assume_init();
4077            // macOS reports in bytes, convert to KB
4078            (usage.ru_maxrss as u64) / 1024
4079        } else {
4080            0
4081        }
4082    }
4083}
4084
4085// ---------------------------------------------------------------------------
4086// Tests
4087// ---------------------------------------------------------------------------
4088
4089#[cfg(test)]
4090mod tests {
4091    use super::*;
4092
4093    #[test]
4094    fn test_peak_rss_returns_nonzero() {
4095        let rss = peak_rss_kb();
4096        // On any platform we support, peak RSS should be > 0 for a running process.
4097        assert!(rss > 0, "peak_rss_kb() should return non-zero, got {rss}");
4098    }
4099
4100    #[test]
4101    fn test_peak_rss_builtin_dispatch() {
4102        let result = dispatch_builtin("peak_rss", &[]);
4103        match result {
4104            Ok(Some(Value::Int(kb))) => {
4105                assert!(kb > 0, "peak_rss should return positive value, got {kb}");
4106            }
4107            other => panic!("Expected Ok(Some(Int)), got: {other:?}"),
4108        }
4109    }
4110
4111    #[test]
4112    fn test_complex_constructor() {
4113        let result = dispatch_builtin("Complex", &[Value::Float(3.0), Value::Float(4.0)]);
4114        match result {
4115            Ok(Some(Value::Complex(c))) => {
4116                assert_eq!(c.re, 3.0);
4117                assert_eq!(c.im, 4.0);
4118            }
4119            _ => panic!("expected Complex value"),
4120        }
4121    }
4122
4123    #[test]
4124    fn test_complex_constructor_from_ints() {
4125        let result = dispatch_builtin("Complex", &[Value::Int(1), Value::Int(-2)]);
4126        match result {
4127            Ok(Some(Value::Complex(c))) => {
4128                assert_eq!(c.re, 1.0);
4129                assert_eq!(c.im, -2.0);
4130            }
4131            _ => panic!("expected Complex value"),
4132        }
4133    }
4134
4135    #[test]
4136    fn test_complex_real_only() {
4137        let result = dispatch_builtin("Complex", &[Value::Float(5.0)]);
4138        match result {
4139            Ok(Some(Value::Complex(c))) => {
4140                assert_eq!(c.re, 5.0);
4141                assert_eq!(c.im, 0.0);
4142            }
4143            _ => panic!("expected Complex value"),
4144        }
4145    }
4146
4147    #[test]
4148    fn test_to_string() {
4149        let result = dispatch_builtin("to_string", &[Value::Int(42)]);
4150        match result {
4151            Ok(Some(Value::String(s))) => assert_eq!(s.as_str(), "42"),
4152            _ => panic!("expected String value"),
4153        }
4154    }
4155
4156    #[test]
4157    fn test_len_array() {
4158        let arr = Value::Array(Rc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
4159        let result = dispatch_builtin("len", &[arr]);
4160        assert!(matches!(result, Ok(Some(Value::Int(3)))));
4161    }
4162
4163    #[test]
4164    fn test_len_string() {
4165        let s = Value::String(Rc::new("hello".to_string()));
4166        let result = dispatch_builtin("len", &[s]);
4167        assert!(matches!(result, Ok(Some(Value::Int(5)))));
4168    }
4169
4170    #[test]
4171    fn test_assert_pass() {
4172        let result = dispatch_builtin("assert", &[Value::Bool(true)]);
4173        assert!(matches!(result, Ok(Some(Value::Void))));
4174    }
4175
4176    #[test]
4177    fn test_assert_fail() {
4178        let result = dispatch_builtin("assert", &[Value::Bool(false)]);
4179        assert!(result.is_err());
4180    }
4181
4182    #[test]
4183    fn test_assert_eq_pass() {
4184        let result = dispatch_builtin("assert_eq", &[Value::Int(42), Value::Int(42)]);
4185        assert!(matches!(result, Ok(Some(Value::Void))));
4186    }
4187
4188    #[test]
4189    fn test_assert_eq_fail() {
4190        let result = dispatch_builtin("assert_eq", &[Value::Int(1), Value::Int(2)]);
4191        assert!(result.is_err());
4192    }
4193
4194    #[test]
4195    fn test_sqrt() {
4196        let result = dispatch_builtin("sqrt", &[Value::Float(4.0)]);
4197        match result {
4198            Ok(Some(Value::Float(v))) => assert_eq!(v, 2.0),
4199            _ => panic!("expected Float"),
4200        }
4201    }
4202
4203    #[test]
4204    fn test_abs_float() {
4205        let result = dispatch_builtin("abs", &[Value::Float(-3.14)]);
4206        match result {
4207            Ok(Some(Value::Float(v))) => assert_eq!(v, 3.14),
4208            _ => panic!("expected Float"),
4209        }
4210    }
4211
4212    #[test]
4213    fn test_abs_int() {
4214        let result = dispatch_builtin("abs", &[Value::Int(-42)]);
4215        assert!(matches!(result, Ok(Some(Value::Int(42)))));
4216    }
4217
4218    #[test]
4219    fn test_floor() {
4220        let result = dispatch_builtin("floor", &[Value::Float(3.7)]);
4221        match result {
4222            Ok(Some(Value::Float(v))) => assert_eq!(v, 3.0),
4223            _ => panic!("expected Float"),
4224        }
4225    }
4226
4227    #[test]
4228    fn test_int_conversion() {
4229        let result = dispatch_builtin("int", &[Value::Float(3.9)]);
4230        assert!(matches!(result, Ok(Some(Value::Int(3)))));
4231    }
4232
4233    #[test]
4234    fn test_float_conversion() {
4235        let result = dispatch_builtin("float", &[Value::Int(42)]);
4236        match result {
4237            Ok(Some(Value::Float(v))) => assert_eq!(v, 42.0),
4238            _ => panic!("expected Float"),
4239        }
4240    }
4241
4242    #[test]
4243    fn test_isnan() {
4244        let result = dispatch_builtin("isnan", &[Value::Float(f64::NAN)]);
4245        assert!(matches!(result, Ok(Some(Value::Bool(true)))));
4246
4247        let result = dispatch_builtin("isnan", &[Value::Float(1.0)]);
4248        assert!(matches!(result, Ok(Some(Value::Bool(false)))));
4249
4250        let result = dispatch_builtin("isnan", &[Value::Int(0)]);
4251        assert!(matches!(result, Ok(Some(Value::Bool(false)))));
4252    }
4253
4254    #[test]
4255    fn test_isinf() {
4256        let result = dispatch_builtin("isinf", &[Value::Float(f64::INFINITY)]);
4257        assert!(matches!(result, Ok(Some(Value::Bool(true)))));
4258
4259        let result = dispatch_builtin("isinf", &[Value::Float(1.0)]);
4260        assert!(matches!(result, Ok(Some(Value::Bool(false)))));
4261    }
4262
4263    #[test]
4264    fn test_push() {
4265        let arr = Value::Array(Rc::new(vec![Value::Int(1)]));
4266        let result = dispatch_builtin("push", &[arr, Value::Int(2)]);
4267        match result {
4268            Ok(Some(Value::Array(a))) => {
4269                assert_eq!(a.len(), 2);
4270                assert!(matches!(&a[1], Value::Int(2)));
4271            }
4272            _ => panic!("expected Array"),
4273        }
4274    }
4275
4276    #[test]
4277    fn test_sort() {
4278        let arr = Value::Array(Rc::new(vec![
4279            Value::Float(3.0),
4280            Value::Float(1.0),
4281            Value::Float(2.0),
4282        ]));
4283        let result = dispatch_builtin("sort", &[arr]);
4284        match result {
4285            Ok(Some(Value::Array(a))) => {
4286                assert!(matches!(&a[0], Value::Float(v) if *v == 1.0));
4287                assert!(matches!(&a[1], Value::Float(v) if *v == 2.0));
4288                assert!(matches!(&a[2], Value::Float(v) if *v == 3.0));
4289            }
4290            _ => panic!("expected sorted Array"),
4291        }
4292    }
4293
4294    #[test]
4295    fn test_unknown_builtin_returns_none() {
4296        let result = dispatch_builtin("unknown_function", &[]);
4297        assert!(matches!(result, Ok(None)));
4298    }
4299
4300    #[test]
4301    fn test_tensor_zeros() {
4302        let shape = Value::Array(Rc::new(vec![Value::Int(2), Value::Int(3)]));
4303        let result = dispatch_builtin("Tensor.zeros", &[shape]);
4304        match result {
4305            Ok(Some(Value::Tensor(t))) => {
4306                assert_eq!(t.shape(), &[2, 3]);
4307            }
4308            _ => panic!("expected Tensor"),
4309        }
4310    }
4311
4312    #[test]
4313    fn test_values_equal() {
4314        assert!(values_equal(&Value::Int(42), &Value::Int(42)));
4315        assert!(!values_equal(&Value::Int(1), &Value::Int(2)));
4316        assert!(values_equal(&Value::Float(3.14), &Value::Float(3.14)));
4317        assert!(values_equal(&Value::Bool(true), &Value::Bool(true)));
4318        assert!(values_equal(&Value::Void, &Value::Void));
4319        assert!(!values_equal(&Value::Int(1), &Value::Float(1.0)));
4320    }
4321
4322    #[test]
4323    fn test_value_to_shape() {
4324        let arr = Value::Array(Rc::new(vec![Value::Int(2), Value::Int(3), Value::Int(4)]));
4325        assert_eq!(value_to_shape(&arr).unwrap(), vec![2, 3, 4]);
4326    }
4327
4328    #[test]
4329    fn test_value_to_shape_negative_rejected() {
4330        let arr = Value::Array(Rc::new(vec![Value::Int(-1)]));
4331        assert!(value_to_shape(&arr).is_err());
4332    }
4333
4334    #[test]
4335    fn test_value_to_f64_vec() {
4336        let arr = Value::Array(Rc::new(vec![
4337            Value::Float(1.0),
4338            Value::Int(2),
4339            Value::Float(3.5),
4340        ]));
4341        assert_eq!(value_to_f64_vec(&arr).unwrap(), vec![1.0, 2.0, 3.5]);
4342    }
4343
4344    #[test]
4345    fn test_log_float() {
4346        let result = dispatch_builtin("log", &[Value::Float(1.0)]);
4347        match result {
4348            Ok(Some(Value::Float(v))) => assert!((v - 0.0).abs() < 1e-15),
4349            _ => panic!("expected Float"),
4350        }
4351    }
4352
4353    #[test]
4354    fn test_log_e() {
4355        let result = dispatch_builtin("log", &[Value::Float(std::f64::consts::E)]);
4356        match result {
4357            Ok(Some(Value::Float(v))) => assert!((v - 1.0).abs() < 1e-15),
4358            _ => panic!("expected Float"),
4359        }
4360    }
4361
4362    #[test]
4363    fn test_exp_float() {
4364        let result = dispatch_builtin("exp", &[Value::Float(0.0)]);
4365        match result {
4366            Ok(Some(Value::Float(v))) => assert!((v - 1.0).abs() < 1e-15),
4367            _ => panic!("expected Float"),
4368        }
4369    }
4370
4371    #[test]
4372    fn test_exp_one() {
4373        let result = dispatch_builtin("exp", &[Value::Float(1.0)]);
4374        match result {
4375            Ok(Some(Value::Float(v))) => assert!((v - std::f64::consts::E).abs() < 1e-15),
4376            _ => panic!("expected Float"),
4377        }
4378    }
4379
4380    #[test]
4381    fn test_categorical_sample_deterministic() {
4382        let probs = Tensor::from_vec(vec![0.0, 0.0, 1.0], &[3]).unwrap();
4383        // u=0.5, cumsum: 0.0, 0.0, 1.0 → picks index 2
4384        assert_eq!(categorical_sample_with_u(&probs, 0.5).unwrap(), 2);
4385    }
4386
4387    #[test]
4388    fn test_categorical_sample_first() {
4389        let probs = Tensor::from_vec(vec![0.5, 0.3, 0.2], &[3]).unwrap();
4390        // u=0.1 → picks index 0 (cumsum 0.5 > 0.1)
4391        assert_eq!(categorical_sample_with_u(&probs, 0.1).unwrap(), 0);
4392    }
4393
4394    #[test]
4395    fn test_categorical_sample_middle() {
4396        let probs = Tensor::from_vec(vec![0.2, 0.5, 0.3], &[3]).unwrap();
4397        // u=0.6 → cumsum 0.2, 0.7 → picks index 1
4398        assert_eq!(categorical_sample_with_u(&probs, 0.6).unwrap(), 1);
4399    }
4400
4401    #[test]
4402    fn test_categorical_sample_last() {
4403        let probs = Tensor::from_vec(vec![0.2, 0.3, 0.5], &[3]).unwrap();
4404        // u=0.99 → cumsum 0.2, 0.5, 1.0 → picks index 2
4405        assert_eq!(categorical_sample_with_u(&probs, 0.99).unwrap(), 2);
4406    }
4407}