1use std::cell::RefCell;
12use std::rc::Rc;
13
14use cjc_regex;
15use crate::accumulator::BinnedAccumulatorF64;
16use crate::complex::ComplexF64;
17use crate::scratchpad::Scratchpad;
18use crate::paged_kv::PagedKvCache;
19use crate::tensor::Tensor;
20use crate::tensor_simd::UnaryOp;
21use crate::value::{Bf16, Value};
22
23pub fn value_to_shape(val: &Value) -> Result<Vec<usize>, String> {
29 match val {
30 Value::Array(arr) => {
31 let mut shape = Vec::with_capacity(arr.len());
32 for v in arr.iter() {
33 shape.push(value_to_usize(v)?);
34 }
35 Ok(shape)
36 }
37 _ => Err(format!("expected Array for shape, got {}", val.type_name())),
38 }
39}
40
41pub fn value_to_usize(val: &Value) -> Result<usize, String> {
43 match val {
44 Value::Int(i) => {
45 if *i < 0 {
46 Err(format!("expected non-negative integer, got {i}"))
47 } else {
48 Ok(*i as usize)
49 }
50 }
51 _ => Err(format!("expected Int, got {}", val.type_name())),
52 }
53}
54
55pub fn value_to_f64_vec(val: &Value) -> Result<Vec<f64>, String> {
57 match val {
58 Value::Array(arr) => {
59 let mut data = Vec::with_capacity(arr.len());
60 for v in arr.iter() {
61 match v {
62 Value::Float(f) => data.push(*f),
63 Value::Int(i) => data.push(*i as f64),
64 Value::Na => {}
66 _ => {
67 return Err(format!(
68 "expected numeric values in array, got {}",
69 v.type_name()
70 ));
71 }
72 }
73 }
74 Ok(data)
75 }
76 _ => Err(format!("expected Array, got {}", val.type_name())),
77 }
78}
79
80pub fn value_to_complex_vec(val: &Value) -> Result<Vec<(f64, f64)>, String> {
82 match val {
83 Value::Array(arr) => {
84 let mut data = Vec::with_capacity(arr.len());
85 for v in arr.iter() {
86 match v {
87 Value::Tuple(t) if t.len() == 2 => {
88 let re = match &t[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("complex tuple element must be numeric".into()) };
89 let im = match &t[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("complex tuple element must be numeric".into()) };
90 data.push((re, im));
91 }
92 _ => return Err("expected array of (re, im) tuples".into()),
93 }
94 }
95 Ok(data)
96 }
97 _ => Err(format!("expected Array of complex tuples, got {}", val.type_name())),
98 }
99}
100
101pub fn value_to_usize_vec(val: &Value) -> Result<Vec<usize>, String> {
103 match val {
104 Value::Array(arr) => {
105 let mut indices = Vec::with_capacity(arr.len());
106 for v in arr.iter() {
107 indices.push(value_to_usize(v)?);
108 }
109 Ok(indices)
110 }
111 _ => Err(format!("expected Array for indices, got {}", val.type_name())),
112 }
113}
114
115pub fn value_to_tensor(val: &Value) -> Result<&Tensor, String> {
117 match val {
118 Value::Tensor(t) => Ok(t),
119 _ => Err(format!("expected Tensor, got {}", val.type_name())),
120 }
121}
122
123pub fn value_to_f64(val: &Value) -> Result<f64, String> {
125 match val {
126 Value::Float(v) => Ok(*v),
127 Value::Int(v) => Ok(*v as f64),
128 _ => Err(format!("expected Float or Int, got {}", val.type_name())),
129 }
130}
131
132pub fn value_to_bytes(val: &Value) -> Result<Vec<u8>, String> {
134 match val {
135 Value::ByteSlice(b) => Ok(b.as_ref().clone()),
136 Value::Bytes(b) => Ok(b.borrow().clone()),
137 Value::String(s) => Ok(s.as_bytes().to_vec()),
138 _ => Err(format!("expected ByteSlice or Bytes, got {}", val.type_name())),
139 }
140}
141
142pub fn values_equal(a: &Value, b: &Value) -> bool {
144 match (a, b) {
145 (Value::Int(a), Value::Int(b)) => a == b,
146 (Value::Float(a), Value::Float(b)) => a == b,
147 (Value::Bool(a), Value::Bool(b)) => a == b,
148 (Value::String(a), Value::String(b)) => a == b,
149 (Value::Void, Value::Void) => true,
150 _ => false,
151 }
152}
153
154pub fn categorical_sample_with_u(probs: &Tensor, u: f64) -> Result<i64, String> {
162 if probs.ndim() == 0 {
163 return Err("categorical_sample requires at least a 1-D tensor".into());
164 }
165 let data = probs.to_vec();
166 if data.is_empty() {
167 return Err("categorical_sample: empty probability tensor".into());
168 }
169 let mut cumsum = 0.0;
170 for (i, &p) in data.iter().enumerate() {
171 cumsum += p;
172 if u < cumsum {
173 return Ok(i as i64);
174 }
175 }
176 Ok((data.len() - 1) as i64)
178}
179
180pub fn dispatch_builtin(name: &str, args: &[Value]) -> Result<Option<Value>, String> {
187 match name {
188 "Complex" => {
189 let re = match args.get(0) {
190 Some(Value::Float(v)) => *v,
191 Some(Value::Int(v)) => *v as f64,
192 _ => return Err("Complex() requires numeric re argument".into()),
193 };
194 let im = match args.get(1) {
195 Some(Value::Float(v)) => *v,
196 Some(Value::Int(v)) => *v as f64,
197 None => 0.0,
198 _ => return Err("Complex() requires numeric im argument".into()),
199 };
200 Ok(Some(Value::Complex(ComplexF64::new(re, im))))
201 }
202 "f16_to_f64" => match &args[0] {
204 Value::F16(v) => Ok(Some(Value::Float(v.to_f64()))),
205 _ => Err("f16_to_f64 expects f16".into()),
206 },
207 "f64_to_f16" => match &args[0] {
208 Value::Float(v) => Ok(Some(Value::F16(crate::f16::F16::from_f64(*v)))),
209 Value::Int(v) => Ok(Some(Value::F16(crate::f16::F16::from_f64(*v as f64)))),
210 _ => Err("f64_to_f16 expects f64".into()),
211 },
212 "f16_to_f32" => match &args[0] {
213 Value::F16(v) => Ok(Some(Value::Float(v.to_f32() as f64))),
214 _ => Err("f16_to_f32 expects f16".into()),
215 },
216 "f32_to_f16" => match &args[0] {
217 Value::Float(v) => Ok(Some(Value::F16(crate::f16::F16::from_f32(*v as f32)))),
218 _ => Err("f32_to_f16 expects f32".into()),
219 },
220 "bf16_to_f32" => match &args[0] {
222 Value::Bf16(v) => Ok(Some(Value::Float(v.to_f32() as f64))),
223 _ => Err("bf16_to_f32 expects bf16".into()),
224 },
225 "f32_to_bf16" => match &args[0] {
226 Value::Float(v) => Ok(Some(Value::Bf16(Bf16::from_f32(*v as f32)))),
227 _ => Err("f32_to_bf16 expects f32".into()),
228 },
229 "Tensor.zeros" => {
231 let shape = value_to_shape(&args[0])?;
232 Ok(Some(Value::Tensor(Tensor::zeros(&shape))))
233 }
234 "Tensor.ones" => {
235 let shape = value_to_shape(&args[0])?;
236 Ok(Some(Value::Tensor(Tensor::ones(&shape))))
237 }
238 "Tensor.from_vec" => {
239 if args.len() != 2 {
240 return Err("Tensor.from_vec requires 2 arguments: data and shape".into());
241 }
242 let data = value_to_f64_vec(&args[0])?;
243 let shape = value_to_shape(&args[1])?;
244 let t = Tensor::from_vec(data, &shape).map_err(|e| format!("{e}"))?;
245 Ok(Some(Value::Tensor(t)))
246 }
247 "matmul" => {
248 if args.len() != 2 {
249 return Err("matmul requires 2 Tensor arguments".into());
250 }
251 let a = value_to_tensor(&args[0])?;
252 let b = value_to_tensor(&args[1])?;
253 Ok(Some(Value::Tensor(a.matmul(b).map_err(|e| format!("{e}"))?)))
254 }
255 "attention" => {
256 if args.len() != 3 {
257 return Err("attention requires 3 Tensor arguments: queries, keys, values".into());
258 }
259 let q = value_to_tensor(&args[0])?;
260 let k = value_to_tensor(&args[1])?;
261 let v = value_to_tensor(&args[2])?;
262 Ok(Some(Value::Tensor(
263 Tensor::scaled_dot_product_attention(q, k, v).map_err(|e| format!("{e}"))?,
264 )))
265 }
266 "Buffer.alloc" => {
267 if args.is_empty() {
268 return Err("Buffer.alloc requires a length argument".into());
269 }
270 let len = value_to_usize(&args[0])?;
271 Ok(Some(Value::Tensor(Tensor::zeros(&[len]))))
272 }
273 "Tensor.from_bytes" => {
274 if args.len() < 2 || args.len() > 3 {
275 return Err(
276 "Tensor.from_bytes requires 2-3 arguments: bytes, shape, [dtype='f64']".into(),
277 );
278 }
279 let bytes = match &args[0] {
280 Value::ByteSlice(b) => b.clone(),
281 Value::Bytes(b) => Rc::new(b.borrow().clone()),
282 _ => {
283 return Err(
284 "Tensor.from_bytes: first argument must be ByteSlice or Bytes".into(),
285 )
286 }
287 };
288 let shape = value_to_shape(&args[1])?;
289 let dtype = if args.len() == 3 {
290 match &args[2] {
291 Value::String(s) => s.as_str().to_string(),
292 _ => return Err("Tensor.from_bytes: dtype must be a string".into()),
293 }
294 } else {
295 "f64".to_string()
296 };
297 Ok(Some(Value::Tensor(
298 Tensor::from_bytes(&bytes, &shape, &dtype).map_err(|e| format!("{e}"))?,
299 )))
300 }
301 "Scratchpad.new" => {
302 if args.len() != 2 {
303 return Err("Scratchpad.new requires 2 arguments: max_seq_len, dim".into());
304 }
305 let max_seq_len = value_to_usize(&args[0])?;
306 let dim = value_to_usize(&args[1])?;
307 Ok(Some(Value::Scratchpad(Rc::new(RefCell::new(
308 Scratchpad::new(max_seq_len, dim),
309 )))))
310 }
311 "PagedKvCache.new" => {
312 if args.len() != 2 {
313 return Err("PagedKvCache.new requires 2 arguments: max_tokens, dim".into());
314 }
315 let max_tokens = value_to_usize(&args[0])?;
316 let dim = value_to_usize(&args[1])?;
317 Ok(Some(Value::PagedKvCache(Rc::new(RefCell::new(
318 PagedKvCache::new(max_tokens, dim),
319 )))))
320 }
321 "AlignedByteSlice.from_bytes" => {
322 if args.len() != 1 {
323 return Err("AlignedByteSlice.from_bytes requires 1 argument: bytes".into());
324 }
325 let bytes = match &args[0] {
326 Value::ByteSlice(b) => b.clone(),
327 Value::Bytes(b) => Rc::new(b.borrow().clone()),
328 _ => {
329 return Err(
330 "AlignedByteSlice.from_bytes: argument must be ByteSlice or Bytes".into(),
331 )
332 }
333 };
334 Ok(Some(Value::AlignedBytes(
335 crate::aligned_pool::AlignedByteSlice::from_bytes(bytes),
336 )))
337 }
338 "to_string" => {
339 if args.len() != 1 {
340 return Err("to_string requires exactly 1 argument".into());
341 }
342 Ok(Some(Value::String(Rc::new(format!("{}", args[0])))))
343 }
344
345 "str_upper" => {
347 if args.len() != 1 { return Err("str_upper requires 1 argument".into()); }
348 match &args[0] {
349 Value::String(s) => Ok(Some(Value::String(Rc::new(s.to_uppercase())))),
350 _ => Err("str_upper: argument must be a string".into()),
351 }
352 }
353 "str_lower" => {
354 if args.len() != 1 { return Err("str_lower requires 1 argument".into()); }
355 match &args[0] {
356 Value::String(s) => Ok(Some(Value::String(Rc::new(s.to_lowercase())))),
357 _ => Err("str_lower: argument must be a string".into()),
358 }
359 }
360 "str_trim" => {
361 if args.len() != 1 { return Err("str_trim requires 1 argument".into()); }
362 match &args[0] {
363 Value::String(s) => Ok(Some(Value::String(Rc::new(s.trim().to_string())))),
364 _ => Err("str_trim: argument must be a string".into()),
365 }
366 }
367 "str_contains" => {
368 if args.len() != 2 { return Err("str_contains requires 2 arguments".into()); }
369 match (&args[0], &args[1]) {
370 (Value::String(haystack), Value::String(needle)) => {
371 Ok(Some(Value::Bool(haystack.contains(needle.as_str()))))
372 }
373 _ => Err("str_contains: both arguments must be strings".into()),
374 }
375 }
376 "str_replace" => {
377 if args.len() != 3 { return Err("str_replace requires 3 arguments (str, from, to)".into()); }
378 match (&args[0], &args[1], &args[2]) {
379 (Value::String(s), Value::String(from), Value::String(to)) => {
380 Ok(Some(Value::String(Rc::new(s.replacen(from.as_str(), to.as_str(), 1)))))
383 }
384 _ => Err("str_replace: all arguments must be strings".into()),
385 }
386 }
387 "str_split" => {
388 if args.len() != 2 { return Err("str_split requires 2 arguments (str, delimiter)".into()); }
389 match (&args[0], &args[1]) {
390 (Value::String(s), Value::String(delim)) => {
391 let parts: Vec<Value> = s.split(delim.as_str())
392 .map(|p| Value::String(Rc::new(p.to_string())))
393 .collect();
394 Ok(Some(Value::Array(Rc::new(parts))))
395 }
396 _ => Err("str_split: both arguments must be strings".into()),
397 }
398 }
399 "str_join" => {
400 if args.len() != 2 { return Err("str_join requires 2 arguments (array, delimiter)".into()); }
401 match (&args[0], &args[1]) {
402 (Value::Array(arr), Value::String(delim)) => {
403 let parts: Vec<String> = arr.iter()
404 .map(|v| format!("{}", v))
405 .collect();
406 Ok(Some(Value::String(Rc::new(parts.join(delim.as_str())))))
407 }
408 _ => Err("str_join: first arg must be array, second must be string".into()),
409 }
410 }
411 "str_starts_with" => {
412 if args.len() != 2 { return Err("str_starts_with requires 2 arguments".into()); }
413 match (&args[0], &args[1]) {
414 (Value::String(s), Value::String(prefix)) => {
415 Ok(Some(Value::Bool(s.starts_with(prefix.as_str()))))
416 }
417 _ => Err("str_starts_with: both arguments must be strings".into()),
418 }
419 }
420 "str_ends_with" => {
421 if args.len() != 2 { return Err("str_ends_with requires 2 arguments".into()); }
422 match (&args[0], &args[1]) {
423 (Value::String(s), Value::String(suffix)) => {
424 Ok(Some(Value::Bool(s.ends_with(suffix.as_str()))))
425 }
426 _ => Err("str_ends_with: both arguments must be strings".into()),
427 }
428 }
429 "regex_or" => {
433 if args.is_empty() { return Err("regex_or requires at least 1 argument".into()); }
436 let mut parts = Vec::with_capacity(args.len());
437 for (i, arg) in args.iter().enumerate() {
438 match arg {
439 Value::String(s) => parts.push(format!("(?:{})", s)),
440 _ => return Err(format!("regex_or: argument {} must be a string pattern", i)),
441 }
442 }
443 Ok(Some(Value::String(Rc::new(parts.join("|")))))
444 }
445 "regex_seq" => {
446 if args.is_empty() { return Err("regex_seq requires at least 1 argument".into()); }
449 let mut result = String::new();
450 for (i, arg) in args.iter().enumerate() {
451 match arg {
452 Value::String(s) => result.push_str(&format!("(?:{})", s)),
453 _ => return Err(format!("regex_seq: argument {} must be a string pattern", i)),
454 }
455 }
456 Ok(Some(Value::String(Rc::new(result))))
457 }
458 "regex_explain" => {
459 if args.is_empty() || args.len() > 2 {
461 return Err("regex_explain requires 1 or 2 arguments (pattern, flags?)".into());
462 }
463 let pattern = match &args[0] {
464 Value::String(s) => s.as_str(),
465 _ => return Err("regex_explain: first argument must be a string".into()),
466 };
467 let flags = if args.len() == 2 {
468 match &args[1] {
469 Value::String(s) => s.as_str().to_string(),
470 _ => return Err("regex_explain: second argument must be a string".into()),
471 }
472 } else {
473 String::new()
474 };
475 match cjc_regex::regex_explain(pattern, &flags) {
476 Ok(desc) => Ok(Some(Value::String(Rc::new(desc)))),
477 Err(e) => Err(format!("regex_explain: {}", e)),
478 }
479 }
480 "regex_captures" => {
482 if args.len() != 3 {
485 return Err("regex_captures requires 3 arguments (pattern, flags, text)".into());
486 }
487 let pattern = match &args[0] {
488 Value::String(s) => s.as_str().to_string(),
489 _ => return Err("regex_captures: first argument must be a string".into()),
490 };
491 let flags = match &args[1] {
492 Value::String(s) => s.as_str().to_string(),
493 _ => return Err("regex_captures: second argument must be a string".into()),
494 };
495 let text = match &args[2] {
496 Value::String(s) => s.as_str().to_string(),
497 _ => return Err("regex_captures: third argument must be a string".into()),
498 };
499 match cjc_regex::find_captures(&pattern, &flags, text.as_bytes()) {
500 Some(cr) => {
501 let mut result = Vec::with_capacity(cr.groups.len());
502 for group in &cr.groups {
503 match group {
504 Some(cap) => {
505 let s = cap.extract_str(text.as_bytes())
506 .unwrap_or("")
507 .to_string();
508 result.push(Value::String(Rc::new(s)));
509 }
510 None => result.push(Value::String(Rc::new(String::new()))),
511 }
512 }
513 Ok(Some(Value::Array(Rc::new(result))))
514 }
515 None => Ok(Some(Value::Array(Rc::new(Vec::new())))),
516 }
517 }
518 "regex_named_capture" => {
519 if args.len() != 4 {
522 return Err("regex_named_capture requires 4 arguments (pattern, flags, text, name)".into());
523 }
524 let pattern = match &args[0] {
525 Value::String(s) => s.as_str().to_string(),
526 _ => return Err("regex_named_capture: first argument must be a string".into()),
527 };
528 let flags = match &args[1] {
529 Value::String(s) => s.as_str().to_string(),
530 _ => return Err("regex_named_capture: second argument must be a string".into()),
531 };
532 let text = match &args[2] {
533 Value::String(s) => s.as_str().to_string(),
534 _ => return Err("regex_named_capture: third argument must be a string".into()),
535 };
536 let name = match &args[3] {
537 Value::String(s) => s.as_str().to_string(),
538 _ => return Err("regex_named_capture: fourth argument must be a string".into()),
539 };
540 match cjc_regex::find_captures(&pattern, &flags, text.as_bytes()) {
541 Some(cr) => {
542 match cr.get_named(&name) {
543 Some(cap) => {
544 let s = cap.extract_str(text.as_bytes())
545 .unwrap_or("")
546 .to_string();
547 Ok(Some(Value::String(Rc::new(s))))
548 }
549 None => Ok(Some(Value::String(Rc::new(String::new())))),
550 }
551 }
552 None => Ok(Some(Value::String(Rc::new(String::new())))),
553 }
554 }
555 "regex_capture_count" => {
556 if args.len() != 2 {
559 return Err("regex_capture_count requires 2 arguments (pattern, flags)".into());
560 }
561 let pattern = match &args[0] {
562 Value::String(s) => s.as_str().to_string(),
563 _ => return Err("regex_capture_count: first argument must be a string".into()),
564 };
565 let flags = match &args[1] {
566 Value::String(s) => s.as_str().to_string(),
567 _ => return Err("regex_capture_count: second argument must be a string".into()),
568 };
569 let count = cjc_regex::capture_count(&pattern, &flags) as i64;
570 Ok(Some(Value::Int(count)))
571 }
572 "str_repeat" => {
574 if args.len() != 2 { return Err("str_repeat requires 2 arguments (str, count)".into()); }
575 match (&args[0], &args[1]) {
576 (Value::String(s), Value::Int(n)) => {
577 if *n < 0 { return Err("str_repeat: count must be non-negative".into()); }
578 Ok(Some(Value::String(Rc::new(s.repeat(*n as usize)))))
579 }
580 _ => Err("str_repeat: first arg must be string, second must be integer".into()),
581 }
582 }
583 "str_chars" => {
584 if args.len() != 1 { return Err("str_chars requires 1 argument".into()); }
585 match &args[0] {
586 Value::String(s) => {
587 let chars: Vec<Value> = s.chars()
588 .map(|c| Value::String(Rc::new(c.to_string())))
589 .collect();
590 Ok(Some(Value::Array(Rc::new(chars))))
591 }
592 _ => Err("str_chars: argument must be a string".into()),
593 }
594 }
595 "str_substr" => {
596 if args.len() != 3 { return Err("str_substr requires 3 arguments (str, start, len)".into()); }
597 match (&args[0], &args[1], &args[2]) {
598 (Value::String(s), Value::Int(start), Value::Int(len)) => {
599 let start = (*start).max(0) as usize;
600 let len = (*len).max(0) as usize;
601 let result: String = s.chars().skip(start).take(len).collect();
602 Ok(Some(Value::String(Rc::new(result))))
603 }
604 _ => Err("str_substr: (str, int, int) expected".into()),
605 }
606 }
607
608 "len" => {
609 if args.len() != 1 {
610 return Err("len requires exactly 1 argument".into());
611 }
612 match &args[0] {
613 Value::Array(arr) => Ok(Some(Value::Int(arr.len() as i64))),
614 Value::String(s) => Ok(Some(Value::Int(s.len() as i64))),
615 Value::Tensor(t) => Ok(Some(Value::Int(t.len() as i64))),
616 Value::Tuple(t) => Ok(Some(Value::Int(t.len() as i64))),
617 other => Err(format!("len not supported for {}", other.type_name())),
618 }
619 }
620 "push" => {
621 if args.len() != 2 {
622 return Err("push requires 2 arguments: array and value".into());
623 }
624 match (&args[0], &args[1]) {
625 (Value::Array(a), val) => {
626 let mut new_arr = (**a).clone();
627 new_arr.push(val.clone());
628 Ok(Some(Value::Array(Rc::new(new_arr))))
629 }
630 _ => Err("push requires an Array as first argument".into()),
631 }
632 }
633 "sort" => {
634 if args.len() != 1 {
635 return Err("sort requires exactly 1 argument".into());
636 }
637 match &args[0] {
638 Value::Array(arr) => {
639 let mut sorted: Vec<Value> = (**arr).clone();
640 sorted.sort_by(|a, b| {
641 let fa = match a {
642 Value::Float(f) => *f,
643 Value::Int(i) => *i as f64,
644 _ => f64::NAN,
645 };
646 let fb = match b {
647 Value::Float(f) => *f,
648 Value::Int(i) => *i as f64,
649 _ => f64::NAN,
650 };
651 fa.partial_cmp(&fb).unwrap_or(std::cmp::Ordering::Equal)
652 });
653 Ok(Some(Value::Array(Rc::new(sorted))))
654 }
655 _ => Err(format!("sort requires an Array, got {}", args[0].type_name())),
656 }
657 }
658 "sqrt" => {
659 if args.len() != 1 {
660 return Err("sqrt requires exactly 1 argument".into());
661 }
662 match &args[0] {
663 Value::Float(f) => Ok(Some(Value::Float(f.sqrt()))),
664 Value::Int(i) => Ok(Some(Value::Float((*i as f64).sqrt()))),
665 _ => Err(format!("sqrt requires a number, got {}", args[0].type_name())),
666 }
667 }
668 "log" => {
669 if args.len() != 1 {
670 return Err("log requires exactly 1 argument".into());
671 }
672 match &args[0] {
673 Value::Float(f) => Ok(Some(Value::Float(f.ln()))),
674 Value::Int(i) => Ok(Some(Value::Float((*i as f64).ln()))),
675 _ => Err(format!("log requires a number, got {}", args[0].type_name())),
676 }
677 }
678 "exp" => {
679 if args.len() != 1 {
680 return Err("exp requires exactly 1 argument".into());
681 }
682 match &args[0] {
683 Value::Float(f) => Ok(Some(Value::Float(f.exp()))),
684 Value::Int(i) => Ok(Some(Value::Float((*i as f64).exp()))),
685 _ => Err(format!("exp requires a number, got {}", args[0].type_name())),
686 }
687 }
688 "sin" => {
690 if args.len() != 1 { return Err("sin requires exactly 1 argument".into()); }
691 match &args[0] {
692 Value::Float(f) => Ok(Some(Value::Float(f.sin()))),
693 Value::Int(i) => Ok(Some(Value::Float((*i as f64).sin()))),
694 _ => Err(format!("sin requires a number, got {}", args[0].type_name())),
695 }
696 }
697 "cos" => {
698 if args.len() != 1 { return Err("cos requires exactly 1 argument".into()); }
699 match &args[0] {
700 Value::Float(f) => Ok(Some(Value::Float(f.cos()))),
701 Value::Int(i) => Ok(Some(Value::Float((*i as f64).cos()))),
702 _ => Err(format!("cos requires a number, got {}", args[0].type_name())),
703 }
704 }
705 "tan" => {
706 if args.len() != 1 { return Err("tan requires exactly 1 argument".into()); }
707 match &args[0] {
708 Value::Float(f) => Ok(Some(Value::Float(f.tan()))),
709 Value::Int(i) => Ok(Some(Value::Float((*i as f64).tan()))),
710 _ => Err(format!("tan requires a number, got {}", args[0].type_name())),
711 }
712 }
713 "asin" => {
714 if args.len() != 1 { return Err("asin requires exactly 1 argument".into()); }
715 match &args[0] {
716 Value::Float(f) => Ok(Some(Value::Float(f.asin()))),
717 Value::Int(i) => Ok(Some(Value::Float((*i as f64).asin()))),
718 _ => Err(format!("asin requires a number, got {}", args[0].type_name())),
719 }
720 }
721 "acos" => {
722 if args.len() != 1 { return Err("acos requires exactly 1 argument".into()); }
723 match &args[0] {
724 Value::Float(f) => Ok(Some(Value::Float(f.acos()))),
725 Value::Int(i) => Ok(Some(Value::Float((*i as f64).acos()))),
726 _ => Err(format!("acos requires a number, got {}", args[0].type_name())),
727 }
728 }
729 "atan" => {
730 if args.len() != 1 { return Err("atan requires exactly 1 argument".into()); }
731 match &args[0] {
732 Value::Float(f) => Ok(Some(Value::Float(f.atan()))),
733 Value::Int(i) => Ok(Some(Value::Float((*i as f64).atan()))),
734 _ => Err(format!("atan requires a number, got {}", args[0].type_name())),
735 }
736 }
737 "atan2" => {
738 if args.len() != 2 { return Err("atan2 requires exactly 2 arguments".into()); }
739 let y = match &args[0] {
740 Value::Float(f) => *f,
741 Value::Int(i) => *i as f64,
742 _ => return Err(format!("atan2 requires numbers, got {}", args[0].type_name())),
743 };
744 let x = match &args[1] {
745 Value::Float(f) => *f,
746 Value::Int(i) => *i as f64,
747 _ => return Err(format!("atan2 requires numbers, got {}", args[1].type_name())),
748 };
749 Ok(Some(Value::Float(y.atan2(x))))
750 }
751 "sinh" => {
753 if args.len() != 1 { return Err("sinh requires exactly 1 argument".into()); }
754 match &args[0] {
755 Value::Float(f) => Ok(Some(Value::Float(f.sinh()))),
756 Value::Int(i) => Ok(Some(Value::Float((*i as f64).sinh()))),
757 _ => Err(format!("sinh requires a number, got {}", args[0].type_name())),
758 }
759 }
760 "cosh" => {
761 if args.len() != 1 { return Err("cosh requires exactly 1 argument".into()); }
762 match &args[0] {
763 Value::Float(f) => Ok(Some(Value::Float(f.cosh()))),
764 Value::Int(i) => Ok(Some(Value::Float((*i as f64).cosh()))),
765 _ => Err(format!("cosh requires a number, got {}", args[0].type_name())),
766 }
767 }
768 "tanh" | "tanh_scalar" => {
769 if args.len() != 1 { return Err("tanh requires exactly 1 argument".into()); }
770 match &args[0] {
771 Value::Float(f) => Ok(Some(Value::Float(f.tanh()))),
772 Value::Int(i) => Ok(Some(Value::Float((*i as f64).tanh()))),
773 Value::Tensor(t) => Ok(Some(Value::Tensor(t.tanh_activation()))),
774 _ => Err(format!("tanh requires a number or Tensor, got {}", args[0].type_name())),
775 }
776 }
777 "pow" => {
779 if args.len() != 2 { return Err("pow requires exactly 2 arguments".into()); }
780 let base = match &args[0] {
781 Value::Float(f) => *f,
782 Value::Int(i) => *i as f64,
783 _ => return Err(format!("pow requires numbers, got {}", args[0].type_name())),
784 };
785 let exp = match &args[1] {
786 Value::Float(f) => *f,
787 Value::Int(i) => *i as f64,
788 _ => return Err(format!("pow requires numbers, got {}", args[1].type_name())),
789 };
790 Ok(Some(Value::Float(base.powf(exp))))
791 }
792 "log2" => {
793 if args.len() != 1 { return Err("log2 requires exactly 1 argument".into()); }
794 match &args[0] {
795 Value::Float(f) => Ok(Some(Value::Float(f.log2()))),
796 Value::Int(i) => Ok(Some(Value::Float((*i as f64).log2()))),
797 _ => Err(format!("log2 requires a number, got {}", args[0].type_name())),
798 }
799 }
800 "log10" => {
801 if args.len() != 1 { return Err("log10 requires exactly 1 argument".into()); }
802 match &args[0] {
803 Value::Float(f) => Ok(Some(Value::Float(f.log10()))),
804 Value::Int(i) => Ok(Some(Value::Float((*i as f64).log10()))),
805 _ => Err(format!("log10 requires a number, got {}", args[0].type_name())),
806 }
807 }
808 "log1p" => {
809 if args.len() != 1 { return Err("log1p requires exactly 1 argument".into()); }
810 match &args[0] {
811 Value::Float(f) => Ok(Some(Value::Float(f.ln_1p()))),
812 Value::Int(i) => Ok(Some(Value::Float((*i as f64).ln_1p()))),
813 _ => Err(format!("log1p requires a number, got {}", args[0].type_name())),
814 }
815 }
816 "expm1" => {
817 if args.len() != 1 { return Err("expm1 requires exactly 1 argument".into()); }
818 match &args[0] {
819 Value::Float(f) => Ok(Some(Value::Float(f.exp_m1()))),
820 Value::Int(i) => Ok(Some(Value::Float((*i as f64).exp_m1()))),
821 _ => Err(format!("expm1 requires a number, got {}", args[0].type_name())),
822 }
823 }
824 "ceil" => {
826 if args.len() != 1 { return Err("ceil requires exactly 1 argument".into()); }
827 match &args[0] {
828 Value::Float(f) => Ok(Some(Value::Float(f.ceil()))),
829 Value::Int(i) => Ok(Some(Value::Int(*i))),
830 _ => Err(format!("ceil requires a number, got {}", args[0].type_name())),
831 }
832 }
833 "round" => {
834 if args.len() != 1 { return Err("round requires exactly 1 argument".into()); }
835 match &args[0] {
836 Value::Float(f) => Ok(Some(Value::Float(f.round()))),
837 Value::Int(i) => Ok(Some(Value::Int(*i))),
838 _ => Err(format!("round requires a number, got {}", args[0].type_name())),
839 }
840 }
841 "floor" => {
842 if args.len() != 1 {
843 return Err("floor requires exactly 1 argument".into());
844 }
845 match &args[0] {
846 Value::Float(f) => Ok(Some(Value::Float(f.floor()))),
847 Value::Int(i) => Ok(Some(Value::Int(*i))),
848 _ => Err(format!("floor requires a number, got {}", args[0].type_name())),
849 }
850 }
851 "int" => {
852 if args.len() != 1 {
853 return Err("int requires exactly 1 argument".into());
854 }
855 match &args[0] {
856 Value::Float(f) => Ok(Some(Value::Int(*f as i64))),
857 Value::Int(i) => Ok(Some(Value::Int(*i))),
858 Value::Bool(b) => Ok(Some(Value::Int(if *b { 1 } else { 0 }))),
859 _ => Err(format!("int requires a number, got {}", args[0].type_name())),
860 }
861 }
862 "float" => {
863 if args.len() != 1 {
864 return Err("float requires exactly 1 argument".into());
865 }
866 match &args[0] {
867 Value::Int(i) => Ok(Some(Value::Float(*i as f64))),
868 Value::Float(f) => Ok(Some(Value::Float(*f))),
869 Value::Bool(b) => Ok(Some(Value::Float(if *b { 1.0 } else { 0.0 }))),
870 _ => Err(format!("float requires a number, got {}", args[0].type_name())),
871 }
872 }
873 "isnan" => {
874 if args.len() != 1 {
875 return Err("isnan requires exactly 1 argument".into());
876 }
877 match &args[0] {
878 Value::Float(f) => Ok(Some(Value::Bool(f.is_nan()))),
879 Value::Int(_) => Ok(Some(Value::Bool(false))),
880 _ => Err(format!("isnan requires a number, got {}", args[0].type_name())),
881 }
882 }
883 "isinf" => {
884 if args.len() != 1 {
885 return Err("isinf requires exactly 1 argument".into());
886 }
887 match &args[0] {
888 Value::Float(f) => Ok(Some(Value::Bool(f.is_infinite()))),
889 Value::Int(_) => Ok(Some(Value::Bool(false))),
890 _ => Err(format!("isinf requires a number, got {}", args[0].type_name())),
891 }
892 }
893 "abs" => {
894 if args.len() != 1 {
895 return Err("abs requires exactly 1 argument".into());
896 }
897 match &args[0] {
898 Value::Float(f) => Ok(Some(Value::Float(f.abs()))),
899 Value::Int(i) => Ok(Some(Value::Int(i.abs()))),
900 _ => Err(format!("abs requires a number, got {}", args[0].type_name())),
901 }
902 }
903 "min" => {
905 if args.len() != 2 { return Err("min requires exactly 2 arguments".into()); }
906 let a = match &args[0] {
907 Value::Float(f) => *f,
908 Value::Int(i) => *i as f64,
909 _ => return Err(format!("min requires numbers, got {}", args[0].type_name())),
910 };
911 let b = match &args[1] {
912 Value::Float(f) => *f,
913 Value::Int(i) => *i as f64,
914 _ => return Err(format!("min requires numbers, got {}", args[1].type_name())),
915 };
916 Ok(Some(Value::Float(a.min(b))))
917 }
918 "max" => {
919 if args.len() != 2 { return Err("max requires exactly 2 arguments".into()); }
920 let a = match &args[0] {
921 Value::Float(f) => *f,
922 Value::Int(i) => *i as f64,
923 _ => return Err(format!("max requires numbers, got {}", args[0].type_name())),
924 };
925 let b = match &args[1] {
926 Value::Float(f) => *f,
927 Value::Int(i) => *i as f64,
928 _ => return Err(format!("max requires numbers, got {}", args[1].type_name())),
929 };
930 Ok(Some(Value::Float(a.max(b))))
931 }
932 "sign" => {
933 if args.len() != 1 { return Err("sign requires exactly 1 argument".into()); }
934 match &args[0] {
935 Value::Float(f) => Ok(Some(Value::Float(f.signum()))),
936 Value::Int(i) => Ok(Some(Value::Float((*i as f64).signum()))),
937 _ => Err(format!("sign requires a number, got {}", args[0].type_name())),
938 }
939 }
940 "hypot" => {
942 if args.len() != 2 { return Err("hypot requires exactly 2 arguments".into()); }
943 let x = match &args[0] {
944 Value::Float(f) => *f,
945 Value::Int(i) => *i as f64,
946 _ => return Err(format!("hypot requires numbers, got {}", args[0].type_name())),
947 };
948 let y = match &args[1] {
949 Value::Float(f) => *f,
950 Value::Int(i) => *i as f64,
951 _ => return Err(format!("hypot requires numbers, got {}", args[1].type_name())),
952 };
953 Ok(Some(Value::Float(x.hypot(y))))
954 }
955 "PI" => {
957 if !args.is_empty() { return Err("PI takes no arguments".into()); }
958 Ok(Some(Value::Float(std::f64::consts::PI)))
959 }
960 "E" => {
961 if !args.is_empty() { return Err("E takes no arguments".into()); }
962 Ok(Some(Value::Float(std::f64::consts::E)))
963 }
964 "TAU" => {
965 if !args.is_empty() { return Err("TAU takes no arguments".into()); }
966 Ok(Some(Value::Float(std::f64::consts::TAU)))
967 }
968 "INF" => {
969 if !args.is_empty() { return Err("INF takes no arguments".into()); }
970 Ok(Some(Value::Float(f64::INFINITY)))
971 }
972 "NAN_VAL" => {
973 if !args.is_empty() { return Err("NAN_VAL takes no arguments".into()); }
974 Ok(Some(Value::Float(f64::NAN)))
975 }
976 "dot" => {
978 if args.len() != 2 { return Err("dot requires exactly 2 arguments".into()); }
979 let a = match &args[0] {
980 Value::Tensor(t) => t,
981 _ => return Err(format!("dot requires tensors, got {}", args[0].type_name())),
982 };
983 let b = match &args[1] {
984 Value::Tensor(t) => t,
985 _ => return Err(format!("dot requires tensors, got {}", args[1].type_name())),
986 };
987 if a.ndim() != 1 || b.ndim() != 1 {
988 return Err("dot requires 1D tensors".into());
989 }
990 if a.len() != b.len() {
991 return Err(format!("dot: length mismatch ({} vs {})", a.len(), b.len()));
992 }
993 let av = a.to_vec();
994 let bv = b.to_vec();
995 let products: Vec<f64> = av.iter().zip(bv.iter()).map(|(x, y)| x * y).collect();
996 let sum = crate::accumulator::binned_sum_f64(&products);
997 Ok(Some(Value::Float(sum)))
998 }
999 "outer" => {
1000 if args.len() != 2 { return Err("outer requires exactly 2 arguments".into()); }
1001 let a = match &args[0] {
1002 Value::Tensor(t) => t,
1003 _ => return Err(format!("outer requires tensors, got {}", args[0].type_name())),
1004 };
1005 let b = match &args[1] {
1006 Value::Tensor(t) => t,
1007 _ => return Err(format!("outer requires tensors, got {}", args[1].type_name())),
1008 };
1009 if a.ndim() != 1 || b.ndim() != 1 {
1010 return Err("outer requires 1D tensors".into());
1011 }
1012 let av = a.to_vec();
1013 let bv = b.to_vec();
1014 let m = av.len();
1015 let n = bv.len();
1016 let mut data = Vec::with_capacity(m * n);
1017 for ai in &av {
1018 for bj in &bv {
1019 data.push(ai * bj);
1020 }
1021 }
1022 Ok(Some(Value::Tensor(Tensor::from_vec(data, &[m, n]).map_err(|e| format!("{e}"))?)))
1023 }
1024 "cross" => {
1025 if args.len() != 2 { return Err("cross requires exactly 2 arguments".into()); }
1026 let a = match &args[0] {
1027 Value::Tensor(t) => t,
1028 _ => return Err(format!("cross requires tensors, got {}", args[0].type_name())),
1029 };
1030 let b = match &args[1] {
1031 Value::Tensor(t) => t,
1032 _ => return Err(format!("cross requires tensors, got {}", args[1].type_name())),
1033 };
1034 if a.ndim() != 1 || b.ndim() != 1 || a.len() != 3 || b.len() != 3 {
1035 return Err("cross requires two 3-element 1D tensors".into());
1036 }
1037 let av = a.to_vec();
1038 let bv = b.to_vec();
1039 let result = vec![
1040 av[1] * bv[2] - av[2] * bv[1],
1041 av[2] * bv[0] - av[0] * bv[2],
1042 av[0] * bv[1] - av[1] * bv[0],
1043 ];
1044 Ok(Some(Value::Tensor(Tensor::from_vec(result, &[3]).map_err(|e| format!("{e}"))?)))
1045 }
1046 "norm" => {
1047 if args.len() < 1 || args.len() > 2 { return Err("norm requires 1-2 arguments".into()); }
1048 let t = match &args[0] {
1049 Value::Tensor(t) => t,
1050 _ => return Err(format!("norm requires a tensor, got {}", args[0].type_name())),
1051 };
1052 let ord = if args.len() == 2 {
1053 match &args[1] {
1054 Value::Int(i) => *i,
1055 Value::Float(f) => *f as i64,
1056 _ => return Err("norm: ord must be an integer".into()),
1057 }
1058 } else {
1059 2 };
1061 let data = t.to_vec();
1062 let result = match ord {
1063 1 => {
1064 let abs_vals: Vec<f64> = data.iter().map(|x| x.abs()).collect();
1065 crate::accumulator::binned_sum_f64(&abs_vals)
1066 }
1067 2 => {
1068 let sq_vals: Vec<f64> = data.iter().map(|x| x * x).collect();
1069 crate::accumulator::binned_sum_f64(&sq_vals).sqrt()
1070 }
1071 _ => {
1072 let p = ord as f64;
1073 let pow_vals: Vec<f64> = data.iter().map(|x| x.abs().powf(p)).collect();
1074 crate::accumulator::binned_sum_f64(&pow_vals).powf(1.0 / p)
1075 }
1076 };
1077 Ok(Some(Value::Float(result)))
1078 }
1079 "Tensor.linspace" => {
1081 if args.len() != 3 { return Err("Tensor.linspace requires 3 arguments (start, end, n)".into()); }
1082 let start = match &args[0] {
1083 Value::Float(f) => *f,
1084 Value::Int(i) => *i as f64,
1085 _ => return Err("Tensor.linspace: start must be a number".into()),
1086 };
1087 let end = match &args[1] {
1088 Value::Float(f) => *f,
1089 Value::Int(i) => *i as f64,
1090 _ => return Err("Tensor.linspace: end must be a number".into()),
1091 };
1092 let n = match &args[2] {
1093 Value::Int(i) => *i as usize,
1094 _ => return Err("Tensor.linspace: n must be an integer".into()),
1095 };
1096 if n == 0 {
1097 return Ok(Some(Value::Tensor(Tensor::from_vec(vec![], &[0]).map_err(|e| format!("{e}"))?)));
1098 }
1099 if n == 1 {
1100 return Ok(Some(Value::Tensor(Tensor::from_vec(vec![start], &[1]).map_err(|e| format!("{e}"))?)));
1101 }
1102 let step = (end - start) / (n as f64 - 1.0);
1103 let data: Vec<f64> = (0..n).map(|i| start + step * i as f64).collect();
1104 Ok(Some(Value::Tensor(Tensor::from_vec(data, &[n]).map_err(|e| format!("{e}"))?)))
1105 }
1106 "Tensor.arange" => {
1107 if args.len() < 2 || args.len() > 3 { return Err("Tensor.arange requires 2-3 arguments (start, end, step?)".into()); }
1108 let start = match &args[0] {
1109 Value::Float(f) => *f,
1110 Value::Int(i) => *i as f64,
1111 _ => return Err("Tensor.arange: start must be a number".into()),
1112 };
1113 let end = match &args[1] {
1114 Value::Float(f) => *f,
1115 Value::Int(i) => *i as f64,
1116 _ => return Err("Tensor.arange: end must be a number".into()),
1117 };
1118 let step = if args.len() == 3 {
1119 match &args[2] {
1120 Value::Float(f) => *f,
1121 Value::Int(i) => *i as f64,
1122 _ => return Err("Tensor.arange: step must be a number".into()),
1123 }
1124 } else {
1125 1.0
1126 };
1127 if step == 0.0 { return Err("Tensor.arange: step cannot be zero".into()); }
1128 let mut data = Vec::new();
1129 let mut val = start;
1130 if step > 0.0 {
1131 while val < end { data.push(val); val += step; }
1132 } else {
1133 while val > end { data.push(val); val += step; }
1134 }
1135 let n = data.len();
1136 Ok(Some(Value::Tensor(Tensor::from_vec(data, &[n]).map_err(|e| format!("{e}"))?)))
1137 }
1138 "Tensor.eye" => {
1139 if args.len() != 1 { return Err("Tensor.eye requires 1 argument (n)".into()); }
1140 let n = match &args[0] {
1141 Value::Int(i) => *i as usize,
1142 _ => return Err("Tensor.eye: n must be an integer".into()),
1143 };
1144 let mut data = vec![0.0; n * n];
1145 for i in 0..n {
1146 data[i * n + i] = 1.0;
1147 }
1148 Ok(Some(Value::Tensor(Tensor::from_vec(data, &[n, n]).map_err(|e| format!("{e}"))?)))
1149 }
1150 "Tensor.full" => {
1151 if args.len() != 2 { return Err("Tensor.full requires 2 arguments (shape, value)".into()); }
1152 let shape = match &args[0] {
1153 Value::Array(arr) => {
1154 let mut s = Vec::new();
1155 for v in arr.iter() {
1156 match v {
1157 Value::Int(i) => s.push(*i as usize),
1158 _ => return Err("Tensor.full: shape must be an array of ints".into()),
1159 }
1160 }
1161 s
1162 }
1163 _ => return Err("Tensor.full: shape must be an array".into()),
1164 };
1165 let fill_val = match &args[1] {
1166 Value::Float(f) => *f,
1167 Value::Int(i) => *i as f64,
1168 _ => return Err("Tensor.full: value must be a number".into()),
1169 };
1170 let total: usize = shape.iter().product();
1171 let data = vec![fill_val; total];
1172 Ok(Some(Value::Tensor(Tensor::from_vec(data, &shape).map_err(|e| format!("{e}"))?)))
1173 }
1174 "Tensor.diag" => {
1175 if args.len() != 1 { return Err("Tensor.diag requires 1 argument".into()); }
1176 let t = match &args[0] {
1177 Value::Tensor(t) => t,
1178 _ => return Err("Tensor.diag requires a tensor".into()),
1179 };
1180 match t.ndim() {
1181 1 => {
1182 let data = t.to_vec();
1184 let n = data.len();
1185 let mut out = vec![0.0; n * n];
1186 for i in 0..n {
1187 out[i * n + i] = data[i];
1188 }
1189 Ok(Some(Value::Tensor(Tensor::from_vec(out, &[n, n]).map_err(|e| format!("{e}"))?)))
1190 }
1191 2 => {
1192 let rows = t.shape()[0];
1194 let cols = t.shape()[1];
1195 let n = rows.min(cols);
1196 let mut data = Vec::with_capacity(n);
1197 for i in 0..n {
1198 data.push(t.get(&[i, i]).map_err(|e| format!("{e}"))?);
1199 }
1200 Ok(Some(Value::Tensor(Tensor::from_vec(data, &[n]).map_err(|e| format!("{e}"))?)))
1201 }
1202 _ => Err("Tensor.diag requires a 1D or 2D tensor".into()),
1203 }
1204 }
1205 "assert" => {
1206 if args.len() != 1 {
1207 return Err("assert requires exactly 1 argument".into());
1208 }
1209 match &args[0] {
1210 Value::Bool(true) => Ok(Some(Value::Void)),
1211 Value::Bool(false) => Err("assertion failed".into()),
1212 other => Err(format!("assert requires Bool, got {}", other.type_name())),
1213 }
1214 }
1215 "assert_eq" => {
1216 if args.len() != 2 {
1217 return Err("assert_eq requires exactly 2 arguments".into());
1218 }
1219 if values_equal(&args[0], &args[1]) {
1220 Ok(Some(Value::Void))
1221 } else {
1222 Err(format!("assertion failed: `{}` != `{}`", args[0], args[1]))
1223 }
1224 }
1225 "json_parse" => {
1227 if args.len() != 1 {
1228 return Err("json_parse requires exactly 1 argument".into());
1229 }
1230 let s = match &args[0] {
1231 Value::String(s) => s.as_str(),
1232 _ => return Err(format!("json_parse requires String, got {}", args[0].type_name())),
1233 };
1234 crate::json::json_parse(s).map(Some)
1235 }
1236 "json_stringify" => {
1237 if args.len() != 1 {
1238 return Err("json_stringify requires exactly 1 argument".into());
1239 }
1240 crate::json::json_stringify(&args[0]).map(|s| Some(Value::String(Rc::new(s))))
1241 }
1242
1243 "datetime_from_epoch" => {
1245 if args.len() != 1 {
1246 return Err("datetime_from_epoch requires exactly 1 argument".into());
1247 }
1248 match &args[0] {
1249 Value::Int(n) => Ok(Some(Value::Int(crate::datetime::datetime_from_epoch(*n)))),
1250 _ => Err(format!("datetime_from_epoch requires Int, got {}", args[0].type_name())),
1251 }
1252 }
1253 "datetime_from_parts" => {
1254 if args.len() != 6 {
1255 return Err("datetime_from_parts requires 6 arguments (year, month, day, hour, min, sec)".into());
1256 }
1257 let mut vals = [0i64; 6];
1258 for (i, arg) in args.iter().enumerate() {
1259 match arg {
1260 Value::Int(n) => vals[i] = *n,
1261 _ => return Err(format!("datetime_from_parts arg {} must be Int", i)),
1262 }
1263 }
1264 Ok(Some(Value::Int(crate::datetime::datetime_from_parts(
1265 vals[0], vals[1], vals[2], vals[3], vals[4], vals[5],
1266 ))))
1267 }
1268 "datetime_year" => {
1269 if args.len() != 1 { return Err("datetime_year requires 1 argument".into()); }
1270 match &args[0] {
1271 Value::Int(n) => Ok(Some(Value::Int(crate::datetime::datetime_year(*n)))),
1272 _ => Err(format!("datetime_year requires Int, got {}", args[0].type_name())),
1273 }
1274 }
1275 "datetime_month" => {
1276 if args.len() != 1 { return Err("datetime_month requires 1 argument".into()); }
1277 match &args[0] {
1278 Value::Int(n) => Ok(Some(Value::Int(crate::datetime::datetime_month(*n)))),
1279 _ => Err(format!("datetime_month requires Int, got {}", args[0].type_name())),
1280 }
1281 }
1282 "datetime_day" => {
1283 if args.len() != 1 { return Err("datetime_day requires 1 argument".into()); }
1284 match &args[0] {
1285 Value::Int(n) => Ok(Some(Value::Int(crate::datetime::datetime_day(*n)))),
1286 _ => Err(format!("datetime_day requires Int, got {}", args[0].type_name())),
1287 }
1288 }
1289 "datetime_hour" => {
1290 if args.len() != 1 { return Err("datetime_hour requires 1 argument".into()); }
1291 match &args[0] {
1292 Value::Int(n) => Ok(Some(Value::Int(crate::datetime::datetime_hour(*n)))),
1293 _ => Err(format!("datetime_hour requires Int, got {}", args[0].type_name())),
1294 }
1295 }
1296 "datetime_minute" => {
1297 if args.len() != 1 { return Err("datetime_minute requires 1 argument".into()); }
1298 match &args[0] {
1299 Value::Int(n) => Ok(Some(Value::Int(crate::datetime::datetime_minute(*n)))),
1300 _ => Err(format!("datetime_minute requires Int, got {}", args[0].type_name())),
1301 }
1302 }
1303 "datetime_second" => {
1304 if args.len() != 1 { return Err("datetime_second requires 1 argument".into()); }
1305 match &args[0] {
1306 Value::Int(n) => Ok(Some(Value::Int(crate::datetime::datetime_second(*n)))),
1307 _ => Err(format!("datetime_second requires Int, got {}", args[0].type_name())),
1308 }
1309 }
1310 "datetime_diff" => {
1311 if args.len() != 2 { return Err("datetime_diff requires 2 arguments".into()); }
1312 match (&args[0], &args[1]) {
1313 (Value::Int(a), Value::Int(b)) => Ok(Some(Value::Int(crate::datetime::datetime_diff(*a, *b)))),
1314 _ => Err("datetime_diff requires two Int arguments".into()),
1315 }
1316 }
1317 "datetime_add_millis" => {
1318 if args.len() != 2 { return Err("datetime_add_millis requires 2 arguments".into()); }
1319 match (&args[0], &args[1]) {
1320 (Value::Int(a), Value::Int(b)) => Ok(Some(Value::Int(crate::datetime::datetime_add_millis(*a, *b)))),
1321 _ => Err("datetime_add_millis requires two Int arguments".into()),
1322 }
1323 }
1324 "datetime_format" => {
1325 if args.len() != 1 { return Err("datetime_format requires 1 argument".into()); }
1326 match &args[0] {
1327 Value::Int(n) => Ok(Some(Value::String(Rc::new(crate::datetime::datetime_format(*n))))),
1328 _ => Err(format!("datetime_format requires Int, got {}", args[0].type_name())),
1329 }
1330 }
1331
1332 "file_read" => {
1334 if args.len() != 1 { return Err("file_read requires 1 argument".into()); }
1335 match &args[0] {
1336 Value::String(path) => {
1337 let content = std::fs::read_to_string(path.as_str())
1338 .map_err(|e| format!("file_read error: {}", e))?;
1339 Ok(Some(Value::String(Rc::new(content))))
1340 }
1341 _ => Err(format!("file_read requires String path, got {}", args[0].type_name())),
1342 }
1343 }
1344 "file_write" => {
1345 if args.len() != 2 { return Err("file_write requires 2 arguments (path, content)".into()); }
1346 match (&args[0], &args[1]) {
1347 (Value::String(path), Value::String(content)) => {
1348 std::fs::write(path.as_str(), content.as_str())
1349 .map_err(|e| format!("file_write error: {}", e))?;
1350 Ok(Some(Value::Void))
1351 }
1352 _ => Err("file_write requires (String, String) arguments".into()),
1353 }
1354 }
1355 "file_append" => {
1356 if args.len() != 2 { return Err("file_append requires 2 arguments (path, content)".into()); }
1357 match (&args[0], &args[1]) {
1358 (Value::String(path), Value::String(content)) => {
1359 use std::io::Write;
1360 let mut f = std::fs::OpenOptions::new()
1361 .create(true)
1362 .append(true)
1363 .open(path.as_str())
1364 .map_err(|e| format!("file_append error: {}", e))?;
1365 f.write_all(content.as_bytes())
1366 .map_err(|e| format!("file_append error: {}", e))?;
1367 Ok(Some(Value::Void))
1368 }
1369 _ => Err("file_append requires (String, String) arguments".into()),
1370 }
1371 }
1372 "file_exists" => {
1373 if args.len() != 1 { return Err("file_exists requires 1 argument".into()); }
1374 match &args[0] {
1375 Value::String(path) => Ok(Some(Value::Bool(std::path::Path::new(path.as_str()).exists()))),
1376 _ => Err(format!("file_exists requires String path, got {}", args[0].type_name())),
1377 }
1378 }
1379 "file_lines" => {
1380 if args.len() != 1 { return Err("file_lines requires 1 argument".into()); }
1381 match &args[0] {
1382 Value::String(path) => {
1383 let content = std::fs::read_to_string(path.as_str())
1384 .map_err(|e| format!("file_lines error: {}", e))?;
1385 let lines: Vec<Value> = content
1386 .lines()
1387 .map(|l| Value::String(Rc::new(l.to_string())))
1388 .collect();
1389 Ok(Some(Value::Array(Rc::new(lines))))
1390 }
1391 _ => Err(format!("file_lines requires String path, got {}", args[0].type_name())),
1392 }
1393 }
1394
1395 "profile_zone_start" => {
1402 if args.len() != 1 {
1403 return Err("profile_zone_start requires 1 argument (name: String)".into());
1404 }
1405 match &args[0] {
1406 Value::String(name) => {
1407 let h = crate::profile::zone_start(name.as_str());
1408 Ok(Some(Value::Int(h)))
1409 }
1410 _ => Err(format!(
1411 "profile_zone_start requires String name, got {}",
1412 args[0].type_name()
1413 )),
1414 }
1415 }
1416 "profile_zone_stop" => {
1417 if args.len() != 1 {
1418 return Err("profile_zone_stop requires 1 argument (handle: Int)".into());
1419 }
1420 match &args[0] {
1421 Value::Int(h) => {
1422 let elapsed = crate::profile::zone_stop(*h);
1423 Ok(Some(Value::Float(elapsed)))
1424 }
1425 _ => Err(format!(
1426 "profile_zone_stop requires Int handle, got {}",
1427 args[0].type_name()
1428 )),
1429 }
1430 }
1431 "profile_dump" => {
1432 if args.len() != 1 {
1433 return Err("profile_dump requires 1 argument (path: String)".into());
1434 }
1435 match &args[0] {
1436 Value::String(path) => {
1437 let rows = crate::profile::dump_to_path(path.as_str())?;
1438 Ok(Some(Value::Int(rows)))
1439 }
1440 _ => Err(format!(
1441 "profile_dump requires String path, got {}",
1442 args[0].type_name()
1443 )),
1444 }
1445 }
1446
1447 "encode_state_fast" => {
1464 if args.len() != 5 {
1465 return Err(
1466 "encode_state_fast requires 5 arguments: board(Array[64]), side(Int), castling(Array[4]), ep_sq(Int), halfmove(Int)"
1467 .into(),
1468 );
1469 }
1470 let board = match &args[0] {
1471 Value::Array(arr) => {
1472 if arr.len() != 64 {
1473 return Err(format!(
1474 "encode_state_fast: board must have 64 elements, got {}",
1475 arr.len()
1476 ));
1477 }
1478 let mut b = [0i64; 64];
1479 for (i, v) in arr.iter().enumerate() {
1480 b[i] = match v {
1481 Value::Int(n) => *n,
1482 _ => {
1483 return Err(format!(
1484 "encode_state_fast: board[{i}] must be Int, got {}",
1485 v.type_name()
1486 ))
1487 }
1488 };
1489 }
1490 b
1491 }
1492 _ => {
1493 return Err(format!(
1494 "encode_state_fast: board must be Array, got {}",
1495 args[0].type_name()
1496 ))
1497 }
1498 };
1499 let side = match &args[1] {
1500 Value::Int(n) => *n,
1501 _ => {
1502 return Err(format!(
1503 "encode_state_fast: side must be Int, got {}",
1504 args[1].type_name()
1505 ))
1506 }
1507 };
1508 let castling = match &args[2] {
1509 Value::Array(arr) => {
1510 if arr.len() != 4 {
1511 return Err(format!(
1512 "encode_state_fast: castling must have 4 elements, got {}",
1513 arr.len()
1514 ));
1515 }
1516 let mut c = [0i64; 4];
1517 for (i, v) in arr.iter().enumerate() {
1518 c[i] = match v {
1519 Value::Int(n) => *n,
1520 _ => {
1521 return Err(format!(
1522 "encode_state_fast: castling[{i}] must be Int, got {}",
1523 v.type_name()
1524 ))
1525 }
1526 };
1527 }
1528 c
1529 }
1530 _ => {
1531 return Err(format!(
1532 "encode_state_fast: castling must be Array, got {}",
1533 args[2].type_name()
1534 ))
1535 }
1536 };
1537 let ep_sq = match &args[3] {
1538 Value::Int(n) => *n,
1539 _ => {
1540 return Err(format!(
1541 "encode_state_fast: ep_sq must be Int, got {}",
1542 args[3].type_name()
1543 ))
1544 }
1545 };
1546 let halfmove = match &args[4] {
1547 Value::Int(n) => *n,
1548 _ => {
1549 return Err(format!(
1550 "encode_state_fast: halfmove must be Int, got {}",
1551 args[4].type_name()
1552 ))
1553 }
1554 };
1555
1556 let mut data = vec![0.0f64; 774];
1558
1559 let feat_sq = |sq: i64, s: i64| -> usize {
1561 if s == 1 {
1562 sq as usize
1563 } else {
1564 let r = sq / 8;
1565 let f = sq % 8;
1566 ((7 - r) * 8 + f) as usize
1567 }
1568 };
1569
1570 for i in 0..64 {
1572 let p = board[i];
1573 if p != 0 {
1574 let abs_p = p.unsigned_abs() as i64;
1575 if abs_p < 1 || abs_p > 6 {
1576 return Err(format!(
1577 "encode_state_fast: invalid piece value {} at square {}",
1578 p, i
1579 ));
1580 }
1581 let piece_idx = abs_p - 1; let owner = if p > 0 { 1i64 } else { -1i64 };
1583 let plane = if owner == side {
1584 piece_idx
1585 } else {
1586 piece_idx + 6
1587 };
1588 let mapped = feat_sq(i as i64, side);
1589 let idx = (plane as usize) * 64 + mapped;
1590 data[idx] = 1.0;
1591 }
1592 }
1593
1594 let (my_k, my_q, op_k, op_q) = if side == 1 {
1596 (castling[0], castling[1], castling[2], castling[3])
1597 } else {
1598 (castling[2], castling[3], castling[0], castling[1])
1599 };
1600 data[768] = my_k as f64;
1601 data[769] = my_q as f64;
1602 data[770] = op_k as f64;
1603 data[771] = op_q as f64;
1604
1605 data[772] = (halfmove as f64) / 100.0;
1607
1608 data[773] = if ep_sq >= 0 { 1.0 } else { 0.0 };
1610
1611 let tensor = Tensor::from_vec(data, &[1, 774]).map_err(|e| format!("{e}"))?;
1612 Ok(Some(Value::Tensor(tensor)))
1613 }
1614
1615 "score_moves_batch" => {
1636 if args.len() != 4 {
1637 return Err(
1638 "score_moves_batch requires 4 arguments: weights(Array[11]), feature(Tensor[1,774]), legal_moves(Array), side(Int)"
1639 .into(),
1640 );
1641 }
1642 let weights = match &args[0] {
1644 Value::Array(arr) => {
1645 if arr.len() < 11 {
1646 return Err(format!(
1647 "score_moves_batch: weights must have ≥11 elements, got {}",
1648 arr.len()
1649 ));
1650 }
1651 arr.clone()
1652 }
1653 _ => {
1654 return Err(format!(
1655 "score_moves_batch: weights must be Array, got {}",
1656 args[0].type_name()
1657 ))
1658 }
1659 };
1660 let feature = value_to_tensor(&args[1])?;
1661 let moves = match &args[2] {
1662 Value::Array(arr) => {
1663 let mut mv = Vec::with_capacity(arr.len());
1664 for (i, v) in arr.iter().enumerate() {
1665 mv.push(match v {
1666 Value::Int(n) => *n,
1667 _ => {
1668 return Err(format!(
1669 "score_moves_batch: legal_moves[{i}] must be Int, got {}",
1670 v.type_name()
1671 ))
1672 }
1673 });
1674 }
1675 mv
1676 }
1677 _ => {
1678 return Err(format!(
1679 "score_moves_batch: legal_moves must be Array, got {}",
1680 args[2].type_name()
1681 ))
1682 }
1683 };
1684 let side = match &args[3] {
1685 Value::Int(n) => *n,
1686 _ => {
1687 return Err(format!(
1688 "score_moves_batch: side must be Int, got {}",
1689 args[3].type_name()
1690 ))
1691 }
1692 };
1693
1694 let num_moves = moves.len() / 2;
1695
1696 let w1 = value_to_tensor(&weights[0])?;
1698 let b1 = value_to_tensor(&weights[1])?;
1699 let w2 = value_to_tensor(&weights[2])?;
1700 let b2 = value_to_tensor(&weights[3])?;
1701 let wpf = value_to_tensor(&weights[5])?;
1703 let bpf = value_to_tensor(&weights[6])?;
1704 let wpt = value_to_tensor(&weights[7])?;
1705 let bpt = value_to_tensor(&weights[8])?;
1706 let wv = value_to_tensor(&weights[9])?;
1707 let bv = value_to_tensor(&weights[10])?;
1708
1709 let z1 = feature.matmul(&w1).map_err(|e| format!("{e}"))?
1712 .add(&b1).map_err(|e| format!("{e}"))?;
1713 let h1 = z1.relu();
1714 let z2 = h1.matmul(&w2).map_err(|e| format!("{e}"))?
1716 .add(&b2).map_err(|e| format!("{e}"))?;
1717 let h2 = z2.relu();
1718 let from_logits = h2.matmul(&wpf).map_err(|e| format!("{e}"))?
1720 .add(&bpf).map_err(|e| format!("{e}"))?;
1721 let to_logits = h2.matmul(&wpt).map_err(|e| format!("{e}"))?
1723 .add(&bpt).map_err(|e| format!("{e}"))?;
1724 let v_pre = h2.matmul(&wv).map_err(|e| format!("{e}"))?
1726 .add(&bv).map_err(|e| format!("{e}"))?;
1727 let v_tanh = v_pre.tanh_activation();
1728 let value = v_tanh.get(&[0, 0]).map_err(|e| format!("{e}"))?;
1729
1730 let feat_sq = |sq: i64, s: i64| -> usize {
1733 if s == 1 {
1734 sq as usize
1735 } else {
1736 let r = sq / 8;
1737 let f = sq % 8;
1738 ((7 - r) * 8 + f) as usize
1739 }
1740 };
1741
1742 let mut scores = Vec::with_capacity(num_moves);
1743 for i in 0..num_moves {
1744 let from_sq = moves[i * 2];
1745 let to_sq = moves[i * 2 + 1];
1746 let from_mapped = feat_sq(from_sq, side);
1747 let to_mapped = feat_sq(to_sq, side);
1748 let fs = from_logits.get(&[0, from_mapped]).map_err(|e| format!("{e}"))?;
1749 let ts = to_logits.get(&[0, to_mapped]).map_err(|e| format!("{e}"))?;
1750 scores.push(fs + ts);
1751 }
1752
1753 let scores_tensor = Tensor::from_vec(scores, &[num_moves]).map_err(|e| format!("{e}"))?;
1754 let result = vec![
1755 Value::Tensor(scores_tensor),
1756 Value::Float(value),
1757 ];
1758 Ok(Some(Value::Array(Rc::new(result))))
1759 }
1760
1761 "tensor_save" => {
1763 if args.len() != 2 {
1764 return Err("tensor_save requires 2 arguments: path, tensor".into());
1765 }
1766 let path = match &args[0] {
1767 Value::String(p) => p.as_str().to_string(),
1768 _ => return Err("tensor_save: first argument must be String path".into()),
1769 };
1770 let tensor = value_to_tensor(&args[1])?;
1771 let bytes = crate::tensor_snap::encode_one(tensor);
1772 std::fs::write(&path, &bytes)
1773 .map_err(|e| format!("tensor_save error: {}", e))?;
1774 Ok(Some(Value::Void))
1775 }
1776 "tensor_load" => {
1777 if args.len() != 1 {
1778 return Err("tensor_load requires 1 argument: path".into());
1779 }
1780 let path = match &args[0] {
1781 Value::String(p) => p.as_str().to_string(),
1782 _ => return Err("tensor_load: argument must be String path".into()),
1783 };
1784 let bytes = std::fs::read(&path)
1785 .map_err(|e| format!("tensor_load error: {}", e))?;
1786 let tensor = crate::tensor_snap::decode_one(&bytes)
1787 .map_err(|e| format!("tensor_load error: {}", e))?;
1788 Ok(Some(Value::Tensor(tensor)))
1789 }
1790 "tensor_list_save" => {
1791 if args.len() != 2 {
1792 return Err("tensor_list_save requires 2 arguments: path, [tensors]".into());
1793 }
1794 let path = match &args[0] {
1795 Value::String(p) => p.as_str().to_string(),
1796 _ => return Err("tensor_list_save: first argument must be String path".into()),
1797 };
1798 let tensors = match &args[1] {
1799 Value::Array(arr) => {
1800 let mut out = Vec::with_capacity(arr.len());
1801 for v in arr.iter() {
1802 out.push(value_to_tensor(v)?.clone());
1803 }
1804 out
1805 }
1806 _ => return Err("tensor_list_save: second argument must be Array of Tensors".into()),
1807 };
1808 let bytes = crate::tensor_snap::encode_list(&tensors);
1809 std::fs::write(&path, &bytes)
1810 .map_err(|e| format!("tensor_list_save error: {}", e))?;
1811 Ok(Some(Value::Void))
1812 }
1813 "tensor_list_load" => {
1814 if args.len() != 1 {
1815 return Err("tensor_list_load requires 1 argument: path".into());
1816 }
1817 let path = match &args[0] {
1818 Value::String(p) => p.as_str().to_string(),
1819 _ => return Err("tensor_list_load: argument must be String path".into()),
1820 };
1821 let bytes = std::fs::read(&path)
1822 .map_err(|e| format!("tensor_list_load error: {}", e))?;
1823 let tensors = crate::tensor_snap::decode_list(&bytes)
1824 .map_err(|e| format!("tensor_list_load error: {}", e))?;
1825 let arr: Vec<Value> = tensors.into_iter().map(Value::Tensor).collect();
1826 Ok(Some(Value::Array(Rc::new(arr))))
1827 }
1828 "tensor_list_hash" => {
1829 if args.len() != 1 {
1830 return Err("tensor_list_hash requires 1 argument: [tensors]".into());
1831 }
1832 let tensors = match &args[0] {
1833 Value::Array(arr) => {
1834 let mut out = Vec::with_capacity(arr.len());
1835 for v in arr.iter() {
1836 out.push(value_to_tensor(v)?.clone());
1837 }
1838 out
1839 }
1840 _ => return Err("tensor_list_hash: argument must be Array of Tensors".into()),
1841 };
1842 let h = crate::tensor_snap::hash_list(&tensors);
1843 Ok(Some(Value::Int(h as i64)))
1846 }
1847 "adam_step" => {
1854 if args.len() != 9 {
1855 return Err("adam_step requires 9 arguments: w, g, m, v, lr, b1, b2, eps, t".into());
1856 }
1857 let w = value_to_tensor(&args[0])?;
1858 let g = value_to_tensor(&args[1])?;
1859 let m = value_to_tensor(&args[2])?;
1860 let v = value_to_tensor(&args[3])?;
1861 let lr = match &args[4] {
1862 Value::Float(f) => *f,
1863 Value::Int(i) => *i as f64,
1864 _ => return Err(format!("adam_step lr must be number, got {}", args[4].type_name())),
1865 };
1866 let b1 = match &args[5] {
1867 Value::Float(f) => *f,
1868 Value::Int(i) => *i as f64,
1869 _ => return Err(format!("adam_step b1 must be number, got {}", args[5].type_name())),
1870 };
1871 let b2 = match &args[6] {
1872 Value::Float(f) => *f,
1873 Value::Int(i) => *i as f64,
1874 _ => return Err(format!("adam_step b2 must be number, got {}", args[6].type_name())),
1875 };
1876 let eps = match &args[7] {
1877 Value::Float(f) => *f,
1878 Value::Int(i) => *i as f64,
1879 _ => return Err(format!("adam_step eps must be number, got {}", args[7].type_name())),
1880 };
1881 let t = match &args[8] {
1882 Value::Int(i) => *i,
1883 Value::Float(f) => *f as i64,
1884 _ => return Err(format!("adam_step t must be integer, got {}", args[8].type_name())),
1885 };
1886 if t < 1 {
1887 return Err(format!("adam_step t must be >= 1, got {}", t));
1888 }
1889 let shape = w.shape();
1890 if g.shape() != shape || m.shape() != shape || v.shape() != shape {
1891 return Err(format!(
1892 "adam_step: w/g/m/v shapes must match; got w={:?} g={:?} m={:?} v={:?}",
1893 shape, g.shape(), m.shape(), v.shape()
1894 ));
1895 }
1896 let shape_vec: Vec<usize> = shape.to_vec();
1897 let w_flat = w.to_vec();
1898 let g_flat = g.to_vec();
1899 let m_flat = m.to_vec();
1900 let v_flat = v.to_vec();
1901 let n = w_flat.len();
1902 let bc1 = 1.0 - b1.powf(t as f64);
1904 let bc2 = 1.0 - b2.powf(t as f64);
1905 let inv_b1 = 1.0 - b1;
1906 let inv_b2 = 1.0 - b2;
1907 let mut new_w = Vec::with_capacity(n);
1908 let mut new_m = Vec::with_capacity(n);
1909 let mut new_v = Vec::with_capacity(n);
1910 for i in 0..n {
1911 let gi = g_flat[i];
1912 let new_mi = b1 * m_flat[i] + inv_b1 * gi;
1913 let new_vi = b2 * v_flat[i] + inv_b2 * gi * gi;
1914 let m_hat = new_mi / bc1;
1915 let v_hat = new_vi / bc2;
1916 let new_wi = w_flat[i] - lr * m_hat / (v_hat.sqrt() + eps);
1917 new_w.push(new_wi);
1918 new_m.push(new_mi);
1919 new_v.push(new_vi);
1920 }
1921 let new_w_t = Tensor::from_vec(new_w, &shape_vec)
1922 .map_err(|e| format!("adam_step: {}", e))?;
1923 let new_m_t = Tensor::from_vec(new_m, &shape_vec)
1924 .map_err(|e| format!("adam_step: {}", e))?;
1925 let new_v_t = Tensor::from_vec(new_v, &shape_vec)
1926 .map_err(|e| format!("adam_step: {}", e))?;
1927 Ok(Some(Value::Array(Rc::new(vec![
1928 Value::Tensor(new_w_t),
1929 Value::Tensor(new_m_t),
1930 Value::Tensor(new_v_t),
1931 ]))))
1932 }
1933 "dir_list" => {
1935 if args.len() != 1 { return Err("dir_list requires 1 argument (path)".into()); }
1936 match &args[0] {
1937 Value::String(path) => {
1938 let entries = std::fs::read_dir(path.as_str())
1939 .map_err(|e| format!("dir_list error: {}", e))?;
1940 let mut sorted = std::collections::BTreeSet::new();
1942 for entry in entries {
1943 let entry = entry.map_err(|e| format!("dir_list error: {}", e))?;
1944 let name = entry.file_name().to_string_lossy().to_string();
1945 sorted.insert(name);
1946 }
1947 let values: Vec<Value> = sorted
1948 .into_iter()
1949 .map(|s| Value::String(Rc::new(s)))
1950 .collect();
1951 Ok(Some(Value::Array(Rc::new(values))))
1952 }
1953 _ => Err(format!("dir_list requires String path, got {}", args[0].type_name())),
1954 }
1955 }
1956 "path_join" => {
1957 if args.len() != 2 { return Err("path_join requires 2 arguments (base, segment)".into()); }
1958 match (&args[0], &args[1]) {
1959 (Value::String(a), Value::String(b)) => {
1960 let joined = std::path::Path::new(a.as_str())
1961 .join(b.as_str())
1962 .to_string_lossy()
1963 .to_string();
1964 Ok(Some(Value::String(Rc::new(joined))))
1965 }
1966 _ => Err(format!(
1967 "path_join requires (String, String) arguments, got ({}, {})",
1968 args[0].type_name(), args[1].type_name()
1969 )),
1970 }
1971 }
1972
1973 "window_sum" | "window_mean" | "window_min" | "window_max" => {
1975 if args.len() != 2 {
1976 return Err(format!("{name} requires 2 arguments (array, window_size)"));
1977 }
1978 let data = value_to_f64_vec(&args[0])?;
1979 let ws = match &args[1] {
1980 Value::Int(i) => {
1981 if *i < 0 {
1982 return Err(format!("{name}: window_size must be non-negative, got {i}"));
1983 }
1984 *i as usize
1985 }
1986 _ => return Err(format!("{name} requires Int window_size, got {}", args[1].type_name())),
1987 };
1988 let result = match name {
1989 "window_sum" => crate::window::window_sum(&data, ws),
1990 "window_mean" => crate::window::window_mean(&data, ws),
1991 "window_min" => crate::window::window_min(&data, ws),
1992 "window_max" => crate::window::window_max(&data, ws),
1993 _ => unreachable!(),
1994 };
1995 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
1996 Ok(Some(Value::Array(Rc::new(values))))
1997 }
1998
1999 "mean" => {
2001 if args.len() != 1 { return Err("mean requires 1 argument".into()); }
2002 let data = value_to_f64_vec(&args[0])?;
2003 if data.is_empty() { return Err("mean: empty data".into()); }
2004 Ok(Some(Value::Float(cjc_repro::kahan_sum_f64(&data) / data.len() as f64)))
2005 }
2006 "variance" => {
2007 if args.len() != 1 { return Err("variance requires 1 argument".into()); }
2008 let data = value_to_f64_vec(&args[0])?;
2009 Ok(Some(Value::Float(crate::stats::variance(&data)?)))
2010 }
2011 "sd" => {
2012 if args.len() != 1 { return Err("sd requires 1 argument".into()); }
2013 let data = value_to_f64_vec(&args[0])?;
2014 Ok(Some(Value::Float(crate::stats::sd(&data)?)))
2015 }
2016 "se" => {
2017 if args.len() != 1 { return Err("se requires 1 argument".into()); }
2018 let data = value_to_f64_vec(&args[0])?;
2019 Ok(Some(Value::Float(crate::stats::se(&data)?)))
2020 }
2021 "median" => {
2022 if args.len() != 1 { return Err("median requires 1 argument".into()); }
2023 let data = value_to_f64_vec(&args[0])?;
2024 Ok(Some(Value::Float(crate::stats::median(&data)?)))
2025 }
2026 "quantile" => {
2027 if args.len() != 2 { return Err("quantile requires 2 arguments".into()); }
2028 let data = value_to_f64_vec(&args[0])?;
2029 let p = match &args[1] {
2030 Value::Float(f) => *f,
2031 Value::Int(i) => *i as f64,
2032 _ => return Err("quantile: p must be a number".into()),
2033 };
2034 Ok(Some(Value::Float(crate::stats::quantile(&data, p)?)))
2035 }
2036 "nth_element" => {
2038 if args.len() != 2 { return Err("nth_element requires 2 arguments: data, k".into()); }
2039 let data = value_to_f64_vec(&args[0])?;
2040 let k = value_to_usize(&args[1])?;
2041 Ok(Some(Value::Float(crate::stats::nth_element_copy(&data, k)?)))
2042 }
2043 "median_fast" => {
2044 if args.len() != 1 { return Err("median_fast requires 1 argument".into()); }
2045 let data = value_to_f64_vec(&args[0])?;
2046 Ok(Some(Value::Float(crate::stats::median_fast(&data)?)))
2047 }
2048 "quantile_fast" => {
2049 if args.len() != 2 { return Err("quantile_fast requires 2 arguments: data, p".into()); }
2050 let data = value_to_f64_vec(&args[0])?;
2051 let p = match &args[1] {
2052 Value::Float(f) => *f,
2053 Value::Int(i) => *i as f64,
2054 _ => return Err("quantile_fast: p must be a number".into()),
2055 };
2056 Ok(Some(Value::Float(crate::stats::quantile_fast(&data, p)?)))
2057 }
2058 "filter_mask" => {
2059 if args.len() != 2 { return Err("filter_mask requires 2 arguments: data, mask".into()); }
2060 let data = value_to_f64_vec(&args[0])?;
2061 let mask: Vec<bool> = match &args[1] {
2062 Value::Array(arr) => arr.iter().map(|v| match v {
2063 Value::Bool(b) => Ok(*b),
2064 Value::Int(i) => Ok(*i != 0),
2065 _ => Err("filter_mask: mask must be array of bools".to_string()),
2066 }).collect::<Result<Vec<_>, _>>()?,
2067 _ => return Err("filter_mask: mask must be an array".into()),
2068 };
2069 let result = crate::stats::filter_mask(&data, &mask)?;
2070 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2071 Ok(Some(Value::Array(Rc::new(values))))
2072 }
2073 "erf" => {
2074 if args.len() != 1 { return Err("erf requires 1 argument".into()); }
2075 let x = match &args[0] {
2076 Value::Float(f) => *f,
2077 Value::Int(i) => *i as f64,
2078 _ => return Err("erf requires a number".into()),
2079 };
2080 Ok(Some(Value::Float(crate::distributions::erf(x))))
2081 }
2082 "erfc" => {
2083 if args.len() != 1 { return Err("erfc requires 1 argument".into()); }
2084 let x = match &args[0] {
2085 Value::Float(f) => *f,
2086 Value::Int(i) => *i as f64,
2087 _ => return Err("erfc requires a number".into()),
2088 };
2089 Ok(Some(Value::Float(crate::distributions::erfc(x))))
2090 }
2091 "iqr" => {
2092 if args.len() != 1 { return Err("iqr requires 1 argument".into()); }
2093 let data = value_to_f64_vec(&args[0])?;
2094 Ok(Some(Value::Float(crate::stats::iqr(&data)?)))
2095 }
2096 "skewness" => {
2097 if args.len() != 1 { return Err("skewness requires 1 argument".into()); }
2098 let data = value_to_f64_vec(&args[0])?;
2099 Ok(Some(Value::Float(crate::stats::skewness(&data)?)))
2100 }
2101 "kurtosis" => {
2102 if args.len() != 1 { return Err("kurtosis requires 1 argument".into()); }
2103 let data = value_to_f64_vec(&args[0])?;
2104 Ok(Some(Value::Float(crate::stats::kurtosis(&data)?)))
2105 }
2106 "z_score" => {
2107 if args.len() != 1 { return Err("z_score requires 1 argument".into()); }
2108 let data = value_to_f64_vec(&args[0])?;
2109 let result = crate::stats::z_score(&data)?;
2110 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2111 Ok(Some(Value::Array(Rc::new(values))))
2112 }
2113 "standardize" => {
2114 if args.len() != 1 { return Err("standardize requires 1 argument".into()); }
2115 let data = value_to_f64_vec(&args[0])?;
2116 let result = crate::stats::standardize(&data)?;
2117 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2118 Ok(Some(Value::Array(Rc::new(values))))
2119 }
2120 "n_distinct" => {
2121 if args.len() != 1 { return Err("n_distinct requires 1 argument".into()); }
2122 let data = value_to_f64_vec(&args[0])?;
2123 Ok(Some(Value::Int(crate::stats::n_distinct(&data) as i64)))
2124 }
2125 "cor" => {
2127 if args.len() != 2 { return Err("cor requires 2 arguments".into()); }
2128 let x = value_to_f64_vec(&args[0])?;
2129 let y = value_to_f64_vec(&args[1])?;
2130 Ok(Some(Value::Float(crate::stats::cor(&x, &y)?)))
2131 }
2132 "cov" => {
2133 if args.len() != 2 { return Err("cov requires 2 arguments".into()); }
2134 let x = value_to_f64_vec(&args[0])?;
2135 let y = value_to_f64_vec(&args[1])?;
2136 Ok(Some(Value::Float(crate::stats::cov(&x, &y)?)))
2137 }
2138 "normal_cdf" => {
2140 if args.len() != 1 { return Err("normal_cdf requires 1 argument".into()); }
2141 let x = match &args[0] {
2142 Value::Float(f) => *f,
2143 Value::Int(i) => *i as f64,
2144 _ => return Err("normal_cdf requires a number".into()),
2145 };
2146 Ok(Some(Value::Float(crate::distributions::normal_cdf(x))))
2147 }
2148 "normal_pdf" => {
2149 if args.len() != 1 { return Err("normal_pdf requires 1 argument".into()); }
2150 let x = match &args[0] {
2151 Value::Float(f) => *f,
2152 Value::Int(i) => *i as f64,
2153 _ => return Err("normal_pdf requires a number".into()),
2154 };
2155 Ok(Some(Value::Float(crate::distributions::normal_pdf(x))))
2156 }
2157 "normal_ppf" => {
2158 if args.len() != 1 { return Err("normal_ppf requires 1 argument".into()); }
2159 let p = match &args[0] {
2160 Value::Float(f) => *f,
2161 Value::Int(i) => *i as f64,
2162 _ => return Err("normal_ppf requires a number".into()),
2163 };
2164 Ok(Some(Value::Float(crate::distributions::normal_ppf(p)?)))
2165 }
2166 "t_cdf" => {
2167 if args.len() != 2 { return Err("t_cdf requires 2 arguments (x, df)".into()); }
2168 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()) };
2169 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()) };
2170 Ok(Some(Value::Float(crate::distributions::t_cdf(x, df))))
2171 }
2172 "chi2_cdf" => {
2173 if args.len() != 2 { return Err("chi2_cdf requires 2 arguments (x, df)".into()); }
2174 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()) };
2175 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()) };
2176 Ok(Some(Value::Float(crate::distributions::chi2_cdf(x, df))))
2177 }
2178 "f_cdf" => {
2179 if args.len() != 3 { return Err("f_cdf requires 3 arguments (x, df1, df2)".into()); }
2180 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()) };
2181 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()) };
2182 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()) };
2183 Ok(Some(Value::Float(crate::distributions::f_cdf(x, df1, df2))))
2184 }
2185 "t_test" => {
2187 if args.len() != 2 { return Err("t_test requires 2 arguments (data, mu)".into()); }
2188 let data = value_to_f64_vec(&args[0])?;
2189 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()) };
2190 let r = crate::hypothesis::t_test(&data, mu)?;
2191 let mut fields = std::collections::BTreeMap::new();
2192 fields.insert("t_statistic".into(), Value::Float(r.t_statistic));
2193 fields.insert("p_value".into(), Value::Float(r.p_value));
2194 fields.insert("df".into(), Value::Float(r.df));
2195 fields.insert("mean".into(), Value::Float(r.mean));
2196 fields.insert("se".into(), Value::Float(r.se));
2197 Ok(Some(Value::Struct { name: "TTestResult".into(), fields }))
2198 }
2199 "t_test_two_sample" => {
2200 if args.len() != 2 { return Err("t_test_two_sample requires 2 arguments".into()); }
2201 let x = value_to_f64_vec(&args[0])?;
2202 let y = value_to_f64_vec(&args[1])?;
2203 let r = crate::hypothesis::t_test_two_sample(&x, &y)?;
2204 let mut fields = std::collections::BTreeMap::new();
2205 fields.insert("t_statistic".into(), Value::Float(r.t_statistic));
2206 fields.insert("p_value".into(), Value::Float(r.p_value));
2207 fields.insert("df".into(), Value::Float(r.df));
2208 Ok(Some(Value::Struct { name: "TTestResult".into(), fields }))
2209 }
2210 "chi_squared_test" => {
2211 if args.len() != 2 { return Err("chi_squared_test requires 2 arguments".into()); }
2212 let obs = value_to_f64_vec(&args[0])?;
2213 let exp = value_to_f64_vec(&args[1])?;
2214 let r = crate::hypothesis::chi_squared_test(&obs, &exp)?;
2215 let mut fields = std::collections::BTreeMap::new();
2216 fields.insert("chi2".into(), Value::Float(r.chi2));
2217 fields.insert("p_value".into(), Value::Float(r.p_value));
2218 fields.insert("df".into(), Value::Float(r.df));
2219 Ok(Some(Value::Struct { name: "ChiSquaredResult".into(), fields }))
2220 }
2221 "det" => {
2223 if args.len() != 1 { return Err("det requires 1 Tensor argument".into()); }
2224 let t = value_to_tensor(&args[0])?;
2225 Ok(Some(Value::Float(t.det().map_err(|e| format!("{e}"))?)))
2226 }
2227 "solve" => {
2228 if args.len() != 2 { return Err("solve requires 2 Tensor arguments".into()); }
2229 let a = value_to_tensor(&args[0])?;
2230 let b = value_to_tensor(&args[1])?;
2231 Ok(Some(Value::Tensor(a.solve(b).map_err(|e| format!("{e}"))?)))
2232 }
2233 "lstsq" => {
2234 if args.len() != 2 { return Err("lstsq requires 2 Tensor arguments".into()); }
2235 let a = value_to_tensor(&args[0])?;
2236 let b = value_to_tensor(&args[1])?;
2237 Ok(Some(Value::Tensor(a.lstsq(b).map_err(|e| format!("{e}"))?)))
2238 }
2239 "trace" => {
2240 if args.len() != 1 { return Err("trace requires 1 Tensor argument".into()); }
2241 let t = value_to_tensor(&args[0])?;
2242 Ok(Some(Value::Float(t.trace().map_err(|e| format!("{e}"))?)))
2243 }
2244 "norm_frobenius" => {
2245 if args.len() != 1 { return Err("norm_frobenius requires 1 Tensor argument".into()); }
2246 let t = value_to_tensor(&args[0])?;
2247 Ok(Some(Value::Float(t.norm_frobenius().map_err(|e| format!("{e}"))?)))
2248 }
2249 "eigh" => {
2250 if args.len() != 1 { return Err("eigh requires 1 Tensor argument".into()); }
2251 let t = value_to_tensor(&args[0])?;
2252 let (vals, vecs) = t.eigh().map_err(|e| format!("{e}"))?;
2253 let val_values: Vec<Value> = vals.into_iter().map(Value::Float).collect();
2254 Ok(Some(Value::Tuple(Rc::new(vec![
2255 Value::Array(Rc::new(val_values)),
2256 Value::Tensor(vecs),
2257 ]))))
2258 }
2259 "matrix_rank" => {
2260 if args.len() != 1 { return Err("matrix_rank requires 1 Tensor argument".into()); }
2261 let t = value_to_tensor(&args[0])?;
2262 Ok(Some(Value::Int(t.matrix_rank().map_err(|e| format!("{e}"))? as i64)))
2263 }
2264 "kron" => {
2265 if args.len() != 2 { return Err("kron requires 2 Tensor arguments".into()); }
2266 let a = value_to_tensor(&args[0])?;
2267 let b = value_to_tensor(&args[1])?;
2268 Ok(Some(Value::Tensor(a.kron(b).map_err(|e| format!("{e}"))?)))
2269 }
2270 "mse_loss" => {
2272 if args.len() != 2 { return Err("mse_loss requires 2 arguments".into()); }
2273 let pred = value_to_f64_vec(&args[0])?;
2274 let target = value_to_f64_vec(&args[1])?;
2275 Ok(Some(Value::Float(crate::ml::mse_loss(&pred, &target)?)))
2276 }
2277 "cross_entropy_loss" => {
2278 if args.len() != 2 { return Err("cross_entropy_loss requires 2 arguments".into()); }
2279 let pred = value_to_f64_vec(&args[0])?;
2280 let target = value_to_f64_vec(&args[1])?;
2281 Ok(Some(Value::Float(crate::ml::cross_entropy_loss(&pred, &target)?)))
2282 }
2283 "huber_loss" => {
2284 if args.len() != 3 { return Err("huber_loss requires 3 arguments".into()); }
2285 let pred = value_to_f64_vec(&args[0])?;
2286 let target = value_to_f64_vec(&args[1])?;
2287 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()) };
2288 Ok(Some(Value::Float(crate::ml::huber_loss(&pred, &target, delta)?)))
2289 }
2290 "cumsum" => {
2292 if args.len() != 1 { return Err("cumsum requires 1 argument".into()); }
2293 let data = value_to_f64_vec(&args[0])?;
2294 let result = crate::stats::cumsum(&data);
2295 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2296 Ok(Some(Value::Array(Rc::new(values))))
2297 }
2298 "cumprod" => {
2299 if args.len() != 1 { return Err("cumprod requires 1 argument".into()); }
2300 let data = value_to_f64_vec(&args[0])?;
2301 let result = crate::stats::cumprod(&data);
2302 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2303 Ok(Some(Value::Array(Rc::new(values))))
2304 }
2305 "cummax" => {
2306 if args.len() != 1 { return Err("cummax requires 1 argument".into()); }
2307 let data = value_to_f64_vec(&args[0])?;
2308 let result = crate::stats::cummax(&data);
2309 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2310 Ok(Some(Value::Array(Rc::new(values))))
2311 }
2312 "cummin" => {
2313 if args.len() != 1 { return Err("cummin requires 1 argument".into()); }
2314 let data = value_to_f64_vec(&args[0])?;
2315 let result = crate::stats::cummin(&data);
2316 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2317 Ok(Some(Value::Array(Rc::new(values))))
2318 }
2319 "lag" => {
2320 if args.len() != 2 { return Err("lag requires 2 arguments".into()); }
2321 let data = value_to_f64_vec(&args[0])?;
2322 let n = value_to_usize(&args[1])?;
2323 let result = crate::stats::lag(&data, n);
2324 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2325 Ok(Some(Value::Array(Rc::new(values))))
2326 }
2327 "lead" => {
2328 if args.len() != 2 { return Err("lead requires 2 arguments".into()); }
2329 let data = value_to_f64_vec(&args[0])?;
2330 let n = value_to_usize(&args[1])?;
2331 let result = crate::stats::lead(&data, n);
2332 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2333 Ok(Some(Value::Array(Rc::new(values))))
2334 }
2335 "rank" => {
2336 if args.len() != 1 { return Err("rank requires 1 argument".into()); }
2337 let data = value_to_f64_vec(&args[0])?;
2338 let result = crate::stats::rank(&data);
2339 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2340 Ok(Some(Value::Array(Rc::new(values))))
2341 }
2342 "dense_rank" => {
2343 if args.len() != 1 { return Err("dense_rank requires 1 argument".into()); }
2344 let data = value_to_f64_vec(&args[0])?;
2345 let result = crate::stats::dense_rank(&data);
2346 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2347 Ok(Some(Value::Array(Rc::new(values))))
2348 }
2349 "histogram" => {
2350 if args.len() != 2 { return Err("histogram requires 2 arguments".into()); }
2351 let data = value_to_f64_vec(&args[0])?;
2352 let n_bins = value_to_usize(&args[1])?;
2353 let (edges, counts) = crate::stats::histogram(&data, n_bins)?;
2354 let edge_values: Vec<Value> = edges.into_iter().map(Value::Float).collect();
2355 let count_values: Vec<Value> = counts.into_iter().map(|c| Value::Int(c as i64)).collect();
2356 Ok(Some(Value::Tuple(Rc::new(vec![
2357 Value::Array(Rc::new(edge_values)),
2358 Value::Array(Rc::new(count_values)),
2359 ]))))
2360 }
2361 "sample_variance" => {
2363 if args.len() != 1 { return Err("sample_variance requires 1 argument".into()); }
2364 let data = value_to_f64_vec(&args[0])?;
2365 Ok(Some(Value::Float(crate::stats::sample_variance(&data)?)))
2366 }
2367 "sample_sd" => {
2368 if args.len() != 1 { return Err("sample_sd requires 1 argument".into()); }
2369 let data = value_to_f64_vec(&args[0])?;
2370 Ok(Some(Value::Float(crate::stats::sample_sd(&data)?)))
2371 }
2372 "sample_cov" => {
2373 if args.len() != 2 { return Err("sample_cov requires 2 arguments".into()); }
2374 let x = value_to_f64_vec(&args[0])?;
2375 let y = value_to_f64_vec(&args[1])?;
2376 Ok(Some(Value::Float(crate::stats::sample_cov(&x, &y)?)))
2377 }
2378 "row_number" => {
2379 if args.len() != 1 { return Err("row_number requires 1 argument".into()); }
2380 let data = value_to_f64_vec(&args[0])?;
2381 let result = crate::stats::row_number(&data);
2382 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2383 Ok(Some(Value::Array(Rc::new(values))))
2384 }
2385 "t_ppf" => {
2387 if args.len() != 2 { return Err("t_ppf requires 2 arguments (p, df)".into()); }
2388 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()) };
2389 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()) };
2390 Ok(Some(Value::Float(crate::distributions::t_ppf(p, df)?)))
2391 }
2392 "chi2_ppf" => {
2393 if args.len() != 2 { return Err("chi2_ppf requires 2 arguments (p, df)".into()); }
2394 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()) };
2395 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()) };
2396 Ok(Some(Value::Float(crate::distributions::chi2_ppf(p, df)?)))
2397 }
2398 "f_ppf" => {
2399 if args.len() != 3 { return Err("f_ppf requires 3 arguments (p, df1, df2)".into()); }
2400 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()) };
2401 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()) };
2402 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()) };
2403 Ok(Some(Value::Float(crate::distributions::f_ppf(p, df1, df2)?)))
2404 }
2405 "binomial_pmf" => {
2407 if args.len() != 3 { return Err("binomial_pmf requires 3 arguments (k, n, p)".into()); }
2408 let k = value_to_usize(&args[0])? as u64;
2409 let n = value_to_usize(&args[1])? as u64;
2410 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()) };
2411 Ok(Some(Value::Float(crate::distributions::binomial_pmf(k, n, p))))
2412 }
2413 "binomial_cdf" => {
2414 if args.len() != 3 { return Err("binomial_cdf requires 3 arguments (k, n, p)".into()); }
2415 let k = value_to_usize(&args[0])? as u64;
2416 let n = value_to_usize(&args[1])? as u64;
2417 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()) };
2418 Ok(Some(Value::Float(crate::distributions::binomial_cdf(k, n, p))))
2419 }
2420 "poisson_pmf" => {
2421 if args.len() != 2 { return Err("poisson_pmf requires 2 arguments (k, lambda)".into()); }
2422 let k = value_to_usize(&args[0])? as u64;
2423 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()) };
2424 Ok(Some(Value::Float(crate::distributions::poisson_pmf(k, lambda))))
2425 }
2426 "poisson_cdf" => {
2427 if args.len() != 2 { return Err("poisson_cdf requires 2 arguments (k, lambda)".into()); }
2428 let k = value_to_usize(&args[0])? as u64;
2429 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()) };
2430 Ok(Some(Value::Float(crate::distributions::poisson_cdf(k, lambda))))
2431 }
2432 "t_test_paired" => {
2434 if args.len() != 2 { return Err("t_test_paired requires 2 arguments".into()); }
2435 let x = value_to_f64_vec(&args[0])?;
2436 let y = value_to_f64_vec(&args[1])?;
2437 let r = crate::hypothesis::t_test_paired(&x, &y)?;
2438 let mut fields = std::collections::BTreeMap::new();
2439 fields.insert("t_statistic".into(), Value::Float(r.t_statistic));
2440 fields.insert("p_value".into(), Value::Float(r.p_value));
2441 fields.insert("df".into(), Value::Float(r.df));
2442 Ok(Some(Value::Struct { name: "TTestResult".into(), fields }))
2443 }
2444 "anova_oneway" => {
2445 if args.len() < 2 { return Err("anova_oneway requires at least 2 group arguments".into()); }
2446 let mut groups = Vec::new();
2447 let mut group_vecs = Vec::new();
2448 for a in args.iter() {
2449 group_vecs.push(value_to_f64_vec(a)?);
2450 }
2451 for gv in &group_vecs {
2452 groups.push(gv.as_slice());
2453 }
2454 let r = crate::hypothesis::anova_oneway(&groups)?;
2455 let mut fields = std::collections::BTreeMap::new();
2456 fields.insert("f_statistic".into(), Value::Float(r.f_statistic));
2457 fields.insert("p_value".into(), Value::Float(r.p_value));
2458 fields.insert("df_between".into(), Value::Float(r.df_between));
2459 fields.insert("df_within".into(), Value::Float(r.df_within));
2460 fields.insert("ss_between".into(), Value::Float(r.ss_between));
2461 fields.insert("ss_within".into(), Value::Float(r.ss_within));
2462 Ok(Some(Value::Struct { name: "AnovaResult".into(), fields }))
2463 }
2464 "f_test" => {
2465 if args.len() != 2 { return Err("f_test requires 2 arguments".into()); }
2466 let x = value_to_f64_vec(&args[0])?;
2467 let y = value_to_f64_vec(&args[1])?;
2468 let (f_stat, p_val) = crate::hypothesis::f_test(&x, &y)?;
2469 let mut fields = std::collections::BTreeMap::new();
2470 fields.insert("f_statistic".into(), Value::Float(f_stat));
2471 fields.insert("p_value".into(), Value::Float(p_val));
2472 Ok(Some(Value::Struct { name: "FTestResult".into(), fields }))
2473 }
2474 "lm" => {
2475 if args.len() != 4 { return Err("lm requires 4 arguments (X_flat, y, n, p)".into()); }
2477 let x_flat = value_to_f64_vec(&args[0])?;
2478 let y = value_to_f64_vec(&args[1])?;
2479 let n = value_to_usize(&args[2])?;
2480 let p = value_to_usize(&args[3])?;
2481 let r = crate::hypothesis::lm(&x_flat, &y, n, p)?;
2482 let coef_values: Vec<Value> = r.coefficients.into_iter().map(Value::Float).collect();
2483 let se_values: Vec<Value> = r.std_errors.into_iter().map(Value::Float).collect();
2484 let t_values: Vec<Value> = r.t_values.into_iter().map(Value::Float).collect();
2485 let p_values: Vec<Value> = r.p_values.into_iter().map(Value::Float).collect();
2486 let resid_values: Vec<Value> = r.residuals.into_iter().map(Value::Float).collect();
2487 let mut fields = std::collections::BTreeMap::new();
2488 fields.insert("coefficients".into(), Value::Array(Rc::new(coef_values)));
2489 fields.insert("std_errors".into(), Value::Array(Rc::new(se_values)));
2490 fields.insert("t_values".into(), Value::Array(Rc::new(t_values)));
2491 fields.insert("p_values".into(), Value::Array(Rc::new(p_values)));
2492 fields.insert("r_squared".into(), Value::Float(r.r_squared));
2493 fields.insert("adj_r_squared".into(), Value::Float(r.adj_r_squared));
2494 fields.insert("residuals".into(), Value::Array(Rc::new(resid_values)));
2495 fields.insert("f_statistic".into(), Value::Float(r.f_statistic));
2496 Ok(Some(Value::Struct { name: "LmResult".into(), fields }))
2497 }
2498 "binary_cross_entropy" => {
2500 if args.len() != 2 { return Err("binary_cross_entropy requires 2 arguments".into()); }
2501 let pred = value_to_f64_vec(&args[0])?;
2502 let target = value_to_f64_vec(&args[1])?;
2503 Ok(Some(Value::Float(crate::ml::binary_cross_entropy(&pred, &target)?)))
2504 }
2505 "hinge_loss" => {
2506 if args.len() != 2 { return Err("hinge_loss requires 2 arguments".into()); }
2507 let pred = value_to_f64_vec(&args[0])?;
2508 let target = value_to_f64_vec(&args[1])?;
2509 Ok(Some(Value::Float(crate::ml::hinge_loss(&pred, &target)?)))
2510 }
2511 "confusion_matrix" => {
2512 if args.len() != 2 { return Err("confusion_matrix requires 2 arguments".into()); }
2513 let pred = value_to_f64_vec(&args[0])?;
2514 let actual = value_to_f64_vec(&args[1])?;
2515 let pred_bool: Vec<bool> = pred.iter().map(|&x| x > 0.5).collect();
2516 let actual_bool: Vec<bool> = actual.iter().map(|&x| x > 0.5).collect();
2517 let cm = crate::ml::confusion_matrix(&pred_bool, &actual_bool);
2518 let mut fields = std::collections::BTreeMap::new();
2519 fields.insert("tp".into(), Value::Int(cm.tp as i64));
2520 fields.insert("fp".into(), Value::Int(cm.fp as i64));
2521 fields.insert("tn".into(), Value::Int(cm.tn as i64));
2522 fields.insert("fn_count".into(), Value::Int(cm.fn_count as i64));
2523 fields.insert("precision".into(), Value::Float(crate::ml::precision(&cm)));
2524 fields.insert("recall".into(), Value::Float(crate::ml::recall(&cm)));
2525 fields.insert("f1_score".into(), Value::Float(crate::ml::f1_score(&cm)));
2526 fields.insert("accuracy".into(), Value::Float(crate::ml::accuracy(&cm)));
2527 Ok(Some(Value::Struct { name: "ConfusionMatrix".into(), fields }))
2528 }
2529 "auc_roc" => {
2530 if args.len() != 2 { return Err("auc_roc requires 2 arguments (scores, labels)".into()); }
2531 let scores = value_to_f64_vec(&args[0])?;
2532 let labels_f = value_to_f64_vec(&args[1])?;
2533 let labels: Vec<bool> = labels_f.iter().map(|&x| x > 0.5).collect();
2534 Ok(Some(Value::Float(crate::ml::auc_roc(&scores, &labels)?)))
2535 }
2536 "embedding" => {
2538 if args.len() != 2 { return Err("embedding requires 2 arguments (weight, indices)".into()); }
2539 let weight = value_to_tensor(&args[0])?;
2540 let indices: Vec<i64> = match &args[1] {
2541 Value::Array(arr) => arr.iter().map(|v| match v {
2542 Value::Int(i) => Ok(*i),
2543 _ => Err("embedding: indices must be integers".to_string()),
2544 }).collect::<Result<Vec<_>, _>>()?,
2545 Value::Tensor(t) => t.to_vec().iter().map(|f| Ok::<i64, String>(*f as i64)).collect::<Result<Vec<_>, _>>()?,
2546 _ => return Err("embedding: indices must be an array or tensor".into()),
2547 };
2548 let result = crate::ml::embedding(weight, &indices)?;
2549 Ok(Some(Value::Tensor(result)))
2550 }
2551 "batch_indices" => {
2553 if args.len() != 3 { return Err("batch_indices requires 3 arguments (dataset_size, batch_size, seed)".into()); }
2554 let ds = match &args[0] { Value::Int(i) => *i as usize, _ => return Err("batch_indices: dataset_size must be int".into()) };
2555 let bs = match &args[1] { Value::Int(i) => *i as usize, _ => return Err("batch_indices: batch_size must be int".into()) };
2556 let seed = match &args[2] { Value::Int(i) => *i as u64, _ => return Err("batch_indices: seed must be int".into()) };
2557 let result = crate::ml::batch_indices(ds, bs, seed);
2558 let arr: Vec<Value> = result.into_iter().map(|(s, e)| {
2559 Value::Array(Rc::new(vec![Value::Int(s as i64), Value::Int(e as i64)]))
2560 }).collect();
2561 Ok(Some(Value::Array(Rc::new(arr))))
2562 }
2563 "sigmoid" => {
2565 if args.len() != 1 { return Err("sigmoid requires 1 argument".into()); }
2566 let t = value_to_tensor(&args[0])?;
2567 Ok(Some(Value::Tensor(t.sigmoid())))
2568 }
2569 "tanh_activation" => {
2570 if args.len() != 1 { return Err("tanh_activation requires 1 argument".into()); }
2571 let t = value_to_tensor(&args[0])?;
2572 Ok(Some(Value::Tensor(t.tanh_activation())))
2573 }
2574 "leaky_relu" => {
2575 if args.len() != 2 { return Err("leaky_relu requires 2 arguments (tensor, alpha)".into()); }
2576 let t = value_to_tensor(&args[0])?;
2577 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()) };
2578 Ok(Some(Value::Tensor(t.leaky_relu(alpha))))
2579 }
2580 "silu" => {
2581 if args.len() != 1 { return Err("silu requires 1 argument".into()); }
2582 let t = value_to_tensor(&args[0])?;
2583 Ok(Some(Value::Tensor(t.silu())))
2584 }
2585 "mish" => {
2586 if args.len() != 1 { return Err("mish requires 1 argument".into()); }
2587 let t = value_to_tensor(&args[0])?;
2588 Ok(Some(Value::Tensor(t.mish())))
2589 }
2590 "relu" => {
2592 if args.len() != 1 { return Err("relu requires 1 argument".into()); }
2593 match &args[0] {
2594 Value::Float(f) => Ok(Some(Value::Float(f.max(0.0)))),
2595 Value::Int(i) => Ok(Some(Value::Int((*i).max(0)))),
2596 Value::Tensor(t) => Ok(Some(Value::Tensor(t.relu()))),
2597 _ => Err(format!("relu requires a number or Tensor, got {}", args[0].type_name())),
2598 }
2599 }
2600 "reshape" => {
2602 if args.len() != 2 { return Err("reshape requires 2 arguments (tensor, shape)".into()); }
2603 let t = value_to_tensor(&args[0])?;
2604 let shape = value_to_shape(&args[1])?;
2605 let result = t.reshape(&shape).map_err(|e| format!("{e}"))?;
2606 Ok(Some(Value::Tensor(result)))
2607 }
2608 "tensor_slice" => {
2610 if args.len() != 3 { return Err("tensor_slice requires 3 arguments (tensor, starts, ends)".into()); }
2611 let t = value_to_tensor(&args[0])?;
2612 let starts = value_to_usize_vec(&args[1])?;
2613 let ends = value_to_usize_vec(&args[2])?;
2614 if starts.len() != ends.len() {
2615 return Err("tensor_slice: starts and ends must have same length".into());
2616 }
2617 let ranges: Vec<(usize, usize)> = starts.into_iter().zip(ends).collect();
2618 let result = t.slice(&ranges).map_err(|e| format!("{e}"))?;
2619 Ok(Some(Value::Tensor(result)))
2620 }
2621 "slice" => {
2623 if args.len() != 4 { return Err("slice requires 4 arguments (tensor, dim, start, end)".into()); }
2624 let t = value_to_tensor(&args[0])?;
2625 let dim = match &args[1] { Value::Int(i) => *i as usize, _ => return Err("slice: dim must be an integer".into()) };
2626 let start = match &args[2] { Value::Int(i) => *i as usize, _ => return Err("slice: start must be an integer".into()) };
2627 let end = match &args[3] { Value::Int(i) => *i as usize, _ => return Err("slice: end must be an integer".into()) };
2628 if dim >= t.ndim() {
2629 return Err(format!("slice: dim {} out of bounds for tensor with {} dimensions", dim, t.ndim()));
2630 }
2631 let mut ranges: Vec<(usize, usize)> = t.shape().iter().map(|&s| (0, s)).collect();
2632 ranges[dim] = (start, end);
2633 let result = t.slice(&ranges).map_err(|e| format!("{e}"))?;
2634 Ok(Some(Value::Tensor(result)))
2635 }
2636 "argmax" => {
2637 if args.len() != 1 { return Err("argmax requires 1 argument".into()); }
2638 let t = value_to_tensor(&args[0])?;
2639 Ok(Some(Value::Int(t.argmax() as i64)))
2640 }
2641 "argmin" => {
2642 if args.len() != 1 { return Err("argmin requires 1 argument".into()); }
2643 let t = value_to_tensor(&args[0])?;
2644 Ok(Some(Value::Int(t.argmin() as i64)))
2645 }
2646 "clamp" => {
2647 if args.len() != 3 { return Err("clamp requires 3 arguments (tensor, min, max)".into()); }
2648 let t = value_to_tensor(&args[0])?;
2649 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()) };
2650 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()) };
2651 Ok(Some(Value::Tensor(t.clamp(min_v, max_v))))
2652 }
2653 "one_hot" => {
2654 if args.len() != 2 { return Err("one_hot requires 2 arguments (indices, depth)".into()); }
2655 let indices = value_to_usize_vec(&args[0])?;
2656 let depth = value_to_usize(&args[1])?;
2657 Ok(Some(Value::Tensor(Tensor::one_hot(&indices, depth).map_err(|e| format!("{e}"))?)))
2658 }
2659 "rfft" => {
2661 if args.len() != 1 { return Err("rfft requires 1 argument".into()); }
2662 let data = value_to_f64_vec(&args[0])?;
2663 let result = crate::fft::rfft(&data);
2664 let pairs: Vec<Value> = result.iter().map(|&(re, im)| {
2665 Value::Tuple(Rc::new(vec![Value::Float(re), Value::Float(im)]))
2666 }).collect();
2667 Ok(Some(Value::Array(Rc::new(pairs))))
2668 }
2669 "psd" => {
2670 if args.len() != 1 { return Err("psd requires 1 argument".into()); }
2671 let data = value_to_f64_vec(&args[0])?;
2672 let result = crate::fft::psd(&data);
2673 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2674 Ok(Some(Value::Array(Rc::new(values))))
2675 }
2676
2677 "weighted_mean" => {
2679 if args.len() != 2 { return Err("weighted_mean requires 2 arguments".into()); }
2680 let data = value_to_f64_vec(&args[0])?;
2681 let weights = value_to_f64_vec(&args[1])?;
2682 Ok(Some(Value::Float(crate::stats::weighted_mean(&data, &weights)?)))
2683 }
2684 "weighted_var" => {
2685 if args.len() != 2 { return Err("weighted_var requires 2 arguments".into()); }
2686 let data = value_to_f64_vec(&args[0])?;
2687 let weights = value_to_f64_vec(&args[1])?;
2688 Ok(Some(Value::Float(crate::stats::weighted_var(&data, &weights)?)))
2689 }
2690 "trimmed_mean" => {
2691 if args.len() != 2 { return Err("trimmed_mean requires 2 arguments".into()); }
2692 let data = value_to_f64_vec(&args[0])?;
2693 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()) };
2694 Ok(Some(Value::Float(crate::stats::trimmed_mean(&data, prop)?)))
2695 }
2696 "winsorize" => {
2697 if args.len() != 2 { return Err("winsorize requires 2 arguments".into()); }
2698 let data = value_to_f64_vec(&args[0])?;
2699 let prop = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("winsorize: proportion must be a number".into()) };
2700 let result = crate::stats::winsorize(&data, prop)?;
2701 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2702 Ok(Some(Value::Array(Rc::new(values))))
2703 }
2704 "mad" => {
2705 if args.len() != 1 { return Err("mad requires 1 argument".into()); }
2706 let data = value_to_f64_vec(&args[0])?;
2707 Ok(Some(Value::Float(crate::stats::mad(&data)?)))
2708 }
2709 "mode" => {
2710 if args.len() != 1 { return Err("mode requires 1 argument".into()); }
2711 let data = value_to_f64_vec(&args[0])?;
2712 Ok(Some(Value::Float(crate::stats::mode(&data)?)))
2713 }
2714 "percentile_rank" => {
2715 if args.len() != 2 { return Err("percentile_rank requires 2 arguments".into()); }
2716 let data = value_to_f64_vec(&args[0])?;
2717 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()) };
2718 Ok(Some(Value::Float(crate::stats::percentile_rank(&data, value)?)))
2719 }
2720
2721 "cat" => {
2723 if args.len() != 2 { return Err("cat requires 2 arguments (array of tensors, axis)".into()); }
2724 let tensors_arr = match &args[0] {
2725 Value::Array(arr) => arr.iter().map(|v| match v {
2726 Value::Tensor(t) => Ok(t),
2727 _ => Err("cat: first argument must be array of tensors".to_string()),
2728 }).collect::<Result<Vec<&Tensor>, String>>()?,
2729 _ => return Err("cat: first argument must be array of tensors".into()),
2730 };
2731 let axis = value_to_usize(&args[1])?;
2732 let refs: Vec<&Tensor> = tensors_arr;
2733 Ok(Some(Value::Tensor(Tensor::cat(&refs, axis).map_err(|e| format!("{e}"))?)))
2734 }
2735 "stack" => {
2736 if args.len() != 2 { return Err("stack requires 2 arguments (array of tensors, axis)".into()); }
2737 let tensors_arr = match &args[0] {
2738 Value::Array(arr) => arr.iter().map(|v| match v {
2739 Value::Tensor(t) => Ok(t),
2740 _ => Err("stack: first argument must be array of tensors".to_string()),
2741 }).collect::<Result<Vec<&Tensor>, String>>()?,
2742 _ => return Err("stack: first argument must be array of tensors".into()),
2743 };
2744 let axis = value_to_usize(&args[1])?;
2745 Ok(Some(Value::Tensor(Tensor::stack(&tensors_arr, axis).map_err(|e| format!("{e}"))?)))
2746 }
2747 "topk" => {
2748 if args.len() != 2 { return Err("topk requires 2 arguments (tensor, k)".into()); }
2749 let t = value_to_tensor(&args[0])?;
2750 let k = value_to_usize(&args[1])?;
2751 let (vals, idxs) = t.topk(k).map_err(|e| format!("{e}"))?;
2752 let idx_values: Vec<Value> = idxs.into_iter().map(|i| Value::Int(i as i64)).collect();
2753 Ok(Some(Value::Tuple(Rc::new(vec![Value::Tensor(vals), Value::Array(Rc::new(idx_values))]))))
2754 }
2755 "batch_norm" => {
2756 if args.len() != 6 { return Err("batch_norm requires 6 arguments".into()); }
2757 let x = value_to_f64_vec(&args[0])?;
2758 let mean = value_to_f64_vec(&args[1])?;
2759 let var = value_to_f64_vec(&args[2])?;
2760 let gamma = value_to_f64_vec(&args[3])?;
2761 let beta = value_to_f64_vec(&args[4])?;
2762 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()) };
2763 let result = crate::ml::batch_norm(&x, &mean, &var, &gamma, &beta, eps)?;
2764 let values: Vec<Value> = result.into_iter().map(Value::Float).collect();
2765 Ok(Some(Value::Array(Rc::new(values))))
2766 }
2767 "dropout_mask" => {
2768 if args.len() != 3 { return Err("dropout_mask requires 3 arguments (n, prob, seed)".into()); }
2769 let n = value_to_usize(&args[0])?;
2770 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()) };
2771 let seed = match &args[2] { Value::Int(i) => *i as u64, _ => return Err("dropout_mask: seed must be an integer".into()) };
2772 let mask = crate::ml::dropout_mask(n, prob, seed);
2773 let values: Vec<Value> = mask.into_iter().map(Value::Float).collect();
2774 Ok(Some(Value::Array(Rc::new(values))))
2775 }
2776 "lr_step_decay" => {
2777 if args.len() != 4 { return Err("lr_step_decay requires 4 arguments".into()); }
2778 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()) };
2779 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()) };
2780 let epoch = value_to_usize(&args[2])?;
2781 let step = value_to_usize(&args[3])?;
2782 Ok(Some(Value::Float(crate::ml::lr_step_decay(lr, rate, epoch, step))))
2783 }
2784 "lr_cosine" => {
2785 if args.len() != 4 { return Err("lr_cosine requires 4 arguments".into()); }
2786 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()) };
2787 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()) };
2788 let epoch = value_to_usize(&args[2])?;
2789 let total = value_to_usize(&args[3])?;
2790 Ok(Some(Value::Float(crate::ml::lr_cosine(max_lr, min_lr, epoch, total))))
2791 }
2792 "lr_linear_warmup" => {
2793 if args.len() != 3 { return Err("lr_linear_warmup requires 3 arguments".into()); }
2794 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()) };
2795 let epoch = value_to_usize(&args[1])?;
2796 let warmup = value_to_usize(&args[2])?;
2797 Ok(Some(Value::Float(crate::ml::lr_linear_warmup(lr, epoch, warmup))))
2798 }
2799 "l1_penalty" => {
2800 if args.len() != 2 { return Err("l1_penalty requires 2 arguments".into()); }
2801 let params = value_to_f64_vec(&args[0])?;
2802 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()) };
2803 Ok(Some(Value::Float(crate::ml::l1_penalty(¶ms, lambda))))
2804 }
2805 "l2_penalty" => {
2806 if args.len() != 2 { return Err("l2_penalty requires 2 arguments".into()); }
2807 let params = value_to_f64_vec(&args[0])?;
2808 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()) };
2809 Ok(Some(Value::Float(crate::ml::l2_penalty(¶ms, lambda))))
2810 }
2811
2812 "cond" => {
2814 if args.len() != 1 { return Err("cond requires 1 Tensor argument".into()); }
2815 let t = value_to_tensor(&args[0])?;
2816 Ok(Some(Value::Float(t.cond().map_err(|e| format!("{e}"))?)))
2817 }
2818 "norm_1" => {
2819 if args.len() != 1 { return Err("norm_1 requires 1 Tensor argument".into()); }
2820 let t = value_to_tensor(&args[0])?;
2821 Ok(Some(Value::Float(t.norm_1().map_err(|e| format!("{e}"))?)))
2822 }
2823 "norm_inf" => {
2824 if args.len() != 1 { return Err("norm_inf requires 1 Tensor argument".into()); }
2825 let t = value_to_tensor(&args[0])?;
2826 Ok(Some(Value::Float(t.norm_inf().map_err(|e| format!("{e}"))?)))
2827 }
2828 "schur" => {
2829 if args.len() != 1 { return Err("schur requires 1 Tensor argument".into()); }
2830 let t = value_to_tensor(&args[0])?;
2831 let (q, t_mat) = t.schur().map_err(|e| format!("{e}"))?;
2832 Ok(Some(Value::Tuple(Rc::new(vec![Value::Tensor(q), Value::Tensor(t_mat)]))))
2833 }
2834 "matrix_exp" => {
2835 if args.len() != 1 { return Err("matrix_exp requires 1 Tensor argument".into()); }
2836 let t = value_to_tensor(&args[0])?;
2837 Ok(Some(Value::Tensor(t.matrix_exp().map_err(|e| format!("{e}"))?)))
2838 }
2839
2840 "spearman_cor" => {
2842 if args.len() != 2 { return Err("spearman_cor requires 2 arguments".into()); }
2843 let x = value_to_f64_vec(&args[0])?;
2844 let y = value_to_f64_vec(&args[1])?;
2845 Ok(Some(Value::Float(crate::stats::spearman_cor(&x, &y)?)))
2846 }
2847 "kendall_cor" => {
2848 if args.len() != 2 { return Err("kendall_cor requires 2 arguments".into()); }
2849 let x = value_to_f64_vec(&args[0])?;
2850 let y = value_to_f64_vec(&args[1])?;
2851 Ok(Some(Value::Float(crate::stats::kendall_cor(&x, &y)?)))
2852 }
2853 "partial_cor" => {
2854 if args.len() != 3 { return Err("partial_cor requires 3 arguments".into()); }
2855 let x = value_to_f64_vec(&args[0])?;
2856 let y = value_to_f64_vec(&args[1])?;
2857 let z = value_to_f64_vec(&args[2])?;
2858 Ok(Some(Value::Float(crate::stats::partial_cor(&x, &y, &z)?)))
2859 }
2860 "cor_ci" => {
2861 if args.len() != 3 { return Err("cor_ci requires 3 arguments".into()); }
2862 let x = value_to_f64_vec(&args[0])?;
2863 let y = value_to_f64_vec(&args[1])?;
2864 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()) };
2865 let (lo, hi) = crate::stats::cor_ci(&x, &y, alpha)?;
2866 Ok(Some(Value::Tuple(Rc::new(vec![Value::Float(lo), Value::Float(hi)]))))
2867 }
2868
2869 "hann" => {
2871 if args.len() != 1 { return Err("hann requires 1 argument (n)".into()); }
2872 let n = match &args[0] { Value::Int(i) => *i as usize, _ => return Err("hann: n must be an integer".into()) };
2873 let w = crate::fft::hann_window(n);
2874 Ok(Some(Value::Array(Rc::new(w.into_iter().map(Value::Float).collect()))))
2875 }
2876 "hamming" => {
2877 if args.len() != 1 { return Err("hamming requires 1 argument (n)".into()); }
2878 let n = match &args[0] { Value::Int(i) => *i as usize, _ => return Err("hamming: n must be an integer".into()) };
2879 let w = crate::fft::hamming_window(n);
2880 Ok(Some(Value::Array(Rc::new(w.into_iter().map(Value::Float).collect()))))
2881 }
2882 "blackman" => {
2883 if args.len() != 1 { return Err("blackman requires 1 argument (n)".into()); }
2884 let n = match &args[0] { Value::Int(i) => *i as usize, _ => return Err("blackman: n must be an integer".into()) };
2885 let w = crate::fft::blackman_window(n);
2886 Ok(Some(Value::Array(Rc::new(w.into_iter().map(Value::Float).collect()))))
2887 }
2888 "fft_arbitrary" => {
2889 if args.len() != 1 { return Err("fft_arbitrary requires 1 argument (complex array)".into()); }
2890 let data = value_to_complex_vec(&args[0])?;
2891 let result = crate::fft::fft_arbitrary(&data);
2892 let pairs: Vec<Value> = result.iter().map(|&(re, im)| {
2893 Value::Tuple(Rc::new(vec![Value::Float(re), Value::Float(im)]))
2894 }).collect();
2895 Ok(Some(Value::Array(Rc::new(pairs))))
2896 }
2897 "fft_2d" => {
2898 if args.len() != 3 { return Err("fft_2d requires 3 arguments (data, rows, cols)".into()); }
2899 let data = value_to_complex_vec(&args[0])?;
2900 let rows = match &args[1] { Value::Int(i) => *i as usize, _ => return Err("fft_2d: rows must be an integer".into()) };
2901 let cols = match &args[2] { Value::Int(i) => *i as usize, _ => return Err("fft_2d: cols must be an integer".into()) };
2902 let result = crate::fft::fft_2d(&data, rows, cols)?;
2903 let pairs: Vec<Value> = result.iter().map(|&(re, im)| {
2904 Value::Tuple(Rc::new(vec![Value::Float(re), Value::Float(im)]))
2905 }).collect();
2906 Ok(Some(Value::Array(Rc::new(pairs))))
2907 }
2908 "ifft_2d" => {
2909 if args.len() != 3 { return Err("ifft_2d requires 3 arguments (data, rows, cols)".into()); }
2910 let data = value_to_complex_vec(&args[0])?;
2911 let rows = match &args[1] { Value::Int(i) => *i as usize, _ => return Err("ifft_2d: rows must be an integer".into()) };
2912 let cols = match &args[2] { Value::Int(i) => *i as usize, _ => return Err("ifft_2d: cols must be an integer".into()) };
2913 let result = crate::fft::ifft_2d(&data, rows, cols)?;
2914 let pairs: Vec<Value> = result.iter().map(|&(re, im)| {
2915 Value::Tuple(Rc::new(vec![Value::Float(re), Value::Float(im)]))
2916 }).collect();
2917 Ok(Some(Value::Array(Rc::new(pairs))))
2918 }
2919 "beta_pdf" => {
2920 if args.len() != 3 { return Err("beta_pdf requires 3 arguments (x, a, b)".into()); }
2921 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()) };
2922 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()) };
2923 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()) };
2924 Ok(Some(Value::Float(crate::distributions::beta_pdf(x, a, b))))
2925 }
2926 "beta_cdf" => {
2927 if args.len() != 3 { return Err("beta_cdf requires 3 arguments (x, a, b)".into()); }
2928 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()) };
2929 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()) };
2930 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()) };
2931 Ok(Some(Value::Float(crate::distributions::beta_cdf(x, a, b))))
2932 }
2933 "gamma_pdf" => {
2934 if args.len() != 3 { return Err("gamma_pdf requires 3 arguments (x, k, theta)".into()); }
2935 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()) };
2936 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()) };
2937 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()) };
2938 Ok(Some(Value::Float(crate::distributions::gamma_pdf(x, k, theta))))
2939 }
2940 "gamma_cdf" => {
2941 if args.len() != 3 { return Err("gamma_cdf requires 3 arguments (x, k, theta)".into()); }
2942 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()) };
2943 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()) };
2944 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()) };
2945 Ok(Some(Value::Float(crate::distributions::gamma_cdf(x, k, theta))))
2946 }
2947 "exp_pdf" => {
2948 if args.len() != 2 { return Err("exp_pdf requires 2 arguments (x, lambda)".into()); }
2949 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()) };
2950 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()) };
2951 Ok(Some(Value::Float(crate::distributions::exp_pdf(x, lambda))))
2952 }
2953 "exp_cdf" => {
2954 if args.len() != 2 { return Err("exp_cdf requires 2 arguments (x, lambda)".into()); }
2955 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()) };
2956 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()) };
2957 Ok(Some(Value::Float(crate::distributions::exp_cdf(x, lambda))))
2958 }
2959 "weibull_pdf" => {
2960 if args.len() != 3 { return Err("weibull_pdf requires 3 arguments (x, k, lambda)".into()); }
2961 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()) };
2962 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()) };
2963 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()) };
2964 Ok(Some(Value::Float(crate::distributions::weibull_pdf(x, k, lambda))))
2965 }
2966 "weibull_cdf" => {
2967 if args.len() != 3 { return Err("weibull_cdf requires 3 arguments (x, k, lambda)".into()); }
2968 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()) };
2969 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()) };
2970 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()) };
2971 Ok(Some(Value::Float(crate::distributions::weibull_cdf(x, k, lambda))))
2972 }
2973
2974 "case_when" => {
2976 if args.len() != 3 { return Err("case_when requires 3 arguments (conditions, values, default)".into()); }
2977 let conditions = match &args[0] {
2978 Value::Array(arr) => arr.iter().map(|v| match v {
2979 Value::Bool(b) => Ok(*b),
2980 _ => Err("case_when conditions must be booleans".into()),
2981 }).collect::<Result<Vec<bool>, String>>()?,
2982 _ => return Err("case_when conditions must be an array".into()),
2983 };
2984 let values = match &args[1] {
2985 Value::Array(arr) => arr.as_ref().clone(),
2986 _ => return Err("case_when values must be an array".into()),
2987 };
2988 if conditions.len() != values.len() {
2989 return Err("case_when conditions and values must have same length".into());
2990 }
2991 for (i, &cond) in conditions.iter().enumerate() {
2992 if cond { return Ok(Some(values[i].clone())); }
2993 }
2994 Ok(Some(args[2].clone())) }
2996 "ntile" => {
2997 if args.len() != 2 { return Err("ntile requires 2 arguments (data, n)".into()); }
2998 let data = value_to_f64_vec(&args[0])?;
2999 let n = match &args[1] { Value::Int(i) => *i as usize, _ => return Err("ntile: n must be an integer".into()) };
3000 let result = crate::stats::ntile(&data, n)?;
3001 Ok(Some(Value::Array(Rc::new(result.into_iter().map(Value::Float).collect()))))
3002 }
3003 "percent_rank" => {
3004 if args.len() != 1 { return Err("percent_rank requires 1 argument".into()); }
3005 let data = value_to_f64_vec(&args[0])?;
3006 let result = crate::stats::percent_rank_fn(&data)?;
3007 Ok(Some(Value::Array(Rc::new(result.into_iter().map(Value::Float).collect()))))
3008 }
3009 "cume_dist" => {
3010 if args.len() != 1 { return Err("cume_dist requires 1 argument".into()); }
3011 let data = value_to_f64_vec(&args[0])?;
3012 let result = crate::stats::cume_dist(&data)?;
3013 Ok(Some(Value::Array(Rc::new(result.into_iter().map(Value::Float).collect()))))
3014 }
3015 "wls" => {
3016 if args.len() != 5 { return Err("wls requires 5 arguments (X, y, weights, n, p)".into()); }
3017 let x = value_to_f64_vec(&args[0])?;
3018 let y = value_to_f64_vec(&args[1])?;
3019 let w = value_to_f64_vec(&args[2])?;
3020 let n = match &args[3] { Value::Int(i) => *i as usize, _ => return Err("wls: n must be an integer".into()) };
3021 let p = match &args[4] { Value::Int(i) => *i as usize, _ => return Err("wls: p must be an integer".into()) };
3022 let r = crate::hypothesis::wls(&x, &y, &w, n, p)?;
3023 let fields = std::collections::BTreeMap::from([
3024 ("coefficients".to_string(), Value::Array(Rc::new(r.coefficients.into_iter().map(Value::Float).collect()))),
3025 ("r_squared".to_string(), Value::Float(r.r_squared)),
3026 ("residuals".to_string(), Value::Array(Rc::new(r.residuals.into_iter().map(Value::Float).collect()))),
3027 ]);
3028 Ok(Some(Value::Struct { name: "LmResult".to_string(), fields }))
3029 }
3030
3031 "tukey_hsd" => {
3033 let groups: Vec<Vec<f64>> = args.iter()
3034 .map(|a| value_to_f64_vec(a))
3035 .collect::<Result<Vec<_>, _>>()?;
3036 let group_refs: Vec<&[f64]> = groups.iter().map(|g| g.as_slice()).collect();
3037 let results = crate::hypothesis::tukey_hsd(&group_refs)?;
3038 let result_values: Vec<Value> = results.iter().map(|pair| {
3039 let mut fields = std::collections::BTreeMap::new();
3040 fields.insert("group_i".into(), Value::Int(pair.group_i as i64));
3041 fields.insert("group_j".into(), Value::Int(pair.group_j as i64));
3042 fields.insert("mean_diff".into(), Value::Float(pair.mean_diff));
3043 fields.insert("q_statistic".into(), Value::Float(pair.q_statistic));
3044 fields.insert("p_value".into(), Value::Float(pair.p_value));
3045 Value::Struct { name: "TukeyHsdPair".into(), fields }
3046 }).collect();
3047 Ok(Some(Value::Array(Rc::new(result_values))))
3048 }
3049 "mann_whitney" => {
3050 if args.len() != 2 { return Err("mann_whitney requires 2 arguments".into()); }
3051 let x = value_to_f64_vec(&args[0])?;
3052 let y = value_to_f64_vec(&args[1])?;
3053 let r = crate::hypothesis::mann_whitney(&x, &y)?;
3054 let mut fields = std::collections::BTreeMap::new();
3055 fields.insert("u_statistic".into(), Value::Float(r.u_statistic));
3056 fields.insert("z_score".into(), Value::Float(r.z_score));
3057 fields.insert("p_value".into(), Value::Float(r.p_value));
3058 Ok(Some(Value::Struct { name: "MannWhitneyResult".into(), fields }))
3059 }
3060 "kruskal_wallis" => {
3061 let groups: Vec<Vec<f64>> = args.iter()
3062 .map(|a| value_to_f64_vec(a))
3063 .collect::<Result<Vec<_>, _>>()?;
3064 let group_refs: Vec<&[f64]> = groups.iter().map(|g| g.as_slice()).collect();
3065 let r = crate::hypothesis::kruskal_wallis(&group_refs)?;
3066 let mut fields = std::collections::BTreeMap::new();
3067 fields.insert("h_statistic".into(), Value::Float(r.h_statistic));
3068 fields.insert("p_value".into(), Value::Float(r.p_value));
3069 fields.insert("df".into(), Value::Float(r.df));
3070 Ok(Some(Value::Struct { name: "KruskalWallisResult".into(), fields }))
3071 }
3072 "wilcoxon_signed_rank" => {
3073 if args.len() != 2 { return Err("wilcoxon_signed_rank requires 2 arguments".into()); }
3074 let x = value_to_f64_vec(&args[0])?;
3075 let y = value_to_f64_vec(&args[1])?;
3076 let r = crate::hypothesis::wilcoxon_signed_rank(&x, &y)?;
3077 let mut fields = std::collections::BTreeMap::new();
3078 fields.insert("w_statistic".into(), Value::Float(r.w_statistic));
3079 fields.insert("z_score".into(), Value::Float(r.z_score));
3080 fields.insert("p_value".into(), Value::Float(r.p_value));
3081 Ok(Some(Value::Struct { name: "WilcoxonResult".into(), fields }))
3082 }
3083 "bonferroni" => {
3084 if args.len() != 1 { return Err("bonferroni requires 1 argument (p_values array)".into()); }
3085 let pvals = value_to_f64_vec(&args[0])?;
3086 let adj = crate::hypothesis::bonferroni(&pvals);
3087 Ok(Some(Value::Array(Rc::new(adj.into_iter().map(Value::Float).collect()))))
3088 }
3089 "fdr_bh" => {
3090 if args.len() != 1 { return Err("fdr_bh requires 1 argument (p_values array)".into()); }
3091 let pvals = value_to_f64_vec(&args[0])?;
3092 let adj = crate::hypothesis::fdr_bh(&pvals);
3093 Ok(Some(Value::Array(Rc::new(adj.into_iter().map(Value::Float).collect()))))
3094 }
3095 "logistic_regression" => {
3096 if args.len() != 4 { return Err("logistic_regression requires 4 arguments (X, y, n, p)".into()); }
3097 let x = value_to_f64_vec(&args[0])?;
3098 let y = value_to_f64_vec(&args[1])?;
3099 let n = match &args[2] { Value::Int(i) => *i as usize, _ => return Err("logistic_regression: n must be an integer".into()) };
3100 let p = match &args[3] { Value::Int(i) => *i as usize, _ => return Err("logistic_regression: p must be an integer".into()) };
3101 let r = crate::hypothesis::logistic_regression(&x, &y, n, p)?;
3102 let mut fields = std::collections::BTreeMap::new();
3103 fields.insert("coefficients".into(), Value::Array(Rc::new(r.coefficients.into_iter().map(Value::Float).collect())));
3104 fields.insert("std_errors".into(), Value::Array(Rc::new(r.std_errors.into_iter().map(Value::Float).collect())));
3105 fields.insert("z_values".into(), Value::Array(Rc::new(r.z_values.into_iter().map(Value::Float).collect())));
3106 fields.insert("p_values".into(), Value::Array(Rc::new(r.p_values.into_iter().map(Value::Float).collect())));
3107 fields.insert("log_likelihood".into(), Value::Float(r.log_likelihood));
3108 fields.insert("aic".into(), Value::Float(r.aic));
3109 fields.insert("iterations".into(), Value::Int(r.iterations as i64));
3110 Ok(Some(Value::Struct { name: "LogisticResult".into(), fields }))
3111 }
3112
3113 "adf_test" => {
3115 if args.len() != 1 { return Err("adf_test requires 1 argument: data".into()); }
3116 let data = value_to_f64_vec(&args[0])?;
3117 let (t_stat, p_val) = crate::stationarity::adf_test(&data)?;
3118 let mut fields = std::collections::BTreeMap::new();
3119 fields.insert("statistic".into(), Value::Float(t_stat));
3120 fields.insert("p_value".into(), Value::Float(p_val));
3121 Ok(Some(Value::Struct { name: "AdfResult".into(), fields }))
3122 }
3123 "kpss_test" => {
3124 if args.len() != 1 { return Err("kpss_test requires 1 argument: data".into()); }
3125 let data = value_to_f64_vec(&args[0])?;
3126 let (stat, p_val) = crate::stationarity::kpss_test(&data)?;
3127 let mut fields = std::collections::BTreeMap::new();
3128 fields.insert("statistic".into(), Value::Float(stat));
3129 fields.insert("p_value".into(), Value::Float(p_val));
3130 Ok(Some(Value::Struct { name: "KpssResult".into(), fields }))
3131 }
3132 "pp_test" => {
3133 if args.len() != 1 { return Err("pp_test requires 1 argument: data".into()); }
3134 let data = value_to_f64_vec(&args[0])?;
3135 let (z_t, p_val) = crate::stationarity::pp_test(&data)?;
3136 let mut fields = std::collections::BTreeMap::new();
3137 fields.insert("statistic".into(), Value::Float(z_t));
3138 fields.insert("p_value".into(), Value::Float(p_val));
3139 Ok(Some(Value::Struct { name: "PpResult".into(), fields }))
3140 }
3141
3142 "argsort" => {
3144 if args.len() != 1 { return Err("argsort requires 1 arg: Tensor".into()); }
3145 let t = value_to_tensor(&args[0])?;
3146 Ok(Some(Value::Tensor(t.argsort())))
3147 }
3148 "gather" => {
3149 if args.len() != 3 { return Err("gather requires 3 args: tensor, dim, indices".into()); }
3150 let t = value_to_tensor(&args[0])?;
3151 let dim = value_to_usize(&args[1])?;
3152 let indices = value_to_tensor(&args[2])?;
3153 Ok(Some(Value::Tensor(t.gather(dim, &indices).map_err(|e| format!("{e}"))?)))
3154 }
3155 "scatter" => {
3156 if args.len() != 4 { return Err("scatter requires 4 args: tensor, dim, indices, src".into()); }
3157 let t = value_to_tensor(&args[0])?;
3158 let dim = value_to_usize(&args[1])?;
3159 let indices = value_to_tensor(&args[2])?;
3160 let src = value_to_tensor(&args[3])?;
3161 Ok(Some(Value::Tensor(t.scatter(dim, &indices, &src).map_err(|e| format!("{e}"))?)))
3162 }
3163 "index_select" => {
3164 if args.len() != 3 { return Err("index_select requires 3 args: tensor, dim, indices".into()); }
3165 let t = value_to_tensor(&args[0])?;
3166 let dim = value_to_usize(&args[1])?;
3167 let indices = value_to_tensor(&args[2])?;
3168 Ok(Some(Value::Tensor(t.index_select(dim, &indices).map_err(|e| format!("{e}"))?)))
3169 }
3170
3171 "array_push" => {
3173 if args.len() != 2 { return Err("array_push requires 2 args: array, value".into()); }
3174 let mut arr_rc = match &args[0] { Value::Array(a) => Rc::clone(a), _ => return Err("array_push: first arg must be Array".into()) };
3175 Rc::make_mut(&mut arr_rc).push(args[1].clone());
3179 Ok(Some(Value::Array(arr_rc)))
3180 }
3181 "array_pop" => {
3182 if args.len() != 1 { return Err("array_pop requires 1 arg: array".into()); }
3183 let mut arr_rc = match &args[0] { Value::Array(a) => Rc::clone(a), _ => return Err("array_pop: expected Array".into()) };
3184 if arr_rc.is_empty() { return Err("array_pop: empty array".into()); }
3185 let last = Rc::make_mut(&mut arr_rc).pop().unwrap();
3187 Ok(Some(Value::Tuple(Rc::new(vec![last, Value::Array(arr_rc)]))))
3188 }
3189 "array_contains" => {
3190 if args.len() != 2 { return Err("array_contains requires 2 args: array, value".into()); }
3191 let arr = match &args[0] { Value::Array(a) => a, _ => return Err("array_contains: first arg must be Array".into()) };
3192 let needle = &args[1];
3193 let found = arr.iter().any(|v| format!("{v}") == format!("{needle}"));
3194 Ok(Some(Value::Bool(found)))
3195 }
3196 "array_reverse" => {
3197 if args.len() != 1 { return Err("array_reverse requires 1 arg: array".into()); }
3198 let mut arr_rc = match &args[0] { Value::Array(a) => Rc::clone(a), _ => return Err("array_reverse: expected Array".into()) };
3199 Rc::make_mut(&mut arr_rc).reverse();
3201 Ok(Some(Value::Array(arr_rc)))
3202 }
3203 "array_flatten" => {
3204 if args.len() != 1 { return Err("array_flatten requires 1 arg: array".into()); }
3205 let arr = match &args[0] { Value::Array(a) => a.clone(), _ => return Err("array_flatten: expected Array".into()) };
3206 let mut result = Vec::new();
3207 fn flatten_recursive(arr: &[Value], result: &mut Vec<Value>) {
3208 for v in arr {
3209 match v {
3210 Value::Array(inner) => flatten_recursive(inner, result),
3211 _ => result.push(v.clone()),
3212 }
3213 }
3214 }
3215 flatten_recursive(&arr, &mut result);
3216 Ok(Some(Value::Array(Rc::new(result))))
3217 }
3218 "array_len" => {
3219 if args.len() != 1 { return Err("array_len requires 1 arg: array".into()); }
3220 match &args[0] {
3221 Value::Array(a) => Ok(Some(Value::Int(a.len() as i64))),
3222 _ => Err("array_len: expected Array".into()),
3223 }
3224 }
3225 "array_slice" => {
3226 if args.len() != 3 { return Err("array_slice requires 3 args: array, start, end".into()); }
3227 let arr = match &args[0] { Value::Array(a) => a, _ => return Err("array_slice: expected Array".into()) };
3228 let start = match &args[1] { Value::Int(i) => *i as usize, _ => return Err("array_slice: start must be Int".into()) };
3229 let end = match &args[2] { Value::Int(i) => *i as usize, _ => return Err("array_slice: end must be Int".into()) };
3230 if start > end || end > arr.len() {
3231 return Err(format!("array_slice: bounds [{start}, {end}) out of range for len {}", arr.len()));
3232 }
3233 Ok(Some(Value::Array(Rc::new(arr[start..end].to_vec()))))
3234 }
3235
3236 "Map.new" => {
3238 if !args.is_empty() { return Err("Map.new takes 0 arguments".into()); }
3239 Ok(Some(Value::Map(Rc::new(RefCell::new(crate::det_map::DetMap::new())))))
3240 }
3241 "Set.new" => {
3242 if !args.is_empty() { return Err("Set.new takes 0 arguments".into()); }
3243 Ok(Some(Value::Map(Rc::new(RefCell::new(crate::det_map::DetMap::new())))))
3244 }
3245
3246 "bit_and" => {
3248 if args.len() != 2 { return Err("bit_and requires 2 Int args".into()); }
3249 let a = match &args[0] { Value::Int(i) => *i, _ => return Err("bit_and: expected Int".into()) };
3250 let b = match &args[1] { Value::Int(i) => *i, _ => return Err("bit_and: expected Int".into()) };
3251 Ok(Some(Value::Int(a & b)))
3252 }
3253 "bit_or" => {
3254 if args.len() != 2 { return Err("bit_or requires 2 Int args".into()); }
3255 let a = match &args[0] { Value::Int(i) => *i, _ => return Err("bit_or: expected Int".into()) };
3256 let b = match &args[1] { Value::Int(i) => *i, _ => return Err("bit_or: expected Int".into()) };
3257 Ok(Some(Value::Int(a | b)))
3258 }
3259 "bit_xor" => {
3260 if args.len() != 2 { return Err("bit_xor requires 2 Int args".into()); }
3261 let a = match &args[0] { Value::Int(i) => *i, _ => return Err("bit_xor: expected Int".into()) };
3262 let b = match &args[1] { Value::Int(i) => *i, _ => return Err("bit_xor: expected Int".into()) };
3263 Ok(Some(Value::Int(a ^ b)))
3264 }
3265 "bit_not" => {
3266 if args.len() != 1 { return Err("bit_not requires 1 Int arg".into()); }
3267 let a = match &args[0] { Value::Int(i) => *i, _ => return Err("bit_not: expected Int".into()) };
3268 Ok(Some(Value::Int(!a)))
3269 }
3270 "bit_shl" => {
3271 if args.len() != 2 { return Err("bit_shl requires 2 Int args".into()); }
3272 let a = match &args[0] { Value::Int(i) => *i, _ => return Err("bit_shl: expected Int".into()) };
3273 let n = match &args[1] { Value::Int(i) => *i, _ => return Err("bit_shl: expected Int".into()) };
3274 if n < 0 || n > 63 { return Err("bit_shl: shift amount must be 0-63".into()); }
3275 Ok(Some(Value::Int(((a as u64) << n) as i64)))
3276 }
3277 "bit_shr" => {
3278 if args.len() != 2 { return Err("bit_shr requires 2 Int args".into()); }
3279 let a = match &args[0] { Value::Int(i) => *i, _ => return Err("bit_shr: expected Int".into()) };
3280 let n = match &args[1] { Value::Int(i) => *i, _ => return Err("bit_shr: expected Int".into()) };
3281 if n < 0 || n > 63 { return Err("bit_shr: shift amount must be 0-63".into()); }
3282 Ok(Some(Value::Int(((a as u64) >> n) as i64)))
3283 }
3284 "popcount" => {
3285 if args.len() != 1 { return Err("popcount requires 1 Int arg".into()); }
3286 let a = match &args[0] { Value::Int(i) => *i, _ => return Err("popcount: expected Int".into()) };
3287 Ok(Some(Value::Int((a as u64).count_ones() as i64)))
3288 }
3289
3290 "Adam.new" => {
3292 if args.len() < 2 || args.len() > 4 {
3293 return Err("Adam.new requires 2-4 args: n_params, lr, [beta1], [beta2]".into());
3294 }
3295 let n = match &args[0] { Value::Int(i) => *i as usize, _ => return Err("Adam.new: n_params must be Int".into()) };
3296 let lr = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("Adam.new: lr must be Float".into()) };
3297 let beta1 = if args.len() > 2 {
3298 match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("Adam.new: beta1 must be Float".into()) }
3299 } else { 0.9 };
3300 let beta2 = if args.len() > 3 {
3301 match &args[3] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("Adam.new: beta2 must be Float".into()) }
3302 } else { 0.999 };
3303 let mut state = crate::ml::AdamState::new(n, lr);
3304 state.beta1 = beta1;
3305 state.beta2 = beta2;
3306 let erased: Rc<RefCell<dyn std::any::Any>> = Rc::new(RefCell::new(state));
3307 Ok(Some(Value::OptimizerState(erased)))
3308 }
3309 "Sgd.new" => {
3310 if args.len() < 2 || args.len() > 3 {
3311 return Err("Sgd.new requires 2-3 args: n_params, lr, [momentum]".into());
3312 }
3313 let n = match &args[0] { Value::Int(i) => *i as usize, _ => return Err("Sgd.new: n_params must be Int".into()) };
3314 let lr = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("Sgd.new: lr must be Float".into()) };
3315 let momentum = if args.len() > 2 {
3316 match &args[2] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("Sgd.new: momentum must be Float".into()) }
3317 } else { 0.0 };
3318 let state = crate::ml::SgdState::new(n, lr, momentum);
3319 let erased: Rc<RefCell<dyn std::any::Any>> = Rc::new(RefCell::new(state));
3320 Ok(Some(Value::OptimizerState(erased)))
3321 }
3322
3323 "stop_gradient" => {
3326 if args.len() != 1 { return Err("stop_gradient requires exactly 1 argument".into()); }
3327 Ok(Some(args[0].clone()))
3328 }
3329 "grad_checkpoint" => {
3331 if args.len() != 1 { return Err("grad_checkpoint requires exactly 1 argument".into()); }
3332 Ok(Some(args[0].clone()))
3333 }
3334 "clip_grad" => {
3336 if args.len() != 3 { return Err("clip_grad requires 3 arguments (value, min, max)".into()); }
3337 let val = match &args[0] {
3338 Value::Float(f) => *f,
3339 Value::Int(i) => *i as f64,
3340 _ => return Err("clip_grad requires numeric arguments".into()),
3341 };
3342 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()) };
3343 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()) };
3344 Ok(Some(Value::Float(val.max(min_val).min(max_val))))
3345 }
3346 "grad_scale" => {
3348 if args.len() != 2 { return Err("grad_scale requires 2 arguments (value, scale)".into()); }
3349 let val = match &args[0] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("grad_scale requires numeric first arg".into()) };
3350 let scale = match &args[1] { Value::Float(f) => *f, Value::Int(i) => *i as f64, _ => return Err("grad_scale requires numeric scale".into()) };
3351 Ok(Some(Value::Float(val * scale)))
3352 }
3353
3354 "broadcast" => {
3357 if args.len() != 2 {
3358 return Err("broadcast requires 2 arguments (fn_name, tensor)".into());
3359 }
3360 let fn_name = match &args[0] {
3361 Value::String(s) => s.clone(),
3362 _ => return Err("broadcast: first argument must be a function name string".into()),
3363 };
3364 let t = value_to_tensor(&args[1])?;
3365
3366 match fn_name.as_str() {
3369 "sqrt" => return Ok(Some(Value::Tensor(t.map_simd(UnaryOp::Sqrt)))),
3370 "abs" => return Ok(Some(Value::Tensor(t.map_simd(UnaryOp::Abs)))),
3371 "neg" => return Ok(Some(Value::Tensor(t.map_simd(UnaryOp::Neg)))),
3372 "relu" => return Ok(Some(Value::Tensor(t.map_simd(UnaryOp::Relu)))),
3373 _ => {} }
3375
3376 let f: Box<dyn Fn(f64) -> f64> = match fn_name.as_str() {
3380 "sin" => Box::new(|x: f64| x.sin()),
3381 "cos" => Box::new(|x: f64| x.cos()),
3382 "tan" => Box::new(|x: f64| x.tan()),
3383 "asin" => Box::new(|x: f64| x.asin()),
3384 "acos" => Box::new(|x: f64| x.acos()),
3385 "atan" => Box::new(|x: f64| x.atan()),
3386 "exp" => Box::new(|x: f64| x.exp()),
3387 "ln" => Box::new(|x: f64| x.ln()),
3388 "log" => Box::new(|x: f64| x.ln()),
3389 "log2" => Box::new(|x: f64| x.log2()),
3390 "log10" => Box::new(|x: f64| x.log10()),
3391 "log1p" => Box::new(|x: f64| x.ln_1p()),
3392 "expm1" => Box::new(|x: f64| x.exp_m1()),
3393 "floor" => Box::new(|x: f64| x.floor()),
3394 "ceil" => Box::new(|x: f64| x.ceil()),
3395 "round" => Box::new(|x: f64| x.round()),
3396 "sigmoid" => Box::new(|x: f64| 1.0 / (1.0 + (-x).exp())),
3397 "tanh" => Box::new(|x: f64| x.tanh()),
3398 "sign" => Box::new(|x: f64| {
3399 if x > 0.0 { 1.0 } else if x < 0.0 { -1.0 } else { 0.0 }
3400 }),
3401 _ => return Err(format!("broadcast: unknown unary function '{fn_name}'")),
3402 };
3403 Ok(Some(Value::Tensor(t.map(f))))
3404 }
3405
3406 "broadcast2" => {
3407 if args.len() != 3 {
3408 return Err("broadcast2 requires 3 arguments (fn_name, tensor1, tensor2)".into());
3409 }
3410 let fn_name = match &args[0] {
3411 Value::String(s) => s.clone(),
3412 _ => return Err("broadcast2: first argument must be a function name string".into()),
3413 };
3414 let t1 = value_to_tensor(&args[1])?;
3415 let t2 = value_to_tensor(&args[2])?;
3416 let result = match fn_name.as_str() {
3417 "add" => t1.add(&t2),
3418 "sub" => t1.sub(&t2),
3419 "mul" => t1.mul_elem(&t2),
3420 "div" => t1.div_elem(&t2),
3421 "pow" => t1.elem_pow(&t2),
3422 "min" => t1.elem_min(&t2),
3423 "max" => t1.elem_max(&t2),
3424 "atan2" => t1.elem_atan2(&t2),
3425 "hypot" => t1.elem_hypot(&t2),
3426 _ => return Err(format!("broadcast2: unknown binary function '{fn_name}'")),
3427 };
3428 match result {
3429 Ok(t) => Ok(Some(Value::Tensor(t))),
3430 Err(e) => Err(format!("broadcast2: {e}")),
3431 }
3432 }
3433
3434 "peak_rss" => {
3436 Ok(Some(Value::Int(peak_rss_kb() as i64)))
3437 }
3438
3439 "broadcast_fma" => {
3441 if args.len() != 3 {
3444 return Err("broadcast_fma requires 3 arguments (a, b, c)".into());
3445 }
3446 let a = value_to_tensor(&args[0])?;
3447 let b = value_to_tensor(&args[1])?;
3448 let c = value_to_tensor(&args[2])?;
3449 let result = a.fused_mul_add(&b, &c)
3450 .map_err(|e| format!("broadcast_fma: {e}"))?;
3451 Ok(Some(Value::Tensor(result)))
3452 }
3453
3454 "tensor_where" => {
3456 let a = value_to_tensor(&args[0])?;
3457 let cond = value_to_tensor(&args[1])?;
3458 let other = value_to_tensor(&args[2])?;
3459 Ok(Some(Value::Tensor(a.tensor_where(cond, other).map_err(|e| format!("{e}"))?)))
3460 }
3461 "tensor_any" => {
3462 let t = value_to_tensor(&args[0])?;
3463 Ok(Some(Value::Bool(t.any())))
3464 }
3465 "tensor_all" => {
3466 let t = value_to_tensor(&args[0])?;
3467 Ok(Some(Value::Bool(t.all())))
3468 }
3469 "tensor_nonzero" => {
3470 let t = value_to_tensor(&args[0])?;
3471 Ok(Some(Value::Tensor(t.nonzero())))
3472 }
3473 "tensor_masked_fill" => {
3474 let t = value_to_tensor(&args[0])?;
3475 let mask = value_to_tensor(&args[1])?;
3476 let val = value_to_f64(&args[2])?;
3477 Ok(Some(Value::Tensor(t.masked_fill(mask, val).map_err(|e| format!("{e}"))?)))
3478 }
3479 "tensor_mean_axis" => {
3481 let t = value_to_tensor(&args[0])?;
3482 let axis = value_to_usize(&args[1])?;
3483 let keepdim = matches!(args.get(2), Some(Value::Bool(true)));
3484 Ok(Some(Value::Tensor(t.mean_axis(axis, keepdim).map_err(|e| format!("{e}"))?)))
3485 }
3486 "tensor_var_axis" => {
3487 let t = value_to_tensor(&args[0])?;
3488 let axis = value_to_usize(&args[1])?;
3489 let keepdim = matches!(args.get(2), Some(Value::Bool(true)));
3490 Ok(Some(Value::Tensor(t.var_axis(axis, keepdim).map_err(|e| format!("{e}"))?)))
3491 }
3492 "tensor_std_axis" => {
3493 let t = value_to_tensor(&args[0])?;
3494 let axis = value_to_usize(&args[1])?;
3495 let keepdim = matches!(args.get(2), Some(Value::Bool(true)));
3496 Ok(Some(Value::Tensor(t.std_axis(axis, keepdim).map_err(|e| format!("{e}"))?)))
3497 }
3498 "tensor_prod_axis" => {
3499 let t = value_to_tensor(&args[0])?;
3500 let axis = value_to_usize(&args[1])?;
3501 let keepdim = matches!(args.get(2), Some(Value::Bool(true)));
3502 Ok(Some(Value::Tensor(t.prod_axis(axis, keepdim).map_err(|e| format!("{e}"))?)))
3503 }
3504 "tensor_sort" => {
3506 let t = value_to_tensor(&args[0])?;
3507 let axis = value_to_usize(&args[1])?;
3508 let desc = matches!(args.get(2), Some(Value::Bool(true)));
3509 Ok(Some(Value::Tensor(t.sort_axis(axis, desc).map_err(|e| format!("{e}"))?)))
3510 }
3511 "tensor_argsort_axis" => {
3512 let t = value_to_tensor(&args[0])?;
3513 let axis = value_to_usize(&args[1])?;
3514 let desc = matches!(args.get(2), Some(Value::Bool(true)));
3515 Ok(Some(Value::Tensor(t.argsort_axis(axis, desc).map_err(|e| format!("{e}"))?)))
3516 }
3517 "einsum" => {
3519 let notation = match &args[0] {
3520 Value::String(s) => s.as_str().to_string(),
3521 _ => return Err("einsum: first arg must be notation string".into()),
3522 };
3523 let tensors: Vec<&Tensor> = args[1..].iter()
3524 .map(value_to_tensor)
3525 .collect::<Result<_, _>>()?;
3526 let result = Tensor::einsum(¬ation, &tensors).map_err(|e| format!("{e}"))?;
3527 Ok(Some(Value::Tensor(result)))
3528 }
3529 "tensor_unsqueeze" => {
3531 let t = value_to_tensor(&args[0])?;
3532 let dim = value_to_usize(&args[1])?;
3533 Ok(Some(Value::Tensor(t.unsqueeze(dim).map_err(|e| format!("{e}"))?)))
3534 }
3535 "tensor_squeeze" => {
3536 let t = value_to_tensor(&args[0])?;
3537 let dim = args.get(1).map(|v| value_to_usize(v)).transpose()?;
3538 Ok(Some(Value::Tensor(t.squeeze(dim).map_err(|e| format!("{e}"))?)))
3539 }
3540 "tensor_flatten" => {
3541 let t = value_to_tensor(&args[0])?;
3542 let start = value_to_usize(&args[1])?;
3543 let end = value_to_usize(&args[2])?;
3544 Ok(Some(Value::Tensor(t.flatten(start, end).map_err(|e| format!("{e}"))?)))
3545 }
3546 "tensor_chunk" => {
3547 let t = value_to_tensor(&args[0])?;
3548 let n = value_to_usize(&args[1])?;
3549 let dim = value_to_usize(&args[2])?;
3550 let chunks = t.chunk(n, dim).map_err(|e| format!("{e}"))?;
3551 Ok(Some(Value::Array(Rc::new(chunks.into_iter().map(Value::Tensor).collect()))))
3552 }
3553 "svd" => {
3555 let t = value_to_tensor(&args[0])?;
3556 let (u, s, vt) = t.svd().map_err(|e| format!("{e}"))?;
3557 let s_tensor = Tensor::from_vec(s, &[u.shape()[1]]).map_err(|e| format!("{e}"))?;
3558 Ok(Some(Value::Tuple(Rc::new(vec![
3559 Value::Tensor(u),
3560 Value::Tensor(s_tensor),
3561 Value::Tensor(vt),
3562 ]))))
3563 }
3564 "pinv" => {
3565 let t = value_to_tensor(&args[0])?;
3566 Ok(Some(Value::Tensor(t.pinv().map_err(|e| format!("{e}"))?)))
3567 }
3568 "pca" => {
3569 let t = value_to_tensor(&args[0])?;
3570 let n_components = value_to_usize(&args[1])?;
3571 let (transformed, components, variance) = crate::ml::pca(&t, n_components).map_err(|e| format!("{e}"))?;
3572 let vlen = variance.len();
3573 let var_tensor = Tensor::from_vec(variance, &[vlen]).map_err(|e| format!("{e}"))?;
3574 Ok(Some(Value::Tuple(Rc::new(vec![
3575 Value::Tensor(transformed),
3576 Value::Tensor(components),
3577 Value::Tensor(var_tensor),
3578 ]))))
3579 }
3580 "sparse_add" => {
3582 let a = value_to_sparse(&args[0])?;
3583 let b = value_to_sparse(&args[1])?;
3584 Ok(Some(Value::SparseTensor(crate::sparse::sparse_add(a, b).map_err(|e| e)?)))
3585 }
3586 "sparse_sub" => {
3587 let a = value_to_sparse(&args[0])?;
3588 let b = value_to_sparse(&args[1])?;
3589 Ok(Some(Value::SparseTensor(crate::sparse::sparse_sub(a, b).map_err(|e| e)?)))
3590 }
3591 "sparse_matmul" => {
3592 let a = value_to_sparse(&args[0])?;
3593 let b = value_to_sparse(&args[1])?;
3594 Ok(Some(Value::SparseTensor(crate::sparse::sparse_matmul(a, b).map_err(|e| e)?)))
3595 }
3596 "sparse_transpose" => {
3597 let a = value_to_sparse(&args[0])?;
3598 Ok(Some(Value::SparseTensor(crate::sparse::sparse_transpose(a))))
3599 }
3600 "kmeans" => {
3602 let data = value_to_f64_vec(&args[0])?;
3603 let n_samples = value_to_usize(&args[1])?;
3604 let n_features = value_to_usize(&args[2])?;
3605 let k = value_to_usize(&args[3])?;
3606 let max_iter = value_to_usize(&args[4])?;
3607 let seed = match &args[5] { Value::Int(v) => *v as u64, _ => 42 };
3608 let (centroids, labels, inertia) = crate::clustering::kmeans(&data, n_samples, n_features, k, max_iter, seed);
3609 let label_vals: Vec<Value> = labels.iter().map(|&l| Value::Int(l as i64)).collect();
3610 let centroid_t = Tensor::from_vec(centroids, &[k, n_features]).map_err(|e| format!("{e}"))?;
3611 Ok(Some(Value::Tuple(Rc::new(vec![
3612 Value::Tensor(centroid_t),
3613 Value::Array(Rc::new(label_vals)),
3614 Value::Float(inertia),
3615 ]))))
3616 }
3617 "dbscan" => {
3618 let data = value_to_f64_vec(&args[0])?;
3619 let n_samples = value_to_usize(&args[1])?;
3620 let n_features = value_to_usize(&args[2])?;
3621 let eps = value_to_f64(&args[3])?;
3622 let min_samples = value_to_usize(&args[4])?;
3623 let labels = crate::clustering::dbscan(&data, n_samples, n_features, eps, min_samples);
3624 let label_vals: Vec<Value> = labels.iter().map(|&l| Value::Int(l)).collect();
3625 Ok(Some(Value::Array(Rc::new(label_vals))))
3626 }
3627 "label_encode" => {
3629 let strs: Vec<String> = match &args[0] {
3632 Value::Array(arr) => arr.iter().map(|v| match v {
3633 Value::String(s) => Ok(s.as_str().to_string()),
3634 _ => Err("label_encode: expected array of strings".to_string()),
3635 }).collect::<Result<_, _>>()?,
3636 _ => return Err("label_encode: expected array".into()),
3637 };
3638 let mut level_set = std::collections::BTreeSet::new();
3639 for s in &strs { level_set.insert(s.clone()); }
3640 let levels: Vec<String> = level_set.into_iter().collect();
3641 let level_map: std::collections::BTreeMap<&str, u32> = levels.iter().enumerate()
3642 .map(|(i, s)| (s.as_str(), i as u32)).collect();
3643 let codes: Vec<u32> = strs.iter().map(|s| level_map[s.as_str()]).collect();
3644 let level_vals: Vec<Value> = levels.iter().map(|s| Value::String(Rc::new(s.clone()))).collect();
3645 let code_vals: Vec<Value> = codes.iter().map(|&c| Value::Int(c as i64)).collect();
3646 Ok(Some(Value::Tuple(Rc::new(vec![
3647 Value::Array(Rc::new(level_vals)),
3648 Value::Array(Rc::new(code_vals)),
3649 ]))))
3650 }
3651 "acf" => {
3653 let data = value_to_f64_vec(&args[0])?;
3654 let max_lag = value_to_usize(&args[1])?;
3655 let result = crate::timeseries::acf(&data, max_lag);
3656 Ok(Some(Value::Array(Rc::new(result.into_iter().map(Value::Float).collect()))))
3657 }
3658 "ewma" => {
3659 let data = value_to_f64_vec(&args[0])?;
3660 let alpha = value_to_f64(&args[1])?;
3661 let result = crate::timeseries::ewma(&data, alpha);
3662 Ok(Some(Value::Array(Rc::new(result.into_iter().map(Value::Float).collect()))))
3663 }
3664 "diff" => {
3665 let data = value_to_f64_vec(&args[0])?;
3666 let periods = value_to_usize(&args[1])?;
3667 let result = crate::timeseries::diff(&data, periods);
3668 Ok(Some(Value::Array(Rc::new(result.into_iter().map(Value::Float).collect()))))
3669 }
3670 "bisect" => {
3672 Ok(None)
3674 }
3675 "polyfit" => {
3677 let x = value_to_f64_vec(&args[0])?;
3678 let y = value_to_f64_vec(&args[1])?;
3679 let degree = value_to_usize(&args[2])?;
3680 let coeffs = crate::interpolate::polyfit(&x, &y, degree).map_err(|e| e)?;
3681 Ok(Some(Value::Array(Rc::new(coeffs.into_iter().map(Value::Float).collect()))))
3682 }
3683 "polyval" => {
3684 let coeffs = value_to_f64_vec(&args[0])?;
3685 let x = value_to_f64_vec(&args[1])?;
3686 let result = crate::interpolate::polyval(&coeffs, &x);
3687 Ok(Some(Value::Array(Rc::new(result.into_iter().map(Value::Float).collect()))))
3688 }
3689
3690 "getenv" => {
3692 if args.len() != 1 { return Err("getenv requires 1 argument (name)".into()); }
3693 let name = match &args[0] {
3694 Value::String(s) => s.as_str().to_string(),
3695 _ => return Err("getenv: argument must be String".into()),
3696 };
3697 let val = std::env::var(&name).unwrap_or_default();
3698 Ok(Some(Value::String(Rc::new(val))))
3699 }
3700
3701 "map_new" => {
3703 if !args.is_empty() { return Err("map_new takes 0 arguments".into()); }
3704 Ok(Some(Value::Map(Rc::new(RefCell::new(crate::det_map::DetMap::new())))))
3705 }
3706 "map_set" => {
3707 if args.len() != 3 { return Err("map_set requires 3 args: map, key, value".into()); }
3708 let m = match &args[0] {
3709 Value::Map(m) => m,
3710 _ => return Err("map_set: first argument must be Map".into()),
3711 };
3712 let mut new_map = m.borrow().clone();
3713 new_map.insert(args[1].clone(), args[2].clone());
3714 Ok(Some(Value::Map(Rc::new(RefCell::new(new_map)))))
3715 }
3716 "map_get" => {
3717 if args.len() != 2 { return Err("map_get requires 2 args: map, key".into()); }
3718 let m = match &args[0] {
3719 Value::Map(m) => m,
3720 _ => return Err("map_get: first argument must be Map".into()),
3721 };
3722 match m.borrow().get(&args[1]) {
3723 Some(v) => Ok(Some(v.clone())),
3724 None => Ok(Some(Value::Void)),
3725 }
3726 }
3727 "map_keys" => {
3728 if args.len() != 1 { return Err("map_keys requires 1 arg: map".into()); }
3729 let m = match &args[0] {
3730 Value::Map(m) => m,
3731 _ => return Err("map_keys: argument must be Map".into()),
3732 };
3733 Ok(Some(Value::Array(Rc::new(m.borrow().keys()))))
3734 }
3735 "map_values" => {
3736 if args.len() != 1 { return Err("map_values requires 1 arg: map".into()); }
3737 let m = match &args[0] {
3738 Value::Map(m) => m,
3739 _ => return Err("map_values: argument must be Map".into()),
3740 };
3741 Ok(Some(Value::Array(Rc::new(m.borrow().values_vec()))))
3742 }
3743 "map_contains" => {
3744 if args.len() != 2 { return Err("map_contains requires 2 args: map, key".into()); }
3745 let m = match &args[0] {
3746 Value::Map(m) => m,
3747 _ => return Err("map_contains: first argument must be Map".into()),
3748 };
3749 Ok(Some(Value::Bool(m.borrow().contains_key(&args[1]))))
3750 }
3751
3752 "trapezoid" | "trapz" => {
3754 if args.len() != 2 {
3755 return Err("trapezoid requires 2 arguments (xs, ys)".into());
3756 }
3757 let xs = value_to_f64_vec(&args[0])?;
3758 let ys = value_to_f64_vec(&args[1])?;
3759 let result = crate::integrate::trapezoid(&xs, &ys)?;
3760 Ok(Some(Value::Float(result)))
3761 }
3762 "simpson" | "simps" => {
3763 if args.len() != 2 {
3764 return Err("simpson requires 2 arguments (xs, ys)".into());
3765 }
3766 let xs = value_to_f64_vec(&args[0])?;
3767 let ys = value_to_f64_vec(&args[1])?;
3768 let result = crate::integrate::simpson(&xs, &ys)?;
3769 Ok(Some(Value::Float(result)))
3770 }
3771 "cumtrapz" => {
3772 if args.len() != 2 {
3773 return Err("cumtrapz requires 2 arguments (xs, ys)".into());
3774 }
3775 let xs = value_to_f64_vec(&args[0])?;
3776 let ys = value_to_f64_vec(&args[1])?;
3777 let result = crate::integrate::cumtrapz(&xs, &ys)?;
3778 Ok(Some(Value::Array(Rc::new(
3779 result.into_iter().map(Value::Float).collect(),
3780 ))))
3781 }
3782 "diff_central" => {
3784 if args.len() != 2 {
3785 return Err("diff_central requires 2 arguments (xs, ys)".into());
3786 }
3787 let xs = value_to_f64_vec(&args[0])?;
3788 let ys = value_to_f64_vec(&args[1])?;
3789 let result = crate::differentiate::diff_central(&xs, &ys)?;
3790 Ok(Some(Value::Array(Rc::new(
3791 result.into_iter().map(Value::Float).collect(),
3792 ))))
3793 }
3794 "diff_forward" => {
3795 if args.len() != 2 {
3796 return Err("diff_forward requires 2 arguments (xs, ys)".into());
3797 }
3798 let xs = value_to_f64_vec(&args[0])?;
3799 let ys = value_to_f64_vec(&args[1])?;
3800 let result = crate::differentiate::diff_forward(&xs, &ys)?;
3801 Ok(Some(Value::Array(Rc::new(
3802 result.into_iter().map(Value::Float).collect(),
3803 ))))
3804 }
3805 "gradient_1d" => {
3806 if args.len() != 2 {
3807 return Err("gradient_1d requires 2 arguments (ys, dx)".into());
3808 }
3809 let ys = value_to_f64_vec(&args[0])?;
3810 let dx = value_to_f64(&args[1])?;
3811 let result = crate::differentiate::gradient_1d(&ys, dx);
3812 Ok(Some(Value::Array(Rc::new(
3813 result.into_iter().map(Value::Float).collect(),
3814 ))))
3815 }
3816 "penalty_objective" => {
3818 if args.len() != 3 {
3819 return Err(
3820 "penalty_objective requires 3 arguments (f_val, constraint_violations, penalty)"
3821 .into(),
3822 );
3823 }
3824 let f_val = value_to_f64(&args[0])?;
3825 let violations = value_to_f64_vec(&args[1])?;
3826 let penalty = value_to_f64(&args[2])?;
3827 let result = crate::optimize::penalty_objective(f_val, &violations, penalty);
3828 Ok(Some(Value::Float(result)))
3829 }
3830 "project_box" => {
3831 if args.len() != 3 {
3832 return Err("project_box requires 3 arguments (x, lower, upper)".into());
3833 }
3834 let x = value_to_f64_vec(&args[0])?;
3835 let lower = value_to_f64_vec(&args[1])?;
3836 let upper = value_to_f64_vec(&args[2])?;
3837 let result = crate::optimize::project_box(&x, &lower, &upper)?;
3838 Ok(Some(Value::Array(Rc::new(
3839 result.into_iter().map(Value::Float).collect(),
3840 ))))
3841 }
3842 "projected_gd_step" => {
3843 if args.len() != 5 {
3844 return Err(
3845 "projected_gd_step requires 5 arguments (x, grad, lr, lower, upper)".into(),
3846 );
3847 }
3848 let x = value_to_f64_vec(&args[0])?;
3849 let grad = value_to_f64_vec(&args[1])?;
3850 let lr = value_to_f64(&args[2])?;
3851 let lower = value_to_f64_vec(&args[3])?;
3852 let upper = value_to_f64_vec(&args[4])?;
3853 let result = crate::optimize::projected_gd_step(&x, &grad, lr, &lower, &upper)?;
3854 Ok(Some(Value::Array(Rc::new(
3855 result.into_iter().map(Value::Float).collect(),
3856 ))))
3857 }
3858
3859 "lstm_cell" => {
3861 if args.len() != 7 {
3862 return Err("lstm_cell requires 7 Tensor arguments: x, h_prev, c_prev, w_ih, w_hh, b_ih, b_hh".into());
3863 }
3864 let x = value_to_tensor(&args[0])?;
3865 let h_prev = value_to_tensor(&args[1])?;
3866 let c_prev = value_to_tensor(&args[2])?;
3867 let w_ih = value_to_tensor(&args[3])?;
3868 let w_hh = value_to_tensor(&args[4])?;
3869 let b_ih = value_to_tensor(&args[5])?;
3870 let b_hh = value_to_tensor(&args[6])?;
3871 let (h_new, c_new) = crate::ml::lstm_cell(x, h_prev, c_prev, w_ih, w_hh, b_ih, b_hh)?;
3872 Ok(Some(Value::Tuple(Rc::new(vec![
3873 Value::Tensor(h_new),
3874 Value::Tensor(c_new),
3875 ]))))
3876 }
3877
3878 "gru_cell" => {
3880 if args.len() != 6 {
3881 return Err("gru_cell requires 6 Tensor arguments: x, h_prev, w_ih, w_hh, b_ih, b_hh".into());
3882 }
3883 let x = value_to_tensor(&args[0])?;
3884 let h_prev = value_to_tensor(&args[1])?;
3885 let w_ih = value_to_tensor(&args[2])?;
3886 let w_hh = value_to_tensor(&args[3])?;
3887 let b_ih = value_to_tensor(&args[4])?;
3888 let b_hh = value_to_tensor(&args[5])?;
3889 let h_new = crate::ml::gru_cell(x, h_prev, w_ih, w_hh, b_ih, b_hh)?;
3890 Ok(Some(Value::Tensor(h_new)))
3891 }
3892
3893 "multi_head_attention" => {
3895 if args.len() != 12 {
3896 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());
3897 }
3898 let q = value_to_tensor(&args[0])?;
3899 let k = value_to_tensor(&args[1])?;
3900 let v = value_to_tensor(&args[2])?;
3901 let w_q = value_to_tensor(&args[3])?;
3902 let w_k = value_to_tensor(&args[4])?;
3903 let w_v = value_to_tensor(&args[5])?;
3904 let w_o = value_to_tensor(&args[6])?;
3905 let b_q = value_to_tensor(&args[7])?;
3906 let b_k = value_to_tensor(&args[8])?;
3907 let b_v = value_to_tensor(&args[9])?;
3908 let b_o = value_to_tensor(&args[10])?;
3909 let num_heads = value_to_usize(&args[11])?;
3910 let out = crate::ml::multi_head_attention(
3911 q, k, v, w_q, w_k, w_v, w_o, b_q, b_k, b_v, b_o, num_heads,
3912 )?;
3913 Ok(Some(Value::Tensor(out)))
3914 }
3915
3916 "ar_fit" => {
3918 if args.len() != 2 {
3919 return Err("ar_fit requires 2 arguments: data (array), p (int)".into());
3920 }
3921 let data = value_to_f64_vec(&args[0])?;
3922 let p = value_to_usize(&args[1])?;
3923 let coeffs = crate::timeseries::ar_fit(&data, p)?;
3924 Ok(Some(Value::Array(Rc::new(
3925 coeffs.into_iter().map(Value::Float).collect(),
3926 ))))
3927 }
3928
3929 "arima_diff" => {
3931 if args.len() != 2 {
3932 return Err("arima_diff requires 2 arguments: data (array), d (int)".into());
3933 }
3934 let data = value_to_f64_vec(&args[0])?;
3935 let d = value_to_usize(&args[1])?;
3936 let result = crate::timeseries::arima_diff(&data, d);
3937 Ok(Some(Value::Array(Rc::new(
3938 result.into_iter().map(Value::Float).collect(),
3939 ))))
3940 }
3941
3942 "ar_forecast" => {
3944 if args.len() != 3 {
3945 return Err("ar_forecast requires 3 arguments: coeffs (array), history (array), steps (int)".into());
3946 }
3947 let coeffs = value_to_f64_vec(&args[0])?;
3948 let history = value_to_f64_vec(&args[1])?;
3949 let steps = value_to_usize(&args[2])?;
3950 let result = crate::timeseries::ar_forecast(&coeffs, &history, steps)?;
3951 Ok(Some(Value::Array(Rc::new(
3952 result.into_iter().map(Value::Float).collect(),
3953 ))))
3954 }
3955
3956 "fillna" => {
3958 if args.len() != 2 { return Err("fillna requires 2 arguments (array, fill_value)".into()); }
3959 let arr = match &args[0] {
3960 Value::Array(a) => a.as_ref().clone(),
3961 _ => return Err(format!("fillna: first argument must be Array, got {}", args[0].type_name())),
3962 };
3963 let fill = &args[1];
3964 let result: Vec<Value> = arr.iter().map(|v| {
3965 match v {
3966 Value::Na => fill.clone(),
3967 Value::Void => fill.clone(),
3968 Value::Float(f) if f.is_nan() => fill.clone(),
3969 other => other.clone(),
3970 }
3971 }).collect();
3972 Ok(Some(Value::Array(Rc::new(result))))
3973 }
3974
3975 "is_na" => {
3976 if args.len() != 1 { return Err("is_na requires 1 argument".into()); }
3977 let result = matches!(&args[0], Value::Na);
3978 Ok(Some(Value::Bool(result)))
3979 }
3980
3981 "is_not_null" => {
3982 if args.len() != 1 { return Err("is_not_null requires 1 argument".into()); }
3983 let result = match &args[0] {
3984 Value::Na => false,
3985 Value::Void => false,
3986 Value::Float(f) => !f.is_nan(),
3987 _ => true,
3988 };
3989 Ok(Some(Value::Bool(result)))
3990 }
3991
3992 "drop_na" => {
3993 if args.len() != 1 { return Err("drop_na requires 1 argument (array)".into()); }
3994 let arr = match &args[0] {
3995 Value::Array(a) => a.as_ref().clone(),
3996 _ => return Err(format!("drop_na: first argument must be Array, got {}", args[0].type_name())),
3997 };
3998 let result: Vec<Value> = arr.into_iter().filter(|v| !matches!(v, Value::Na)).collect();
3999 Ok(Some(Value::Array(Rc::new(result))))
4000 }
4001
4002 "interpolate_linear" => {
4003 if args.len() != 1 { return Err("interpolate_linear requires 1 argument (array of f64)".into()); }
4004 let data = value_to_f64_vec(&args[0])?;
4005 let n = data.len();
4006 if n == 0 {
4007 return Ok(Some(Value::Array(Rc::new(vec![]))));
4008 }
4009 let mut result = data.clone();
4010
4011 let first_valid = result.iter().position(|x| !x.is_nan());
4013 let last_valid = result.iter().rposition(|x| !x.is_nan());
4014
4015 if let (Some(fv), Some(lv)) = (first_valid, last_valid) {
4016 for i in 0..fv {
4018 result[i] = result[fv];
4019 }
4020 for i in (lv + 1)..n {
4022 result[i] = result[lv];
4023 }
4024 let mut i = fv + 1;
4026 while i < lv {
4027 if result[i].is_nan() {
4028 let start = i - 1;
4030 let mut end = i + 1;
4031 while end < n && result[end].is_nan() {
4032 end += 1;
4033 }
4034 let v0 = result[start];
4035 let v1 = result[end];
4036 let span = (end - start) as f64;
4037 for j in (start + 1)..end {
4038 let t = (j - start) as f64 / span;
4039 use cjc_repro::KahanAccumulatorF64;
4042 let mut acc = KahanAccumulatorF64::new();
4043 acc.add(v0 * (1.0 - t));
4044 acc.add(v1 * t);
4045 result[j] = acc.finalize();
4046 }
4047 i = end + 1;
4048 } else {
4049 i += 1;
4050 }
4051 }
4052 }
4053 Ok(Some(Value::Array(Rc::new(
4056 result.into_iter().map(Value::Float).collect(),
4057 ))))
4058 }
4059
4060 "coalesce" => {
4061 if args.is_empty() { return Err("coalesce requires at least 1 argument".into()); }
4062 let first_is_array = matches!(&args[0], Value::Array(_));
4064 if args.len() == 2 && first_is_array {
4065 let a = match &args[0] {
4067 Value::Array(a) => a.as_ref().clone(),
4068 _ => unreachable!(),
4069 };
4070 let b = match &args[1] {
4071 Value::Array(b) => b.as_ref().clone(),
4072 _ => return Err(format!("coalesce: second argument must be Array, got {}", args[1].type_name())),
4073 };
4074 if a.len() != b.len() {
4075 return Err(format!("coalesce: arrays must have equal length, got {} and {}", a.len(), b.len()));
4076 }
4077 let result: Vec<Value> = a.iter().zip(b.iter()).map(|(va, vb)| {
4078 let is_null_a = matches!(va, Value::Na) || matches!(va, Value::Void) || matches!(va, Value::Float(f) if f.is_nan());
4079 if is_null_a { vb.clone() } else { va.clone() }
4080 }).collect();
4081 Ok(Some(Value::Array(Rc::new(result))))
4082 } else {
4083 for arg in args {
4085 let is_null = matches!(arg, Value::Na) || matches!(arg, Value::Void) || matches!(arg, Value::Float(f) if f.is_nan());
4086 if !is_null { return Ok(Some(arg.clone())); }
4087 }
4088 Ok(Some(args.last().cloned().unwrap_or(Value::Na)))
4090 }
4091 }
4092
4093 "cut" => {
4094 if args.len() != 2 { return Err("cut requires 2 arguments (array, breaks)".into()); }
4095 let data = value_to_f64_vec(&args[0])?;
4096 let breaks = value_to_f64_vec(&args[1])?;
4097 if breaks.is_empty() {
4098 return Err("cut: breaks array must not be empty".into());
4099 }
4100 let mut sorted_breaks = breaks.clone();
4101 sorted_breaks.sort_by(f64::total_cmp);
4102 let labels: Vec<Value> = data.iter().map(|&x| {
4103 let label = if x <= sorted_breaks[0] {
4105 format!("(-inf,{}]", sorted_breaks[0])
4106 } else if x > sorted_breaks[sorted_breaks.len() - 1] {
4107 format!("({},inf)", sorted_breaks[sorted_breaks.len() - 1])
4108 } else {
4109 let mut found = String::new();
4110 for i in 1..sorted_breaks.len() {
4111 if x <= sorted_breaks[i] {
4112 found = format!("({},{}]", sorted_breaks[i - 1], sorted_breaks[i]);
4113 break;
4114 }
4115 }
4116 if found.is_empty() {
4117 format!("({},inf)", sorted_breaks[sorted_breaks.len() - 1])
4119 } else {
4120 found
4121 }
4122 };
4123 Value::String(Rc::new(label))
4124 }).collect();
4125 Ok(Some(Value::Array(Rc::new(labels))))
4126 }
4127
4128 "qcut" => {
4129 if args.len() != 2 { return Err("qcut requires 2 arguments (array, n_bins)".into()); }
4130 let data = value_to_f64_vec(&args[0])?;
4131 let n = value_to_usize(&args[1])?;
4132 if n == 0 {
4133 return Err("qcut: n_bins must be > 0".into());
4134 }
4135 let mut breaks = Vec::with_capacity(n - 1);
4137 for i in 1..n {
4138 let p = i as f64 / n as f64;
4139 let q = crate::stats::quantile(&data, p)?;
4140 breaks.push(q);
4141 }
4142 breaks.dedup_by(|a, b| *a == *b);
4144
4145 let mut sorted_breaks = breaks.clone();
4147 sorted_breaks.sort_by(f64::total_cmp);
4148 let labels: Vec<Value> = data.iter().map(|&x| {
4149 let label = if sorted_breaks.is_empty() || x <= sorted_breaks[0] {
4150 format!("(-inf,{}]", sorted_breaks.first().copied().unwrap_or(x))
4151 } else if x > sorted_breaks[sorted_breaks.len() - 1] {
4152 format!("({},inf)", sorted_breaks[sorted_breaks.len() - 1])
4153 } else {
4154 let mut found = String::new();
4155 for i in 1..sorted_breaks.len() {
4156 if x <= sorted_breaks[i] {
4157 found = format!("({},{}]", sorted_breaks[i - 1], sorted_breaks[i]);
4158 break;
4159 }
4160 }
4161 if found.is_empty() {
4162 format!("({},inf)", sorted_breaks[sorted_breaks.len() - 1])
4163 } else {
4164 found
4165 }
4166 };
4167 Value::String(Rc::new(label))
4168 }).collect();
4169 Ok(Some(Value::Array(Rc::new(labels))))
4170 }
4171
4172 "min_max_scale" => {
4173 if args.len() != 3 { return Err("min_max_scale requires 3 arguments (array, low, high)".into()); }
4174 let data = value_to_f64_vec(&args[0])?;
4175 let low = value_to_f64(&args[1])?;
4176 let high = value_to_f64(&args[2])?;
4177 if data.is_empty() {
4178 return Ok(Some(Value::Array(Rc::new(vec![]))));
4179 }
4180 let mut min_val = f64::INFINITY;
4181 let mut max_val = f64::NEG_INFINITY;
4182 for &x in &data {
4183 if x < min_val { min_val = x; }
4184 if x > max_val { max_val = x; }
4185 }
4186 let range = max_val - min_val;
4187 let result: Vec<Value> = if range == 0.0 {
4188 let mid = (low + high) / 2.0;
4190 data.iter().map(|_| Value::Float(mid)).collect()
4191 } else {
4192 data.iter().map(|&x| {
4193 use cjc_repro::KahanAccumulatorF64;
4194 let t = (x - min_val) / range;
4195 let mut acc = KahanAccumulatorF64::new();
4196 acc.add(low * (1.0 - t));
4197 acc.add(high * t);
4198 Value::Float(acc.finalize())
4199 }).collect()
4200 };
4201 Ok(Some(Value::Array(Rc::new(result))))
4202 }
4203
4204 "robust_scale" => {
4205 if args.len() != 1 { return Err("robust_scale requires 1 argument (array)".into()); }
4206 let data = value_to_f64_vec(&args[0])?;
4207 if data.is_empty() {
4208 return Ok(Some(Value::Array(Rc::new(vec![]))));
4209 }
4210 let med = crate::stats::median(&data)?;
4211 let iqr_val = crate::stats::iqr(&data)?;
4212 let result: Vec<Value> = if iqr_val == 0.0 {
4213 data.iter().map(|&x| Value::Float(x - med)).collect()
4214 } else {
4215 data.iter().map(|&x| Value::Float((x - med) / iqr_val)).collect()
4216 };
4217 Ok(Some(Value::Array(Rc::new(result))))
4218 }
4219
4220 "as_factor" => {
4222 if args.len() != 1 { return Err("as_factor requires 1 argument (string array)".into()); }
4224 let arr = match &args[0] {
4225 Value::Array(a) => a.as_ref().clone(),
4226 _ => return Err("as_factor: argument must be Array of strings".into()),
4227 };
4228 let mut levels: Vec<String> = Vec::new();
4230 let mut level_index = std::collections::BTreeMap::new();
4231 let mut codes: Vec<Value> = Vec::with_capacity(arr.len());
4232 for item in &arr {
4233 let s = match item {
4234 Value::String(s) => s.as_str().to_string(),
4235 _ => format!("{}", item),
4236 };
4237 let idx = if let Some(&idx) = level_index.get(&s) {
4238 idx
4239 } else {
4240 let idx = levels.len() as i64;
4241 level_index.insert(s.clone(), idx);
4242 levels.push(s);
4243 idx
4244 };
4245 codes.push(Value::Int(idx));
4246 }
4247 let level_values: Vec<Value> = levels.into_iter()
4248 .map(|s| Value::String(Rc::new(s)))
4249 .collect();
4250 let mut fields = std::collections::BTreeMap::new();
4251 fields.insert("__type".to_string(), Value::String(Rc::new("Factor".to_string())));
4252 fields.insert("levels".to_string(), Value::Array(Rc::new(level_values)));
4253 fields.insert("codes".to_string(), Value::Array(Rc::new(codes)));
4254 Ok(Some(Value::Struct { name: "Factor".to_string(), fields }))
4255 }
4256
4257 "factor_levels" => {
4258 if args.len() != 1 { return Err("factor_levels requires 1 argument (Factor)".into()); }
4260 match &args[0] {
4261 Value::Struct { name, fields } if name == "Factor" => {
4262 match fields.get("levels") {
4263 Some(v) => Ok(Some(v.clone())),
4264 None => Err("factor_levels: Factor missing 'levels' field".into()),
4265 }
4266 }
4267 _ => Err("factor_levels: argument must be a Factor".into()),
4268 }
4269 }
4270
4271 "factor_codes" => {
4272 if args.len() != 1 { return Err("factor_codes requires 1 argument (Factor)".into()); }
4274 match &args[0] {
4275 Value::Struct { name, fields } if name == "Factor" => {
4276 match fields.get("codes") {
4277 Some(v) => Ok(Some(v.clone())),
4278 None => Err("factor_codes: Factor missing 'codes' field".into()),
4279 }
4280 }
4281 _ => Err("factor_codes: argument must be a Factor".into()),
4282 }
4283 }
4284
4285 "fct_relevel" => {
4286 if args.len() != 2 { return Err("fct_relevel requires 2 arguments (Factor, new_level_order)".into()); }
4288 let (old_levels, old_codes) = match &args[0] {
4289 Value::Struct { name, fields } if name == "Factor" => {
4290 let levels = match fields.get("levels") {
4291 Some(Value::Array(a)) => a.as_ref().clone(),
4292 _ => return Err("fct_relevel: Factor missing 'levels'".into()),
4293 };
4294 let codes = match fields.get("codes") {
4295 Some(Value::Array(a)) => a.as_ref().clone(),
4296 _ => return Err("fct_relevel: Factor missing 'codes'".into()),
4297 };
4298 (levels, codes)
4299 }
4300 _ => return Err("fct_relevel: first argument must be a Factor".into()),
4301 };
4302 let new_order = match &args[1] {
4303 Value::Array(a) => a.as_ref().clone(),
4304 _ => return Err("fct_relevel: second argument must be array of level strings".into()),
4305 };
4306 let old_strs: Vec<String> = old_levels.iter().map(|v| match v {
4308 Value::String(s) => s.as_str().to_string(),
4309 _ => format!("{}", v),
4310 }).collect();
4311 let new_strs: Vec<String> = new_order.iter().map(|v| match v {
4313 Value::String(s) => s.as_str().to_string(),
4314 _ => format!("{}", v),
4315 }).collect();
4316 let mut remap = std::collections::BTreeMap::new();
4318 for (old_idx, s) in old_strs.iter().enumerate() {
4319 if let Some(new_idx) = new_strs.iter().position(|ns| ns == s) {
4320 remap.insert(old_idx as i64, new_idx as i64);
4321 }
4322 }
4323 let new_codes: Vec<Value> = old_codes.iter().map(|v| {
4325 if let Value::Int(c) = v {
4326 Value::Int(remap.get(c).copied().unwrap_or(*c))
4327 } else { v.clone() }
4328 }).collect();
4329 let new_level_values: Vec<Value> = new_strs.into_iter()
4330 .map(|s| Value::String(Rc::new(s)))
4331 .collect();
4332 let mut fields = std::collections::BTreeMap::new();
4333 fields.insert("__type".to_string(), Value::String(Rc::new("Factor".to_string())));
4334 fields.insert("levels".to_string(), Value::Array(Rc::new(new_level_values)));
4335 fields.insert("codes".to_string(), Value::Array(Rc::new(new_codes)));
4336 Ok(Some(Value::Struct { name: "Factor".to_string(), fields }))
4337 }
4338
4339 "fct_lump" => {
4340 if args.len() != 2 { return Err("fct_lump requires 2 arguments (Factor, n)".into()); }
4343 let (old_levels, old_codes) = match &args[0] {
4344 Value::Struct { name, fields } if name == "Factor" => {
4345 let levels = match fields.get("levels") {
4346 Some(Value::Array(a)) => a.as_ref().clone(),
4347 _ => return Err("fct_lump: Factor missing 'levels'".into()),
4348 };
4349 let codes = match fields.get("codes") {
4350 Some(Value::Array(a)) => a.as_ref().clone(),
4351 _ => return Err("fct_lump: Factor missing 'codes'".into()),
4352 };
4353 (levels, codes)
4354 }
4355 _ => return Err("fct_lump: first argument must be a Factor".into()),
4356 };
4357 let n = match &args[1] {
4358 Value::Int(n) => *n as usize,
4359 _ => return Err("fct_lump: second argument must be Int".into()),
4360 };
4361 let mut freq = std::collections::BTreeMap::new();
4363 for v in &old_codes {
4364 if let Value::Int(c) = v { *freq.entry(*c).or_insert(0usize) += 1; }
4365 }
4366 let mut freq_vec: Vec<(i64, usize)> = freq.into_iter().collect();
4368 freq_vec.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
4369 let keep_codes: std::collections::BTreeSet<i64> = freq_vec.iter().take(n).map(|(c, _)| *c).collect();
4371 let old_strs: Vec<String> = old_levels.iter().map(|v| match v {
4373 Value::String(s) => s.as_str().to_string(),
4374 _ => format!("{}", v),
4375 }).collect();
4376 let mut new_levels: Vec<String> = Vec::new();
4377 let mut code_remap = std::collections::BTreeMap::new();
4378 for (old_idx, s) in old_strs.iter().enumerate() {
4379 if keep_codes.contains(&(old_idx as i64)) {
4380 let new_idx = new_levels.len() as i64;
4381 code_remap.insert(old_idx as i64, new_idx);
4382 new_levels.push(s.clone());
4383 }
4384 }
4385 let other_idx = new_levels.len() as i64;
4386 new_levels.push("Other".to_string());
4387 let new_codes: Vec<Value> = old_codes.iter().map(|v| {
4389 if let Value::Int(c) = v {
4390 Value::Int(*code_remap.get(c).unwrap_or(&other_idx))
4391 } else { v.clone() }
4392 }).collect();
4393 let new_level_values: Vec<Value> = new_levels.into_iter()
4394 .map(|s| Value::String(Rc::new(s)))
4395 .collect();
4396 let mut fields = std::collections::BTreeMap::new();
4397 fields.insert("__type".to_string(), Value::String(Rc::new("Factor".to_string())));
4398 fields.insert("levels".to_string(), Value::Array(Rc::new(new_level_values)));
4399 fields.insert("codes".to_string(), Value::Array(Rc::new(new_codes)));
4400 Ok(Some(Value::Struct { name: "Factor".to_string(), fields }))
4401 }
4402
4403 "fct_count" => {
4404 if args.len() != 1 { return Err("fct_count requires 1 argument (Factor)".into()); }
4406 let (levels, codes) = match &args[0] {
4407 Value::Struct { name, fields } if name == "Factor" => {
4408 let levels = match fields.get("levels") {
4409 Some(Value::Array(a)) => a.as_ref().clone(),
4410 _ => return Err("fct_count: Factor missing 'levels'".into()),
4411 };
4412 let codes = match fields.get("codes") {
4413 Some(Value::Array(a)) => a.as_ref().clone(),
4414 _ => return Err("fct_count: Factor missing 'codes'".into()),
4415 };
4416 (levels, codes)
4417 }
4418 _ => return Err("fct_count: argument must be a Factor".into()),
4419 };
4420 let mut freq = std::collections::BTreeMap::new();
4421 for v in &codes {
4422 if let Value::Int(c) = v { *freq.entry(*c).or_insert(0i64) += 1; }
4423 }
4424 let result: Vec<Value> = levels.iter().enumerate().map(|(i, lev)| {
4425 let count = freq.get(&(i as i64)).copied().unwrap_or(0);
4426 Value::Tuple(Rc::new(vec![lev.clone(), Value::Int(count)]))
4427 }).collect();
4428 Ok(Some(Value::Array(Rc::new(result))))
4429 }
4430
4431 "jarque_bera" => {
4433 if args.len() != 1 { return Err("jarque_bera requires 1 argument (data array)".into()); }
4434 let data = value_to_f64_vec(&args[0])?;
4435 let r = crate::hypothesis::jarque_bera(&data)?;
4436 let mut fields = std::collections::BTreeMap::new();
4437 fields.insert("statistic".to_string(), Value::Float(r.statistic));
4438 fields.insert("p_value".to_string(), Value::Float(r.p_value));
4439 Ok(Some(Value::Struct { name: "JarqueBeraResult".to_string(), fields }))
4440 }
4441
4442 "anderson_darling" => {
4443 if args.len() != 1 { return Err("anderson_darling requires 1 argument (data array)".into()); }
4444 let data = value_to_f64_vec(&args[0])?;
4445 let r = crate::hypothesis::anderson_darling(&data)?;
4446 let mut fields = std::collections::BTreeMap::new();
4447 fields.insert("statistic".to_string(), Value::Float(r.statistic));
4448 fields.insert("p_value".to_string(), Value::Float(r.p_value));
4449 Ok(Some(Value::Struct { name: "AndersonDarlingResult".to_string(), fields }))
4450 }
4451
4452 "ks_test" => {
4453 if args.len() != 1 { return Err("ks_test requires 1 argument (data array)".into()); }
4454 let data = value_to_f64_vec(&args[0])?;
4455 let r = crate::hypothesis::ks_test_normal(&data)?;
4456 let mut fields = std::collections::BTreeMap::new();
4457 fields.insert("statistic".to_string(), Value::Float(r.statistic));
4458 fields.insert("p_value".to_string(), Value::Float(r.p_value));
4459 Ok(Some(Value::Struct { name: "KSResult".to_string(), fields }))
4460 }
4461
4462 "cohens_d" => {
4464 if args.len() != 2 { return Err("cohens_d requires 2 arguments (x, y)".into()); }
4465 let x = value_to_f64_vec(&args[0])?;
4466 let y = value_to_f64_vec(&args[1])?;
4467 let d = crate::hypothesis::cohens_d(&x, &y)?;
4468 Ok(Some(Value::Float(d)))
4469 }
4470
4471 "eta_squared" => {
4472 if args.len() < 2 { return Err("eta_squared requires at least 2 group arguments".into()); }
4473 let groups: Vec<Vec<f64>> = args.iter().map(|a| value_to_f64_vec(a)).collect::<Result<Vec<_>, _>>()?;
4474 let refs: Vec<&[f64]> = groups.iter().map(|g| g.as_slice()).collect();
4475 let es = crate::hypothesis::eta_squared(&refs)?;
4476 Ok(Some(Value::Float(es)))
4477 }
4478
4479 "cramers_v" => {
4480 if args.len() != 3 { return Err("cramers_v requires 3 arguments (table, nrows, ncols)".into()); }
4482 let table = value_to_f64_vec(&args[0])?;
4483 let nrows = match &args[1] { Value::Int(n) => *n as usize, _ => return Err("cramers_v: nrows must be Int".into()) };
4484 let ncols = match &args[2] { Value::Int(n) => *n as usize, _ => return Err("cramers_v: ncols must be Int".into()) };
4485 let v = crate::hypothesis::cramers_v(&table, nrows, ncols)?;
4486 Ok(Some(Value::Float(v)))
4487 }
4488
4489 "levene_test" => {
4491 if args.len() < 2 { return Err("levene_test requires at least 2 group arguments".into()); }
4492 let groups: Vec<Vec<f64>> = args.iter().map(|a| value_to_f64_vec(a)).collect::<Result<Vec<_>, _>>()?;
4493 let refs: Vec<&[f64]> = groups.iter().map(|g| g.as_slice()).collect();
4494 let (w, p) = crate::hypothesis::levene_test(&refs)?;
4495 let mut fields = std::collections::BTreeMap::new();
4496 fields.insert("statistic".to_string(), Value::Float(w));
4497 fields.insert("p_value".to_string(), Value::Float(p));
4498 Ok(Some(Value::Struct { name: "LeveneResult".to_string(), fields }))
4499 }
4500
4501 "bartlett_test" => {
4502 if args.len() < 2 { return Err("bartlett_test requires at least 2 group arguments".into()); }
4503 let groups: Vec<Vec<f64>> = args.iter().map(|a| value_to_f64_vec(a)).collect::<Result<Vec<_>, _>>()?;
4504 let refs: Vec<&[f64]> = groups.iter().map(|g| g.as_slice()).collect();
4505 let (t, p) = crate::hypothesis::bartlett_test(&refs)?;
4506 let mut fields = std::collections::BTreeMap::new();
4507 fields.insert("statistic".to_string(), Value::Float(t));
4508 fields.insert("p_value".to_string(), Value::Float(p));
4509 Ok(Some(Value::Struct { name: "BartlettResult".to_string(), fields }))
4510 }
4511
4512 "latin_hypercube" => {
4514 if args.len() != 3 { return Err("latin_hypercube requires 3 arguments (n, dims, seed)".into()); }
4516 let n = match &args[0] { Value::Int(n) => *n as usize, _ => return Err("latin_hypercube: n must be Int".into()) };
4517 let dims = match &args[1] { Value::Int(d) => *d as usize, _ => return Err("latin_hypercube: dims must be Int".into()) };
4518 let seed = match &args[2] { Value::Int(s) => *s as u64, _ => return Err("latin_hypercube: seed must be Int".into()) };
4519 let t = crate::distributions::latin_hypercube_sample(n, dims, seed);
4520 Ok(Some(Value::Tensor(t)))
4521 }
4522
4523 "sobol_sequence" => {
4524 if args.len() != 2 { return Err("sobol_sequence requires 2 arguments (n, dims)".into()); }
4526 let n = match &args[0] { Value::Int(n) => *n as usize, _ => return Err("sobol_sequence: n must be Int".into()) };
4527 let dims = match &args[1] { Value::Int(d) => *d as usize, _ => return Err("sobol_sequence: dims must be Int".into()) };
4528 let t = crate::distributions::sobol_sequence(n, dims);
4529 Ok(Some(Value::Tensor(t)))
4530 }
4531
4532 "train_test_split" => {
4533 if args.len() != 3 { return Err("train_test_split requires 3 arguments (n, test_fraction, seed)".into()); }
4535 let n = match &args[0] { Value::Int(n) => *n as usize, _ => return Err("train_test_split: n must be Int".into()) };
4536 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()) };
4537 let seed = match &args[2] { Value::Int(s) => *s as u64, _ => return Err("train_test_split: seed must be Int".into()) };
4538 let (train, test) = crate::ml::train_test_split(n, frac, seed);
4539 let train_vals: Vec<Value> = train.into_iter().map(|i| Value::Int(i as i64)).collect();
4540 let test_vals: Vec<Value> = test.into_iter().map(|i| Value::Int(i as i64)).collect();
4541 Ok(Some(Value::Tuple(Rc::new(vec![
4542 Value::Array(Rc::new(train_vals)),
4543 Value::Array(Rc::new(test_vals)),
4544 ]))))
4545 }
4546
4547 "kfold_indices" => {
4548 if args.len() != 3 { return Err("kfold_indices requires 3 arguments (n, k, seed)".into()); }
4550 let n = match &args[0] { Value::Int(n) => *n as usize, _ => return Err("kfold_indices: n must be Int".into()) };
4551 let k = match &args[1] { Value::Int(k) => *k as usize, _ => return Err("kfold_indices: k must be Int".into()) };
4552 let seed = match &args[2] { Value::Int(s) => *s as u64, _ => return Err("kfold_indices: seed must be Int".into()) };
4553 let folds = crate::ml::kfold_indices(n, k, seed);
4554 let result: Vec<Value> = folds.into_iter().map(|(train, test)| {
4555 let train_vals: Vec<Value> = train.into_iter().map(|i| Value::Int(i as i64)).collect();
4556 let test_vals: Vec<Value> = test.into_iter().map(|i| Value::Int(i as i64)).collect();
4557 Value::Tuple(Rc::new(vec![
4558 Value::Array(Rc::new(train_vals)),
4559 Value::Array(Rc::new(test_vals)),
4560 ]))
4561 }).collect();
4562 Ok(Some(Value::Array(Rc::new(result))))
4563 }
4564
4565 "bootstrap" => {
4566 if args.len() != 4 { return Err("bootstrap requires 4 arguments (data, n_resamples, stat_fn, seed)".into()); }
4569 let data = match &args[0] {
4570 Value::Array(a) => {
4571 let mut v = Vec::with_capacity(a.len());
4572 for val in a.iter() {
4573 match val {
4574 Value::Float(f) => v.push(*f),
4575 Value::Int(i) => v.push(*i as f64),
4576 _ => return Err("bootstrap: data elements must be numeric".into()),
4577 }
4578 }
4579 v
4580 }
4581 _ => return Err("bootstrap: data must be an array".into()),
4582 };
4583 let n_resamples = match &args[1] { Value::Int(n) => *n as usize, _ => return Err("bootstrap: n_resamples must be Int".into()) };
4584 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()) };
4585 let seed = match &args[3] { Value::Int(s) => *s as u64, _ => return Err("bootstrap: seed must be Int".into()) };
4586 let (point, ci_lower, ci_upper, se) = crate::ml::bootstrap(&data, n_resamples, stat_fn, seed)?;
4587 let mut fields = std::collections::BTreeMap::new();
4588 fields.insert("point".to_string(), Value::Float(point));
4589 fields.insert("ci_lower".to_string(), Value::Float(ci_lower));
4590 fields.insert("ci_upper".to_string(), Value::Float(ci_upper));
4591 fields.insert("se".to_string(), Value::Float(se));
4592 Ok(Some(Value::Struct {
4593 name: "BootstrapResult".into(),
4594 fields,
4595 }))
4596 }
4597 "permutation_test" => {
4598 if args.len() != 4 { return Err("permutation_test requires 4 arguments (x, y, n_perms, seed)".into()); }
4600 let extract_floats = |val: &Value, name: &str| -> Result<Vec<f64>, String> {
4601 match val {
4602 Value::Array(a) => {
4603 let mut v = Vec::with_capacity(a.len());
4604 for el in a.iter() {
4605 match el {
4606 Value::Float(f) => v.push(*f),
4607 Value::Int(i) => v.push(*i as f64),
4608 _ => return Err(format!("permutation_test: {} elements must be numeric", name)),
4609 }
4610 }
4611 Ok(v)
4612 }
4613 _ => Err(format!("permutation_test: {} must be an array", name)),
4614 }
4615 };
4616 let x = extract_floats(&args[0], "x")?;
4617 let y = extract_floats(&args[1], "y")?;
4618 let n_perms = match &args[2] { Value::Int(n) => *n as usize, _ => return Err("permutation_test: n_perms must be Int".into()) };
4619 let seed = match &args[3] { Value::Int(s) => *s as u64, _ => return Err("permutation_test: seed must be Int".into()) };
4620 let (observed, p_value) = crate::ml::permutation_test(&x, &y, n_perms, seed)?;
4621 let mut fields = std::collections::BTreeMap::new();
4622 fields.insert("observed_diff".to_string(), Value::Float(observed));
4623 fields.insert("p_value".to_string(), Value::Float(p_value));
4624 Ok(Some(Value::Struct {
4625 name: "PermutationResult".into(),
4626 fields,
4627 }))
4628 }
4629
4630 "stratified_split" => {
4631 if args.len() != 3 { return Err("stratified_split requires 3 arguments (labels, test_fraction, seed)".into()); }
4633 let labels = match &args[0] {
4634 Value::Array(a) => {
4635 let mut v = Vec::with_capacity(a.len());
4636 for val in a.iter() {
4637 match val {
4638 Value::Int(i) => v.push(*i),
4639 _ => return Err("stratified_split: labels must be integer array".into()),
4640 }
4641 }
4642 v
4643 }
4644 _ => return Err("stratified_split: labels must be an array".into()),
4645 };
4646 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()) };
4647 let seed = match &args[2] { Value::Int(s) => *s as u64, _ => return Err("stratified_split: seed must be Int".into()) };
4648 let (train, test) = crate::ml::stratified_split(&labels, frac, seed);
4649 let train_vals: Vec<Value> = train.into_iter().map(|i| Value::Int(i as i64)).collect();
4650 let test_vals: Vec<Value> = test.into_iter().map(|i| Value::Int(i as i64)).collect();
4651 Ok(Some(Value::Tuple(Rc::new(vec![
4652 Value::Array(Rc::new(train_vals)),
4653 Value::Array(Rc::new(test_vals)),
4654 ]))))
4655 }
4656
4657 "parse_date" => {
4659 if args.len() != 2 { return Err("parse_date requires 2 arguments (string, format)".into()); }
4660 let s = match &args[0] { Value::String(s) => s.as_str().to_string(), _ => return Err("parse_date: first arg must be String".into()) };
4661 let fmt = match &args[1] { Value::String(s) => s.as_str().to_string(), _ => return Err("parse_date: second arg must be String".into()) };
4662 Ok(Some(Value::Int(crate::datetime::parse_date(&s, &fmt))))
4663 }
4664 "date_format" => {
4665 if args.len() != 2 { return Err("date_format requires 2 arguments (timestamp, format)".into()); }
4666 let ts = match &args[0] { Value::Int(v) => *v, Value::Float(v) => *v as i64, _ => return Err("date_format: first arg must be numeric".into()) };
4667 let fmt = match &args[1] { Value::String(s) => s.as_str().to_string(), _ => return Err("date_format: second arg must be String".into()) };
4668 Ok(Some(Value::String(Rc::new(crate::datetime::date_format_custom(ts, &fmt)))))
4669 }
4670 "year" => {
4671 if args.len() != 1 { return Err("year requires 1 argument (timestamp)".into()); }
4672 let ts = match &args[0] { Value::Int(v) => *v, Value::Float(v) => *v as i64, _ => return Err("year: arg must be numeric".into()) };
4673 Ok(Some(Value::Int(crate::datetime::datetime_year(ts))))
4674 }
4675 "month" => {
4676 if args.len() != 1 { return Err("month requires 1 argument (timestamp)".into()); }
4677 let ts = match &args[0] { Value::Int(v) => *v, Value::Float(v) => *v as i64, _ => return Err("month: arg must be numeric".into()) };
4678 Ok(Some(Value::Int(crate::datetime::datetime_month(ts))))
4679 }
4680 "day" => {
4681 if args.len() != 1 { return Err("day requires 1 argument (timestamp)".into()); }
4682 let ts = match &args[0] { Value::Int(v) => *v, Value::Float(v) => *v as i64, _ => return Err("day: arg must be numeric".into()) };
4683 Ok(Some(Value::Int(crate::datetime::datetime_day(ts))))
4684 }
4685 "hour" => {
4686 if args.len() != 1 { return Err("hour requires 1 argument (timestamp)".into()); }
4687 let ts = match &args[0] { Value::Int(v) => *v, Value::Float(v) => *v as i64, _ => return Err("hour: arg must be numeric".into()) };
4688 Ok(Some(Value::Int(crate::datetime::datetime_hour(ts))))
4689 }
4690 "minute" => {
4691 if args.len() != 1 { return Err("minute requires 1 argument (timestamp)".into()); }
4692 let ts = match &args[0] { Value::Int(v) => *v, Value::Float(v) => *v as i64, _ => return Err("minute: arg must be numeric".into()) };
4693 Ok(Some(Value::Int(crate::datetime::datetime_minute(ts))))
4694 }
4695 "second" => {
4696 if args.len() != 1 { return Err("second requires 1 argument (timestamp)".into()); }
4697 let ts = match &args[0] { Value::Int(v) => *v, Value::Float(v) => *v as i64, _ => return Err("second: arg must be numeric".into()) };
4698 Ok(Some(Value::Int(crate::datetime::datetime_second(ts))))
4699 }
4700 "date_diff" => {
4701 if args.len() != 3 { return Err("date_diff requires 3 arguments (ts1, ts2, unit)".into()); }
4702 let ts1 = match &args[0] { Value::Int(v) => *v, Value::Float(v) => *v as i64, _ => return Err("date_diff: first arg must be numeric".into()) };
4703 let ts2 = match &args[1] { Value::Int(v) => *v, Value::Float(v) => *v as i64, _ => return Err("date_diff: second arg must be numeric".into()) };
4704 let unit = match &args[2] { Value::String(s) => s.as_str().to_string(), _ => return Err("date_diff: third arg must be String".into()) };
4705 match crate::datetime::date_diff_units(ts1, ts2, &unit) {
4706 Ok(v) => Ok(Some(Value::Int(v))),
4707 Err(e) => Err(e),
4708 }
4709 }
4710 "date_add" => {
4711 if args.len() != 3 { return Err("date_add requires 3 arguments (ts, amount, unit)".into()); }
4712 let ts = match &args[0] { Value::Int(v) => *v, Value::Float(v) => *v as i64, _ => return Err("date_add: first arg must be numeric".into()) };
4713 let amount = match &args[1] { Value::Int(v) => *v, Value::Float(v) => *v as i64, _ => return Err("date_add: second arg must be numeric".into()) };
4714 let unit = match &args[2] { Value::String(s) => s.as_str().to_string(), _ => return Err("date_add: third arg must be String".into()) };
4715 match crate::datetime::date_add_units(ts, amount, &unit) {
4716 Ok(v) => Ok(Some(Value::Int(v))),
4717 Err(e) => Err(e),
4718 }
4719 }
4720 "fill_na" => {
4721 if args.len() != 2 { return Err("fill_na requires 2 arguments (array, fill_value)".into()); }
4723 let arr = match &args[0] {
4724 Value::Array(a) => a.as_ref().clone(),
4725 _ => return Err("fill_na: first arg must be Array".into()),
4726 };
4727 let fill = &args[1];
4728 let result: Vec<Value> = arr.iter().map(|v| {
4729 if matches!(v, Value::Na) { fill.clone() } else { v.clone() }
4730 }).collect();
4731 Ok(Some(Value::Array(Rc::new(result))))
4732 }
4733
4734 "ridge_regression" => {
4736 dispatch_ridge_regression(&args)
4737 }
4738 "lasso_regression" => {
4739 dispatch_lasso_regression(&args)
4740 }
4741 "elastic_net" => {
4742 dispatch_elastic_net(&args)
4743 }
4744
4745 _ => Ok(None), }
4747}
4748
4749fn value_to_sparse(val: &Value) -> Result<&crate::sparse::SparseCsr, String> {
4751 match val {
4752 Value::SparseTensor(s) => Ok(s),
4753 _ => Err(format!("expected SparseTensor, got {}", val.type_name())),
4754 }
4755}
4756
4757pub fn peak_rss_kb() -> u64 {
4769 #[cfg(target_os = "windows")]
4770 {
4771 peak_rss_windows()
4772 }
4773 #[cfg(target_os = "linux")]
4774 {
4775 peak_rss_linux()
4776 }
4777 #[cfg(target_os = "macos")]
4778 {
4779 peak_rss_macos()
4780 }
4781 #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
4782 {
4783 0
4784 }
4785}
4786
4787#[cfg(target_os = "windows")]
4788fn peak_rss_windows() -> u64 {
4789 use std::mem::{size_of, MaybeUninit};
4790
4791 #[repr(C)]
4792 #[allow(non_snake_case)]
4793 struct ProcessMemoryCounters {
4794 cb: u32,
4795 PageFaultCount: u32,
4796 PeakWorkingSetSize: usize,
4797 WorkingSetSize: usize,
4798 QuotaPeakPagedPoolUsage: usize,
4799 QuotaPagedPoolUsage: usize,
4800 QuotaPeakNonPagedPoolUsage: usize,
4801 QuotaNonPagedPoolUsage: usize,
4802 PagefileUsage: usize,
4803 PeakPagefileUsage: usize,
4804 }
4805
4806 extern "system" {
4807 fn GetCurrentProcess() -> isize;
4808 fn K32GetProcessMemoryInfo(
4809 hProcess: isize,
4810 ppsmemCounters: *mut ProcessMemoryCounters,
4811 cb: u32,
4812 ) -> i32;
4813 }
4814
4815 unsafe {
4816 let mut pmc = MaybeUninit::<ProcessMemoryCounters>::zeroed();
4817 let pmc_ref = pmc.as_mut_ptr();
4818 (*pmc_ref).cb = size_of::<ProcessMemoryCounters>() as u32;
4819 let handle = GetCurrentProcess();
4820 if K32GetProcessMemoryInfo(handle, pmc_ref, (*pmc_ref).cb) != 0 {
4821 let pmc = pmc.assume_init();
4822 (pmc.PeakWorkingSetSize / 1024) as u64
4823 } else {
4824 0
4825 }
4826 }
4827}
4828
4829#[cfg(target_os = "linux")]
4830fn peak_rss_linux() -> u64 {
4831 if let Ok(contents) = std::fs::read_to_string("/proc/self/status") {
4833 for line in contents.lines() {
4834 if line.starts_with("VmHWM:") {
4835 let parts: Vec<&str> = line.split_whitespace().collect();
4836 if parts.len() >= 2 {
4837 if let Ok(kb) = parts[1].parse::<u64>() {
4838 return kb;
4839 }
4840 }
4841 }
4842 }
4843 }
4844 0
4845}
4846
4847#[cfg(target_os = "macos")]
4848fn peak_rss_macos() -> u64 {
4849 #[repr(C)]
4850 struct Rusage {
4851 ru_utime: [i64; 2], ru_stime: [i64; 2], ru_maxrss: i64, _padding: [i64; 11],
4856 }
4857
4858 extern "C" {
4859 fn getrusage(who: i32, usage: *mut Rusage) -> i32;
4860 }
4861
4862 unsafe {
4863 let mut usage = std::mem::MaybeUninit::<Rusage>::zeroed();
4864 if getrusage(0 , usage.as_mut_ptr()) == 0 {
4865 let usage = usage.assume_init();
4866 (usage.ru_maxrss as u64) / 1024
4868 } else {
4869 0
4870 }
4871 }
4872}
4873
4874fn regression_extract_x(val: &Value) -> Result<(Vec<f64>, usize, usize), String> {
4880 let t = value_to_tensor(val)?;
4881 let shape = t.shape();
4882 if shape.len() != 2 {
4883 return Err(format!("regression: X must be a 2D tensor, got {}D", shape.len()));
4884 }
4885 let n = shape[0];
4886 let p = shape[1];
4887 Ok((t.to_vec(), n, p))
4888}
4889
4890fn regression_extract_y(val: &Value) -> Result<Vec<f64>, String> {
4892 let t = value_to_tensor(val)?;
4893 let shape = t.shape();
4894 if shape.len() != 1 {
4895 return Err(format!("regression: y must be a 1D tensor, got {}D", shape.len()));
4896 }
4897 Ok(t.to_vec())
4898}
4899
4900fn standardize_x(data: &[f64], n: usize, p: usize) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
4903 let mut means = vec![0.0; p];
4904 let mut stds = vec![0.0; p];
4905
4906 for j in 0..p {
4908 let mut acc = cjc_repro::KahanAccumulatorF64::new();
4909 for i in 0..n {
4910 acc.add(data[i * p + j]);
4911 }
4912 means[j] = acc.finalize() / n as f64;
4913 }
4914
4915 for j in 0..p {
4917 let mut acc = cjc_repro::KahanAccumulatorF64::new();
4918 for i in 0..n {
4919 let d = data[i * p + j] - means[j];
4920 acc.add(d * d);
4921 }
4922 let variance = acc.finalize() / n as f64;
4923 stds[j] = if variance > 0.0 { variance.sqrt() } else { 1.0 };
4924 }
4925
4926 let mut standardized = vec![0.0; n * p];
4928 for i in 0..n {
4929 for j in 0..p {
4930 standardized[i * p + j] = (data[i * p + j] - means[j]) / stds[j];
4931 }
4932 }
4933
4934 (standardized, means, stds)
4935}
4936
4937fn compute_r_squared(y: &[f64], predictions: &[f64]) -> f64 {
4939 let n = y.len();
4940 let mut y_acc = cjc_repro::KahanAccumulatorF64::new();
4941 for &v in y { y_acc.add(v); }
4942 let y_mean = y_acc.finalize() / n as f64;
4943
4944 let mut ss_res = cjc_repro::KahanAccumulatorF64::new();
4945 let mut ss_tot = cjc_repro::KahanAccumulatorF64::new();
4946 for i in 0..n {
4947 let r = y[i] - predictions[i];
4948 ss_res.add(r * r);
4949 let t = y[i] - y_mean;
4950 ss_tot.add(t * t);
4951 }
4952 if ss_tot.finalize() == 0.0 { 1.0 } else { 1.0 - ss_res.finalize() / ss_tot.finalize() }
4953}
4954
4955fn regression_result_struct(
4957 name: &str,
4958 coefficients: Vec<f64>,
4959 intercept: f64,
4960 r_squared: f64,
4961 alpha: f64,
4962 n_iter: Option<i64>,
4963 converged: Option<bool>,
4964) -> Value {
4965 let p = coefficients.len();
4966 let coef_tensor = Tensor::from_vec(coefficients, &[p]).unwrap();
4967 let mut fields = std::collections::BTreeMap::new();
4968 fields.insert("coefficients".into(), Value::Tensor(coef_tensor));
4969 fields.insert("intercept".into(), Value::Float(intercept));
4970 fields.insert("r_squared".into(), Value::Float(r_squared));
4971 fields.insert("alpha".into(), Value::Float(alpha));
4972 if let Some(ni) = n_iter {
4973 fields.insert("n_iter".into(), Value::Int(ni));
4974 }
4975 if let Some(conv) = converged {
4976 fields.insert("converged".into(), Value::Bool(conv));
4977 }
4978 Value::Struct { name: name.to_string(), fields }
4979}
4980
4981fn dispatch_ridge_regression(args: &[Value]) -> Result<Option<Value>, String> {
4983 if args.len() != 3 {
4984 return Err("ridge_regression requires 3 arguments (X, y, alpha)".into());
4985 }
4986 let (x_data, n, p) = regression_extract_x(&args[0])?;
4987 let y = regression_extract_y(&args[1])?;
4988 if y.len() != n {
4989 return Err(format!("ridge_regression: X has {} rows but y has {} elements", n, y.len()));
4990 }
4991 let alpha = match &args[2] {
4992 Value::Float(v) => *v,
4993 Value::Int(v) => *v as f64,
4994 _ => return Err("ridge_regression: alpha must be numeric".into()),
4995 };
4996
4997 let (xs, means, stds) = standardize_x(&x_data, n, p);
4999
5000 let mut y_acc = cjc_repro::KahanAccumulatorF64::new();
5002 for &v in &y { y_acc.add(v); }
5003 let y_mean = y_acc.finalize() / n as f64;
5004 let yc: Vec<f64> = y.iter().map(|v| v - y_mean).collect();
5005
5006 let mut xtx = vec![0.0; p * p];
5008 for j1 in 0..p {
5009 for j2 in j1..p {
5010 let mut acc = cjc_repro::KahanAccumulatorF64::new();
5011 for i in 0..n {
5012 acc.add(xs[i * p + j1] * xs[i * p + j2]);
5013 }
5014 xtx[j1 * p + j2] = acc.finalize();
5015 xtx[j2 * p + j1] = acc.finalize();
5016 }
5017 xtx[j1 * p + j1] += alpha;
5018 }
5019
5020 let mut xty = vec![0.0; p];
5022 for j in 0..p {
5023 let mut acc = cjc_repro::KahanAccumulatorF64::new();
5024 for i in 0..n {
5025 acc.add(xs[i * p + j] * yc[i]);
5026 }
5027 xty[j] = acc.finalize();
5028 }
5029
5030 let beta_std = solve_linear_system(&xtx, &xty, p)?;
5032
5033 let mut coefficients = vec![0.0; p];
5035 let mut intercept = y_mean;
5036 for j in 0..p {
5037 coefficients[j] = beta_std[j] / stds[j];
5038 intercept -= coefficients[j] * means[j];
5039 }
5040
5041 let mut predictions = vec![0.0; n];
5043 for i in 0..n {
5044 let mut acc = cjc_repro::KahanAccumulatorF64::new();
5045 acc.add(intercept);
5046 for j in 0..p {
5047 acc.add(coefficients[j] * x_data[i * p + j]);
5048 }
5049 predictions[i] = acc.finalize();
5050 }
5051 let r_squared = compute_r_squared(&y, &predictions);
5052
5053 Ok(Some(regression_result_struct("RidgeResult", coefficients, intercept, r_squared, alpha, None, None)))
5054}
5055
5056fn dispatch_lasso_regression(args: &[Value]) -> Result<Option<Value>, String> {
5058 if args.len() < 3 || args.len() > 5 {
5059 return Err("lasso_regression requires 3-5 arguments (X, y, alpha, [max_iter], [tol])".into());
5060 }
5061 let (x_data, n, p) = regression_extract_x(&args[0])?;
5062 let y = regression_extract_y(&args[1])?;
5063 if y.len() != n {
5064 return Err(format!("lasso_regression: X has {} rows but y has {} elements", n, y.len()));
5065 }
5066 let alpha = match &args[2] {
5067 Value::Float(v) => *v,
5068 Value::Int(v) => *v as f64,
5069 _ => return Err("lasso_regression: alpha must be numeric".into()),
5070 };
5071 let max_iter = if args.len() > 3 {
5072 match &args[3] { Value::Int(v) => *v as usize, Value::Float(v) => *v as usize, _ => 1000 }
5073 } else { 1000 };
5074 let tol = if args.len() > 4 {
5075 match &args[4] { Value::Float(v) => *v, Value::Int(v) => *v as f64, _ => 1e-4 }
5076 } else { 1e-4 };
5077
5078 let (coefficients, intercept, n_iter, converged) =
5079 coordinate_descent(&x_data, &y, n, p, alpha, 1.0, max_iter, tol);
5080
5081 let mut predictions = vec![0.0; n];
5083 for i in 0..n {
5084 let mut acc = cjc_repro::KahanAccumulatorF64::new();
5085 acc.add(intercept);
5086 for j in 0..p {
5087 acc.add(coefficients[j] * x_data[i * p + j]);
5088 }
5089 predictions[i] = acc.finalize();
5090 }
5091 let r_squared = compute_r_squared(&y, &predictions);
5092
5093 Ok(Some(regression_result_struct("LassoResult", coefficients, intercept, r_squared, alpha, Some(n_iter as i64), Some(converged))))
5094}
5095
5096fn dispatch_elastic_net(args: &[Value]) -> Result<Option<Value>, String> {
5098 if args.len() < 4 || args.len() > 6 {
5099 return Err("elastic_net requires 4-6 arguments (X, y, alpha, l1_ratio, [max_iter], [tol])".into());
5100 }
5101 let (x_data, n, p) = regression_extract_x(&args[0])?;
5102 let y = regression_extract_y(&args[1])?;
5103 if y.len() != n {
5104 return Err(format!("elastic_net: X has {} rows but y has {} elements", n, y.len()));
5105 }
5106 let alpha = match &args[2] {
5107 Value::Float(v) => *v,
5108 Value::Int(v) => *v as f64,
5109 _ => return Err("elastic_net: alpha must be numeric".into()),
5110 };
5111 let l1_ratio = match &args[3] {
5112 Value::Float(v) => *v,
5113 Value::Int(v) => *v as f64,
5114 _ => return Err("elastic_net: l1_ratio must be numeric".into()),
5115 };
5116 let max_iter = if args.len() > 4 {
5117 match &args[4] { Value::Int(v) => *v as usize, Value::Float(v) => *v as usize, _ => 1000 }
5118 } else { 1000 };
5119 let tol = if args.len() > 5 {
5120 match &args[5] { Value::Float(v) => *v, Value::Int(v) => *v as f64, _ => 1e-4 }
5121 } else { 1e-4 };
5122
5123 let (coefficients, intercept, n_iter, converged) =
5124 coordinate_descent(&x_data, &y, n, p, alpha, l1_ratio, max_iter, tol);
5125
5126 let mut predictions = vec![0.0; n];
5128 for i in 0..n {
5129 let mut acc = cjc_repro::KahanAccumulatorF64::new();
5130 acc.add(intercept);
5131 for j in 0..p {
5132 acc.add(coefficients[j] * x_data[i * p + j]);
5133 }
5134 predictions[i] = acc.finalize();
5135 }
5136 let r_squared = compute_r_squared(&y, &predictions);
5137
5138 let mut fields = std::collections::BTreeMap::new();
5139 fields.insert("coefficients".into(), Value::Tensor(Tensor::from_vec(coefficients, &[p]).unwrap()));
5140 fields.insert("intercept".into(), Value::Float(intercept));
5141 fields.insert("r_squared".into(), Value::Float(r_squared));
5142 fields.insert("alpha".into(), Value::Float(alpha));
5143 fields.insert("l1_ratio".into(), Value::Float(l1_ratio));
5144 fields.insert("n_iter".into(), Value::Int(n_iter as i64));
5145 fields.insert("converged".into(), Value::Bool(converged));
5146
5147 Ok(Some(Value::Struct { name: "ElasticNetResult".to_string(), fields }))
5148}
5149
5150fn coordinate_descent(
5157 x_data: &[f64], y: &[f64], n: usize, p: usize,
5158 alpha: f64, l1_ratio: f64, max_iter: usize, tol: f64,
5159) -> (Vec<f64>, f64, usize, bool) {
5160 let (xs, means, stds) = standardize_x(x_data, n, p);
5162
5163 let mut y_acc = cjc_repro::KahanAccumulatorF64::new();
5165 for &v in y { y_acc.add(v); }
5166 let y_mean = y_acc.finalize() / n as f64;
5167 let yc: Vec<f64> = y.iter().map(|v| v - y_mean).collect();
5168
5169 let mut col_norms_sq = vec![0.0; p];
5172 for j in 0..p {
5173 let mut acc = cjc_repro::KahanAccumulatorF64::new();
5174 for i in 0..n {
5175 let v = xs[i * p + j];
5176 acc.add(v * v);
5177 }
5178 col_norms_sq[j] = acc.finalize();
5179 }
5180
5181 let l1_pen = alpha * l1_ratio * n as f64;
5182 let l2_pen = alpha * (1.0 - l1_ratio) * n as f64;
5183
5184 let mut beta = vec![0.0; p];
5185 let mut residual = yc.clone();
5186 let mut converged = false;
5187 let mut n_iter = 0usize;
5188
5189 for iter in 0..max_iter {
5190 let mut max_delta: f64 = 0.0;
5191 for j in 0..p {
5192 let old_beta = beta[j];
5193
5194 let mut acc = cjc_repro::KahanAccumulatorF64::new();
5196 for i in 0..n {
5197 acc.add(xs[i * p + j] * residual[i]);
5198 }
5199 let rho = acc.finalize() + col_norms_sq[j] * old_beta;
5200
5201 let new_beta = soft_threshold(rho, l1_pen) / (col_norms_sq[j] + l2_pen);
5203
5204 let delta = new_beta - old_beta;
5206 if delta != 0.0 {
5207 for i in 0..n {
5208 residual[i] -= xs[i * p + j] * delta;
5209 }
5210 }
5211 beta[j] = new_beta;
5212 let abs_delta = delta.abs();
5213 if abs_delta > max_delta { max_delta = abs_delta; }
5214 }
5215 n_iter = iter + 1;
5216 if max_delta < tol {
5217 converged = true;
5218 break;
5219 }
5220 }
5221
5222 let mut coefficients = vec![0.0; p];
5224 let mut intercept = y_mean;
5225 for j in 0..p {
5226 coefficients[j] = beta[j] / stds[j];
5227 intercept -= coefficients[j] * means[j];
5228 }
5229
5230 (coefficients, intercept, n_iter, converged)
5231}
5232
5233fn soft_threshold(rho: f64, lambda: f64) -> f64 {
5235 if rho > lambda {
5236 rho - lambda
5237 } else if rho < -lambda {
5238 rho + lambda
5239 } else {
5240 0.0
5241 }
5242}
5243
5244fn solve_linear_system(a: &[f64], b: &[f64], p: usize) -> Result<Vec<f64>, String> {
5247 let mut l = vec![0.0; p * p];
5249 for j in 0..p {
5250 let mut sum = cjc_repro::KahanAccumulatorF64::new();
5251 for k in 0..j {
5252 sum.add(l[j * p + k] * l[j * p + k]);
5253 }
5254 let diag = a[j * p + j] - sum.finalize();
5255 if diag <= 0.0 {
5256 return Err("ridge_regression: matrix not positive definite (try increasing alpha)".into());
5257 }
5258 l[j * p + j] = diag.sqrt();
5259 for i in (j + 1)..p {
5260 let mut sum2 = cjc_repro::KahanAccumulatorF64::new();
5261 for k in 0..j {
5262 sum2.add(l[i * p + k] * l[j * p + k]);
5263 }
5264 l[i * p + j] = (a[i * p + j] - sum2.finalize()) / l[j * p + j];
5265 }
5266 }
5267
5268 let mut z = vec![0.0; p];
5270 for i in 0..p {
5271 let mut acc = cjc_repro::KahanAccumulatorF64::new();
5272 for k in 0..i {
5273 acc.add(l[i * p + k] * z[k]);
5274 }
5275 z[i] = (b[i] - acc.finalize()) / l[i * p + i];
5276 }
5277
5278 let mut x = vec![0.0; p];
5280 for i in (0..p).rev() {
5281 let mut acc = cjc_repro::KahanAccumulatorF64::new();
5282 for k in (i + 1)..p {
5283 acc.add(l[k * p + i] * x[k]);
5284 }
5285 x[i] = (z[i] - acc.finalize()) / l[i * p + i];
5286 }
5287
5288 Ok(x)
5289}
5290
5291#[cfg(test)]
5296mod tests {
5297 use super::*;
5298
5299 #[test]
5300 fn test_peak_rss_returns_nonzero() {
5301 let rss = peak_rss_kb();
5302 assert!(rss > 0, "peak_rss_kb() should return non-zero, got {rss}");
5304 }
5305
5306 #[test]
5307 fn test_peak_rss_builtin_dispatch() {
5308 let result = dispatch_builtin("peak_rss", &[]);
5309 match result {
5310 Ok(Some(Value::Int(kb))) => {
5311 assert!(kb > 0, "peak_rss should return positive value, got {kb}");
5312 }
5313 other => panic!("Expected Ok(Some(Int)), got: {other:?}"),
5314 }
5315 }
5316
5317 #[test]
5318 fn test_complex_constructor() {
5319 let result = dispatch_builtin("Complex", &[Value::Float(3.0), Value::Float(4.0)]);
5320 match result {
5321 Ok(Some(Value::Complex(c))) => {
5322 assert_eq!(c.re, 3.0);
5323 assert_eq!(c.im, 4.0);
5324 }
5325 _ => panic!("expected Complex value"),
5326 }
5327 }
5328
5329 #[test]
5330 fn test_complex_constructor_from_ints() {
5331 let result = dispatch_builtin("Complex", &[Value::Int(1), Value::Int(-2)]);
5332 match result {
5333 Ok(Some(Value::Complex(c))) => {
5334 assert_eq!(c.re, 1.0);
5335 assert_eq!(c.im, -2.0);
5336 }
5337 _ => panic!("expected Complex value"),
5338 }
5339 }
5340
5341 #[test]
5342 fn test_complex_real_only() {
5343 let result = dispatch_builtin("Complex", &[Value::Float(5.0)]);
5344 match result {
5345 Ok(Some(Value::Complex(c))) => {
5346 assert_eq!(c.re, 5.0);
5347 assert_eq!(c.im, 0.0);
5348 }
5349 _ => panic!("expected Complex value"),
5350 }
5351 }
5352
5353 #[test]
5354 fn test_to_string() {
5355 let result = dispatch_builtin("to_string", &[Value::Int(42)]);
5356 match result {
5357 Ok(Some(Value::String(s))) => assert_eq!(s.as_str(), "42"),
5358 _ => panic!("expected String value"),
5359 }
5360 }
5361
5362 #[test]
5363 fn test_len_array() {
5364 let arr = Value::Array(Rc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
5365 let result = dispatch_builtin("len", &[arr]);
5366 assert!(matches!(result, Ok(Some(Value::Int(3)))));
5367 }
5368
5369 #[test]
5370 fn test_len_string() {
5371 let s = Value::String(Rc::new("hello".to_string()));
5372 let result = dispatch_builtin("len", &[s]);
5373 assert!(matches!(result, Ok(Some(Value::Int(5)))));
5374 }
5375
5376 #[test]
5377 fn test_assert_pass() {
5378 let result = dispatch_builtin("assert", &[Value::Bool(true)]);
5379 assert!(matches!(result, Ok(Some(Value::Void))));
5380 }
5381
5382 #[test]
5383 fn test_assert_fail() {
5384 let result = dispatch_builtin("assert", &[Value::Bool(false)]);
5385 assert!(result.is_err());
5386 }
5387
5388 #[test]
5389 fn test_assert_eq_pass() {
5390 let result = dispatch_builtin("assert_eq", &[Value::Int(42), Value::Int(42)]);
5391 assert!(matches!(result, Ok(Some(Value::Void))));
5392 }
5393
5394 #[test]
5395 fn test_assert_eq_fail() {
5396 let result = dispatch_builtin("assert_eq", &[Value::Int(1), Value::Int(2)]);
5397 assert!(result.is_err());
5398 }
5399
5400 #[test]
5401 fn test_sqrt() {
5402 let result = dispatch_builtin("sqrt", &[Value::Float(4.0)]);
5403 match result {
5404 Ok(Some(Value::Float(v))) => assert_eq!(v, 2.0),
5405 _ => panic!("expected Float"),
5406 }
5407 }
5408
5409 #[test]
5410 fn test_abs_float() {
5411 let result = dispatch_builtin("abs", &[Value::Float(-3.14)]);
5412 match result {
5413 Ok(Some(Value::Float(v))) => assert_eq!(v, 3.14),
5414 _ => panic!("expected Float"),
5415 }
5416 }
5417
5418 #[test]
5419 fn test_abs_int() {
5420 let result = dispatch_builtin("abs", &[Value::Int(-42)]);
5421 assert!(matches!(result, Ok(Some(Value::Int(42)))));
5422 }
5423
5424 #[test]
5425 fn test_floor() {
5426 let result = dispatch_builtin("floor", &[Value::Float(3.7)]);
5427 match result {
5428 Ok(Some(Value::Float(v))) => assert_eq!(v, 3.0),
5429 _ => panic!("expected Float"),
5430 }
5431 }
5432
5433 #[test]
5434 fn test_int_conversion() {
5435 let result = dispatch_builtin("int", &[Value::Float(3.9)]);
5436 assert!(matches!(result, Ok(Some(Value::Int(3)))));
5437 }
5438
5439 #[test]
5440 fn test_float_conversion() {
5441 let result = dispatch_builtin("float", &[Value::Int(42)]);
5442 match result {
5443 Ok(Some(Value::Float(v))) => assert_eq!(v, 42.0),
5444 _ => panic!("expected Float"),
5445 }
5446 }
5447
5448 #[test]
5449 fn test_isnan() {
5450 let result = dispatch_builtin("isnan", &[Value::Float(f64::NAN)]);
5451 assert!(matches!(result, Ok(Some(Value::Bool(true)))));
5452
5453 let result = dispatch_builtin("isnan", &[Value::Float(1.0)]);
5454 assert!(matches!(result, Ok(Some(Value::Bool(false)))));
5455
5456 let result = dispatch_builtin("isnan", &[Value::Int(0)]);
5457 assert!(matches!(result, Ok(Some(Value::Bool(false)))));
5458 }
5459
5460 #[test]
5461 fn test_isinf() {
5462 let result = dispatch_builtin("isinf", &[Value::Float(f64::INFINITY)]);
5463 assert!(matches!(result, Ok(Some(Value::Bool(true)))));
5464
5465 let result = dispatch_builtin("isinf", &[Value::Float(1.0)]);
5466 assert!(matches!(result, Ok(Some(Value::Bool(false)))));
5467 }
5468
5469 #[test]
5470 fn test_push() {
5471 let arr = Value::Array(Rc::new(vec![Value::Int(1)]));
5472 let result = dispatch_builtin("push", &[arr, Value::Int(2)]);
5473 match result {
5474 Ok(Some(Value::Array(a))) => {
5475 assert_eq!(a.len(), 2);
5476 assert!(matches!(&a[1], Value::Int(2)));
5477 }
5478 _ => panic!("expected Array"),
5479 }
5480 }
5481
5482 #[test]
5483 fn test_sort() {
5484 let arr = Value::Array(Rc::new(vec![
5485 Value::Float(3.0),
5486 Value::Float(1.0),
5487 Value::Float(2.0),
5488 ]));
5489 let result = dispatch_builtin("sort", &[arr]);
5490 match result {
5491 Ok(Some(Value::Array(a))) => {
5492 assert!(matches!(&a[0], Value::Float(v) if *v == 1.0));
5493 assert!(matches!(&a[1], Value::Float(v) if *v == 2.0));
5494 assert!(matches!(&a[2], Value::Float(v) if *v == 3.0));
5495 }
5496 _ => panic!("expected sorted Array"),
5497 }
5498 }
5499
5500 #[test]
5501 fn test_unknown_builtin_returns_none() {
5502 let result = dispatch_builtin("unknown_function", &[]);
5503 assert!(matches!(result, Ok(None)));
5504 }
5505
5506 #[test]
5507 fn test_tensor_zeros() {
5508 let shape = Value::Array(Rc::new(vec![Value::Int(2), Value::Int(3)]));
5509 let result = dispatch_builtin("Tensor.zeros", &[shape]);
5510 match result {
5511 Ok(Some(Value::Tensor(t))) => {
5512 assert_eq!(t.shape(), &[2, 3]);
5513 }
5514 _ => panic!("expected Tensor"),
5515 }
5516 }
5517
5518 #[test]
5519 fn test_values_equal() {
5520 assert!(values_equal(&Value::Int(42), &Value::Int(42)));
5521 assert!(!values_equal(&Value::Int(1), &Value::Int(2)));
5522 assert!(values_equal(&Value::Float(3.14), &Value::Float(3.14)));
5523 assert!(values_equal(&Value::Bool(true), &Value::Bool(true)));
5524 assert!(values_equal(&Value::Void, &Value::Void));
5525 assert!(!values_equal(&Value::Int(1), &Value::Float(1.0)));
5526 }
5527
5528 #[test]
5529 fn test_value_to_shape() {
5530 let arr = Value::Array(Rc::new(vec![Value::Int(2), Value::Int(3), Value::Int(4)]));
5531 assert_eq!(value_to_shape(&arr).unwrap(), vec![2, 3, 4]);
5532 }
5533
5534 #[test]
5535 fn test_value_to_shape_negative_rejected() {
5536 let arr = Value::Array(Rc::new(vec![Value::Int(-1)]));
5537 assert!(value_to_shape(&arr).is_err());
5538 }
5539
5540 #[test]
5541 fn test_value_to_f64_vec() {
5542 let arr = Value::Array(Rc::new(vec![
5543 Value::Float(1.0),
5544 Value::Int(2),
5545 Value::Float(3.5),
5546 ]));
5547 assert_eq!(value_to_f64_vec(&arr).unwrap(), vec![1.0, 2.0, 3.5]);
5548 }
5549
5550 #[test]
5551 fn test_log_float() {
5552 let result = dispatch_builtin("log", &[Value::Float(1.0)]);
5553 match result {
5554 Ok(Some(Value::Float(v))) => assert!((v - 0.0).abs() < 1e-15),
5555 _ => panic!("expected Float"),
5556 }
5557 }
5558
5559 #[test]
5560 fn test_log_e() {
5561 let result = dispatch_builtin("log", &[Value::Float(std::f64::consts::E)]);
5562 match result {
5563 Ok(Some(Value::Float(v))) => assert!((v - 1.0).abs() < 1e-15),
5564 _ => panic!("expected Float"),
5565 }
5566 }
5567
5568 #[test]
5569 fn test_exp_float() {
5570 let result = dispatch_builtin("exp", &[Value::Float(0.0)]);
5571 match result {
5572 Ok(Some(Value::Float(v))) => assert!((v - 1.0).abs() < 1e-15),
5573 _ => panic!("expected Float"),
5574 }
5575 }
5576
5577 #[test]
5578 fn test_exp_one() {
5579 let result = dispatch_builtin("exp", &[Value::Float(1.0)]);
5580 match result {
5581 Ok(Some(Value::Float(v))) => assert!((v - std::f64::consts::E).abs() < 1e-15),
5582 _ => panic!("expected Float"),
5583 }
5584 }
5585
5586 #[test]
5587 fn test_categorical_sample_deterministic() {
5588 let probs = Tensor::from_vec(vec![0.0, 0.0, 1.0], &[3]).unwrap();
5589 assert_eq!(categorical_sample_with_u(&probs, 0.5).unwrap(), 2);
5591 }
5592
5593 #[test]
5594 fn test_categorical_sample_first() {
5595 let probs = Tensor::from_vec(vec![0.5, 0.3, 0.2], &[3]).unwrap();
5596 assert_eq!(categorical_sample_with_u(&probs, 0.1).unwrap(), 0);
5598 }
5599
5600 #[test]
5601 fn test_categorical_sample_middle() {
5602 let probs = Tensor::from_vec(vec![0.2, 0.5, 0.3], &[3]).unwrap();
5603 assert_eq!(categorical_sample_with_u(&probs, 0.6).unwrap(), 1);
5605 }
5606
5607 #[test]
5608 fn test_categorical_sample_last() {
5609 let probs = Tensor::from_vec(vec![0.2, 0.3, 0.5], &[3]).unwrap();
5610 assert_eq!(categorical_sample_with_u(&probs, 0.99).unwrap(), 2);
5612 }
5613}