1use std::cell::{Cell, RefCell};
2use std::collections::{HashMap, HashSet};
3use std::io::Write;
4
5use indexmap::IndexMap;
6use ndarray::Array2;
7use num_complex::Complex;
8use rand::{Rng, SeedableRng, rngs::SmallRng};
9
10use crate::env::{Env, LambdaFn, Value};
11use crate::io::IoContext;
12
13pub type FnCallHook = fn(
23 name: &str,
24 func: &Value,
25 args: &[Value],
26 caller_env: &Env,
27 io: &mut IoContext,
28) -> Result<Value, String>;
29
30thread_local! {
31 static FN_CALL_HOOK: Cell<Option<FnCallHook>> = const { Cell::new(None) };
32}
33
34pub fn set_fn_call_hook(f: FnCallHook) {
38 FN_CALL_HOOK.with(|c| c.set(Some(f)));
39}
40
41pub type AutoloadHook = fn(name: &str) -> bool;
50
51thread_local! {
52 static AUTOLOAD_HOOK: Cell<Option<AutoloadHook>> = const { Cell::new(None) };
53 static AUTOLOAD_CACHE: RefCell<Env> = RefCell::new(Env::new());
55 static AUTOLOAD_MISS_CACHE: RefCell<HashSet<String>> = RefCell::new(HashSet::new());
58}
59
60pub fn set_autoload_hook(f: AutoloadHook) {
62 AUTOLOAD_HOOK.with(|c| c.set(Some(f)));
63}
64
65pub fn autoload_cache_insert(name: String, val: Value) {
67 AUTOLOAD_CACHE.with(|c| c.borrow_mut().insert(name, val));
68}
69
70pub fn clear_autoload_miss_cache() {
75 AUTOLOAD_MISS_CACHE.with(|c| c.borrow_mut().clear());
76}
77
78pub fn resolve_autoloaded(name: &str) -> Option<Value> {
84 let cached = AUTOLOAD_CACHE.with(|c| c.borrow().get(name).cloned());
85 if cached.is_some() {
86 return cached;
87 }
88 if AUTOLOAD_MISS_CACHE.with(|c| c.borrow().contains(name)) {
89 return None;
90 }
91 let hook = AUTOLOAD_HOOK.with(|c| c.get());
92 if let Some(f) = hook {
93 f(name);
94 }
95 let found = AUTOLOAD_CACHE.with(|c| c.borrow().get(name).cloned());
96 if found.is_none() {
97 AUTOLOAD_MISS_CACHE.with(|c| c.borrow_mut().insert(name.to_string()));
98 }
99 found
100}
101
102pub type EvalStrHook = fn(code: &str, env: &Env) -> Result<Value, String>;
111
112thread_local! {
113 static EVAL_STR_HOOK: Cell<Option<EvalStrHook>> = const { Cell::new(None) };
114}
115
116pub fn set_eval_str_hook(f: EvalStrHook) {
120 EVAL_STR_HOOK.with(|c| c.set(Some(f)));
121}
122
123fn call_eval_str_hook(code: &str, env: &Env) -> Result<Value, String> {
124 match EVAL_STR_HOOK.with(|c| c.get()) {
125 Some(hook) => hook(code, env),
126 None => Err("eval: exec::init() not called".to_string()),
127 }
128}
129
130thread_local! {
133 static TIC_TIME: Cell<Option<std::time::Instant>> = const { Cell::new(None) };
135}
136
137thread_local! {
140 static LAST_ERR: RefCell<String> = const { RefCell::new(String::new()) };
141}
142
143pub fn set_last_err(msg: &str) {
145 LAST_ERR.with(|e| *e.borrow_mut() = msg.to_string());
146}
147
148pub fn get_last_err() -> String {
150 LAST_ERR.with(|e| e.borrow().clone())
151}
152
153thread_local! {
156 static NARGOUT: Cell<usize> = const { Cell::new(1) };
157}
158
159pub fn set_nargout(n: usize) {
165 NARGOUT.with(|c| c.set(n));
166}
167
168fn get_nargout() -> usize {
169 NARGOUT.with(|c| c.get())
170}
171
172thread_local! {
175 static DISPLAY_FMT: RefCell<FormatMode> = const { RefCell::new(FormatMode::Short) };
176 static DISPLAY_BASE: Cell<Base> = const { Cell::new(Base::Dec) };
177 static DISPLAY_COMPACT: Cell<bool> = const { Cell::new(false) };
178}
179
180pub fn set_display_ctx(fmt: &FormatMode, base: Base, compact: bool) {
185 DISPLAY_FMT.with(|f| *f.borrow_mut() = fmt.clone());
186 DISPLAY_BASE.with(|b| b.set(base));
187 DISPLAY_COMPACT.with(|c| c.set(compact));
188}
189
190pub fn get_display_fmt() -> FormatMode {
192 DISPLAY_FMT.with(|f| f.borrow().clone())
193}
194
195pub fn get_display_base() -> Base {
197 DISPLAY_BASE.with(|b| b.get())
198}
199
200pub fn get_display_compact() -> bool {
202 DISPLAY_COMPACT.with(|c| c.get())
203}
204
205thread_local! {
208 static GLOBAL_ENV: RefCell<Env> = RefCell::new(Env::new());
213
214 static GLOBAL_NAMES_STACK: RefCell<Vec<HashSet<String>>> =
219 RefCell::new(vec![HashSet::new()]);
220}
221
222pub fn global_frame_push() {
224 GLOBAL_NAMES_STACK.with(|s| s.borrow_mut().push(HashSet::new()));
225}
226
227pub fn global_frame_pop() {
229 GLOBAL_NAMES_STACK.with(|s| {
230 s.borrow_mut().pop();
231 });
232}
233
234pub fn global_declare(name: &str) {
236 GLOBAL_NAMES_STACK.with(|s| {
237 if let Some(frame) = s.borrow_mut().last_mut() {
238 frame.insert(name.to_string());
239 }
240 });
241}
242
243pub fn is_global(name: &str) -> bool {
245 GLOBAL_NAMES_STACK.with(|s| s.borrow().last().is_some_and(|f| f.contains(name)))
246}
247
248pub fn global_get(name: &str) -> Option<Value> {
250 GLOBAL_ENV.with(|e| e.borrow().get(name).cloned())
251}
252
253pub fn global_set(name: &str, val: Value) {
255 GLOBAL_ENV.with(|e| e.borrow_mut().insert(name.to_string(), val));
256}
257
258pub fn global_init_if_absent(name: &str) {
260 GLOBAL_ENV.with(|e| {
261 e.borrow_mut()
262 .entry(name.to_string())
263 .or_insert(Value::Scalar(0.0));
264 });
265}
266
267pub fn global_refresh_into_env(env: &mut crate::env::Env) {
272 GLOBAL_NAMES_STACK.with(|s| {
273 GLOBAL_ENV.with(|ge| {
274 if let Some(frame) = s.borrow().last() {
275 let store = ge.borrow();
276 for name in frame {
277 if let Some(val) = store.get(name) {
278 env.insert(name.clone(), val.clone());
279 }
280 }
281 }
282 });
283 });
284}
285
286thread_local! {
289 static PERSISTENT_STORE: RefCell<HashMap<String, Value>> =
294 RefCell::new(HashMap::new());
295
296 static FUNC_NAME_STACK: RefCell<Vec<String>> =
301 RefCell::new(vec![String::new()]);
302
303 static PERSISTENT_NAMES_STACK: RefCell<Vec<HashSet<String>>> =
305 RefCell::new(vec![HashSet::new()]);
306}
307
308pub fn persistent_frame_push(func_name: &str) {
310 FUNC_NAME_STACK.with(|s| s.borrow_mut().push(func_name.to_string()));
311 PERSISTENT_NAMES_STACK.with(|s| s.borrow_mut().push(HashSet::new()));
312}
313
314pub fn persistent_frame_pop() -> (String, HashSet<String>) {
316 let func_name = FUNC_NAME_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default());
317 let names = PERSISTENT_NAMES_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default());
318 (func_name, names)
319}
320
321pub fn persistent_declare(name: &str) {
323 PERSISTENT_NAMES_STACK.with(|s| {
324 if let Some(frame) = s.borrow_mut().last_mut() {
325 frame.insert(name.to_string());
326 }
327 });
328}
329
330pub fn persistent_load(func_name: &str, var_name: &str) -> Option<Value> {
332 let key = format!("{func_name}\x00{var_name}");
333 PERSISTENT_STORE.with(|s| s.borrow().get(&key).cloned())
334}
335
336pub fn persistent_save(func_name: &str, var_name: &str, val: Value) {
338 let key = format!("{func_name}\x00{var_name}");
339 PERSISTENT_STORE.with(|s| s.borrow_mut().insert(key, val));
340}
341
342pub fn current_func_name() -> String {
346 FUNC_NAME_STACK.with(|s| s.borrow().last().cloned().unwrap_or_default())
347}
348
349pub fn is_persistent(name: &str) -> bool {
351 PERSISTENT_NAMES_STACK.with(|s| s.borrow().last().is_some_and(|frame| frame.contains(name)))
352}
353
354thread_local! {
357 static RNG: RefCell<SmallRng> = RefCell::new(SmallRng::from_entropy());
361}
362
363pub fn rng_seed(seed: u64) {
365 RNG.with(|r| *r.borrow_mut() = SmallRng::seed_from_u64(seed));
366}
367
368pub fn rng_shuffle() {
370 RNG.with(|r| *r.borrow_mut() = SmallRng::from_entropy());
371}
372
373fn rand_uniform() -> f64 {
375 RNG.with(|r| r.borrow_mut().gen_range(0.0_f64..1.0))
376}
377
378fn rand_normal() -> f64 {
380 let u1 = rand_uniform().max(f64::EPSILON);
381 let u2 = rand_uniform();
382 (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
383}
384
385#[derive(Debug, Clone)]
391pub enum Expr {
392 Number(f64),
394 Var(String),
396 UnaryMinus(Box<Expr>),
398 UnaryNot(Box<Expr>),
400 BinOp(Box<Expr>, Op, Box<Expr>),
402 Call(String, Vec<Expr>),
407 Matrix(Vec<Vec<Expr>>),
409 Transpose(Box<Expr>),
411 Range(Box<Expr>, Option<Box<Expr>>, Box<Expr>),
414 Colon,
417 StrLiteral(String),
419 StringObjLiteral(String),
421 Lambda {
426 params: Vec<String>,
428 body: Box<Expr>,
430 source: String,
432 },
433 PlainTranspose(Box<Expr>),
438 CellLiteral(Vec<Expr>),
442 CellIndex(Box<Expr>, Box<Expr>),
447 FuncHandle(String),
452 FieldGet(Box<Expr>, String),
456 DotCall(Vec<String>, Vec<Expr>),
466 NaT,
468}
469
470#[derive(Debug, Clone)]
472pub enum Op {
473 Add,
475 Sub,
477 Mul,
479 Div,
481 Pow,
483 ElemMul,
485 ElemDiv,
487 ElemPow,
489 Eq,
492 NotEq,
494 Lt,
496 Gt,
498 LtEq,
500 GtEq,
502 And,
505 Or,
507 ElemAnd,
510 ElemOr,
512 LDiv,
514}
515
516#[derive(Debug, Clone, Copy, PartialEq, Default)]
518pub enum Base {
519 #[default]
521 Dec,
522 Hex,
524 Bin,
526 Oct,
528}
529
530#[derive(Debug, Clone, PartialEq)]
532pub enum FormatMode {
533 Short,
535 Long,
537 ShortE,
539 LongE,
541 ShortG,
543 LongG,
545 Bank,
547 Rat,
549 Hex,
551 Plus,
553 Custom(usize),
555}
556
557impl Default for FormatMode {
558 fn default() -> Self {
559 FormatMode::Custom(10)
560 }
561}
562
563impl FormatMode {
564 pub fn name(&self) -> String {
566 match self {
567 FormatMode::Short => "short".to_string(),
568 FormatMode::Long => "long".to_string(),
569 FormatMode::ShortE => "shortE".to_string(),
570 FormatMode::LongE => "longE".to_string(),
571 FormatMode::ShortG => "shortG".to_string(),
572 FormatMode::LongG => "longG".to_string(),
573 FormatMode::Bank => "bank".to_string(),
574 FormatMode::Rat => "rat".to_string(),
575 FormatMode::Hex => "hex".to_string(),
576 FormatMode::Plus => "+".to_string(),
577 FormatMode::Custom(n) => format!("custom({n})"),
578 }
579 }
580}
581
582pub fn eval(expr: &Expr, env: &Env) -> Result<Value, String> {
585 eval_inner(expr, env, None)
586}
587
588pub fn eval_with_io(expr: &Expr, env: &Env, io: &mut IoContext) -> Result<Value, String> {
591 eval_inner(expr, env, Some(io))
592}
593
594fn eval_inner(expr: &Expr, env: &Env, mut io: Option<&mut IoContext>) -> Result<Value, String> {
595 match expr {
596 Expr::Number(n) => Ok(Value::Scalar(*n)),
597 Expr::Var(name) => env.get(name).cloned().ok_or(()).or_else(|_| {
598 if is_global(name)
600 && let Some(val) = global_get(name)
601 {
602 return Ok(val);
603 }
604 if name == "e" {
606 return Ok(Value::Scalar(std::f64::consts::E));
607 }
608 if let Ok(val) = call_builtin(name, &[], env, io.as_deref_mut()) {
610 return Ok(val);
611 }
612 let hint = suggest_similar(name, env);
613 match hint {
614 Some(s) => Err(format!("Undefined variable '{name}'; did you mean '{s}'?")),
615 None => Err(format!("Undefined variable: '{name}'")),
616 }
617 }),
618 Expr::UnaryMinus(e) => match eval_inner(e, env, io)? {
619 Value::Void => Err("Unary minus is not applicable to void".to_string()),
620 Value::Scalar(n) => Ok(Value::Scalar(-n)),
621 Value::Matrix(m) => Ok(Value::Matrix(m.mapv(|x| -x))),
622 Value::Complex(re, im) => Ok(Value::Complex(-re, -im)),
623 Value::ComplexMatrix(m) => Ok(Value::ComplexMatrix(m.mapv(|c| -c))),
624 Value::Str(s) => match str_to_numeric(&s) {
625 Value::Scalar(n) => Ok(Value::Scalar(-n)),
626 Value::Matrix(m) => Ok(Value::Matrix(m.mapv(|x| -x))),
627 _ => unreachable!(),
628 },
629 Value::StringObj(_) => {
630 Err("Unary minus is not applicable to string objects".to_string())
631 }
632 Value::Lambda(_)
633 | Value::Function { .. }
634 | Value::Tuple(_)
635 | Value::Cell(_)
636 | Value::Struct(_)
637 | Value::StructArray(_)
638 | Value::DateTime(_)
639 | Value::Duration(_)
640 | Value::DateTimeArray(_)
641 | Value::DurationArray(_) => {
642 Err("Unary minus is not applicable to this type".to_string())
643 }
644 },
645 Expr::UnaryNot(e) => match eval_inner(e, env, io)? {
646 Value::Void => Err("Logical NOT is not applicable to void".to_string()),
647 Value::Scalar(n) => Ok(Value::Scalar(if n == 0.0 { 1.0 } else { 0.0 })),
648 Value::Matrix(m) => Ok(Value::Matrix(m.mapv(|x| if x == 0.0 { 1.0 } else { 0.0 }))),
649 Value::Complex(re, im) => Ok(Value::Scalar(if re == 0.0 && im == 0.0 {
650 1.0
651 } else {
652 0.0
653 })),
654 Value::ComplexMatrix(m) => {
655 Ok(Value::Matrix(m.mapv(|c| {
656 if c.re == 0.0 && c.im == 0.0 { 1.0 } else { 0.0 }
657 })))
658 }
659 Value::Str(s) => match str_to_numeric(&s) {
660 Value::Scalar(n) => Ok(Value::Scalar(if n == 0.0 { 1.0 } else { 0.0 })),
661 Value::Matrix(m) => Ok(Value::Matrix(m.mapv(|x| if x == 0.0 { 1.0 } else { 0.0 }))),
662 _ => unreachable!(),
663 },
664 Value::StringObj(_) => {
665 Err("Logical NOT is not applicable to string objects".to_string())
666 }
667 Value::Lambda(_)
668 | Value::Function { .. }
669 | Value::Tuple(_)
670 | Value::Cell(_)
671 | Value::Struct(_)
672 | Value::StructArray(_)
673 | Value::DateTime(_)
674 | Value::Duration(_)
675 | Value::DateTimeArray(_)
676 | Value::DurationArray(_) => {
677 Err("Logical NOT is not applicable to this type".to_string())
678 }
679 },
680 Expr::BinOp(left, op, right) => {
681 let l = eval_inner(left, env, io.as_deref_mut())?;
682 let r = eval_inner(right, env, io)?;
683 eval_binop(l, op, r)
684 }
685 Expr::Call(name, args) => {
686 if name == "try" && args.len() == 2 {
689 return match eval_inner(&args[0], env, io.as_deref_mut()) {
690 Ok(v) => Ok(v),
691 Err(msg) => {
692 set_last_err(&msg);
693 eval_inner(&args[1], env, io.as_deref_mut())
694 }
695 };
696 }
697
698 if let Some(env_val) = env.get(name) {
705 if !matches!(env_val, Value::Lambda(_) | Value::Function { .. }) {
706 return eval_index(env_val, args, env);
707 }
708 let val = env_val.clone();
710 match &val {
711 Value::Lambda(f) => {
712 let mut evaled = Vec::with_capacity(args.len().max(1));
715 for a in args {
716 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
717 }
718 if evaled.is_empty() {
719 evaled.push(env.get("ans").cloned().unwrap_or(Value::Scalar(0.0)));
720 }
721 let f = f.clone();
722 return f.0(&evaled, io);
723 }
724 Value::Function { .. } => {
725 let mut evaled = Vec::with_capacity(args.len());
729 for a in args {
730 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
731 }
732 return match io.as_deref_mut() {
733 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
734 Some(hook) => hook(name, &val, &evaled, env, io_ref),
735 None => Err(format!(
736 "'{name}': user function execution not initialized \
737 (call exec::init() first)"
738 )),
739 }),
740 None => {
741 let mut tmp_io = IoContext::new();
744 FN_CALL_HOOK.with(|c| match c.get() {
745 Some(hook) => hook(name, &val, &evaled, env, &mut tmp_io),
746 None => Err(format!(
747 "'{name}': user function execution not initialized"
748 )),
749 })
750 }
751 };
752 }
753 _ => unreachable!(),
754 }
755 }
756 let autoloaded_val = AUTOLOAD_CACHE
761 .with(|c| c.borrow().get(name).cloned())
762 .or_else(|| {
763 if AUTOLOAD_MISS_CACHE.with(|c| c.borrow().contains(name.as_str())) {
764 return None;
765 }
766 let loaded = AUTOLOAD_HOOK
767 .with(|c| c.get())
768 .is_some_and(|hook| hook(name));
769 if loaded {
770 AUTOLOAD_CACHE.with(|c| c.borrow().get(name).cloned())
771 } else {
772 AUTOLOAD_MISS_CACHE.with(|c| c.borrow_mut().insert(name.to_string()));
773 None
774 }
775 });
776 if let Some(val) = autoloaded_val {
777 let mut evaled = Vec::with_capacity(args.len());
778 for a in args {
779 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
780 }
781 return match io.as_deref_mut() {
782 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
783 Some(hook) => hook(name, &val, &evaled, env, io_ref),
784 None => Err(format!("'{name}': exec::init() not called")),
785 }),
786 None => {
787 let mut tmp_io = IoContext::new();
788 FN_CALL_HOOK.with(|c| match c.get() {
789 Some(hook) => hook(name, &val, &evaled, env, &mut tmp_io),
790 None => Err(format!("'{name}': exec::init() not called")),
791 })
792 }
793 };
794 }
795
796 let mut evaled = Vec::with_capacity(args.len().max(1));
798 for a in args {
799 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
800 }
801 let no_ans_inject = matches!(
804 name.as_str(),
805 "struct"
806 | "fieldnames"
807 | "isfield"
808 | "rmfield"
809 | "isstruct"
810 | "cell"
811 | "iscell"
812 | "isempty"
813 | "cellfun"
814 | "error"
815 | "warning"
816 | "lasterr"
817 | "pcall"
818 | "rand"
819 | "randn"
820 | "rng"
821 | "tic"
822 | "toc"
823 );
824 if evaled.is_empty() && !no_ans_inject {
825 evaled.push(env.get("ans").cloned().unwrap_or(Value::Scalar(0.0)));
826 }
827 call_builtin(name, &evaled, env, io)
828 }
829
830 Expr::Lambda {
831 params,
832 body,
833 source,
834 } => {
835 let captured_env = env.clone();
838 let captured_params = params.clone();
839 let captured_body = *body.clone();
840 let src = source.clone();
841 let lambda = LambdaFn(
842 std::rc::Rc::new(move |args: &[Value], io: Option<&mut IoContext>| {
843 let effective = if args.len() > captured_params.len() {
845 if args.len() > captured_params.len() + 1 {
846 return Err(format!(
847 "Lambda: too many arguments (expected at most {}, got {})",
848 captured_params.len(),
849 args.len()
850 ));
851 }
852 &args[..captured_params.len()]
853 } else {
854 args
855 };
856 let mut local_env = captured_env.clone();
857 for (p, a) in captured_params.iter().zip(effective.iter()) {
858 local_env.insert(p.clone(), a.clone());
859 }
860 local_env.insert("nargin".to_string(), Value::Scalar(effective.len() as f64));
861 eval_inner(&captured_body, &local_env, io)
862 }),
863 src,
864 );
865 Ok(Value::Lambda(lambda))
866 }
867 Expr::CellLiteral(elems) => {
868 let mut vals = Vec::with_capacity(elems.len());
869 for e in elems {
870 vals.push(eval_inner(e, env, io.as_deref_mut())?);
871 }
872 Ok(Value::Cell(vals))
873 }
874 Expr::CellIndex(cell_expr, idx_expr) => {
875 let cell = eval_inner(cell_expr, env, io.as_deref_mut())?;
876 let idx = eval_inner(idx_expr, env, io)?;
877 match (cell, idx) {
878 (Value::Cell(v), Value::Scalar(i)) => {
879 let i = i as isize;
880 if i < 1 || i as usize > v.len() {
881 Err(format!("Cell index {} out of range (1..{})", i, v.len()))
882 } else {
883 Ok(v[(i - 1) as usize].clone())
884 }
885 }
886 (Value::Cell(_), _) => Err("Cell index must be a scalar integer".to_string()),
887 _ => Err("Brace indexing '{}' is only valid on cell arrays".to_string()),
888 }
889 }
890 Expr::FieldGet(base_expr, field) => {
891 let base_val = eval_inner(base_expr, env, io)?;
892 match base_val {
893 Value::Struct(map) => map
894 .get(field)
895 .cloned()
896 .ok_or_else(|| format!("No field '{field}' in struct")),
897 Value::StructArray(arr) => {
899 let mut values: Vec<Value> = Vec::with_capacity(arr.len());
900 for (idx, elem) in arr.iter().enumerate() {
901 let v = elem.get(field).cloned().ok_or_else(|| {
902 format!("No field '{field}' in struct array element {}", idx + 1)
903 })?;
904 values.push(v);
905 }
906 let all_scalar = values.iter().all(|v| matches!(v, Value::Scalar(_)));
908 if all_scalar {
909 let nums: Vec<f64> = values
910 .into_iter()
911 .map(|v| {
912 if let Value::Scalar(n) = v {
913 n
914 } else {
915 unreachable!()
916 }
917 })
918 .collect();
919 let n = nums.len();
920 Ok(Value::Matrix(Array2::from_shape_vec((1, n), nums).unwrap()))
921 } else {
922 Ok(Value::Cell(values))
923 }
924 }
925 _ => Err(format!(
926 "Cannot access field '{field}' on a non-struct value"
927 )),
928 }
929 }
930 Expr::DotCall(segs, args) => {
931 let qualified = segs.join(".");
932 if let Some(head_val) = env.get(&segs[0]).cloned() {
934 let mut val = head_val;
935 for field in &segs[1..] {
936 val = match val {
937 Value::Struct(ref map) => map
938 .get(field)
939 .cloned()
940 .ok_or_else(|| format!("No field '{field}' in struct"))?,
941 _ => {
942 return Err(format!(
943 "Cannot access field '{field}' on a non-struct value"
944 ));
945 }
946 };
947 }
948 let mut evaled = Vec::with_capacity(args.len());
949 for a in args {
950 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
951 }
952 return match val {
953 Value::Lambda(f) => {
954 if evaled.is_empty() {
955 evaled.push(env.get("ans").cloned().unwrap_or(Value::Scalar(0.0)));
956 }
957 f.0(&evaled, io)
958 }
959 Value::Function { .. } => match io.as_deref_mut() {
960 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
961 Some(hook) => hook(&qualified, &val, &evaled, env, io_ref),
962 None => Err(format!("'{qualified}': exec::init() not called")),
963 }),
964 None => {
965 let mut tmp_io = IoContext::new();
966 FN_CALL_HOOK.with(|c| match c.get() {
967 Some(hook) => hook(&qualified, &val, &evaled, env, &mut tmp_io),
968 None => Err(format!("'{qualified}': exec::init() not called")),
969 })
970 }
971 },
972 _ => Err(format!("'{qualified}': not a callable")),
973 };
974 }
975 let cached = AUTOLOAD_CACHE.with(|c| c.borrow().get(&qualified).cloned());
977 let autoloaded_val = cached.or_else(|| {
978 let loaded = AUTOLOAD_HOOK
979 .with(|c| c.get())
980 .is_some_and(|hook| hook(&qualified));
981 if loaded {
982 AUTOLOAD_CACHE.with(|c| c.borrow().get(&qualified).cloned())
983 } else {
984 None
985 }
986 });
987 if let Some(val) = autoloaded_val {
988 let mut evaled = Vec::with_capacity(args.len());
989 for a in args {
990 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
991 }
992 return match io.as_deref_mut() {
993 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
994 Some(hook) => hook(&qualified, &val, &evaled, env, io_ref),
995 None => Err(format!("'{qualified}': exec::init() not called")),
996 }),
997 None => {
998 let mut tmp_io = IoContext::new();
999 FN_CALL_HOOK.with(|c| match c.get() {
1000 Some(hook) => hook(&qualified, &val, &evaled, env, &mut tmp_io),
1001 None => Err(format!("'{qualified}': exec::init() not called")),
1002 })
1003 }
1004 };
1005 }
1006 Err(format!("Unknown package function: '{qualified}'"))
1007 }
1008 Expr::FuncHandle(name) => {
1009 let name = name.clone();
1010 let captured_env = env.clone();
1011 let src = format!("@{name}");
1012 let lambda = LambdaFn(
1013 std::rc::Rc::new(move |args: &[Value], io: Option<&mut IoContext>| {
1014 if let Some(f) = captured_env.get(&name) {
1016 let f = f.clone();
1017 call_function_value(&f, args, io)
1018 } else {
1019 call_builtin(&name, args, &captured_env, io)
1020 }
1021 }),
1022 src,
1023 );
1024 Ok(Value::Lambda(lambda))
1025 }
1026 Expr::PlainTranspose(e) => match eval_inner(e, env, io)? {
1027 Value::Void => Err("Transpose is not applicable to void".to_string()),
1028 Value::Scalar(n) => Ok(Value::Scalar(n)),
1029 Value::Matrix(m) => Ok(Value::Matrix(m.t().to_owned())),
1030 Value::Complex(re, im) => Ok(Value::Complex(re, im)),
1032 Value::ComplexMatrix(m) => Ok(Value::ComplexMatrix(m.t().to_owned())),
1034 Value::Str(s) => Ok(Value::Str(s)),
1035 Value::StringObj(s) => Ok(Value::StringObj(s)),
1036 v @ (Value::DateTimeArray(_) | Value::DurationArray(_)) => Ok(v),
1038 Value::Lambda(_)
1039 | Value::Function { .. }
1040 | Value::Tuple(_)
1041 | Value::Cell(_)
1042 | Value::Struct(_)
1043 | Value::StructArray(_)
1044 | Value::DateTime(_)
1045 | Value::Duration(_) => Err("Transpose is not applicable to this type".to_string()),
1046 },
1047 Expr::Colon => Err("':' is only valid inside index expressions".to_string()),
1048 Expr::NaT => Ok(Value::DateTime(f64::NAN)),
1049 Expr::Matrix(rows) => {
1050 if rows.is_empty() {
1051 return Ok(Value::Matrix(Array2::<f64>::zeros((0, 0))));
1052 }
1053
1054 let mut evaluated: Vec<Vec<Value>> = Vec::with_capacity(rows.len());
1056 for row in rows {
1057 if row.is_empty() {
1058 continue;
1059 }
1060 let mut ev_row: Vec<Value> = Vec::with_capacity(row.len());
1061 for elem_expr in row {
1062 ev_row.push(eval_inner(elem_expr, env, io.as_deref_mut())?);
1063 }
1064 evaluated.push(ev_row);
1065 }
1066 if evaluated.is_empty() {
1067 return Ok(Value::Matrix(Array2::<f64>::zeros((0, 0))));
1068 }
1069
1070 let has_complex = evaluated
1072 .iter()
1073 .flat_map(|row| row.iter())
1074 .any(|v| matches!(v, Value::Complex(_, _) | Value::ComplexMatrix(_)));
1075
1076 enum MatKind {
1078 ComplexNumeric,
1079 Numeric,
1080 DateTime,
1081 Duration,
1082 Str,
1083 }
1084 let kind = if has_complex {
1085 MatKind::ComplexNumeric
1086 } else {
1087 match &evaluated[0][0] {
1088 Value::Scalar(_) | Value::Matrix(_) => MatKind::Numeric,
1089 Value::DateTime(_) | Value::DateTimeArray(_) => MatKind::DateTime,
1090 Value::Duration(_) | Value::DurationArray(_) => MatKind::Duration,
1091 Value::Str(_) | Value::StringObj(_) => MatKind::Str,
1092 Value::Void => {
1093 return Err("Void value cannot be used in matrix literal".to_string());
1094 }
1095 Value::Lambda(_)
1096 | Value::Function { .. }
1097 | Value::Tuple(_)
1098 | Value::Cell(_)
1099 | Value::Struct(_)
1100 | Value::StructArray(_) => {
1101 return Err("This type cannot be used in matrix literals".to_string());
1102 }
1103 Value::Complex(_, _) | Value::ComplexMatrix(_) => unreachable!(),
1105 }
1106 };
1107
1108 match kind {
1109 MatKind::ComplexNumeric => {
1110 let mut row_blocks: Vec<Array2<Complex<f64>>> =
1113 Vec::with_capacity(evaluated.len());
1114 for ev_row in &evaluated {
1115 let mut elem_mats: Vec<Array2<Complex<f64>>> =
1116 Vec::with_capacity(ev_row.len());
1117 for val in ev_row {
1118 let block: Array2<Complex<f64>> = match val {
1119 Value::Scalar(n) => {
1120 Array2::from_elem((1, 1), Complex::new(*n, 0.0))
1121 }
1122 Value::Complex(re, im) => {
1123 Array2::from_elem((1, 1), Complex::new(*re, *im))
1124 }
1125 Value::Matrix(m) => cm_from_real(m),
1126 Value::ComplexMatrix(m) => m.clone(),
1127 _ => {
1128 return Err(
1129 "This type cannot be used in a complex matrix literal"
1130 .to_string(),
1131 );
1132 }
1133 };
1134 elem_mats.push(block);
1135 }
1136 let nrows = elem_mats[0].nrows();
1137 for (i, m) in elem_mats.iter().enumerate().skip(1) {
1138 if m.nrows() != nrows {
1139 return Err(format!(
1140 "Matrix row height mismatch: expected {} rows, element {} has {} rows",
1141 nrows,
1142 i + 1,
1143 m.nrows()
1144 ));
1145 }
1146 }
1147 let ncols: usize = elem_mats.iter().map(|m| m.ncols()).sum();
1148 let mut flat: Vec<Complex<f64>> = Vec::with_capacity(nrows * ncols);
1149 for r in 0..nrows {
1150 for m in &elem_mats {
1151 flat.extend(m.row(r).iter().copied());
1152 }
1153 }
1154 row_blocks.push(
1155 Array2::from_shape_vec((nrows, ncols), flat)
1156 .map_err(|e| format!("Matrix shape error: {e}"))?,
1157 );
1158 }
1159 if row_blocks.is_empty() {
1160 return Ok(Value::ComplexMatrix(Array2::zeros((0, 0))));
1161 }
1162 let ncols = row_blocks[0].ncols();
1163 for (i, blk) in row_blocks.iter().enumerate().skip(1) {
1164 if blk.ncols() != ncols {
1165 return Err(format!(
1166 "Matrix column count mismatch: expected {} columns, row {} has {} columns",
1167 ncols,
1168 i + 1,
1169 blk.ncols()
1170 ));
1171 }
1172 }
1173 let total_rows: usize = row_blocks.iter().map(|b| b.nrows()).sum();
1174 let mut flat: Vec<Complex<f64>> = Vec::with_capacity(total_rows * ncols);
1175 for blk in &row_blocks {
1176 flat.extend(blk.iter().copied());
1177 }
1178 let m = Array2::from_shape_vec((total_rows, ncols), flat)
1179 .map_err(|e| format!("Matrix shape error: {e}"))?;
1180 Ok(Value::ComplexMatrix(m))
1181 }
1182 MatKind::DateTime => {
1183 let mut ts: Vec<f64> = Vec::new();
1184 for ev_row in &evaluated {
1185 for val in ev_row {
1186 match val {
1187 Value::DateTime(t) => ts.push(*t),
1188 Value::DateTimeArray(v) => ts.extend_from_slice(v),
1189 _ => {
1190 return Err(
1191 "Matrix literal: cannot mix datetime with other types"
1192 .to_string(),
1193 );
1194 }
1195 }
1196 }
1197 }
1198 Ok(Value::DateTimeArray(ts))
1199 }
1200 MatKind::Duration => {
1201 let mut sv: Vec<f64> = Vec::new();
1202 for ev_row in &evaluated {
1203 for val in ev_row {
1204 match val {
1205 Value::Duration(s) => sv.push(*s),
1206 Value::DurationArray(v) => sv.extend_from_slice(v),
1207 _ => {
1208 return Err(
1209 "Matrix literal: cannot mix duration with other types"
1210 .to_string(),
1211 );
1212 }
1213 }
1214 }
1215 }
1216 Ok(Value::DurationArray(sv))
1217 }
1218 MatKind::Numeric => {
1219 let mut row_blocks: Vec<Array2<f64>> = Vec::with_capacity(evaluated.len());
1222 for ev_row in &evaluated {
1223 let mut elem_mats: Vec<Array2<f64>> = Vec::with_capacity(ev_row.len());
1224 for val in ev_row {
1225 match val {
1226 Value::Scalar(n) => {
1227 elem_mats.push(Array2::from_elem((1, 1), *n));
1228 }
1229 Value::Matrix(m) => elem_mats.push(m.clone()),
1230 Value::Void => {
1231 return Err(
1232 "Void value cannot be used in matrix literal".to_string()
1233 );
1234 }
1235 Value::Str(s) | Value::StringObj(s) => {
1238 let codes: Vec<f64> =
1239 s.chars().map(|c| c as u32 as f64).collect();
1240 let mat = if codes.is_empty() {
1241 Array2::<f64>::zeros((1, 0))
1242 } else {
1243 Array2::from_shape_vec((1, codes.len()), codes)
1244 .map_err(|e| format!("Matrix shape error: {e}"))?
1245 };
1246 elem_mats.push(mat);
1247 }
1248 _ => {
1249 return Err(
1250 "This type cannot be used in matrix literals".to_string()
1251 );
1252 }
1253 }
1254 }
1255 let nrows = elem_mats[0].nrows();
1256 for (i, m) in elem_mats.iter().enumerate().skip(1) {
1257 if m.nrows() != nrows {
1258 return Err(format!(
1259 "Matrix row height mismatch: expected {} rows, element {} has {} rows",
1260 nrows,
1261 i + 1,
1262 m.nrows()
1263 ));
1264 }
1265 }
1266 let ncols: usize = elem_mats.iter().map(|m| m.ncols()).sum();
1267 let mut flat: Vec<f64> = Vec::with_capacity(nrows * ncols);
1268 for r in 0..nrows {
1269 for m in &elem_mats {
1270 flat.extend(m.row(r).iter().copied());
1271 }
1272 }
1273 row_blocks.push(
1274 Array2::from_shape_vec((nrows, ncols), flat)
1275 .map_err(|e| format!("Matrix shape error: {e}"))?,
1276 );
1277 }
1278 if row_blocks.is_empty() {
1279 return Ok(Value::Matrix(Array2::<f64>::zeros((0, 0))));
1280 }
1281 let ncols = row_blocks[0].ncols();
1282 if ncols == 0 {
1283 let total_rows: usize = row_blocks.iter().map(|b| b.nrows()).sum();
1284 return Ok(Value::Matrix(Array2::zeros((total_rows, 0))));
1285 }
1286 for (i, blk) in row_blocks.iter().enumerate().skip(1) {
1287 if blk.ncols() != ncols {
1288 return Err(format!(
1289 "Matrix column count mismatch: expected {} columns, row {} has {} columns",
1290 ncols,
1291 i + 1,
1292 blk.ncols()
1293 ));
1294 }
1295 }
1296 let total_rows: usize = row_blocks.iter().map(|b| b.nrows()).sum();
1297 let mut flat: Vec<f64> = Vec::with_capacity(total_rows * ncols);
1298 for blk in &row_blocks {
1299 flat.extend(blk.iter().copied());
1300 }
1301 let m = Array2::from_shape_vec((total_rows, ncols), flat)
1302 .map_err(|e| format!("Matrix shape error: {e}"))?;
1303 Ok(Value::Matrix(m))
1304 }
1305 MatKind::Str => {
1306 if evaluated.len() > 1 {
1307 return Err("Multi-row char-array literals are not supported".to_string());
1308 }
1309 let mut out = String::new();
1310 for val in &evaluated[0] {
1311 match val {
1312 Value::Str(s) | Value::StringObj(s) => out.push_str(s),
1313 Value::Scalar(n) => {
1314 let code = n.round();
1315 out.push(
1316 char::from_u32(code as u32)
1317 .ok_or_else(|| format!("char: invalid code {n}"))?,
1318 );
1319 }
1320 Value::Matrix(m) => {
1321 for &n in m.iter() {
1322 out.push(
1323 char::from_u32(n.round() as u32)
1324 .ok_or_else(|| format!("char: invalid code {n}"))?,
1325 );
1326 }
1327 }
1328 _ => {
1329 return Err(
1330 "This type cannot be used in a char-array literal".to_string()
1331 );
1332 }
1333 }
1334 }
1335 Ok(Value::Str(out))
1336 }
1337 }
1338 }
1339 Expr::Transpose(e) => match eval_inner(e, env, io)? {
1340 Value::Void => Err("Transpose is not applicable to void".to_string()),
1341 Value::Scalar(n) => Ok(Value::Scalar(n)),
1342 Value::Matrix(m) => Ok(Value::Matrix(m.t().to_owned())),
1343 Value::Complex(re, im) => Ok(Value::Complex(re, -im)),
1344 Value::ComplexMatrix(m) => Ok(Value::ComplexMatrix(m.t().mapv(|c| c.conj()))),
1346 Value::Str(s) => Ok(Value::Str(s)),
1348 Value::StringObj(s) => Ok(Value::StringObj(s)),
1349 v @ (Value::DateTimeArray(_) | Value::DurationArray(_)) => Ok(v),
1351 Value::Lambda(_)
1352 | Value::Function { .. }
1353 | Value::Tuple(_)
1354 | Value::Cell(_)
1355 | Value::Struct(_)
1356 | Value::StructArray(_)
1357 | Value::DateTime(_)
1358 | Value::Duration(_) => Err("Transpose is not applicable to this type".to_string()),
1359 },
1360 Expr::StrLiteral(s) => Ok(Value::Str(s.clone())),
1361 Expr::StringObjLiteral(s) => Ok(Value::StringObj(s.clone())),
1362 Expr::Range(start_expr, step_expr, stop_expr) => {
1363 let start = match eval_inner(start_expr, env, io.as_deref_mut())? {
1364 Value::Scalar(n) => n,
1365 _ => return Err("Range bounds must be real scalars".to_string()),
1366 };
1367 let stop = match eval_inner(stop_expr, env, io.as_deref_mut())? {
1368 Value::Scalar(n) => n,
1369 _ => return Err("Range bounds must be real scalars".to_string()),
1370 };
1371 let step = match step_expr {
1372 None => 1.0,
1373 Some(s) => match eval_inner(s, env, io)? {
1374 Value::Scalar(n) => n,
1375 _ => return Err("Range step must be a real scalar".to_string()),
1376 },
1377 };
1378 if step == 0.0 {
1379 return Err("Range step cannot be zero".to_string());
1380 }
1381 let n_float = (stop - start) / step;
1382 if n_float < -1e-10 {
1383 return Ok(Value::Matrix(Array2::zeros((1, 0))));
1385 }
1386 let n = (n_float + 1e-10).floor() as usize + 1;
1387 let vals: Vec<f64> = (0..n).map(|i| start + i as f64 * step).collect();
1388 let m =
1389 Array2::from_shape_vec((1, n), vals).map_err(|e| format!("Range error: {e}"))?;
1390 Ok(Value::Matrix(m))
1391 }
1392 }
1393}
1394
1395fn eval_binop(l: Value, op: &Op, r: Value) -> Result<Value, String> {
1396 match (l, r) {
1397 (Value::Void, _) | (_, Value::Void) => {
1398 Err("Cannot apply operator to void value".to_string())
1399 }
1400 (Value::StringObj(a), Value::StringObj(b)) => match op {
1402 Op::Add => Ok(Value::StringObj(a + &b)),
1403 Op::Eq => Ok(Value::Scalar(bool_to_f64(a == b))),
1404 Op::NotEq => Ok(Value::Scalar(bool_to_f64(a != b))),
1405 _ => Err("Operator not supported on string objects".to_string()),
1406 },
1407 (Value::Str(s), r) => eval_binop(str_to_numeric(&s), op, r),
1409 (l, Value::Str(s)) => eval_binop(l, op, str_to_numeric(&s)),
1410 (Value::StringObj(_), _) | (_, Value::StringObj(_)) => {
1412 Err("String object cannot be combined with non-string values".to_string())
1413 }
1414 (Value::Lambda(_), _)
1416 | (_, Value::Lambda(_))
1417 | (Value::Function { .. }, _)
1418 | (_, Value::Function { .. })
1419 | (Value::Tuple(_), _)
1420 | (_, Value::Tuple(_))
1421 | (Value::Cell(_), _)
1422 | (_, Value::Cell(_))
1423 | (Value::Struct(_), _)
1424 | (_, Value::Struct(_))
1425 | (Value::StructArray(_), _)
1426 | (_, Value::StructArray(_)) => Err("Cannot apply operator to a struct value".to_string()),
1427 (Value::DateTime(t), Value::Duration(d)) => match op {
1430 Op::Add => Ok(Value::DateTime(t + d)),
1431 Op::Sub => Ok(Value::DateTime(t - d)),
1432 _ => Err("Unsupported operator between datetime and duration".to_string()),
1433 },
1434 (Value::Duration(d), Value::DateTime(t)) => match op {
1436 Op::Add => Ok(Value::DateTime(t + d)),
1437 _ => Err("Unsupported operator between duration and datetime".to_string()),
1438 },
1439 (Value::DateTime(t1), Value::DateTime(t2)) => match op {
1441 Op::Sub => Ok(Value::Duration(t1 - t2)),
1442 Op::Eq => Ok(Value::Scalar(bool_to_f64(
1443 (t1 - t2).abs() < 1e-9 || (t1.is_nan() && t2.is_nan()),
1444 ))),
1445 Op::NotEq => Ok(Value::Scalar(bool_to_f64(
1446 (t1 - t2).abs() >= 1e-9 && !(t1.is_nan() && t2.is_nan()),
1447 ))),
1448 Op::Lt => Ok(Value::Scalar(bool_to_f64(t1 < t2))),
1449 Op::Gt => Ok(Value::Scalar(bool_to_f64(t1 > t2))),
1450 Op::LtEq => Ok(Value::Scalar(bool_to_f64(t1 <= t2))),
1451 Op::GtEq => Ok(Value::Scalar(bool_to_f64(t1 >= t2))),
1452 _ => Err("Unsupported operator between two datetimes".to_string()),
1453 },
1454 (Value::Duration(d1), Value::Duration(d2)) => match op {
1456 Op::Add => Ok(Value::Duration(d1 + d2)),
1457 Op::Sub => Ok(Value::Duration(d1 - d2)),
1458 Op::Div | Op::ElemDiv => Ok(Value::Scalar(d1 / d2)),
1459 Op::Eq => Ok(Value::Scalar(bool_to_f64((d1 - d2).abs() < 1e-9))),
1460 Op::NotEq => Ok(Value::Scalar(bool_to_f64((d1 - d2).abs() >= 1e-9))),
1461 Op::Lt => Ok(Value::Scalar(bool_to_f64(d1 < d2))),
1462 Op::Gt => Ok(Value::Scalar(bool_to_f64(d1 > d2))),
1463 Op::LtEq => Ok(Value::Scalar(bool_to_f64(d1 <= d2))),
1464 Op::GtEq => Ok(Value::Scalar(bool_to_f64(d1 >= d2))),
1465 _ => Err("Unsupported operator between two durations".to_string()),
1466 },
1467 (Value::Duration(d), Value::Scalar(s)) => match op {
1468 Op::Mul | Op::ElemMul => Ok(Value::Duration(d * s)),
1469 Op::Div | Op::ElemDiv => Ok(Value::Duration(d / s)),
1470 _ => Err("Unsupported operator between duration and scalar".to_string()),
1471 },
1472 (Value::Scalar(s), Value::Duration(d)) => match op {
1473 Op::Mul | Op::ElemMul => Ok(Value::Duration(s * d)),
1474 _ => Err("Unsupported operator between scalar and duration".to_string()),
1475 },
1476 (Value::DateTime(t), Value::DurationArray(dv)) => match op {
1478 Op::Add => Ok(Value::DateTimeArray(dv.iter().map(|d| t + d).collect())),
1479 Op::Sub => Ok(Value::DateTimeArray(dv.iter().map(|d| t - d).collect())),
1480 _ => Err("Unsupported operator between datetime and duration array".to_string()),
1481 },
1482 (Value::DurationArray(dv), Value::DateTime(t)) => match op {
1483 Op::Add => Ok(Value::DateTimeArray(dv.iter().map(|d| t + d).collect())),
1484 _ => Err("Unsupported operator between duration array and datetime".to_string()),
1485 },
1486 (Value::DateTimeArray(tv), Value::Duration(d)) => match op {
1487 Op::Add => Ok(Value::DateTimeArray(tv.iter().map(|t| t + d).collect())),
1488 Op::Sub => Ok(Value::DateTimeArray(tv.iter().map(|t| t - d).collect())),
1489 _ => Err("Unsupported operator between datetime array and duration".to_string()),
1490 },
1491 (Value::DateTimeArray(tv), Value::DurationArray(dv)) => match op {
1492 Op::Add if tv.len() == dv.len() => Ok(Value::DateTimeArray(
1493 tv.iter().zip(&dv).map(|(t, d)| t + d).collect(),
1494 )),
1495 Op::Sub if tv.len() == dv.len() => Ok(Value::DateTimeArray(
1496 tv.iter().zip(&dv).map(|(t, d)| t - d).collect(),
1497 )),
1498 _ => Err("Unsupported or mismatched datetime/duration array operation".to_string()),
1499 },
1500 (Value::DateTimeArray(tv1), Value::DateTimeArray(tv2)) => match op {
1501 Op::Sub if tv1.len() == tv2.len() => Ok(Value::DurationArray(
1502 tv1.iter().zip(&tv2).map(|(a, b)| a - b).collect(),
1503 )),
1504 _ => Err("Unsupported operator between two datetime arrays".to_string()),
1505 },
1506 (Value::DurationArray(dv), Value::Scalar(s)) => match op {
1507 Op::Mul | Op::ElemMul => Ok(Value::DurationArray(dv.iter().map(|d| d * s).collect())),
1508 Op::Div | Op::ElemDiv => Ok(Value::DurationArray(dv.iter().map(|d| d / s).collect())),
1509 _ => Err("Unsupported operator between duration array and scalar".to_string()),
1510 },
1511 (Value::Scalar(s), Value::DurationArray(dv)) => match op {
1512 Op::Mul | Op::ElemMul => Ok(Value::DurationArray(dv.iter().map(|d| s * d).collect())),
1513 _ => Err("Unsupported operator between scalar and duration array".to_string()),
1514 },
1515 (Value::DateTime(_), _)
1517 | (_, Value::DateTime(_))
1518 | (Value::Duration(_), _)
1519 | (_, Value::Duration(_))
1520 | (Value::DateTimeArray(_), _)
1521 | (_, Value::DateTimeArray(_))
1522 | (Value::DurationArray(_), _)
1523 | (_, Value::DurationArray(_)) => {
1524 Err("Unsupported operation on datetime or duration value".to_string())
1525 }
1526 (Value::Complex(re1, im1), Value::Complex(re2, im2)) => {
1528 complex_binop(re1, im1, op, re2, im2)
1529 }
1530 (Value::Complex(re, im), Value::Scalar(s)) => complex_binop(re, im, op, s, 0.0),
1531 (Value::Scalar(s), Value::Complex(re, im)) => complex_binop(s, 0.0, op, re, im),
1532 (Value::Complex(re, im), Value::Matrix(m)) => {
1534 complex_binop_cm(re, im, op, cm_from_real(&m))
1535 }
1536 (Value::Matrix(m), Value::Complex(re, im)) => {
1537 cm_binop_complex(cm_from_real(&m), op, re, im)
1538 }
1539 (Value::ComplexMatrix(a), Value::ComplexMatrix(b)) => complex_matrix_binop(a, op, b),
1541 (Value::ComplexMatrix(cm), Value::Matrix(m)) => {
1542 complex_matrix_binop(cm, op, cm_from_real(&m))
1543 }
1544 (Value::Matrix(m), Value::ComplexMatrix(cm)) => {
1545 complex_matrix_binop(cm_from_real(&m), op, cm)
1546 }
1547 (Value::ComplexMatrix(cm), Value::Scalar(s)) => cm_binop_scalar(cm, op, s),
1548 (Value::Scalar(s), Value::ComplexMatrix(cm)) => scalar_binop_cm(s, op, cm),
1549 (Value::ComplexMatrix(cm), Value::Complex(re, im)) => cm_binop_complex(cm, op, re, im),
1550 (Value::Complex(re, im), Value::ComplexMatrix(cm)) => complex_binop_cm(re, im, op, cm),
1551 (Value::Scalar(lv), Value::Scalar(rv)) => {
1552 let result = match op {
1553 Op::Add => lv + rv,
1554 Op::Sub => lv - rv,
1555 Op::Mul | Op::ElemMul => lv * rv,
1556 Op::Div | Op::ElemDiv => {
1557 if rv == 0.0 {
1558 return Err("Division by zero".to_string());
1559 }
1560 lv / rv
1561 }
1562 Op::LDiv => {
1563 if lv == 0.0 {
1564 return Err("Left division by zero (a \\ b requires a ≠ 0)".to_string());
1565 }
1566 rv / lv
1567 }
1568 Op::Pow | Op::ElemPow => lv.powf(rv),
1569 Op::Eq => bool_to_f64(lv == rv),
1570 Op::NotEq => bool_to_f64(lv != rv),
1571 Op::Lt => bool_to_f64(lv < rv),
1572 Op::Gt => bool_to_f64(lv > rv),
1573 Op::LtEq => bool_to_f64(lv <= rv),
1574 Op::GtEq => bool_to_f64(lv >= rv),
1575 Op::And | Op::ElemAnd => bool_to_f64(lv != 0.0 && rv != 0.0),
1576 Op::Or | Op::ElemOr => bool_to_f64(lv != 0.0 || rv != 0.0),
1577 };
1578 Ok(Value::Scalar(result))
1579 }
1580 (Value::Matrix(lm), Value::Matrix(rm)) => match op {
1581 Op::Add => {
1582 check_same_shape(&lm, &rm)?;
1583 Ok(Value::Matrix(&lm + &rm))
1584 }
1585 Op::Sub => {
1586 check_same_shape(&lm, &rm)?;
1587 Ok(Value::Matrix(&lm - &rm))
1588 }
1589 Op::Mul => {
1590 if lm.ncols() != rm.nrows() {
1591 return Err(format!(
1592 "Inner dimensions must agree: {}x{} * {}x{}",
1593 lm.nrows(),
1594 lm.ncols(),
1595 rm.nrows(),
1596 rm.ncols()
1597 ));
1598 }
1599 Ok(Value::Matrix(lm.dot(&rm)))
1600 }
1601 Op::ElemMul => {
1602 check_same_shape(&lm, &rm)?;
1603 Ok(Value::Matrix(&lm * &rm))
1604 }
1605 Op::ElemDiv => {
1606 check_same_shape(&lm, &rm)?;
1607 Ok(Value::Matrix(&lm / &rm))
1608 }
1609 Op::ElemPow => {
1610 check_same_shape(&lm, &rm)?;
1611 Ok(Value::Matrix(
1612 ndarray::Zip::from(&lm)
1613 .and(&rm)
1614 .map_collect(|a, b| a.powf(*b)),
1615 ))
1616 }
1617 Op::Eq | Op::NotEq | Op::Lt | Op::Gt | Op::LtEq | Op::GtEq => {
1618 check_same_shape(&lm, &rm)?;
1619 Ok(Value::Matrix(
1620 ndarray::Zip::from(&lm)
1621 .and(&rm)
1622 .map_collect(|a, b| bool_to_f64(cmp_op(op, *a, *b))),
1623 ))
1624 }
1625 Op::And | Op::Or | Op::ElemAnd | Op::ElemOr => {
1626 check_same_shape(&lm, &rm)?;
1627 Ok(Value::Matrix(
1628 ndarray::Zip::from(&lm)
1629 .and(&rm)
1630 .map_collect(|a, b| bool_to_f64(cmp_op(op, *a, *b))),
1631 ))
1632 }
1633 Op::Div => Err("Matrix / Matrix: use inv(B)*A or A*inv(B)".to_string()),
1634 Op::LDiv => Ok(Value::Matrix(solve_linear(&lm, &rm)?)),
1635 Op::Pow => Err("Matrix ^ Matrix: not supported".to_string()),
1636 },
1637 (Value::Scalar(s), Value::Matrix(m)) => match op {
1638 Op::Add => Ok(Value::Matrix(s + &m)),
1639 Op::Sub => Ok(Value::Matrix(m.mapv(|x| s - x))),
1640 Op::Mul | Op::ElemMul => Ok(Value::Matrix(s * &m)),
1641 Op::Div => Err("Scalar / Matrix: not supported".to_string()),
1642 Op::ElemDiv => Err("Scalar ./ Matrix: not supported".to_string()),
1643 Op::LDiv => {
1644 if s == 0.0 {
1645 return Err("Left division by zero (a \\ B requires a ≠ 0)".to_string());
1646 }
1647 Ok(Value::Matrix(m.mapv(|x| x / s)))
1648 }
1649 Op::Pow | Op::ElemPow => Ok(Value::Matrix(m.mapv(|x| s.powf(x)))),
1650 Op::Eq
1651 | Op::NotEq
1652 | Op::Lt
1653 | Op::Gt
1654 | Op::LtEq
1655 | Op::GtEq
1656 | Op::And
1657 | Op::Or
1658 | Op::ElemAnd
1659 | Op::ElemOr => Ok(Value::Matrix(m.mapv(|x| bool_to_f64(cmp_op(op, s, x))))),
1660 },
1661 (Value::Matrix(m), Value::Scalar(s)) => match op {
1662 Op::Add => Ok(Value::Matrix(&m + s)),
1663 Op::Sub => Ok(Value::Matrix(&m - s)),
1664 Op::Mul | Op::ElemMul => Ok(Value::Matrix(&m * s)),
1665 Op::Div | Op::ElemDiv => Ok(Value::Matrix(m.mapv(|x| x / s))),
1666 Op::LDiv => {
1667 let b = Array2::from_elem((m.nrows(), 1), s);
1668 Ok(Value::Matrix(solve_linear(&m, &b)?))
1669 }
1670 Op::Pow | Op::ElemPow => Ok(Value::Matrix(m.mapv(|x| x.powf(s)))),
1671 Op::Eq
1672 | Op::NotEq
1673 | Op::Lt
1674 | Op::Gt
1675 | Op::LtEq
1676 | Op::GtEq
1677 | Op::And
1678 | Op::Or
1679 | Op::ElemAnd
1680 | Op::ElemOr => Ok(Value::Matrix(m.mapv(|x| bool_to_f64(cmp_op(op, x, s))))),
1681 },
1682 }
1683}
1684
1685#[inline]
1686fn bool_to_f64(b: bool) -> f64 {
1687 if b { 1.0 } else { 0.0 }
1688}
1689
1690fn cmp_op(op: &Op, a: f64, b: f64) -> bool {
1692 match op {
1693 Op::Eq => a == b,
1694 Op::NotEq => a != b,
1695 Op::Lt => a < b,
1696 Op::Gt => a > b,
1697 Op::LtEq => a <= b,
1698 Op::GtEq => a >= b,
1699 Op::And | Op::ElemAnd => a != 0.0 && b != 0.0,
1700 Op::Or | Op::ElemOr => a != 0.0 || b != 0.0,
1701 _ => unreachable!(),
1702 }
1703}
1704
1705fn complex_binop(re1: f64, im1: f64, op: &Op, re2: f64, im2: f64) -> Result<Value, String> {
1707 match op {
1708 Op::Add => Ok(make_complex(re1 + re2, im1 + im2)),
1709 Op::Sub => Ok(make_complex(re1 - re2, im1 - im2)),
1710 Op::Mul | Op::ElemMul => {
1711 Ok(make_complex(re1 * re2 - im1 * im2, re1 * im2 + im1 * re2))
1713 }
1714 Op::Div | Op::ElemDiv => {
1715 let denom = re2 * re2 + im2 * im2;
1717 if denom == 0.0 {
1718 return Err("Division by zero (complex)".to_string());
1719 }
1720 Ok(make_complex(
1721 (re1 * re2 + im1 * im2) / denom,
1722 (im1 * re2 - re1 * im2) / denom,
1723 ))
1724 }
1725 Op::Pow | Op::ElemPow => {
1726 let r1 = (re1 * re1 + im1 * im1).sqrt();
1727 if r1 == 0.0 {
1728 if re2 > 0.0 {
1729 return Ok(Value::Scalar(0.0));
1730 }
1731 return Ok(Value::Complex(f64::NAN, f64::NAN));
1732 }
1733 if im2 == 0.0 && re2.fract() == 0.0 && re2.abs() < 1_000_000.0 {
1736 let n = re2 as i64;
1737 if n == 0 {
1738 return Ok(Value::Scalar(1.0));
1739 }
1740 let abs_n = n.unsigned_abs();
1742 let (mut rr, mut ri) = (1.0_f64, 0.0_f64);
1743 let (mut br, mut bi) = (re1, im1);
1744 let mut exp = abs_n;
1745 while exp > 0 {
1746 if exp & 1 == 1 {
1747 let nr = rr * br - ri * bi;
1748 let ni = rr * bi + ri * br;
1749 rr = nr;
1750 ri = ni;
1751 }
1752 let nr = br * br - bi * bi;
1753 let ni = 2.0 * br * bi;
1754 br = nr;
1755 bi = ni;
1756 exp >>= 1;
1757 }
1758 if n < 0 {
1759 let denom = rr * rr + ri * ri;
1761 return Ok(make_complex(rr / denom, -ri / denom));
1762 }
1763 return Ok(make_complex(rr, ri));
1764 }
1765 let theta1 = im1.atan2(re1);
1767 let ln_r1 = r1.ln();
1768 let exp_re = re2 * ln_r1 - im2 * theta1;
1769 let exp_im = im2 * ln_r1 + re2 * theta1;
1770 let mag = exp_re.exp();
1771 Ok(make_complex(mag * exp_im.cos(), mag * exp_im.sin()))
1772 }
1773 Op::Eq => Ok(Value::Scalar(bool_to_f64(re1 == re2 && im1 == im2))),
1774 Op::NotEq => Ok(Value::Scalar(bool_to_f64(re1 != re2 || im1 != im2))),
1775 Op::Lt | Op::Gt | Op::LtEq | Op::GtEq => {
1776 Err("Ordering is not defined for complex numbers".to_string())
1777 }
1778 Op::And | Op::ElemAnd => Ok(Value::Scalar(bool_to_f64(
1779 (re1 != 0.0 || im1 != 0.0) && (re2 != 0.0 || im2 != 0.0),
1780 ))),
1781 Op::Or | Op::ElemOr => Ok(Value::Scalar(bool_to_f64(
1782 re1 != 0.0 || im1 != 0.0 || re2 != 0.0 || im2 != 0.0,
1783 ))),
1784 Op::LDiv => Err("Left division (\\) is not supported for complex numbers".to_string()),
1785 }
1786}
1787
1788#[inline]
1790fn make_complex(re: f64, im: f64) -> Value {
1791 if im == 0.0 {
1792 Value::Scalar(re)
1793 } else {
1794 Value::Complex(re, im)
1795 }
1796}
1797
1798#[inline]
1800fn cm_from_real(m: &Array2<f64>) -> Array2<Complex<f64>> {
1801 m.mapv(|x| Complex::new(x, 0.0))
1802}
1803
1804fn complex_matrix_binop(
1806 a: Array2<Complex<f64>>,
1807 op: &Op,
1808 b: Array2<Complex<f64>>,
1809) -> Result<Value, String> {
1810 let same_shape = || {
1811 if a.shape() != b.shape() {
1812 Err(format!(
1813 "Matrix dimensions must agree: {}×{} vs {}×{}",
1814 a.nrows(),
1815 a.ncols(),
1816 b.nrows(),
1817 b.ncols()
1818 ))
1819 } else {
1820 Ok(())
1821 }
1822 };
1823 match op {
1824 Op::Add => {
1825 same_shape()?;
1826 Ok(Value::ComplexMatrix(a + b))
1827 }
1828 Op::Sub => {
1829 same_shape()?;
1830 Ok(Value::ComplexMatrix(a - b))
1831 }
1832 Op::Mul => {
1833 if a.ncols() != b.nrows() {
1834 return Err(format!(
1835 "Inner dimensions must agree: {}×{} * {}×{}",
1836 a.nrows(),
1837 a.ncols(),
1838 b.nrows(),
1839 b.ncols()
1840 ));
1841 }
1842 Ok(Value::ComplexMatrix(a.dot(&b)))
1843 }
1844 Op::ElemMul => {
1845 same_shape()?;
1846 Ok(Value::ComplexMatrix(a * b))
1847 }
1848 Op::ElemDiv => {
1849 same_shape()?;
1850 Ok(Value::ComplexMatrix(a / b))
1851 }
1852 Op::ElemPow => {
1853 same_shape()?;
1854 Ok(Value::ComplexMatrix(
1855 ndarray::Zip::from(&a)
1856 .and(&b)
1857 .map_collect(|x, y| x.powc(*y)),
1858 ))
1859 }
1860 Op::Pow => Err(
1861 "ComplexMatrix ^ ComplexMatrix: not supported; use .^ for element-wise power"
1862 .to_string(),
1863 ),
1864 Op::Div | Op::LDiv => {
1865 Err("Complex matrix / and \\ not supported; use inv(A)*B".to_string())
1866 }
1867 Op::Eq => {
1868 same_shape()?;
1869 Ok(Value::Matrix(
1870 ndarray::Zip::from(&a)
1871 .and(&b)
1872 .map_collect(|x, y| bool_to_f64(x == y)),
1873 ))
1874 }
1875 Op::NotEq => {
1876 same_shape()?;
1877 Ok(Value::Matrix(
1878 ndarray::Zip::from(&a)
1879 .and(&b)
1880 .map_collect(|x, y| bool_to_f64(x != y)),
1881 ))
1882 }
1883 Op::Lt | Op::Gt | Op::LtEq | Op::GtEq => {
1884 Err("Ordering comparison not defined for complex matrices".to_string())
1885 }
1886 Op::And | Op::ElemAnd => {
1887 same_shape()?;
1888 Ok(Value::Matrix(ndarray::Zip::from(&a).and(&b).map_collect(
1889 |x, y| bool_to_f64((x.re != 0.0 || x.im != 0.0) && (y.re != 0.0 || y.im != 0.0)),
1890 )))
1891 }
1892 Op::Or | Op::ElemOr => {
1893 same_shape()?;
1894 Ok(Value::Matrix(ndarray::Zip::from(&a).and(&b).map_collect(
1895 |x, y| bool_to_f64(x.re != 0.0 || x.im != 0.0 || y.re != 0.0 || y.im != 0.0),
1896 )))
1897 }
1898 }
1899}
1900
1901fn cm_binop_scalar(cm: Array2<Complex<f64>>, op: &Op, s: f64) -> Result<Value, String> {
1903 let c = Complex::new(s, 0.0);
1904 match op {
1905 Op::Add => Ok(Value::ComplexMatrix(cm.mapv(|x| x + c))),
1906 Op::Sub => Ok(Value::ComplexMatrix(cm.mapv(|x| x - c))),
1907 Op::Mul | Op::ElemMul => Ok(Value::ComplexMatrix(cm.mapv(|x| x * c))),
1908 Op::Div | Op::ElemDiv => Ok(Value::ComplexMatrix(cm.mapv(|x| x / c))),
1909 Op::Pow | Op::ElemPow => Ok(Value::ComplexMatrix(cm.mapv(|x| x.powf(s)))),
1910 Op::Eq => Ok(Value::Matrix(cm.mapv(|x| bool_to_f64(x == c)))),
1911 Op::NotEq => Ok(Value::Matrix(cm.mapv(|x| bool_to_f64(x != c)))),
1912 _ => Err("Unsupported operator between complex matrix and scalar".to_string()),
1913 }
1914}
1915
1916fn scalar_binop_cm(s: f64, op: &Op, cm: Array2<Complex<f64>>) -> Result<Value, String> {
1918 let c = Complex::new(s, 0.0);
1919 match op {
1920 Op::Add => Ok(Value::ComplexMatrix(cm.mapv(|x| c + x))),
1921 Op::Sub => Ok(Value::ComplexMatrix(cm.mapv(|x| c - x))),
1922 Op::Mul | Op::ElemMul => Ok(Value::ComplexMatrix(cm.mapv(|x| c * x))),
1923 Op::Pow | Op::ElemPow => Ok(Value::ComplexMatrix(cm.mapv(|x| c.powc(x)))),
1924 Op::Eq => Ok(Value::Matrix(cm.mapv(|x| bool_to_f64(c == x)))),
1925 Op::NotEq => Ok(Value::Matrix(cm.mapv(|x| bool_to_f64(c != x)))),
1926 _ => Err("Unsupported operator between scalar and complex matrix".to_string()),
1927 }
1928}
1929
1930fn cm_binop_complex(cm: Array2<Complex<f64>>, op: &Op, re: f64, im: f64) -> Result<Value, String> {
1932 let c = Complex::new(re, im);
1933 match op {
1934 Op::Add => Ok(Value::ComplexMatrix(cm.mapv(|x| x + c))),
1935 Op::Sub => Ok(Value::ComplexMatrix(cm.mapv(|x| x - c))),
1936 Op::Mul | Op::ElemMul => Ok(Value::ComplexMatrix(cm.mapv(|x| x * c))),
1937 Op::Div | Op::ElemDiv => Ok(Value::ComplexMatrix(cm.mapv(|x| x / c))),
1938 Op::Pow | Op::ElemPow => Ok(Value::ComplexMatrix(cm.mapv(|x| x.powc(c)))),
1939 Op::Eq => Ok(Value::Matrix(cm.mapv(|x| bool_to_f64(x == c)))),
1940 Op::NotEq => Ok(Value::Matrix(cm.mapv(|x| bool_to_f64(x != c)))),
1941 _ => Err("Unsupported operator between complex matrix and complex scalar".to_string()),
1942 }
1943}
1944
1945fn complex_binop_cm(re: f64, im: f64, op: &Op, cm: Array2<Complex<f64>>) -> Result<Value, String> {
1947 let c = Complex::new(re, im);
1948 match op {
1949 Op::Add => Ok(Value::ComplexMatrix(cm.mapv(|x| c + x))),
1950 Op::Sub => Ok(Value::ComplexMatrix(cm.mapv(|x| c - x))),
1951 Op::Mul | Op::ElemMul => Ok(Value::ComplexMatrix(cm.mapv(|x| c * x))),
1952 Op::Pow | Op::ElemPow => Ok(Value::ComplexMatrix(cm.mapv(|x| c.powc(x)))),
1953 Op::Eq => Ok(Value::Matrix(cm.mapv(|x| bool_to_f64(c == x)))),
1954 Op::NotEq => Ok(Value::Matrix(cm.mapv(|x| bool_to_f64(c != x)))),
1955 _ => Err("Unsupported operator between complex scalar and complex matrix".to_string()),
1956 }
1957}
1958
1959fn str_to_numeric(s: &str) -> Value {
1962 let codes: Vec<f64> = s.chars().map(|c| c as u32 as f64).collect();
1963 match codes.len() {
1964 0 => Value::Matrix(Array2::zeros((1, 0))),
1965 1 => Value::Scalar(codes[0]),
1966 n => Value::Matrix(Array2::from_shape_vec((1, n), codes).unwrap()),
1967 }
1968}
1969
1970fn string_arg<'a>(v: &'a Value, fname: &str, pos: usize) -> Result<&'a str, String> {
1972 match v {
1973 Value::Str(s) | Value::StringObj(s) => Ok(s.as_str()),
1974 _ => Err(format!(
1975 "Function '{fname}' argument {pos} must be a string"
1976 )),
1977 }
1978}
1979
1980fn check_same_shape(lm: &Array2<f64>, rm: &Array2<f64>) -> Result<(), String> {
1981 if lm.shape() != rm.shape() {
1982 return Err(format!(
1983 "Matrix size mismatch: {}x{} vs {}x{}",
1984 lm.nrows(),
1985 lm.ncols(),
1986 rm.nrows(),
1987 rm.ncols()
1988 ));
1989 }
1990 Ok(())
1991}
1992
1993fn scalar_arg(v: &Value, fname: &str, pos: usize) -> Result<f64, String> {
1994 match v {
1995 Value::Void => Err(format!(
1996 "Function '{fname}' argument {pos} must be a scalar, got void"
1997 )),
1998 Value::Scalar(n) => Ok(*n),
1999 Value::Complex(re, im) if *im == 0.0 => Ok(*re),
2000 Value::Complex(_, _) => Err(format!(
2001 "Function '{fname}' argument {pos} must be real, got a complex number"
2002 )),
2003 Value::Matrix(_) => Err(format!(
2004 "Function '{fname}' argument {pos} must be a scalar, got a matrix"
2005 )),
2006 Value::ComplexMatrix(_) => Err(format!(
2007 "Function '{fname}' argument {pos} must be a scalar, got a complex matrix"
2008 )),
2009 Value::Str(s) if s.chars().count() == 1 => Ok(s.chars().next().unwrap() as u32 as f64),
2010 Value::Str(_) | Value::StringObj(_) => Err(format!(
2011 "Function '{fname}' argument {pos} must be a scalar, got a string"
2012 )),
2013 Value::Lambda(_)
2014 | Value::Function { .. }
2015 | Value::Tuple(_)
2016 | Value::Cell(_)
2017 | Value::Struct(_)
2018 | Value::StructArray(_)
2019 | Value::DateTime(_)
2020 | Value::Duration(_)
2021 | Value::DateTimeArray(_)
2022 | Value::DurationArray(_) => Err(format!(
2023 "Function '{fname}' argument {pos} must be a scalar, got a non-numeric value"
2024 )),
2025 }
2026}
2027
2028fn randi_range(v: &Value) -> Result<(i64, i64), String> {
2033 match v {
2034 Value::Scalar(n) => {
2035 let hi = *n as i64;
2036 if hi < 1 {
2037 return Err("randi: max must be a positive integer".to_string());
2038 }
2039 Ok((1, hi))
2040 }
2041 Value::Matrix(m) if m.len() == 2 => {
2042 let vals: Vec<f64> = m.iter().copied().collect();
2043 let lo = vals[0] as i64;
2044 let hi = vals[1] as i64;
2045 if lo > hi {
2046 return Err("randi: [min, max] range is empty".to_string());
2047 }
2048 Ok((lo, hi))
2049 }
2050 _ => Err("randi: first argument must be a scalar max or a [min, max] vector".to_string()),
2051 }
2052}
2053
2054fn numeric_vec(v: &Value, fname: &str) -> Result<Vec<f64>, String> {
2058 match v {
2059 Value::Scalar(n) => Ok(vec![*n]),
2060 Value::Matrix(m) => Ok(m.iter().copied().collect()),
2061 _ => Err(format!("{fname}: argument must be numeric")),
2062 }
2063}
2064
2065fn stat_var_vec(vals: &[f64], population: bool) -> f64 {
2068 let n = vals.len();
2069 if n == 0 {
2070 return f64::NAN;
2071 }
2072 if n == 1 {
2073 return 0.0;
2074 }
2075 let mean = vals.iter().sum::<f64>() / n as f64;
2076 let ss: f64 = vals.iter().map(|&x| (x - mean).powi(2)).sum();
2077 let denom = if population { n as f64 } else { (n - 1) as f64 };
2078 ss / denom
2079}
2080
2081fn apply_stat<F>(v: &Value, mut f: F, fname: &str) -> Result<Value, String>
2087where
2088 F: FnMut(&[f64]) -> f64,
2089{
2090 match v {
2091 Value::Scalar(n) => Ok(Value::Scalar(f(&[*n]))),
2092 Value::Matrix(m) => {
2093 if m.nrows() == 1 || m.ncols() == 1 {
2094 let vals: Vec<f64> = m.iter().copied().collect();
2095 Ok(Value::Scalar(f(&vals)))
2096 } else {
2097 let ncols = m.ncols();
2098 let result: Vec<f64> = (0..ncols)
2099 .map(|c| {
2100 let col: Vec<f64> = m.column(c).iter().copied().collect();
2101 f(&col)
2102 })
2103 .collect();
2104 Ok(Value::Matrix(
2105 Array2::from_shape_vec((1, ncols), result).unwrap(),
2106 ))
2107 }
2108 }
2109 _ => Err(format!("{fname}: argument must be numeric")),
2110 }
2111}
2112
2113fn percentile_sorted(sorted: &[f64], p: f64) -> f64 {
2115 let n = sorted.len();
2116 if n == 0 {
2117 return f64::NAN;
2118 }
2119 if n == 1 {
2120 return sorted[0];
2121 }
2122 let p = p.clamp(0.0, 100.0);
2123 let idx = (p / 100.0 * n as f64 - 0.5).max(0.0).min((n - 1) as f64);
2125 let lo = idx.floor() as usize;
2126 let hi = idx.ceil() as usize;
2127 let frac = idx - lo as f64;
2128 sorted[lo] * (1.0 - frac) + sorted[hi] * frac
2129}
2130
2131fn apply_elem<F: Fn(f64) -> f64>(v: &Value, f: F) -> Result<Value, String> {
2132 match v {
2133 Value::Void => Err("Element-wise function not applicable to void".to_string()),
2134 Value::Scalar(n) => Ok(Value::Scalar(f(*n))),
2135 Value::Matrix(m) => Ok(Value::Matrix(m.mapv(f))),
2136 Value::Complex(re, im) if *im == 0.0 => Ok(Value::Scalar(f(*re))),
2137 Value::Complex(_, _) => {
2138 Err("Element-wise real function not applicable to complex values".to_string())
2139 }
2140 Value::ComplexMatrix(_) => {
2141 Err("Element-wise real function not applicable to complex matrices".to_string())
2142 }
2143 Value::Str(_) | Value::StringObj(_) => {
2144 Err("Element-wise function not applicable to strings".to_string())
2145 }
2146 Value::Lambda(_)
2147 | Value::Function { .. }
2148 | Value::Tuple(_)
2149 | Value::Cell(_)
2150 | Value::Struct(_)
2151 | Value::StructArray(_)
2152 | Value::DateTime(_)
2153 | Value::Duration(_)
2154 | Value::DateTimeArray(_)
2155 | Value::DurationArray(_) => {
2156 Err("Element-wise function not applicable to this type".to_string())
2157 }
2158 }
2159}
2160
2161fn apply_reduction<F>(v: &Value, f: F) -> Result<Value, String>
2167where
2168 F: Fn(&[f64]) -> f64,
2169{
2170 match v {
2171 Value::Void => Err("Reduction not applicable to void".to_string()),
2172 Value::Scalar(n) => Ok(Value::Scalar(f(&[*n]))),
2173 Value::Complex(_, _) => Err("Reduction not applicable to complex values".to_string()),
2174 Value::ComplexMatrix(_) => Err("Reduction not applicable to complex matrices".to_string()),
2175 Value::Str(_) | Value::StringObj(_) => {
2176 Err("Reduction not applicable to strings".to_string())
2177 }
2178 Value::Lambda(_)
2179 | Value::Function { .. }
2180 | Value::Tuple(_)
2181 | Value::Cell(_)
2182 | Value::Struct(_)
2183 | Value::StructArray(_)
2184 | Value::DateTime(_)
2185 | Value::Duration(_)
2186 | Value::DateTimeArray(_)
2187 | Value::DurationArray(_) => Err("Reduction not applicable to this type".to_string()),
2188 Value::Matrix(m) => {
2189 if m.nrows() == 1 || m.ncols() == 1 {
2190 let vals: Vec<f64> = m.iter().copied().collect();
2191 Ok(Value::Scalar(f(&vals)))
2192 } else {
2193 let ncols = m.ncols();
2194 let result: Vec<f64> = (0..ncols)
2195 .map(|c| {
2196 let col: Vec<f64> = m.column(c).iter().copied().collect();
2197 f(&col)
2198 })
2199 .collect();
2200 Ok(Value::Matrix(
2201 Array2::from_shape_vec((1, ncols), result).unwrap(),
2202 ))
2203 }
2204 }
2205 }
2206}
2207
2208fn apply_cm_reduction<F>(v: &Value, f: F) -> Result<Value, String>
2213where
2214 F: Fn(&[Complex<f64>]) -> Complex<f64>,
2215{
2216 let make_scalar = |c: Complex<f64>| -> Value {
2217 if c.im == 0.0 {
2218 Value::Scalar(c.re)
2219 } else {
2220 Value::Complex(c.re, c.im)
2221 }
2222 };
2223 match v {
2224 Value::Scalar(n) => Ok(make_scalar(f(&[Complex::new(*n, 0.0)]))),
2225 Value::Complex(re, im) => Ok(make_scalar(f(&[Complex::new(*re, *im)]))),
2226 Value::Matrix(m) => {
2227 if m.nrows() == 1 || m.ncols() == 1 {
2228 let vals: Vec<Complex<f64>> = m.iter().map(|&x| Complex::new(x, 0.0)).collect();
2229 Ok(make_scalar(f(&vals)))
2230 } else {
2231 let ncols = m.ncols();
2232 let result: Vec<Complex<f64>> = (0..ncols)
2233 .map(|c| {
2234 let col: Vec<Complex<f64>> =
2235 m.column(c).iter().map(|&x| Complex::new(x, 0.0)).collect();
2236 f(&col)
2237 })
2238 .collect();
2239 if result.iter().all(|c| c.im == 0.0) {
2240 let reals: Vec<f64> = result.iter().map(|c| c.re).collect();
2241 Ok(Value::Matrix(
2242 Array2::from_shape_vec((1, ncols), reals).unwrap(),
2243 ))
2244 } else {
2245 Ok(Value::ComplexMatrix(
2246 Array2::from_shape_vec((1, ncols), result).unwrap(),
2247 ))
2248 }
2249 }
2250 }
2251 Value::ComplexMatrix(m) => {
2252 if m.nrows() == 1 || m.ncols() == 1 {
2253 let vals: Vec<Complex<f64>> = m.iter().copied().collect();
2254 Ok(make_scalar(f(&vals)))
2255 } else {
2256 let ncols = m.ncols();
2257 let result: Vec<Complex<f64>> = (0..ncols)
2258 .map(|c| {
2259 let col: Vec<Complex<f64>> = m.column(c).iter().copied().collect();
2260 f(&col)
2261 })
2262 .collect();
2263 if result.iter().all(|c| c.im == 0.0) {
2264 let reals: Vec<f64> = result.iter().map(|c| c.re).collect();
2265 Ok(Value::Matrix(
2266 Array2::from_shape_vec((1, ncols), reals).unwrap(),
2267 ))
2268 } else {
2269 Ok(Value::ComplexMatrix(
2270 Array2::from_shape_vec((1, ncols), result).unwrap(),
2271 ))
2272 }
2273 }
2274 }
2275 _ => Err("Reduction not applicable to this type".to_string()),
2276 }
2277}
2278
2279fn apply_cumulative<F>(v: &Value, combine: F) -> Result<Value, String>
2283where
2284 F: Fn(f64, f64) -> f64,
2285{
2286 match v {
2287 Value::Void => Err("Cumulative reduction not applicable to void".to_string()),
2288 Value::Scalar(n) => Ok(Value::Scalar(*n)),
2289 Value::Complex(_, _) => {
2290 Err("Cumulative reduction not applicable to complex values".to_string())
2291 }
2292 Value::ComplexMatrix(_) => {
2293 Err("Cumulative reduction not applicable to complex matrices".to_string())
2294 }
2295 Value::Str(_) | Value::StringObj(_) => {
2296 Err("Cumulative reduction not applicable to strings".to_string())
2297 }
2298 Value::Lambda(_)
2299 | Value::Function { .. }
2300 | Value::Tuple(_)
2301 | Value::Cell(_)
2302 | Value::Struct(_)
2303 | Value::StructArray(_)
2304 | Value::DateTime(_)
2305 | Value::Duration(_)
2306 | Value::DateTimeArray(_)
2307 | Value::DurationArray(_) => {
2308 Err("Cumulative reduction not applicable to this type".to_string())
2309 }
2310 Value::Matrix(m) => {
2311 let initial = combine(0.0, 0.0); let identity = if (combine(1.0, 1.0) - 1.0).abs() < 1e-15 && initial == 0.0 {
2315 1.0 } else {
2317 0.0 };
2319 let (nrows, ncols) = (m.nrows(), m.ncols());
2320 let mut result = m.clone();
2321 if nrows == 1 || ncols == 1 {
2322 let mut acc = identity;
2324 for v in result.iter_mut() {
2325 acc = combine(acc, *v);
2326 *v = acc;
2327 }
2328 } else {
2329 for c in 0..ncols {
2331 let mut acc = identity;
2332 for r in 0..nrows {
2333 acc = combine(acc, result[[r, c]]);
2334 result[[r, c]] = acc;
2335 }
2336 }
2337 }
2338 Ok(Value::Matrix(result))
2339 }
2340 }
2341}
2342
2343fn find_nonzero(v: &Value, max_k: usize) -> Result<Value, String> {
2345 match v {
2346 Value::Void => Err("find: not applicable to void".to_string()),
2347 Value::ComplexMatrix(_) => Err("find: not applicable to complex matrices".to_string()),
2348 Value::Str(_) | Value::StringObj(_) => Err("find: not applicable to strings".to_string()),
2349 Value::Lambda(_)
2350 | Value::Function { .. }
2351 | Value::Tuple(_)
2352 | Value::Cell(_)
2353 | Value::Struct(_)
2354 | Value::StructArray(_)
2355 | Value::DateTime(_)
2356 | Value::Duration(_)
2357 | Value::DateTimeArray(_)
2358 | Value::DurationArray(_) => Err("find: not applicable to this type".to_string()),
2359 Value::Complex(re, im) => {
2360 if (*re != 0.0 || *im != 0.0) && max_k >= 1 {
2361 Ok(Value::Matrix(
2362 Array2::from_shape_vec((1, 1), vec![1.0]).unwrap(),
2363 ))
2364 } else {
2365 Ok(Value::Matrix(Array2::zeros((1, 0))))
2366 }
2367 }
2368 Value::Scalar(n) => {
2369 if *n != 0.0 && max_k >= 1 {
2370 Ok(Value::Matrix(
2371 Array2::from_shape_vec((1, 1), vec![1.0]).unwrap(),
2372 ))
2373 } else {
2374 Ok(Value::Matrix(Array2::zeros((1, 0))))
2375 }
2376 }
2377 Value::Matrix(m) => {
2378 let nrows = m.nrows();
2379 let total = m.len();
2380 let mut idxs: Vec<f64> = Vec::new();
2381 for i in 0..total {
2382 if idxs.len() >= max_k {
2383 break;
2384 }
2385 let row = i % nrows;
2386 let col = i / nrows;
2387 if m[[row, col]] != 0.0 {
2388 idxs.push((i + 1) as f64);
2389 }
2390 }
2391 let n = idxs.len();
2392 if n == 0 {
2393 Ok(Value::Matrix(Array2::zeros((1, 0))))
2394 } else {
2395 Ok(Value::Matrix(Array2::from_shape_vec((1, n), idxs).unwrap()))
2396 }
2397 }
2398 }
2399}
2400
2401pub fn format_printf(fmt: &str, args: &[Value]) -> Result<String, String> {
2415 let mut result = String::new();
2416 let mut arg_idx = 0;
2417
2418 loop {
2419 let consumed_before = arg_idx;
2420 let mut chars = fmt.chars().peekable();
2421
2422 while let Some(c) = chars.next() {
2423 if c == '\\' {
2424 match chars.next() {
2425 Some('n') => result.push('\n'),
2426 Some('t') => result.push('\t'),
2427 Some('\\') => result.push('\\'),
2428 Some('\'') => result.push('\''),
2429 Some('"') => result.push('"'),
2430 Some(other) => {
2431 result.push('\\');
2432 result.push(other);
2433 }
2434 None => result.push('\\'),
2435 }
2436 continue;
2437 }
2438
2439 if c != '%' {
2440 result.push(c);
2441 continue;
2442 }
2443
2444 if chars.peek() == Some(&'%') {
2446 chars.next();
2447 result.push('%');
2448 continue;
2449 }
2450
2451 let mut flag_minus = false;
2453 let mut flag_plus = false;
2454 let mut flag_zero = false;
2455 let mut flag_space = false;
2456 loop {
2457 match chars.peek() {
2458 Some('-') => {
2459 flag_minus = true;
2460 chars.next();
2461 }
2462 Some('+') => {
2463 flag_plus = true;
2464 chars.next();
2465 }
2466 Some('0') => {
2467 flag_zero = true;
2468 chars.next();
2469 }
2470 Some(' ') => {
2471 flag_space = true;
2472 chars.next();
2473 }
2474 _ => break,
2475 }
2476 }
2477
2478 let mut width_str = String::new();
2480 while let Some(&d) = chars.peek() {
2481 if d.is_ascii_digit() {
2482 width_str.push(d);
2483 chars.next();
2484 } else {
2485 break;
2486 }
2487 }
2488 let width: usize = width_str.parse().unwrap_or(0);
2489
2490 let mut precision: Option<usize> = None;
2492 if chars.peek() == Some(&'.') {
2493 chars.next();
2494 let mut p = String::new();
2495 while let Some(&d) = chars.peek() {
2496 if d.is_ascii_digit() {
2497 p.push(d);
2498 chars.next();
2499 } else {
2500 break;
2501 }
2502 }
2503 precision = Some(p.parse().unwrap_or(0));
2504 }
2505
2506 let spec = match chars.next() {
2508 Some(s) => s,
2509 None => {
2510 return Err("fprintf: incomplete format specifier at end of string".to_string());
2511 }
2512 };
2513
2514 if arg_idx >= args.len() {
2516 continue;
2517 }
2518
2519 let arg = &args[arg_idx];
2520 arg_idx += 1;
2521
2522 let formatted = match spec {
2523 'd' | 'i' => {
2524 let n = printf_scalar(arg, spec)?;
2525 let i = n.trunc() as i64;
2526 let s = printf_sign_str(i >= 0, flag_plus, flag_space, format!("{}", i.abs()));
2527 printf_pad(s, width, flag_minus, flag_zero)
2528 }
2529 'f' => {
2530 let n = printf_scalar(arg, spec)?;
2531 let prec = precision.unwrap_or(6);
2532 let s = printf_sign_str(
2533 n >= 0.0,
2534 flag_plus,
2535 flag_space,
2536 format!("{:.prec$}", n.abs(), prec = prec),
2537 );
2538 printf_pad(s, width, flag_minus, flag_zero)
2539 }
2540 'e' | 'E' => {
2541 let n = printf_scalar(arg, spec)?;
2542 let prec = precision.unwrap_or(6);
2543 let s = printf_format_sci(n, prec, flag_plus, flag_space, spec == 'E');
2544 printf_pad(s, width, flag_minus, flag_zero)
2545 }
2546 'g' | 'G' => {
2547 let n = printf_scalar(arg, spec)?;
2548 let prec = precision.unwrap_or(6).max(1);
2549 let s = printf_format_g(n, prec, flag_plus, flag_space, spec == 'G');
2550 printf_pad(s, width, flag_minus, flag_zero)
2551 }
2552 's' => {
2553 let s = printf_string(arg)?;
2554 let s = if let Some(max_len) = precision {
2555 s.chars().take(max_len).collect::<String>()
2556 } else {
2557 s
2558 };
2559 printf_pad(s, width, flag_minus, false)
2560 }
2561 other => return Err(format!("fprintf: unknown format specifier '%{other}'")),
2562 };
2563
2564 result.push_str(&formatted);
2565 }
2566
2567 if arg_idx >= args.len() || arg_idx == consumed_before {
2569 break;
2570 }
2571 }
2572
2573 Ok(result)
2574}
2575
2576fn printf_scalar(v: &Value, spec: char) -> Result<f64, String> {
2578 match v {
2579 Value::Scalar(n) => Ok(*n),
2580 Value::Complex(re, im) if *im == 0.0 => Ok(*re),
2581 Value::Str(s) if s.chars().count() == 1 => Ok(s.chars().next().unwrap() as u32 as f64),
2582 _ => Err(format!(
2583 "fprintf: expected numeric argument for '%{spec}', got {:?}",
2584 std::mem::discriminant(v)
2585 )),
2586 }
2587}
2588
2589fn printf_string(v: &Value) -> Result<String, String> {
2591 match v {
2592 Value::Str(s) | Value::StringObj(s) => Ok(s.clone()),
2593 Value::Scalar(n) => Ok(format_number(*n)),
2594 Value::Complex(re, im) => Ok(format_complex(*re, *im, &FormatMode::Custom(6))),
2595 Value::Void => Err("fprintf: cannot format void as string".to_string()),
2596 Value::Matrix(_) => Err("fprintf: cannot format matrix as string".to_string()),
2597 Value::ComplexMatrix(_) => {
2598 Err("fprintf: cannot format complex matrix as string".to_string())
2599 }
2600 Value::DateTime(ts) => Ok(crate::datetime::format_datetime(*ts)),
2601 Value::Duration(s) => Ok(crate::datetime::format_duration(*s)),
2602 Value::Lambda(_)
2603 | Value::Function { .. }
2604 | Value::Tuple(_)
2605 | Value::Cell(_)
2606 | Value::Struct(_)
2607 | Value::StructArray(_)
2608 | Value::DateTimeArray(_)
2609 | Value::DurationArray(_) => Err("fprintf: cannot format this type as string".to_string()),
2610 }
2611}
2612
2613fn printf_sign_str(positive: bool, flag_plus: bool, flag_space: bool, digits: String) -> String {
2615 if positive {
2616 if flag_plus {
2617 format!("+{digits}")
2618 } else if flag_space {
2619 format!(" {digits}")
2620 } else {
2621 digits
2622 }
2623 } else {
2624 format!("-{digits}")
2625 }
2626}
2627
2628fn printf_pad(s: String, width: usize, left_align: bool, zero_pad: bool) -> String {
2630 if s.len() >= width {
2631 return s;
2632 }
2633 let pad_len = width - s.len();
2634 if left_align {
2635 format!("{s}{}", " ".repeat(pad_len))
2636 } else if zero_pad {
2637 let (prefix, rest) = if s.starts_with(['+', '-', ' ']) {
2639 s.split_at(1)
2640 } else {
2641 ("", s.as_str())
2642 };
2643 format!("{prefix}{}{rest}", "0".repeat(pad_len))
2644 } else {
2645 format!("{}{s}", " ".repeat(pad_len))
2646 }
2647}
2648
2649fn printf_format_sci(
2652 n: f64,
2653 prec: usize,
2654 flag_plus: bool,
2655 flag_space: bool,
2656 upper: bool,
2657) -> String {
2658 if n == 0.0 {
2659 let zeros = "0".repeat(prec);
2660 let sep = if prec > 0 {
2661 format!(".{zeros}")
2662 } else {
2663 String::new()
2664 };
2665 let e_char = if upper { 'E' } else { 'e' };
2666 let sign = if flag_plus {
2667 "+"
2668 } else if flag_space {
2669 " "
2670 } else {
2671 ""
2672 };
2673 return format!("{sign}0{sep}{e_char}+00");
2674 }
2675
2676 let neg = n < 0.0;
2677 let abs_n = n.abs();
2678 let exp = abs_n.log10().floor() as i32;
2679 let mantissa = abs_n / 10f64.powi(exp);
2680 let man_str = format!("{:.prec$}", mantissa, prec = prec);
2681
2682 let e_char = if upper { 'E' } else { 'e' };
2683 let exp_sign = if exp >= 0 { '+' } else { '-' };
2684 let exp_abs = exp.unsigned_abs();
2685 let exp_str = if exp_abs < 10 {
2686 format!("{e_char}{exp_sign}0{exp_abs}")
2687 } else {
2688 format!("{e_char}{exp_sign}{exp_abs}")
2689 };
2690
2691 let sign_str = if neg {
2692 "-"
2693 } else if flag_plus {
2694 "+"
2695 } else if flag_space {
2696 " "
2697 } else {
2698 ""
2699 };
2700 format!("{sign_str}{man_str}{exp_str}")
2701}
2702
2703fn printf_format_g(n: f64, prec: usize, flag_plus: bool, flag_space: bool, upper: bool) -> String {
2706 if n == 0.0 {
2707 let sign = if flag_plus {
2708 "+"
2709 } else if flag_space {
2710 " "
2711 } else {
2712 ""
2713 };
2714 return format!("{sign}0");
2715 }
2716 let abs_n = n.abs();
2717 let exp = abs_n.log10().floor() as i32;
2718 if exp < -4 || exp >= prec as i32 {
2719 let s = printf_format_sci(n, prec.saturating_sub(1), flag_plus, flag_space, upper);
2720 trim_g_sci(s, upper)
2721 } else {
2722 let decimal_places = (prec as i32 - 1 - exp).max(0) as usize;
2723 let neg = n < 0.0;
2724 let s = format!("{:.prec$}", abs_n, prec = decimal_places);
2725 let s = if s.contains('.') {
2726 s.trim_end_matches('0').trim_end_matches('.').to_string()
2727 } else {
2728 s
2729 };
2730 let sign = if neg {
2731 "-"
2732 } else if flag_plus {
2733 "+"
2734 } else if flag_space {
2735 " "
2736 } else {
2737 ""
2738 };
2739 format!("{sign}{s}")
2740 }
2741}
2742
2743fn trim_g_sci(s: String, upper: bool) -> String {
2745 let e_char = if upper { 'E' } else { 'e' };
2746 if let Some(e_pos) = s.find(e_char) {
2747 let mantissa = &s[..e_pos];
2748 let exp_part = &s[e_pos..];
2749 let trimmed = if mantissa.contains('.') {
2750 mantissa.trim_end_matches('0').trim_end_matches('.')
2751 } else {
2752 mantissa
2753 };
2754 format!("{trimmed}{exp_part}")
2755 } else {
2756 s
2757 }
2758}
2759
2760fn call_function_value(
2765 f: &Value,
2766 args: &[Value],
2767 io: Option<&mut IoContext>,
2768) -> Result<Value, String> {
2769 match f {
2770 Value::Lambda(lf) => {
2771 let lf = lf.clone();
2772 lf.0(args, io)
2773 }
2774 Value::Function { .. } => {
2775 let empty_env = Env::new();
2779 match io {
2780 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
2781 Some(hook) => hook("<anonymous>", f, args, &empty_env, io_ref),
2782 None => Err("User function execution not initialized".to_string()),
2783 }),
2784 None => {
2785 let mut tmp_io = IoContext::new();
2786 FN_CALL_HOOK.with(|c| match c.get() {
2787 Some(hook) => hook("<anonymous>", f, args, &empty_env, &mut tmp_io),
2788 None => Err("User function execution not initialized".to_string()),
2789 })
2790 }
2791 }
2792 }
2793 _ => Err("cellfun/arrayfun: first argument must be a function or lambda (@fn)".to_string()),
2794 }
2795}
2796
2797pub fn builtin_names() -> &'static [&'static str] {
2801 &[
2802 "abs",
2803 "acos",
2804 "all",
2805 "angle",
2806 "any",
2807 "arrayfun",
2808 "asin",
2809 "assert",
2810 "atan",
2811 "atan2",
2812 "bitand",
2813 "bitnot",
2814 "bitor",
2815 "bitshift",
2816 "bitxor",
2817 "ceil",
2818 "cell",
2819 "cellfun",
2820 "chol",
2821 "complex",
2822 "cond",
2823 "cross",
2824 "conj",
2825 "contains",
2826 "conv",
2827 "cos",
2828 "cov",
2829 "cumprod",
2830 "cumsum",
2831 "datenum",
2832 "datestr",
2833 "datevec",
2834 "datetime",
2835 "day",
2836 "days",
2837 "deconv",
2838 "det",
2839 "diag",
2840 "diff",
2841 "dot",
2842 "disp",
2843 "dlmread",
2844 "dlmwrite",
2845 "eig",
2846 "endsWith",
2847 "erf",
2848 "eval",
2849 "erfc",
2850 "exist",
2851 "exp",
2852 "eye",
2853 "fclose",
2854 "fft",
2855 "fftfreq",
2856 "fftshift",
2857 "fgetl",
2858 "fgets",
2859 "fieldnames",
2860 "ifft",
2861 "ifftshift",
2862 "find",
2863 "fliplr",
2864 "flipud",
2865 "floor",
2866 "fopen",
2867 "fprintf",
2868 "genpath",
2869 "histc",
2870 "hour",
2871 "hours",
2872 "hypot",
2873 "imag",
2874 "ind2sub",
2875 "int2str",
2876 "interp1",
2877 "intersect",
2878 "inv",
2879 "iqr",
2880 "iscell",
2881 "ismember",
2882 "ischar",
2883 "isdatetime",
2884 "isduration",
2885 "isempty",
2886 "isfield",
2887 "isfile",
2888 "isfinite",
2889 "isfolder",
2890 "isinf",
2891 "isnan",
2892 "isnat",
2893 "isreal",
2894 "isstring",
2895 "isstruct",
2896 "jsonencode",
2897 "jsondecode",
2898 "kron",
2899 "kurtosis",
2900 "lasterr",
2901 "length",
2902 "linspace",
2903 "load",
2904 "log",
2905 "log10",
2906 "log2",
2907 "lower",
2908 "lu",
2909 "mat2str",
2910 "max",
2911 "mean",
2912 "median",
2913 "milliseconds",
2914 "min",
2915 "minute",
2916 "minutes",
2917 "mod",
2918 "mode",
2919 "month",
2920 "nan",
2921 "norm",
2922 "normcdf",
2923 "normpdf",
2924 "not",
2925 "null",
2926 "num2str",
2927 "numel",
2928 "ones",
2929 "orth",
2930 "pinv",
2931 "poly",
2932 "polyfit",
2933 "polyval",
2934 "posixtime",
2935 "prctile",
2936 "prod",
2937 "qr",
2938 "rand",
2939 "randi",
2940 "randn",
2941 "rank",
2942 "readmatrix",
2943 "readtable",
2944 "real",
2945 "regexp",
2946 "regexpi",
2947 "regexprep",
2948 "rem",
2949 "repelem",
2950 "repmat",
2951 "reshape",
2952 "rmfield",
2953 "rng",
2954 "roots",
2955 "round",
2956 "setdiff",
2957 "second",
2958 "seconds",
2959 "sign",
2960 "sin",
2961 "size",
2962 "skewness",
2963 "sort",
2964 "sprintf",
2965 "sqrt",
2966 "startsWith",
2967 "std",
2968 "str2double",
2969 "str2num",
2970 "strcmp",
2971 "strcmpi",
2972 "strjoin",
2973 "strrep",
2974 "strsplit",
2975 "strtrim",
2976 "sub2ind",
2977 "sum",
2978 "svd",
2979 "tan",
2980 "tic",
2981 "toc",
2982 "trace",
2983 "tril",
2984 "triu",
2985 "union",
2986 "unique",
2987 "upper",
2988 "var",
2989 "writetable",
2990 "xor",
2991 "year",
2992 "years",
2993 "zeros",
2994 "zscore",
2995 ]
2996}
2997
2998fn levenshtein(a: &str, b: &str) -> usize {
3000 let a: Vec<char> = a.chars().collect();
3001 let b: Vec<char> = b.chars().collect();
3002 let (m, n) = (a.len(), b.len());
3003 let mut row: Vec<usize> = (0..=n).collect();
3004 for i in 1..=m {
3005 let mut prev = row[0];
3006 row[0] = i;
3007 for j in 1..=n {
3008 let next = if a[i - 1] == b[j - 1] {
3009 prev
3010 } else {
3011 1 + prev.min(row[j]).min(row[j - 1])
3012 };
3013 prev = row[j];
3014 row[j] = next;
3015 }
3016 }
3017 row[n]
3018}
3019
3020fn suggest_similar(name: &str, env: &Env) -> Option<String> {
3022 const MAX_DIST: usize = 2;
3023 let mut best: Option<(String, usize)> = None;
3024 let mut update = |candidate: &str| {
3025 let d = levenshtein(name, candidate);
3026 if d <= MAX_DIST && best.as_ref().is_none_or(|(_, bd)| d < *bd) {
3027 best = Some((candidate.to_string(), d));
3028 }
3029 };
3030 for key in env.keys() {
3031 update(key);
3032 }
3033 for &bname in builtin_names() {
3034 update(bname);
3035 }
3036 best.map(|(s, _)| s)
3037}
3038
3039fn assert_values_equal(a: &Value, b: &Value, tol: Option<f64>) -> Result<Value, String> {
3041 match (a, b) {
3042 (Value::Scalar(x), Value::Scalar(y)) => {
3043 let ok = match tol {
3044 None => x == y,
3045 Some(t) => (x - y).abs() <= t,
3046 };
3047 if ok {
3048 Ok(Value::Void)
3049 } else if let Some(t) = tol {
3050 Err(format!(
3051 "assert: |{x} - {y}| = {} exceeds tolerance {t}",
3052 (x - y).abs()
3053 ))
3054 } else {
3055 Err(format!("assert: {x} ~= {y}"))
3056 }
3057 }
3058 (Value::Matrix(ma), Value::Matrix(mb)) => {
3059 if ma.shape() != mb.shape() {
3060 return Err(format!(
3061 "assert: size mismatch [{}×{}] vs [{}×{}]",
3062 ma.nrows(),
3063 ma.ncols(),
3064 mb.nrows(),
3065 mb.ncols()
3066 ));
3067 }
3068 for (x, y) in ma.iter().zip(mb.iter()) {
3069 let ok = match tol {
3070 None => x == y,
3071 Some(t) => (x - y).abs() <= t,
3072 };
3073 if !ok {
3074 if let Some(t) = tol {
3075 return Err(format!(
3076 "assert: difference {} exceeds tolerance {t}",
3077 (x - y).abs()
3078 ));
3079 } else {
3080 return Err(format!("assert: {x} ~= {y}"));
3081 }
3082 }
3083 }
3084 Ok(Value::Void)
3085 }
3086 _ => {
3087 if tol.is_some() {
3088 return Err("assert: tolerance requires numeric arguments".to_string());
3089 }
3090 if a == b {
3091 Ok(Value::Void)
3092 } else {
3093 Err("assert: values not equal".to_string())
3094 }
3095 }
3096 }
3097}
3098
3099fn call_builtin(
3100 name: &str,
3101 args: &[Value],
3102 env: &Env,
3103 mut io: Option<&mut IoContext>,
3104) -> Result<Value, String> {
3105 match (name, args.len()) {
3106 ("sqrt", 1) => apply_elem(&args[0], |x| x.sqrt()),
3108 ("floor", 1) => apply_elem(&args[0], |x| x.floor()),
3109 ("ceil", 1) => apply_elem(&args[0], |x| x.ceil()),
3110 ("round", 1) => apply_elem(&args[0], |x| x.round()),
3111 ("sign", 1) => apply_elem(&args[0], |x| x.signum()),
3112 ("log", 1) => apply_elem(&args[0], |x| x.ln()),
3113 ("log2", 1) => apply_elem(&args[0], |x| x.log2()),
3114 ("log10", 1) => apply_elem(&args[0], |x| x.log10()),
3115 ("exp", 1) => apply_elem(&args[0], |x| x.exp()),
3116 ("sin", 1) => apply_elem(&args[0], |x| x.sin()),
3117 ("cos", 1) => apply_elem(&args[0], |x| x.cos()),
3118 ("tan", 1) => apply_elem(&args[0], |x| x.tan()),
3119 ("asin", 1) => apply_elem(&args[0], |x| x.asin()),
3120 ("acos", 1) => apply_elem(&args[0], |x| x.acos()),
3121 ("atan", 1) => apply_elem(&args[0], |x| x.atan()),
3122 ("erf", 1) => apply_elem(&args[0], libm::erf),
3124 ("erfc", 1) => apply_elem(&args[0], libm::erfc),
3125 ("normcdf", 1) => apply_elem(&args[0], |x| {
3126 0.5 * (1.0 + libm::erf(x / std::f64::consts::SQRT_2))
3127 }),
3128 ("normcdf", 3) => {
3129 let mu = scalar_arg(&args[1], name, 2)?;
3130 let s = scalar_arg(&args[2], name, 3)?;
3131 if s <= 0.0 {
3132 return Err("normcdf: sigma must be positive".to_string());
3133 }
3134 apply_elem(&args[0], move |x| {
3135 0.5 * (1.0 + libm::erf((x - mu) / (s * std::f64::consts::SQRT_2)))
3136 })
3137 }
3138 ("normpdf", 1) => apply_elem(&args[0], |x| {
3139 (-0.5 * x * x).exp() / (2.0 * std::f64::consts::PI).sqrt()
3140 }),
3141 ("normpdf", 3) => {
3142 let mu = scalar_arg(&args[1], name, 2)?;
3143 let s = scalar_arg(&args[2], name, 3)?;
3144 if s <= 0.0 {
3145 return Err("normpdf: sigma must be positive".to_string());
3146 }
3147 apply_elem(&args[0], move |x| {
3148 let z = (x - mu) / s;
3149 (-0.5 * z * z).exp() / (s * (2.0 * std::f64::consts::PI).sqrt())
3150 })
3151 }
3152 ("atan2", 2) => Ok(Value::Scalar(
3154 scalar_arg(&args[0], name, 1)?.atan2(scalar_arg(&args[1], name, 2)?),
3155 )),
3156 ("mod", 2) => {
3157 let a = scalar_arg(&args[0], name, 1)?;
3158 let b = scalar_arg(&args[1], name, 2)?;
3159 Ok(Value::Scalar(a - b * (a / b).floor()))
3160 }
3161 ("rem", 2) => {
3162 let a = scalar_arg(&args[0], name, 1)?;
3163 let b = scalar_arg(&args[1], name, 2)?;
3164 Ok(Value::Scalar(a - b * (a / b).trunc()))
3165 }
3166 ("max", 2) => Ok(Value::Scalar(
3167 scalar_arg(&args[0], name, 1)?.max(scalar_arg(&args[1], name, 2)?),
3168 )),
3169 ("min", 2) => Ok(Value::Scalar(
3170 scalar_arg(&args[0], name, 1)?.min(scalar_arg(&args[1], name, 2)?),
3171 )),
3172 ("hypot", 2) => Ok(Value::Scalar(
3173 scalar_arg(&args[0], name, 1)?.hypot(scalar_arg(&args[1], name, 2)?),
3174 )),
3175 ("log", 2) => Ok(Value::Scalar(
3176 scalar_arg(&args[0], name, 1)?.log(scalar_arg(&args[1], name, 2)?),
3177 )),
3178 ("zeros", 1) => {
3180 let n = scalar_arg(&args[0], name, 1)? as usize;
3181 Ok(Value::Matrix(Array2::zeros((n, n))))
3182 }
3183 ("zeros", 2) => {
3184 let r = scalar_arg(&args[0], name, 1)? as usize;
3185 let c = scalar_arg(&args[1], name, 2)? as usize;
3186 Ok(Value::Matrix(Array2::zeros((r, c))))
3187 }
3188 ("ones", 1) => {
3189 let n = scalar_arg(&args[0], name, 1)? as usize;
3190 Ok(Value::Matrix(Array2::ones((n, n))))
3191 }
3192 ("ones", 2) => {
3193 let r = scalar_arg(&args[0], name, 1)? as usize;
3194 let c = scalar_arg(&args[1], name, 2)? as usize;
3195 Ok(Value::Matrix(Array2::ones((r, c))))
3196 }
3197 ("eye", 1) => {
3198 let n = scalar_arg(&args[0], name, 1)? as usize;
3199 let mut m = Array2::<f64>::zeros((n, n));
3200 for i in 0..n {
3201 m[[i, i]] = 1.0;
3202 }
3203 Ok(Value::Matrix(m))
3204 }
3205 ("size", 1) => match &args[0] {
3207 Value::Void => Err("size: not applicable to void".to_string()),
3208 Value::Scalar(_) | Value::Complex(_, _) | Value::Struct(_) => Ok(Value::Matrix(
3209 Array2::from_shape_vec((1, 2), vec![1.0, 1.0]).unwrap(),
3210 )),
3211 Value::Matrix(m) => Ok(Value::Matrix(
3212 Array2::from_shape_vec((1, 2), vec![m.nrows() as f64, m.ncols() as f64]).unwrap(),
3213 )),
3214 Value::ComplexMatrix(m) => Ok(Value::Matrix(
3215 Array2::from_shape_vec((1, 2), vec![m.nrows() as f64, m.ncols() as f64]).unwrap(),
3216 )),
3217 Value::Str(s) => Ok(Value::Matrix(
3218 Array2::from_shape_vec((1, 2), vec![1.0, s.chars().count() as f64]).unwrap(),
3219 )),
3220 Value::StringObj(_) => Ok(Value::Matrix(
3221 Array2::from_shape_vec((1, 2), vec![1.0, 1.0]).unwrap(),
3222 )),
3223 Value::Cell(v) => Ok(Value::Matrix(
3224 Array2::from_shape_vec((1, 2), vec![1.0, v.len() as f64]).unwrap(),
3225 )),
3226 Value::StructArray(arr) => Ok(Value::Matrix(
3227 Array2::from_shape_vec((1, 2), vec![1.0, arr.len() as f64]).unwrap(),
3228 )),
3229 Value::Lambda(_)
3230 | Value::Function { .. }
3231 | Value::Tuple(_)
3232 | Value::DateTime(_)
3233 | Value::Duration(_)
3234 | Value::DateTimeArray(_)
3235 | Value::DurationArray(_) => Err("size: not applicable to this type".to_string()),
3236 },
3237 ("size", 2) => {
3238 let dim = scalar_arg(&args[1], name, 2)? as usize;
3239 match &args[0] {
3240 Value::Void => Err("size: not applicable to void".to_string()),
3241 Value::Scalar(_) | Value::Complex(_, _) | Value::Struct(_) => {
3242 Ok(Value::Scalar(1.0))
3243 }
3244 Value::Matrix(m) => match dim {
3245 1 => Ok(Value::Scalar(m.nrows() as f64)),
3246 2 => Ok(Value::Scalar(m.ncols() as f64)),
3247 _ => Err(format!("size: invalid dimension {dim}, must be 1 or 2")),
3248 },
3249 Value::ComplexMatrix(m) => match dim {
3250 1 => Ok(Value::Scalar(m.nrows() as f64)),
3251 2 => Ok(Value::Scalar(m.ncols() as f64)),
3252 _ => Err(format!("size: invalid dimension {dim}, must be 1 or 2")),
3253 },
3254 Value::Str(s) => match dim {
3255 1 => Ok(Value::Scalar(1.0)),
3256 2 => Ok(Value::Scalar(s.chars().count() as f64)),
3257 _ => Err(format!("size: invalid dimension {dim}")),
3258 },
3259 Value::StringObj(_) => Ok(Value::Scalar(1.0)),
3260 Value::Cell(v) => match dim {
3261 1 => Ok(Value::Scalar(1.0)),
3262 2 => Ok(Value::Scalar(v.len() as f64)),
3263 _ => Err(format!("size: invalid dimension {dim}")),
3264 },
3265 Value::StructArray(arr) => match dim {
3266 1 => Ok(Value::Scalar(1.0)),
3267 2 => Ok(Value::Scalar(arr.len() as f64)),
3268 _ => Err(format!("size: invalid dimension {dim}")),
3269 },
3270 Value::Lambda(_)
3271 | Value::Function { .. }
3272 | Value::Tuple(_)
3273 | Value::DateTime(_)
3274 | Value::Duration(_)
3275 | Value::DateTimeArray(_)
3276 | Value::DurationArray(_) => Err("size: not applicable to this type".to_string()),
3277 }
3278 }
3279 ("length", 1) => match &args[0] {
3280 Value::Void => Err("length: not applicable to void".to_string()),
3281 Value::Scalar(_) | Value::Complex(_, _) | Value::Struct(_) => Ok(Value::Scalar(1.0)),
3282 Value::Matrix(m) => Ok(Value::Scalar(m.nrows().max(m.ncols()) as f64)),
3283 Value::ComplexMatrix(m) => Ok(Value::Scalar(m.nrows().max(m.ncols()) as f64)),
3284 Value::Str(s) => Ok(Value::Scalar(s.chars().count() as f64)),
3285 Value::StringObj(_) => Ok(Value::Scalar(1.0)),
3286 Value::Cell(v) => Ok(Value::Scalar(v.len() as f64)),
3287 Value::StructArray(arr) => Ok(Value::Scalar(arr.len() as f64)),
3288 Value::DateTimeArray(v) | Value::DurationArray(v) => Ok(Value::Scalar(v.len() as f64)),
3289 Value::DateTime(_) | Value::Duration(_) => Ok(Value::Scalar(1.0)),
3290 Value::Lambda(_) | Value::Function { .. } | Value::Tuple(_) => {
3291 Err("length: not applicable to function values".to_string())
3292 }
3293 },
3294 ("numel", 1) => match &args[0] {
3295 Value::Void => Err("numel: not applicable to void".to_string()),
3296 Value::Scalar(_) | Value::Complex(_, _) | Value::Struct(_) => Ok(Value::Scalar(1.0)),
3297 Value::Matrix(m) => Ok(Value::Scalar(m.len() as f64)),
3298 Value::ComplexMatrix(m) => Ok(Value::Scalar(m.len() as f64)),
3299 Value::Str(s) => Ok(Value::Scalar(s.chars().count() as f64)),
3300 Value::StringObj(_) => Ok(Value::Scalar(1.0)),
3301 Value::Cell(v) => Ok(Value::Scalar(v.len() as f64)),
3302 Value::StructArray(arr) => Ok(Value::Scalar(arr.len() as f64)),
3303 Value::DateTimeArray(v) | Value::DurationArray(v) => Ok(Value::Scalar(v.len() as f64)),
3304 Value::DateTime(_) | Value::Duration(_) => Ok(Value::Scalar(1.0)),
3305 Value::Lambda(_) | Value::Function { .. } | Value::Tuple(_) => {
3306 Err("numel: not applicable to function values".to_string())
3307 }
3308 },
3309 ("trace", 1) => match &args[0] {
3310 Value::Void => Err("trace: not applicable to void".to_string()),
3311 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3312 Value::Complex(re, _) => Ok(Value::Scalar(*re)),
3313 Value::Matrix(m) => {
3314 let n = m.nrows().min(m.ncols());
3315 Ok(Value::Scalar((0..n).map(|i| m[[i, i]]).sum()))
3316 }
3317 Value::ComplexMatrix(m) => {
3318 let n = m.nrows().min(m.ncols());
3319 let s: Complex<f64> = (0..n).map(|i| m[[i, i]]).sum();
3320 Ok(if s.im == 0.0 {
3321 Value::Scalar(s.re)
3322 } else {
3323 Value::Complex(s.re, s.im)
3324 })
3325 }
3326 Value::Str(_)
3327 | Value::StringObj(_)
3328 | Value::Lambda(_)
3329 | Value::Function { .. }
3330 | Value::Tuple(_)
3331 | Value::Cell(_)
3332 | Value::Struct(_)
3333 | Value::StructArray(_)
3334 | Value::DateTime(_)
3335 | Value::Duration(_)
3336 | Value::DateTimeArray(_)
3337 | Value::DurationArray(_) => {
3338 Err("trace: not applicable to non-numeric values".to_string())
3339 }
3340 },
3341 ("det", 1) => match &args[0] {
3342 Value::Void => Err("det: not applicable to void".to_string()),
3343 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3344 Value::Complex(_, _) => Err("det: not applicable to complex scalars".to_string()),
3345 Value::ComplexMatrix(_) => Err("det: not supported for complex matrices".to_string()),
3346 Value::Matrix(m) => Ok(Value::Scalar(det_matrix(m)?)),
3347 Value::Str(_)
3348 | Value::StringObj(_)
3349 | Value::Lambda(_)
3350 | Value::Function { .. }
3351 | Value::Tuple(_)
3352 | Value::Cell(_)
3353 | Value::Struct(_)
3354 | Value::StructArray(_)
3355 | Value::DateTime(_)
3356 | Value::Duration(_)
3357 | Value::DateTimeArray(_)
3358 | Value::DurationArray(_) => {
3359 Err("det: not applicable to non-numeric values".to_string())
3360 }
3361 },
3362 ("inv", 1) => match &args[0] {
3363 Value::Void => Err("inv: not applicable to void".to_string()),
3364 Value::Scalar(n) => {
3365 if *n == 0.0 {
3366 Err("inv: singular (zero scalar)".to_string())
3367 } else {
3368 Ok(Value::Scalar(1.0 / n))
3369 }
3370 }
3371 Value::Complex(re, im) => {
3372 let denom = re * re + im * im;
3374 if denom == 0.0 {
3375 Err("inv: singular (zero complex)".to_string())
3376 } else {
3377 Ok(make_complex(re / denom, -im / denom))
3378 }
3379 }
3380 Value::Matrix(m) => Ok(Value::Matrix(inv_matrix(m)?)),
3381 Value::ComplexMatrix(_) => Err("inv: not supported for complex matrices".to_string()),
3382 Value::Str(_)
3383 | Value::StringObj(_)
3384 | Value::Lambda(_)
3385 | Value::Function { .. }
3386 | Value::Tuple(_)
3387 | Value::Cell(_)
3388 | Value::Struct(_)
3389 | Value::StructArray(_)
3390 | Value::DateTime(_)
3391 | Value::Duration(_)
3392 | Value::DateTimeArray(_)
3393 | Value::DurationArray(_) => {
3394 Err("inv: not applicable to non-numeric values".to_string())
3395 }
3396 },
3397 ("linspace", 3) => {
3399 let a = scalar_arg(&args[0], name, 1)?;
3400 let b = scalar_arg(&args[1], name, 2)?;
3401 let n = scalar_arg(&args[2], name, 3)? as usize;
3402 if n == 0 {
3403 return Ok(Value::Matrix(Array2::zeros((1, 0))));
3404 }
3405 if n == 1 {
3406 return Ok(Value::Matrix(
3407 Array2::from_shape_vec((1, 1), vec![b]).unwrap(),
3408 ));
3409 }
3410 let vals: Vec<f64> = (0..n)
3411 .map(|i| a + (b - a) * i as f64 / (n - 1) as f64)
3412 .collect();
3413 Ok(Value::Matrix(Array2::from_shape_vec((1, n), vals).unwrap()))
3414 }
3415 ("bitand", 2) => {
3419 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
3420 let b = to_bits(scalar_arg(&args[1], name, 2)?, name, 2)?;
3421 Ok(Value::Scalar((a & b) as f64))
3422 }
3423 ("bitor", 2) => {
3424 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
3425 let b = to_bits(scalar_arg(&args[1], name, 2)?, name, 2)?;
3426 Ok(Value::Scalar((a | b) as f64))
3427 }
3428 ("bitxor", 2) => {
3429 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
3430 let b = to_bits(scalar_arg(&args[1], name, 2)?, name, 2)?;
3431 Ok(Value::Scalar((a ^ b) as f64))
3432 }
3433 ("bitshift", 2) => {
3436 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
3437 let n = scalar_arg(&args[1], name, 2)?;
3438 if n.fract() != 0.0 {
3439 return Err("bitshift: shift amount must be an integer".to_string());
3440 }
3441 let n = n as i64;
3442 let result: u64 = if n >= 64 || n <= -64 {
3443 0
3444 } else if n >= 0 {
3445 a.wrapping_shl(n as u32)
3446 } else {
3447 a.wrapping_shr((-n) as u32)
3448 };
3449 Ok(Value::Scalar(result as f64))
3450 }
3451 ("bitnot", 1) => {
3454 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
3455 let mask: u64 = 0xFFFF_FFFF;
3456 Ok(Value::Scalar(((a ^ mask) & mask) as f64))
3457 }
3458 ("bitnot", 2) => {
3459 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
3460 let bits = scalar_arg(&args[1], name, 2)?;
3461 if bits.fract() != 0.0 || !(1.0..=53.0).contains(&bits) {
3462 return Err(format!(
3463 "bitnot: bit-width must be an integer in [1, 53], got {bits}"
3464 ));
3465 }
3466 let mask: u64 = (1u64 << bits as u32) - 1;
3467 Ok(Value::Scalar(((a ^ mask) & mask) as f64))
3468 }
3469 ("isnan", 1) => apply_elem(&args[0], |x| if x.is_nan() { 1.0 } else { 0.0 }),
3471 ("isinf", 1) => apply_elem(&args[0], |x| if x.is_infinite() { 1.0 } else { 0.0 }),
3472 ("isfinite", 1) => apply_elem(&args[0], |x| if x.is_finite() { 1.0 } else { 0.0 }),
3473 ("nan", 1) => {
3475 let n = scalar_arg(&args[0], name, 1)? as usize;
3476 Ok(Value::Matrix(Array2::from_elem((n, n), f64::NAN)))
3477 }
3478 ("nan", 2) => {
3479 let r = scalar_arg(&args[0], name, 1)? as usize;
3480 let c = scalar_arg(&args[1], name, 2)? as usize;
3481 Ok(Value::Matrix(Array2::from_elem((r, c), f64::NAN)))
3482 }
3483 ("rand", 0) => Ok(Value::Scalar(rand_uniform())),
3485 ("rand", 1) => {
3486 let n = scalar_arg(&args[0], name, 1)? as usize;
3487 let data: Vec<f64> = (0..n * n).map(|_| rand_uniform()).collect();
3488 Ok(Value::Matrix(Array2::from_shape_vec((n, n), data).unwrap()))
3489 }
3490 ("rand", 2) => {
3491 let r = scalar_arg(&args[0], name, 1)? as usize;
3492 let c = scalar_arg(&args[1], name, 2)? as usize;
3493 let data: Vec<f64> = (0..r * c).map(|_| rand_uniform()).collect();
3494 Ok(Value::Matrix(Array2::from_shape_vec((r, c), data).unwrap()))
3495 }
3496 ("randn", 0) => Ok(Value::Scalar(rand_normal())),
3497 ("randn", 1) => {
3498 let n = scalar_arg(&args[0], name, 1)? as usize;
3499 let data: Vec<f64> = (0..n * n).map(|_| rand_normal()).collect();
3500 Ok(Value::Matrix(Array2::from_shape_vec((n, n), data).unwrap()))
3501 }
3502 ("randn", 2) => {
3503 let r = scalar_arg(&args[0], name, 1)? as usize;
3504 let c = scalar_arg(&args[1], name, 2)? as usize;
3505 let data: Vec<f64> = (0..r * c).map(|_| rand_normal()).collect();
3506 Ok(Value::Matrix(Array2::from_shape_vec((r, c), data).unwrap()))
3507 }
3508 ("randi", 1) => {
3509 let (lo, hi) = randi_range(&args[0])?;
3510 let v = RNG.with(|r| r.borrow_mut().gen_range(lo..=hi)) as f64;
3511 Ok(Value::Scalar(v))
3512 }
3513 ("randi", 2) => {
3514 let (lo, hi) = randi_range(&args[0])?;
3515 let n = scalar_arg(&args[1], name, 2)? as usize;
3516 let data: Vec<f64> = (0..n * n)
3517 .map(|_| RNG.with(|r| r.borrow_mut().gen_range(lo..=hi)) as f64)
3518 .collect();
3519 Ok(Value::Matrix(Array2::from_shape_vec((n, n), data).unwrap()))
3520 }
3521 ("randi", 3) => {
3522 let (lo, hi) = randi_range(&args[0])?;
3523 let r = scalar_arg(&args[1], name, 2)? as usize;
3524 let c = scalar_arg(&args[2], name, 3)? as usize;
3525 let data: Vec<f64> = (0..r * c)
3526 .map(|_| RNG.with(|rng| rng.borrow_mut().gen_range(lo..=hi)) as f64)
3527 .collect();
3528 Ok(Value::Matrix(Array2::from_shape_vec((r, c), data).unwrap()))
3529 }
3530 ("rng", 1) => match &args[0] {
3531 Value::Scalar(n) => {
3532 rng_seed(*n as u64);
3533 Ok(Value::Void)
3534 }
3535 Value::Str(s) | Value::StringObj(s) if s == "shuffle" => {
3536 rng_shuffle();
3537 Ok(Value::Void)
3538 }
3539 _ => Err("rng: argument must be a numeric seed or 'shuffle'".to_string()),
3540 },
3541 ("sum", 1) => {
3545 if matches!(&args[0], Value::Complex(_, _) | Value::ComplexMatrix(_)) {
3546 apply_cm_reduction(&args[0], |v| v.iter().copied().sum())
3547 } else {
3548 apply_reduction(&args[0], |v| v.iter().copied().sum())
3549 }
3550 }
3551 ("prod", 1) => {
3552 if matches!(&args[0], Value::Complex(_, _) | Value::ComplexMatrix(_)) {
3553 apply_cm_reduction(&args[0], |v| v.iter().copied().product())
3554 } else {
3555 apply_reduction(&args[0], |v| v.iter().copied().product())
3556 }
3557 }
3558 ("any", 1) => apply_reduction(&args[0], |v| {
3559 if v.iter().any(|&x| x != 0.0) {
3560 1.0
3561 } else {
3562 0.0
3563 }
3564 }),
3565 ("all", 1) => apply_reduction(&args[0], |v| {
3566 if v.iter().all(|&x| x != 0.0) {
3567 1.0
3568 } else {
3569 0.0
3570 }
3571 }),
3572 ("mean", 1) => {
3573 if matches!(&args[0], Value::Complex(_, _) | Value::ComplexMatrix(_)) {
3574 apply_cm_reduction(&args[0], |v| {
3575 if v.is_empty() {
3576 Complex::new(f64::NAN, 0.0)
3577 } else {
3578 v.iter().copied().sum::<Complex<f64>>() / v.len() as f64
3579 }
3580 })
3581 } else {
3582 apply_reduction(&args[0], |v| {
3583 if v.is_empty() {
3584 f64::NAN
3585 } else {
3586 v.iter().copied().sum::<f64>() / v.len() as f64
3587 }
3588 })
3589 }
3590 }
3591 ("min", 1) => apply_reduction(&args[0], |v| {
3594 v.iter().copied().fold(f64::INFINITY, f64::min)
3595 }),
3596 ("max", 1) => apply_reduction(&args[0], |v| {
3597 v.iter().copied().fold(f64::NEG_INFINITY, f64::max)
3598 }),
3599 ("norm", 1) => match &args[0] {
3601 Value::Void => Err("norm: not applicable to void".to_string()),
3602 Value::Scalar(n) => Ok(Value::Scalar(n.abs())),
3603 Value::Complex(re, im) => Ok(Value::Scalar((re * re + im * im).sqrt())),
3604 Value::Matrix(m) => {
3605 if m.nrows() <= 1 || m.ncols() <= 1 {
3606 Ok(Value::Scalar(m.iter().map(|x| x * x).sum::<f64>().sqrt()))
3608 } else {
3609 let (_, s, _) = svd_compute(m)?;
3611 Ok(Value::Scalar(s.first().copied().unwrap_or(0.0)))
3612 }
3613 }
3614 Value::ComplexMatrix(m) => Ok(Value::Scalar(
3615 m.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt(),
3616 )),
3617 Value::Str(_)
3618 | Value::StringObj(_)
3619 | Value::Lambda(_)
3620 | Value::Function { .. }
3621 | Value::Tuple(_)
3622 | Value::Cell(_)
3623 | Value::Struct(_)
3624 | Value::StructArray(_)
3625 | Value::DateTime(_)
3626 | Value::Duration(_)
3627 | Value::DateTimeArray(_)
3628 | Value::DurationArray(_) => {
3629 Err("norm: not applicable to non-numeric values".to_string())
3630 }
3631 },
3632 ("norm", 2) => match &args[1] {
3633 Value::Str(s) | Value::StringObj(s) => match s.as_str() {
3634 "fro" => match &args[0] {
3635 Value::Scalar(n) => Ok(Value::Scalar(n.abs())),
3636 Value::Matrix(m) => {
3637 Ok(Value::Scalar(m.iter().map(|x| x * x).sum::<f64>().sqrt()))
3638 }
3639 _ => Err("norm: first argument must be numeric".to_string()),
3640 },
3641 other => Err(format!("norm: unknown norm type '{other}'")),
3642 },
3643 _ => {
3644 let p = scalar_arg(&args[1], name, 2)?;
3645 match &args[0] {
3646 Value::Void => Err("norm: not applicable to void".to_string()),
3647 Value::Scalar(n) => Ok(Value::Scalar(n.abs())),
3648 Value::Complex(re, im) => Ok(Value::Scalar((re * re + im * im).sqrt().powf(p))),
3649 Value::Matrix(m) => {
3650 if m.nrows() > 1 && m.ncols() > 1 {
3651 if (p - 2.0).abs() < 1e-15 {
3653 let (_, s, _) = svd_compute(m)?;
3654 return Ok(Value::Scalar(s.first().copied().unwrap_or(0.0)));
3655 } else if (p - 1.0).abs() < 1e-15 {
3656 let v = (0..m.ncols())
3658 .map(|j| m.column(j).iter().map(|&x| x.abs()).sum::<f64>())
3659 .fold(0.0_f64, f64::max);
3660 return Ok(Value::Scalar(v));
3661 } else if p == f64::INFINITY {
3662 let v = (0..m.nrows())
3664 .map(|i| m.row(i).iter().map(|&x| x.abs()).sum::<f64>())
3665 .fold(0.0_f64, f64::max);
3666 return Ok(Value::Scalar(v));
3667 }
3668 }
3669 if p == f64::INFINITY {
3671 Ok(Value::Scalar(
3672 m.iter().copied().fold(0.0_f64, |acc, x| acc.max(x.abs())),
3673 ))
3674 } else {
3675 Ok(Value::Scalar(
3676 m.iter().map(|x| x.abs().powf(p)).sum::<f64>().powf(1.0 / p),
3677 ))
3678 }
3679 }
3680 Value::ComplexMatrix(m) => Ok(Value::Scalar(
3681 m.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt().powf(p),
3682 )),
3683 Value::Str(_)
3684 | Value::StringObj(_)
3685 | Value::Lambda(_)
3686 | Value::Function { .. }
3687 | Value::Tuple(_)
3688 | Value::Cell(_)
3689 | Value::Struct(_)
3690 | Value::StructArray(_)
3691 | Value::DateTime(_)
3692 | Value::Duration(_)
3693 | Value::DateTimeArray(_)
3694 | Value::DurationArray(_) => {
3695 Err("norm: not applicable to non-numeric values".to_string())
3696 }
3697 }
3698 }
3699 },
3700 ("cumsum", 1) => apply_cumulative(&args[0], |acc, x| acc + x),
3702 ("cumprod", 1) => apply_cumulative(&args[0], |acc, x| acc * x),
3703 ("sort", 1) => match &args[0] {
3705 Value::Void => Err("sort: not applicable to void".to_string()),
3706 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3707 Value::Complex(_, _) => Err("sort: not applicable to complex values".to_string()),
3708 Value::ComplexMatrix(_) => Err("sort: not applicable to complex values".to_string()),
3709 Value::Str(_)
3710 | Value::StringObj(_)
3711 | Value::Lambda(_)
3712 | Value::Function { .. }
3713 | Value::Tuple(_)
3714 | Value::Cell(_)
3715 | Value::Struct(_)
3716 | Value::StructArray(_)
3717 | Value::DateTime(_)
3718 | Value::Duration(_)
3719 | Value::DateTimeArray(_)
3720 | Value::DurationArray(_) => {
3721 Err("sort: not applicable to non-numeric values".to_string())
3722 }
3723 Value::Matrix(m) => {
3724 if m.nrows() > 1 && m.ncols() > 1 {
3725 return Err("sort: input must be a vector".to_string());
3726 }
3727 let mut vals: Vec<f64> = m.iter().copied().collect();
3728 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
3729 Ok(Value::Matrix(
3730 Array2::from_shape_vec(m.raw_dim(), vals).unwrap(),
3731 ))
3732 }
3733 },
3734 ("reshape", 3) => {
3736 let r = scalar_arg(&args[1], name, 2)? as usize;
3737 let c = scalar_arg(&args[2], name, 3)? as usize;
3738 match &args[0] {
3739 Value::Void => Err("reshape: not applicable to void".to_string()),
3740 Value::Scalar(n) => {
3741 if r * c != 1 {
3742 return Err(format!("reshape: cannot reshape 1 element into {r}x{c}"));
3743 }
3744 Ok(Value::Matrix(
3745 Array2::from_shape_vec((1, 1), vec![*n]).unwrap(),
3746 ))
3747 }
3748 Value::Complex(_, _) => {
3749 Err("reshape: not applicable to complex values".to_string())
3750 }
3751 Value::ComplexMatrix(_) => {
3752 Err("reshape: not supported for complex matrices".to_string())
3753 }
3754 Value::Str(_)
3755 | Value::StringObj(_)
3756 | Value::Lambda(_)
3757 | Value::Function { .. }
3758 | Value::Tuple(_)
3759 | Value::Cell(_)
3760 | Value::Struct(_)
3761 | Value::StructArray(_)
3762 | Value::DateTime(_)
3763 | Value::Duration(_)
3764 | Value::DateTimeArray(_)
3765 | Value::DurationArray(_) => {
3766 Err("reshape: not applicable to non-numeric values".to_string())
3767 }
3768 Value::Matrix(m) => {
3769 let total = m.len();
3770 if r * c != total {
3771 return Err(format!(
3772 "reshape: cannot reshape {total} elements into {r}x{c}"
3773 ));
3774 }
3775 let flat: Vec<f64> = (0..m.ncols())
3777 .flat_map(|col| (0..m.nrows()).map(move |row| m[[row, col]]))
3778 .collect();
3779 let mut result = Array2::<f64>::zeros((r, c));
3780 for (i, &v) in flat.iter().enumerate() {
3781 result[[i % r, i / r]] = v;
3782 }
3783 Ok(Value::Matrix(result))
3784 }
3785 }
3786 }
3787 ("fliplr", 1) => match &args[0] {
3789 Value::Void => Err(format!("{name}: not applicable to void")),
3790 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3791 Value::Complex(re, im) => Ok(Value::Complex(*re, *im)),
3792 Value::ComplexMatrix(_) => Err(format!("{name}: not supported for complex matrices")),
3793 Value::Str(_)
3794 | Value::StringObj(_)
3795 | Value::Lambda(_)
3796 | Value::Function { .. }
3797 | Value::Tuple(_)
3798 | Value::Cell(_)
3799 | Value::Struct(_)
3800 | Value::StructArray(_)
3801 | Value::DateTime(_)
3802 | Value::Duration(_)
3803 | Value::DateTimeArray(_)
3804 | Value::DurationArray(_) => {
3805 Err(format!("{name}: not applicable to non-numeric values"))
3806 }
3807 Value::Matrix(m) => {
3808 let (nrows, ncols) = (m.nrows(), m.ncols());
3809 let mut result = m.clone();
3810 for r in 0..nrows {
3811 for c in 0..ncols / 2 {
3812 let tmp = result[[r, c]];
3813 result[[r, c]] = result[[r, ncols - 1 - c]];
3814 result[[r, ncols - 1 - c]] = tmp;
3815 }
3816 }
3817 Ok(Value::Matrix(result))
3818 }
3819 },
3820 ("flipud", 1) => match &args[0] {
3821 Value::Void => Err(format!("{name}: not applicable to void")),
3822 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3823 Value::Complex(re, im) => Ok(Value::Complex(*re, *im)),
3824 Value::ComplexMatrix(_) => Err(format!("{name}: not supported for complex matrices")),
3825 Value::Str(_)
3826 | Value::StringObj(_)
3827 | Value::Lambda(_)
3828 | Value::Function { .. }
3829 | Value::Tuple(_)
3830 | Value::Cell(_)
3831 | Value::Struct(_)
3832 | Value::StructArray(_)
3833 | Value::DateTime(_)
3834 | Value::Duration(_)
3835 | Value::DateTimeArray(_)
3836 | Value::DurationArray(_) => {
3837 Err(format!("{name}: not applicable to non-numeric values"))
3838 }
3839 Value::Matrix(m) => {
3840 let (nrows, ncols) = (m.nrows(), m.ncols());
3841 let mut result = m.clone();
3842 for c in 0..ncols {
3843 for r in 0..nrows / 2 {
3844 let tmp = result[[r, c]];
3845 result[[r, c]] = result[[nrows - 1 - r, c]];
3846 result[[nrows - 1 - r, c]] = tmp;
3847 }
3848 }
3849 Ok(Value::Matrix(result))
3850 }
3851 },
3852 ("find", 1) => find_nonzero(&args[0], usize::MAX),
3854 ("find", 2) => {
3855 let k = scalar_arg(&args[1], name, 2)?;
3856 if k < 0.0 {
3857 return Err("find: k must be non-negative".to_string());
3858 }
3859 find_nonzero(&args[0], k as usize)
3860 }
3861 ("unique", 1) => match &args[0] {
3863 Value::Void => Err("unique: not applicable to void".to_string()),
3864 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3865 Value::Matrix(m) => {
3866 let mut vals: Vec<f64> = m.iter().copied().collect();
3867 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
3868 let mut unique: Vec<f64> = Vec::new();
3869 for v in vals {
3870 if unique.last().is_none_or(|&last| last != v) {
3871 unique.push(v);
3872 }
3873 }
3874 let n = unique.len();
3875 Ok(Value::Matrix(
3876 Array2::from_shape_vec((1, n), unique).unwrap(),
3877 ))
3878 }
3879 Value::Complex(_, _) => Err("unique: not applicable to complex values".to_string()),
3880 Value::ComplexMatrix(_) => Err("unique: not applicable to complex values".to_string()),
3881 Value::Str(_)
3882 | Value::StringObj(_)
3883 | Value::Lambda(_)
3884 | Value::Function { .. }
3885 | Value::Tuple(_)
3886 | Value::Cell(_)
3887 | Value::Struct(_)
3888 | Value::StructArray(_)
3889 | Value::DateTime(_)
3890 | Value::Duration(_)
3891 | Value::DateTimeArray(_)
3892 | Value::DurationArray(_) => {
3893 Err("unique: not applicable to non-numeric values".to_string())
3894 }
3895 },
3896 ("std", 1) => apply_stat(&args[0], |s| stat_var_vec(s, false).sqrt(), "std"),
3898 ("std", 2) => {
3899 let w = scalar_arg(&args[1], name, 2)?;
3900 let population = w != 0.0;
3901 apply_stat(&args[0], |s| stat_var_vec(s, population).sqrt(), "std")
3902 }
3903 ("var", 1) => apply_stat(&args[0], |s| stat_var_vec(s, false), "var"),
3904 ("var", 2) => {
3905 let w = scalar_arg(&args[1], name, 2)?;
3906 let population = w != 0.0;
3907 apply_stat(&args[0], |s| stat_var_vec(s, population), "var")
3908 }
3909 ("cov", 1) => match &args[0] {
3910 Value::Scalar(_) => Ok(Value::Scalar(0.0)),
3911 Value::Matrix(m) => {
3912 if m.nrows() == 1 || m.ncols() == 1 {
3913 let vals: Vec<f64> = m.iter().copied().collect();
3914 Ok(Value::Scalar(stat_var_vec(&vals, false)))
3915 } else {
3916 let (nobs, nvars) = (m.nrows(), m.ncols());
3917 if nobs < 2 {
3918 return Err("cov: need at least 2 observations".to_string());
3919 }
3920 let mut centered = m.clone();
3921 for c in 0..nvars {
3922 let col_mean: f64 = m.column(c).iter().sum::<f64>() / nobs as f64;
3923 for r in 0..nobs {
3924 centered[[r, c]] -= col_mean;
3925 }
3926 }
3927 let denom = (nobs - 1) as f64;
3928 let mut cov_mat = Array2::<f64>::zeros((nvars, nvars));
3929 for i in 0..nvars {
3930 for j in 0..nvars {
3931 let dot: f64 =
3932 (0..nobs).map(|r| centered[[r, i]] * centered[[r, j]]).sum();
3933 cov_mat[[i, j]] = dot / denom;
3934 }
3935 }
3936 Ok(Value::Matrix(cov_mat))
3937 }
3938 }
3939 _ => Err("cov: argument must be numeric".to_string()),
3940 },
3941 ("median", 1) => apply_stat(
3942 &args[0],
3943 |s| {
3944 if s.is_empty() {
3945 return f64::NAN;
3946 }
3947 let mut v = s.to_vec();
3948 v.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
3949 let n = v.len();
3950 if n % 2 == 0 {
3951 (v[n / 2 - 1] + v[n / 2]) / 2.0
3952 } else {
3953 v[n / 2]
3954 }
3955 },
3956 "median",
3957 ),
3958 ("mode", 1) => apply_stat(
3959 &args[0],
3960 |s| {
3961 if s.is_empty() {
3962 return f64::NAN;
3963 }
3964 let mut counts: std::collections::HashMap<u64, usize> =
3965 std::collections::HashMap::new();
3966 for &x in s {
3967 *counts.entry(x.to_bits()).or_insert(0) += 1;
3968 }
3969 let max_count = counts.values().copied().max().unwrap_or(0);
3970 let mut candidates: Vec<f64> = counts
3971 .iter()
3972 .filter(|&(_, &c)| c == max_count)
3973 .map(|(&bits, _)| f64::from_bits(bits))
3974 .collect();
3975 candidates.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
3976 candidates[0]
3977 },
3978 "mode",
3979 ),
3980 ("skewness", 1) => apply_stat(
3981 &args[0],
3982 |s| {
3983 let n = s.len();
3984 if n == 0 {
3985 return f64::NAN;
3986 }
3987 if n == 1 {
3988 return 0.0;
3989 }
3990 let mean = s.iter().sum::<f64>() / n as f64;
3991 let m2 = s.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n as f64;
3992 if m2 == 0.0 {
3993 return f64::NAN;
3994 }
3995 let m3 = s.iter().map(|&x| (x - mean).powi(3)).sum::<f64>() / n as f64;
3996 m3 / m2.powf(1.5)
3997 },
3998 "skewness",
3999 ),
4000 ("kurtosis", 1) => apply_stat(
4001 &args[0],
4002 |s| {
4003 let n = s.len();
4004 if n < 2 {
4005 return f64::NAN;
4006 }
4007 let mean = s.iter().sum::<f64>() / n as f64;
4008 let m2 = s.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n as f64;
4009 if m2 == 0.0 {
4010 return f64::NAN;
4011 }
4012 let m4 = s.iter().map(|&x| (x - mean).powi(4)).sum::<f64>() / n as f64;
4013 m4 / m2.powi(2)
4014 },
4015 "kurtosis",
4016 ),
4017 ("hist", n) if n == 1 || n == 2 => {
4018 let n_bins = if args.len() == 2 {
4019 scalar_arg(&args[1], name, 2)? as usize
4020 } else {
4021 10
4022 };
4023 if n_bins == 0 {
4024 return Err("hist: number of bins must be positive".to_string());
4025 }
4026 let vals = numeric_vec(&args[0], name)?;
4027 if vals.is_empty() {
4028 return Ok(Value::Void);
4029 }
4030 let min_v = vals.iter().cloned().fold(f64::INFINITY, f64::min);
4031 let max_v = vals.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
4032 let range = if max_v > min_v { max_v - min_v } else { 1.0 };
4033 let mut counts = vec![0usize; n_bins];
4034 for &v in &vals {
4035 let b = ((v - min_v) / range * n_bins as f64) as usize;
4036 counts[b.min(n_bins - 1)] += 1;
4037 }
4038 let bar_cols: usize = std::env::var("COLUMNS")
4039 .ok()
4040 .and_then(|s| s.parse::<usize>().ok())
4041 .unwrap_or(80)
4042 .saturating_sub(26)
4043 .max(10);
4044 let max_count = *counts.iter().max().unwrap_or(&1).max(&1);
4045 let mut output = String::new();
4046 for (i, &c) in counts.iter().enumerate() {
4047 let lo = min_v + range * (i as f64 / n_bins as f64);
4048 let hi = min_v + range * ((i + 1) as f64 / n_bins as f64);
4049 let bar_len = c * bar_cols / max_count;
4050 output.push_str(&format!(
4051 "{lo:8.4} {hi:8.4} |{bar:<bar_cols$}| {c}\n",
4052 bar = "#".repeat(bar_len),
4053 ));
4054 }
4055 match io.as_deref_mut() {
4056 Some(ctx) => ctx.write_to_fd(1, &output)?,
4057 None => {
4058 print!("{output}");
4059 if output.contains('\n') {
4060 std::io::stdout().flush().ok();
4061 }
4062 }
4063 }
4064 Ok(Value::Void)
4065 }
4066 ("histc", 2) => {
4067 let vals = numeric_vec(&args[0], name)?;
4068 let edges = numeric_vec(&args[1], name)?;
4069 if edges.is_empty() {
4070 return Err("histc: edges must not be empty".to_string());
4071 }
4072 let n_edges = edges.len();
4073 let mut counts = vec![0.0f64; n_edges];
4074 for &v in &vals {
4075 let last = n_edges - 1;
4077 if v == edges[last] {
4078 counts[last] += 1.0;
4079 } else {
4080 for i in 0..last {
4081 if v >= edges[i] && v < edges[i + 1] {
4082 counts[i] += 1.0;
4083 break;
4084 }
4085 }
4086 }
4087 }
4088 Ok(Value::Matrix(
4089 Array2::from_shape_vec((1, n_edges), counts).unwrap(),
4090 ))
4091 }
4092 ("prctile", 2) => {
4094 let p_vals = numeric_vec(&args[1], name)?;
4095 let n_p = p_vals.len();
4096
4097 let compute_col = |vals: &[f64]| -> Vec<f64> {
4099 let mut s = vals.to_vec();
4100 s.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
4101 p_vals.iter().map(|&p| percentile_sorted(&s, p)).collect()
4102 };
4103
4104 match &args[0] {
4105 Value::Scalar(n) => {
4106 let pr = compute_col(&[*n]);
4107 if n_p == 1 {
4108 Ok(Value::Scalar(pr[0]))
4109 } else {
4110 Ok(Value::Matrix(Array2::from_shape_vec((1, n_p), pr).unwrap()))
4111 }
4112 }
4113 Value::Matrix(m) if m.nrows() == 1 || m.ncols() == 1 => {
4114 let vals: Vec<f64> = m.iter().copied().collect();
4115 let pr = compute_col(&vals);
4116 if n_p == 1 {
4117 Ok(Value::Scalar(pr[0]))
4118 } else {
4119 Ok(Value::Matrix(Array2::from_shape_vec((1, n_p), pr).unwrap()))
4120 }
4121 }
4122 Value::Matrix(m) => {
4123 let ncols = m.ncols();
4125 let mut result = Array2::<f64>::zeros((n_p, ncols));
4126 for j in 0..ncols {
4127 let col: Vec<f64> = m.column(j).iter().copied().collect();
4128 let pr = compute_col(&col);
4129 for (i, &v) in pr.iter().enumerate() {
4130 result[[i, j]] = v;
4131 }
4132 }
4133 if n_p == 1 {
4134 let row: Vec<f64> = result.row(0).iter().copied().collect();
4135 Ok(Value::Matrix(
4136 Array2::from_shape_vec((1, ncols), row).unwrap(),
4137 ))
4138 } else {
4139 Ok(Value::Matrix(result))
4140 }
4141 }
4142 _ => Err("prctile: first argument must be numeric".to_string()),
4143 }
4144 }
4145 ("iqr", 1) => apply_stat(
4146 &args[0],
4147 |s| {
4148 let mut sorted = s.to_vec();
4149 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
4150 percentile_sorted(&sorted, 75.0) - percentile_sorted(&sorted, 25.0)
4151 },
4152 "iqr",
4153 ),
4154 ("zscore", 1) => match &args[0] {
4155 Value::Scalar(_) => Ok(Value::Scalar(0.0)),
4156 Value::Matrix(m) => {
4157 if m.nrows() == 1 || m.ncols() == 1 {
4158 let vals: Vec<f64> = m.iter().copied().collect();
4159 let n = vals.len() as f64;
4160 let mean = vals.iter().sum::<f64>() / n;
4161 let s = stat_var_vec(&vals, false).sqrt();
4162 let result: Vec<f64> = vals
4163 .iter()
4164 .map(|&x| if s == 0.0 { 0.0 } else { (x - mean) / s })
4165 .collect();
4166 Ok(Value::Matrix(
4167 Array2::from_shape_vec(m.raw_dim(), result).unwrap(),
4168 ))
4169 } else {
4170 let (nrows, ncols) = (m.nrows(), m.ncols());
4171 let mut result = m.clone();
4172 for j in 0..ncols {
4173 let col: Vec<f64> = m.column(j).iter().copied().collect();
4174 let mean = col.iter().sum::<f64>() / col.len() as f64;
4175 let s = stat_var_vec(&col, false).sqrt();
4176 for i in 0..nrows {
4177 result[[i, j]] = if s == 0.0 {
4178 0.0
4179 } else {
4180 (m[[i, j]] - mean) / s
4181 };
4182 }
4183 }
4184 Ok(Value::Matrix(result))
4185 }
4186 }
4187 _ => Err("zscore: argument must be numeric".to_string()),
4188 },
4189 ("diag", 1) => match &args[0] {
4191 Value::Scalar(n) => Ok(Value::Matrix(Array2::from_elem((1, 1), *n))),
4192 Value::Matrix(m) => {
4193 let (rows, cols) = (m.nrows(), m.ncols());
4194 if rows == 1 || cols == 1 {
4195 let v: Vec<f64> = m.iter().copied().collect();
4197 let n = v.len();
4198 let mut result = Array2::<f64>::zeros((n, n));
4199 for (i, &val) in v.iter().enumerate() {
4200 result[[i, i]] = val;
4201 }
4202 Ok(Value::Matrix(result))
4203 } else {
4204 let n = rows.min(cols);
4206 let d: Vec<f64> = (0..n).map(|i| m[[i, i]]).collect();
4207 Ok(Value::Matrix(Array2::from_shape_vec((n, 1), d).unwrap()))
4208 }
4209 }
4210 Value::Void => Err("diag: not applicable to void".to_string()),
4211 Value::Complex(re, im) => {
4212 let mut result = Array2::<Complex<f64>>::zeros((1, 1));
4213 result[[0, 0]] = Complex::new(*re, *im);
4214 Ok(Value::ComplexMatrix(result))
4215 }
4216 Value::ComplexMatrix(m) => {
4217 let (rows, cols) = (m.nrows(), m.ncols());
4218 if rows == 1 || cols == 1 {
4219 let v: Vec<Complex<f64>> = m.iter().copied().collect();
4220 let n = v.len();
4221 let mut result = Array2::<Complex<f64>>::zeros((n, n));
4222 for (i, &val) in v.iter().enumerate() {
4223 result[[i, i]] = val;
4224 }
4225 Ok(Value::ComplexMatrix(result))
4226 } else {
4227 let n = rows.min(cols);
4228 let d: Vec<Complex<f64>> = (0..n).map(|i| m[[i, i]]).collect();
4229 Ok(Value::ComplexMatrix(
4230 Array2::from_shape_vec((n, 1), d).unwrap(),
4231 ))
4232 }
4233 }
4234 Value::Str(_)
4235 | Value::StringObj(_)
4236 | Value::Lambda(_)
4237 | Value::Function { .. }
4238 | Value::Tuple(_)
4239 | Value::Cell(_)
4240 | Value::Struct(_)
4241 | Value::StructArray(_)
4242 | Value::DateTime(_)
4243 | Value::Duration(_)
4244 | Value::DateTimeArray(_)
4245 | Value::DurationArray(_) => {
4246 Err("diag: not applicable to non-numeric values".to_string())
4247 }
4248 },
4249
4250 ("real", 1) => match &args[0] {
4253 Value::Void => Err("real: not applicable to void".to_string()),
4254 Value::Scalar(n) => Ok(Value::Scalar(*n)),
4255 Value::Complex(re, _) => Ok(Value::Scalar(*re)),
4256 Value::Matrix(m) => Ok(Value::Matrix(m.clone())),
4257 Value::ComplexMatrix(m) => Ok(Value::Matrix(m.mapv(|c| c.re))),
4258 Value::Str(_)
4259 | Value::StringObj(_)
4260 | Value::Lambda(_)
4261 | Value::Function { .. }
4262 | Value::Tuple(_)
4263 | Value::Cell(_)
4264 | Value::Struct(_)
4265 | Value::StructArray(_)
4266 | Value::DateTime(_)
4267 | Value::Duration(_)
4268 | Value::DateTimeArray(_)
4269 | Value::DurationArray(_) => {
4270 Err("real: not applicable to non-numeric values".to_string())
4271 }
4272 },
4273 ("imag", 1) => match &args[0] {
4275 Value::Void => Err("imag: not applicable to void".to_string()),
4276 Value::Scalar(_) => Ok(Value::Scalar(0.0)),
4277 Value::Complex(_, im) => Ok(Value::Scalar(*im)),
4278 Value::Matrix(m) => Ok(Value::Matrix(Array2::zeros(m.raw_dim()))),
4279 Value::ComplexMatrix(m) => Ok(Value::Matrix(m.mapv(|c| c.im))),
4280 Value::Str(_)
4281 | Value::StringObj(_)
4282 | Value::Lambda(_)
4283 | Value::Function { .. }
4284 | Value::Tuple(_)
4285 | Value::Cell(_)
4286 | Value::Struct(_)
4287 | Value::StructArray(_)
4288 | Value::DateTime(_)
4289 | Value::Duration(_)
4290 | Value::DateTimeArray(_)
4291 | Value::DurationArray(_) => {
4292 Err("imag: not applicable to non-numeric values".to_string())
4293 }
4294 },
4295 ("abs", 1) => match &args[0] {
4297 Value::Void => Err("abs: not applicable to void".to_string()),
4298 Value::Scalar(n) => Ok(Value::Scalar(n.abs())),
4299 Value::Complex(re, im) => Ok(Value::Scalar((re * re + im * im).sqrt())),
4300 Value::Matrix(m) => Ok(Value::Matrix(m.mapv(|x| x.abs()))),
4301 Value::ComplexMatrix(m) => Ok(Value::Matrix(m.mapv(|c| c.norm()))),
4302 Value::Str(_)
4303 | Value::StringObj(_)
4304 | Value::Lambda(_)
4305 | Value::Function { .. }
4306 | Value::Tuple(_)
4307 | Value::Cell(_)
4308 | Value::Struct(_)
4309 | Value::StructArray(_)
4310 | Value::DateTime(_)
4311 | Value::Duration(_)
4312 | Value::DateTimeArray(_)
4313 | Value::DurationArray(_) => {
4314 Err("abs: not applicable to non-numeric values".to_string())
4315 }
4316 },
4317 ("angle", 1) => match &args[0] {
4319 Value::Void => Err("angle: not applicable to void".to_string()),
4320 Value::Scalar(n) => Ok(Value::Scalar(if *n >= 0.0 {
4321 0.0
4322 } else {
4323 std::f64::consts::PI
4324 })),
4325 Value::Complex(re, im) => Ok(Value::Scalar(im.atan2(*re))),
4326 Value::Matrix(m) => {
4327 Ok(Value::Matrix(m.mapv(|x| {
4328 if x >= 0.0 { 0.0 } else { std::f64::consts::PI }
4329 })))
4330 }
4331 Value::ComplexMatrix(m) => Ok(Value::Matrix(m.mapv(|c| c.im.atan2(c.re)))),
4332 Value::Str(_)
4333 | Value::StringObj(_)
4334 | Value::Lambda(_)
4335 | Value::Function { .. }
4336 | Value::Tuple(_)
4337 | Value::Cell(_)
4338 | Value::Struct(_)
4339 | Value::StructArray(_)
4340 | Value::DateTime(_)
4341 | Value::Duration(_)
4342 | Value::DateTimeArray(_)
4343 | Value::DurationArray(_) => {
4344 Err("angle: not applicable to non-numeric values".to_string())
4345 }
4346 },
4347 ("conj", 1) => match &args[0] {
4349 Value::Void => Err("conj: not applicable to void".to_string()),
4350 Value::Scalar(n) => Ok(Value::Scalar(*n)),
4351 Value::Complex(re, im) => Ok(make_complex(*re, -*im)),
4352 Value::Matrix(m) => Ok(Value::Matrix(m.clone())),
4353 Value::ComplexMatrix(m) => Ok(Value::ComplexMatrix(m.mapv(|c| c.conj()))),
4354 Value::Str(_)
4355 | Value::StringObj(_)
4356 | Value::Lambda(_)
4357 | Value::Function { .. }
4358 | Value::Tuple(_)
4359 | Value::Cell(_)
4360 | Value::Struct(_)
4361 | Value::StructArray(_)
4362 | Value::DateTime(_)
4363 | Value::Duration(_)
4364 | Value::DateTimeArray(_)
4365 | Value::DurationArray(_) => {
4366 Err("conj: not applicable to non-numeric values".to_string())
4367 }
4368 },
4369 ("complex", 2) => {
4371 let re = scalar_arg(&args[0], name, 1)?;
4372 let im = scalar_arg(&args[1], name, 2)?;
4373 Ok(make_complex(re, im))
4374 }
4375 ("isreal", 1) => match &args[0] {
4377 Value::Void => Ok(Value::Scalar(0.0)),
4378 Value::Scalar(_) => Ok(Value::Scalar(1.0)),
4379 Value::Complex(_, im) => Ok(Value::Scalar(if *im == 0.0 { 1.0 } else { 0.0 })),
4380 Value::Matrix(_) => Ok(Value::Scalar(1.0)),
4381 Value::ComplexMatrix(_) => Ok(Value::Scalar(0.0)),
4382 Value::Str(_) | Value::StringObj(_) => Ok(Value::Scalar(0.0)),
4384 Value::Lambda(_)
4385 | Value::Function { .. }
4386 | Value::Tuple(_)
4387 | Value::Cell(_)
4388 | Value::Struct(_)
4389 | Value::StructArray(_)
4390 | Value::DateTime(_)
4391 | Value::Duration(_)
4392 | Value::DateTimeArray(_)
4393 | Value::DurationArray(_) => Ok(Value::Scalar(0.0)),
4394 },
4395 ("num2str", 1) => match &args[0] {
4398 Value::Void => Err("num2str: not applicable to void".to_string()),
4399 Value::Str(s) => Ok(Value::Str(s.clone())),
4400 Value::StringObj(s) => Ok(Value::Str(s.clone())),
4401 Value::Scalar(n) => Ok(Value::Str(fmt_auto_sig(*n, 5))),
4402 Value::Complex(re, im) => Ok(Value::Str(format_complex(*re, *im, &FormatMode::Short))),
4403 Value::Matrix(m) => {
4404 let s = m
4405 .iter()
4406 .map(|x| fmt_auto_sig(*x, 5))
4407 .collect::<Vec<_>>()
4408 .join(" ");
4409 Ok(Value::Str(s))
4410 }
4411 Value::ComplexMatrix(_) => {
4412 Err("num2str: not supported for complex matrices".to_string())
4413 }
4414 Value::Lambda(_)
4415 | Value::Function { .. }
4416 | Value::Tuple(_)
4417 | Value::Cell(_)
4418 | Value::Struct(_)
4419 | Value::StructArray(_)
4420 | Value::DateTime(_)
4421 | Value::Duration(_)
4422 | Value::DateTimeArray(_)
4423 | Value::DurationArray(_) => Err("num2str: not applicable to this type".to_string()),
4424 },
4425 ("num2str", 2) => {
4427 let n = scalar_arg(&args[1], name, 2)? as usize;
4428 match &args[0] {
4429 Value::Void => Err("num2str: not applicable to void".to_string()),
4430 Value::Str(s) => Ok(Value::Str(s.clone())),
4431 Value::StringObj(s) => Ok(Value::Str(s.clone())),
4432 Value::Scalar(v) => Ok(Value::Str(fmt_auto_sig(*v, n))),
4433 Value::Complex(re, im) => {
4434 Ok(Value::Str(format_complex(*re, *im, &FormatMode::Custom(n))))
4435 }
4436 Value::Matrix(m) => {
4437 let s = m
4438 .iter()
4439 .map(|x| fmt_auto_sig(*x, n))
4440 .collect::<Vec<_>>()
4441 .join(" ");
4442 Ok(Value::Str(s))
4443 }
4444 Value::ComplexMatrix(_) => {
4445 Err("num2str: not supported for complex matrices".to_string())
4446 }
4447 Value::Lambda(_)
4448 | Value::Function { .. }
4449 | Value::Tuple(_)
4450 | Value::Cell(_)
4451 | Value::Struct(_)
4452 | Value::StructArray(_)
4453 | Value::DateTime(_)
4454 | Value::Duration(_)
4455 | Value::DateTimeArray(_)
4456 | Value::DurationArray(_) => {
4457 Err("num2str: not applicable to this type".to_string())
4458 }
4459 }
4460 }
4461 ("str2double", 1) => {
4463 let s = string_arg(&args[0], name, 1)?;
4464 match s.trim().parse::<f64>() {
4465 Ok(n) => Ok(Value::Scalar(n)),
4466 Err(_) => Ok(Value::Scalar(f64::NAN)),
4467 }
4468 }
4469 ("str2num", 1) => {
4471 let s = string_arg(&args[0], name, 1)?;
4472 s.trim()
4473 .parse::<f64>()
4474 .map(Value::Scalar)
4475 .map_err(|_| format!("str2num: cannot convert '{}' to number", s.trim()))
4476 }
4477 ("strcat", n) if n >= 2 => {
4479 let mut result = String::new();
4480 let mut any_obj = false;
4481 for (i, arg) in args.iter().enumerate() {
4482 match arg {
4483 Value::Str(s) => result.push_str(s.trim_end()),
4484 Value::StringObj(s) => {
4485 result.push_str(s);
4486 any_obj = true;
4487 }
4488 _ => return Err(format!("strcat: argument {} must be a string", i + 1)),
4489 }
4490 }
4491 if any_obj {
4492 Ok(Value::StringObj(result))
4493 } else {
4494 Ok(Value::Str(result))
4495 }
4496 }
4497 ("ischar", 1) => Ok(Value::Scalar(if matches!(&args[0], Value::Str(_)) {
4499 1.0
4500 } else {
4501 0.0
4502 })),
4503 ("isstring", 1) => Ok(Value::Scalar(if matches!(&args[0], Value::StringObj(_)) {
4505 1.0
4506 } else {
4507 0.0
4508 })),
4509 ("struct", _) => {
4512 if !args.len().is_multiple_of(2) {
4513 return Err(
4514 "struct: requires an even number of arguments (name, value, ...)".to_string(),
4515 );
4516 }
4517 let mut map = IndexMap::new();
4518 for pair in args.chunks(2) {
4519 let key = match &pair[0] {
4520 Value::Str(s) | Value::StringObj(s) => s.clone(),
4521 _ => return Err("struct: field names must be strings".to_string()),
4522 };
4523 map.insert(key, pair[1].clone());
4524 }
4525 Ok(Value::Struct(map))
4526 }
4527 ("fieldnames", 1) => match &args[0] {
4529 Value::Struct(map) => {
4530 let names: Vec<Value> = map.keys().map(|k| Value::Str(k.clone())).collect();
4531 Ok(Value::Cell(names))
4532 }
4533 Value::StructArray(arr) => {
4534 let names: Vec<Value> = arr
4536 .first()
4537 .map(|m| m.keys().map(|k| Value::Str(k.clone())).collect())
4538 .unwrap_or_default();
4539 Ok(Value::Cell(names))
4540 }
4541 _ => Err("fieldnames: argument must be a struct".to_string()),
4542 },
4543 ("isfield", 2) => {
4545 let field = match &args[1] {
4546 Value::Str(s) | Value::StringObj(s) => s.clone(),
4547 _ => return Err("isfield: second argument must be a string".to_string()),
4548 };
4549 Ok(Value::Scalar(match &args[0] {
4550 Value::Struct(map) if map.contains_key(&field) => 1.0,
4551 Value::StructArray(arr) if arr.first().is_some_and(|m| m.contains_key(&field)) => {
4552 1.0
4553 }
4554 _ => 0.0,
4555 }))
4556 }
4557 ("rmfield", 2) => {
4559 let field = match &args[1] {
4560 Value::Str(s) | Value::StringObj(s) => s.clone(),
4561 _ => return Err("rmfield: second argument must be a string".to_string()),
4562 };
4563 match &args[0] {
4564 Value::Struct(map) => {
4565 if !map.contains_key(&field) {
4566 return Err(format!("rmfield: field '{field}' does not exist"));
4567 }
4568 let mut updated = map.clone();
4569 updated.shift_remove(&field);
4570 Ok(Value::Struct(updated))
4571 }
4572 Value::StructArray(arr) => {
4573 let updated: Result<Vec<_>, _> = arr
4574 .iter()
4575 .map(|m| {
4576 if !m.contains_key(&field) {
4577 return Err(format!("rmfield: field '{field}' does not exist"));
4578 }
4579 let mut m2 = m.clone();
4580 m2.shift_remove(&field);
4581 Ok(m2)
4582 })
4583 .collect();
4584 Ok(Value::StructArray(updated?))
4585 }
4586 _ => Err("rmfield: first argument must be a struct".to_string()),
4587 }
4588 }
4589 ("isstruct", 1) => Ok(Value::Scalar(
4591 if matches!(&args[0], Value::Struct(_) | Value::StructArray(_)) {
4592 1.0
4593 } else {
4594 0.0
4595 },
4596 )),
4597 ("isempty", 1) => {
4601 let empty = match &args[0] {
4602 Value::Matrix(m) => m.is_empty(),
4603 Value::Str(s) | Value::StringObj(s) => s.is_empty(),
4604 Value::Cell(v) => v.is_empty(),
4605 Value::Void => true,
4606 _ => false,
4607 };
4608 Ok(Value::Scalar(if empty { 1.0 } else { 0.0 }))
4609 }
4610 ("iscell", 1) => Ok(Value::Scalar(if matches!(&args[0], Value::Cell(_)) {
4612 1.0
4613 } else {
4614 0.0
4615 })),
4616 ("cell", 1) => {
4618 let n = scalar_arg(&args[0], name, 1)? as usize;
4619 Ok(Value::Cell(vec![Value::Scalar(0.0); n]))
4620 }
4621 ("cell", 2) => {
4623 let m = scalar_arg(&args[0], name, 1)? as usize;
4624 let n = scalar_arg(&args[1], name, 2)? as usize;
4625 Ok(Value::Cell(vec![Value::Scalar(0.0); m * n]))
4626 }
4627 ("cellfun", 2) => {
4630 let f = args[0].clone();
4631 match &args[1] {
4632 Value::Cell(elems) => {
4633 let elems = elems.clone();
4634 let mut results = Vec::with_capacity(elems.len());
4635 for elem in &elems {
4636 let result =
4637 call_function_value(&f, std::slice::from_ref(elem), io.as_deref_mut())?;
4638 results.push(result);
4639 }
4640 let all_scalar = results.iter().all(|v| matches!(v, Value::Scalar(_)));
4642 if all_scalar {
4643 let vals: Vec<f64> = results
4644 .iter()
4645 .map(|v| {
4646 if let Value::Scalar(n) = v {
4647 *n
4648 } else {
4649 unreachable!()
4650 }
4651 })
4652 .collect();
4653 let n = vals.len();
4654 if n == 0 {
4655 Ok(Value::Matrix(Array2::zeros((1, 0))))
4656 } else {
4657 Ok(Value::Matrix(Array2::from_shape_vec((1, n), vals).unwrap()))
4658 }
4659 } else {
4660 Ok(Value::Cell(results))
4661 }
4662 }
4663 _ => Err("cellfun: second argument must be a cell array".to_string()),
4664 }
4665 }
4666 ("arrayfun", 2) => {
4669 let f = args[0].clone();
4670 match &args[1] {
4671 Value::Matrix(m) => {
4672 let m = m.clone();
4673 let mut flat = Vec::with_capacity(m.len());
4674 for col in 0..m.ncols() {
4676 for row in 0..m.nrows() {
4677 let elem = Value::Scalar(m[[row, col]]);
4678 let result = call_function_value(&f, &[elem], io.as_deref_mut())?;
4679 match result {
4680 Value::Scalar(n) => flat.push(n),
4681 _ => {
4682 return Err(
4683 "arrayfun: function must return a scalar".to_string()
4684 );
4685 }
4686 }
4687 }
4688 }
4689 Ok(Value::Matrix(
4690 Array2::from_shape_vec((m.nrows(), m.ncols()), flat).unwrap(),
4691 ))
4692 }
4693 Value::Scalar(n) => {
4694 let elem = Value::Scalar(*n);
4695 let result = call_function_value(&f, &[elem], io.as_deref_mut())?;
4696 Ok(result)
4697 }
4698 _ => {
4699 Err("arrayfun: second argument must be a numeric matrix or scalar".to_string())
4700 }
4701 }
4702 }
4703 ("lower", 1) => match &args[0] {
4705 Value::Str(s) => Ok(Value::Str(s.to_lowercase())),
4706 Value::StringObj(s) => Ok(Value::StringObj(s.to_lowercase())),
4707 _ => Err("lower: argument must be a string".to_string()),
4708 },
4709 ("upper", 1) => match &args[0] {
4711 Value::Str(s) => Ok(Value::Str(s.to_uppercase())),
4712 Value::StringObj(s) => Ok(Value::StringObj(s.to_uppercase())),
4713 _ => Err("upper: argument must be a string".to_string()),
4714 },
4715 ("strtrim", 1) => match &args[0] {
4717 Value::Str(s) => Ok(Value::Str(s.trim().to_string())),
4718 Value::StringObj(s) => Ok(Value::StringObj(s.trim().to_string())),
4719 _ => Err("strtrim: argument must be a string".to_string()),
4720 },
4721 ("strrep", 3) => {
4723 let s = string_arg(&args[0], name, 1)?.to_string();
4724 let old = string_arg(&args[1], name, 2)?;
4725 let new = string_arg(&args[2], name, 3)?;
4726 let result = s.replace(old, new);
4727 match &args[0] {
4728 Value::StringObj(_) => Ok(Value::StringObj(result)),
4729 _ => Ok(Value::Str(result)),
4730 }
4731 }
4732 ("strcmp", 2) => {
4734 let a = string_arg(&args[0], name, 1)?;
4735 let b = string_arg(&args[1], name, 2)?;
4736 Ok(Value::Scalar(bool_to_f64(a == b)))
4737 }
4738 ("strcmpi", 2) => {
4740 let a = string_arg(&args[0], name, 1)?.to_lowercase();
4741 let b = string_arg(&args[1], name, 2)?.to_lowercase();
4742 Ok(Value::Scalar(bool_to_f64(a == b)))
4743 }
4744 ("disp", 1) => {
4746 use std::io::Write;
4747 let mode = get_display_fmt();
4748 let output = match &args[0] {
4749 Value::Str(s) | Value::StringObj(s) => format!("{s}\n"),
4750 v => match format_value_full(v, &mode) {
4751 Some(block) => format!("{block}\n\n"),
4752 None => format!("{}\n", format_value(v, get_display_base(), &mode)),
4753 },
4754 };
4755 match io {
4756 Some(ctx) => ctx.write_to_fd(1, &output)?,
4757 None => {
4758 print!("{output}");
4759 if output.contains('\n') {
4760 std::io::stdout().flush().ok();
4761 }
4762 }
4763 }
4764 Ok(Value::Void)
4765 }
4766 ("sprintf", n) if n >= 1 => {
4768 let fmt = string_arg(&args[0], name, 1)?.to_string();
4769 let result = format_printf(&fmt, &args[1..])?;
4770 Ok(Value::Str(result))
4771 }
4772 ("fprintf", n) if n >= 1 => {
4774 let (fd, fmt_idx) = match &args[0] {
4776 Value::Scalar(n) => (*n as i32, 1),
4777 _ => (1, 0),
4778 };
4779 if fmt_idx >= args.len() {
4780 return Err("fprintf: missing format string".to_string());
4781 }
4782 let fmt = string_arg(&args[fmt_idx], name, fmt_idx + 1)?.to_string();
4783 let output = format_printf(&fmt, &args[fmt_idx + 1..])?;
4784 match io {
4785 Some(ctx) => ctx.write_to_fd(fd, &output)?,
4786 None => {
4787 if fd == 1 {
4789 use std::io::Write;
4790 print!("{output}");
4791 if output.contains('\n') {
4792 std::io::stdout().flush().ok();
4793 }
4794 } else {
4795 return Err("fprintf: file I/O not available in this context".to_string());
4796 }
4797 }
4798 }
4799 Ok(Value::Void)
4800 }
4801 ("fopen", 2) => {
4803 let path = string_arg(&args[0], name, 1)?;
4804 let mode = string_arg(&args[1], name, 2)?;
4805 match io {
4806 Some(ctx) => Ok(Value::Scalar(ctx.fopen(path, mode) as f64)),
4807 None => Err("fopen: file I/O not available in this context".to_string()),
4808 }
4809 }
4810 ("fclose", 1) => match &args[0] {
4812 Value::Str(s) if s == "all" => {
4813 if let Some(ctx) = io {
4814 ctx.fclose_all();
4815 }
4816 Ok(Value::Scalar(0.0))
4817 }
4818 _ => {
4819 let fd = scalar_arg(&args[0], name, 1)? as i32;
4820 match io {
4821 Some(ctx) => Ok(Value::Scalar(ctx.fclose(fd) as f64)),
4822 None => Err("fclose: file I/O not available in this context".to_string()),
4823 }
4824 }
4825 },
4826 ("fgetl", 1) => {
4828 let fd = scalar_arg(&args[0], name, 1)? as i32;
4829 match io {
4830 Some(ctx) => match ctx.fgetl(fd) {
4831 Some(line) => Ok(Value::Str(line)),
4832 None => Ok(Value::Scalar(-1.0)),
4833 },
4834 None => Err("fgetl: file I/O not available in this context".to_string()),
4835 }
4836 }
4837 ("fgets", 1) => {
4839 let fd = scalar_arg(&args[0], name, 1)? as i32;
4840 match io {
4841 Some(ctx) => match ctx.fgets(fd) {
4842 Some(line) => Ok(Value::Str(line)),
4843 None => Ok(Value::Scalar(-1.0)),
4844 },
4845 None => Err("fgets: file I/O not available in this context".to_string()),
4846 }
4847 }
4848 ("isfile", 1) => {
4850 let path = string_arg(&args[0], name, 1)?;
4851 let is_file = std::fs::metadata(path)
4852 .map(|m| m.is_file())
4853 .unwrap_or(false);
4854 Ok(Value::Scalar(bool_to_f64(is_file)))
4855 }
4856 ("isfolder", 1) => {
4858 let path = string_arg(&args[0], name, 1)?;
4859 let is_dir = std::fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false);
4860 Ok(Value::Scalar(bool_to_f64(is_dir)))
4861 }
4862 ("genpath", 1) => {
4864 let root = string_arg(&args[0], name, 1)?;
4865 let sep = if cfg!(windows) { ';' } else { ':' };
4866 let mut dirs: Vec<String> = Vec::new();
4867 let mut stack = vec![std::path::PathBuf::from(root)];
4868 while let Some(dir) = stack.pop() {
4869 if !dir.is_dir() {
4870 continue;
4871 }
4872 dirs.push(dir.to_string_lossy().into_owned());
4873 if let Ok(entries) = std::fs::read_dir(&dir) {
4874 let mut children: Vec<std::path::PathBuf> = entries
4875 .filter_map(|e| e.ok())
4876 .map(|e| e.path())
4877 .filter(|p| p.is_dir())
4878 .collect();
4879 children.sort();
4880 children.reverse();
4881 stack.extend(children);
4882 }
4883 }
4884 Ok(Value::Str(dirs.join(&sep.to_string())))
4885 }
4886 ("pwd", _) => {
4888 let cwd = std::env::current_dir()
4889 .map(|p| p.to_string_lossy().into_owned())
4890 .unwrap_or_default();
4891 Ok(Value::Str(cwd))
4892 }
4893 ("exist", 1) => {
4895 let name_arg = string_arg(&args[0], name, 1)?;
4896 if env.contains_key(name_arg) {
4897 Ok(Value::Scalar(1.0))
4898 } else if std::path::Path::new(name_arg).is_file() {
4899 Ok(Value::Scalar(2.0))
4900 } else {
4901 Ok(Value::Scalar(0.0))
4902 }
4903 }
4904 ("exist", 2) => {
4906 let name_arg = string_arg(&args[0], name, 1)?;
4907 let kind = string_arg(&args[1], name, 2)?;
4908 match kind {
4909 "var" => Ok(Value::Scalar(if env.contains_key(name_arg) {
4910 1.0
4911 } else {
4912 0.0
4913 })),
4914 "file" => Ok(Value::Scalar(if std::path::Path::new(name_arg).is_file() {
4915 2.0
4916 } else {
4917 0.0
4918 })),
4919 other => Err(format!(
4920 "exist: unknown type '{other}', expected 'var' or 'file'"
4921 )),
4922 }
4923 }
4924 ("dlmread", 1) => {
4926 let path = string_arg(&args[0], name, 1)?.to_string();
4927 dlmread_impl(&path, None)
4928 }
4929 ("dlmread", 2) => {
4930 let path = string_arg(&args[0], name, 1)?.to_string();
4931 let delim = interpret_delim(string_arg(&args[1], name, 2)?);
4932 dlmread_impl(&path, Some(delim))
4933 }
4934 ("dlmwrite", 2) => {
4936 let path = string_arg(&args[0], name, 1)?.to_string();
4937 dlmwrite_impl(&path, &args[1], None)
4938 }
4939 ("dlmwrite", 3) => {
4940 let path = string_arg(&args[0], name, 1)?.to_string();
4941 let delim = interpret_delim(string_arg(&args[2], name, 3)?);
4942 dlmwrite_impl(&path, &args[1], Some(delim))
4943 }
4944 ("readmatrix", n) if n == 1 || n == 3 => {
4946 let path = string_arg(&args[0], name, 1)?.to_string();
4947 let delim = parse_delimiter_opt(name, args, 1)?;
4948 readmatrix_impl(&path, delim)
4949 }
4950 ("readtable", n) if n == 1 || n == 3 => {
4952 let path = string_arg(&args[0], name, 1)?.to_string();
4953 let delim = parse_delimiter_opt(name, args, 1)?;
4954 readtable_impl(&path, delim)
4955 }
4956 ("writetable", n) if n == 2 || n == 4 => {
4958 let path = string_arg(&args[1], name, 2)?.to_string();
4959 let delim = parse_delimiter_opt(name, args, 2)?;
4960 writetable_impl(&args[0], &path, delim)
4961 }
4962 ("xor", 2) => {
4964 let a = &args[0];
4965 let b = &args[1];
4966 match (a, b) {
4967 (Value::Scalar(x), Value::Scalar(y)) => {
4968 Ok(Value::Scalar(bool_to_f64((*x != 0.0) ^ (*y != 0.0))))
4969 }
4970 (Value::Matrix(mx), Value::Matrix(my)) => {
4971 if mx.shape() != my.shape() {
4972 return Err("xor: matrices must have the same dimensions".to_string());
4973 }
4974 Ok(Value::Matrix(ndarray::Zip::from(mx).and(my).map_collect(
4975 |a, b| bool_to_f64((*a != 0.0) ^ (*b != 0.0)),
4976 )))
4977 }
4978 (Value::Scalar(s), Value::Matrix(m)) => {
4979 let sv = *s != 0.0;
4980 Ok(Value::Matrix(m.mapv(|x| bool_to_f64(sv ^ (x != 0.0)))))
4981 }
4982 (Value::Matrix(m), Value::Scalar(s)) => {
4983 let sv = *s != 0.0;
4984 Ok(Value::Matrix(m.mapv(|x| bool_to_f64((x != 0.0) ^ sv))))
4985 }
4986 _ => Err("xor: arguments must be numeric".to_string()),
4987 }
4988 }
4989 ("not", 1) => apply_elem(&args[0], |x| if x == 0.0 { 1.0 } else { 0.0 }),
4991 ("int2str", 1) => match &args[0] {
4993 Value::Scalar(n) => Ok(Value::Str(format!("{}", n.round() as i64))),
4994 Value::Matrix(m) => {
4995 let parts: Vec<String> =
4996 m.iter().map(|x| format!("{}", x.round() as i64)).collect();
4997 Ok(Value::Str(parts.join(" ")))
4998 }
4999 _ => Err("int2str: argument must be numeric".to_string()),
5000 },
5001 ("mat2str", 1) => match &args[0] {
5003 Value::Scalar(n) => Ok(Value::Str(format!("{n}"))),
5004 Value::Matrix(m) => {
5005 if m.nrows() == 0 || m.ncols() == 0 {
5006 return Ok(Value::Str("[]".to_string()));
5007 }
5008 let mut s = String::from("[");
5009 for (r, row) in m.rows().into_iter().enumerate() {
5010 if r > 0 {
5011 s.push(';');
5012 }
5013 for (c, val) in row.iter().enumerate() {
5014 if c > 0 {
5015 s.push(' ');
5016 }
5017 s.push_str(&format!("{val}"));
5018 }
5019 }
5020 s.push(']');
5021 Ok(Value::Str(s))
5022 }
5023 _ => Err("mat2str: argument must be numeric".to_string()),
5024 },
5025 ("strsplit", 2) => {
5027 let s = string_arg(&args[0], name, 1)?.to_string();
5028 let delim = string_arg(&args[1], name, 2)?.to_string();
5029 let parts: Vec<Value> = s
5030 .split(delim.as_str())
5031 .map(|p| Value::Str(p.to_string()))
5032 .collect();
5033 Ok(Value::Cell(parts))
5034 }
5035 ("strsplit", 1) => {
5037 let s = string_arg(&args[0], name, 1)?.to_string();
5038 let parts: Vec<Value> = s
5039 .split_whitespace()
5040 .map(|p| Value::Str(p.to_string()))
5041 .collect();
5042 Ok(Value::Cell(parts))
5043 }
5044 ("strjoin", n) if n == 1 || n == 2 => {
5046 let cells = match &args[0] {
5047 Value::Cell(v) => v,
5048 _ => {
5049 return Err(
5050 "strjoin: first argument must be a cell array of strings".to_string()
5051 );
5052 }
5053 };
5054 let delim = if n == 2 {
5055 string_arg(&args[1], name, 2)?.to_string()
5056 } else {
5057 " ".to_string()
5058 };
5059 let mut parts: Vec<String> = Vec::with_capacity(cells.len());
5060 for (i, v) in cells.iter().enumerate() {
5061 match v {
5062 Value::Str(s) | Value::StringObj(s) => parts.push(s.clone()),
5063 _ => return Err(format!("strjoin: element {} must be a string", i + 1)),
5064 }
5065 }
5066 Ok(Value::Str(parts.join(&delim)))
5067 }
5068 ("contains", 2) => {
5070 let s = string_arg(&args[0], name, 1)?;
5071 let pat = string_arg(&args[1], name, 2)?;
5072 Ok(Value::Scalar(bool_to_f64(s.contains(pat))))
5073 }
5074 ("contains", 4) => {
5075 let s = string_arg(&args[0], name, 1)?;
5076 let pat = string_arg(&args[1], name, 2)?;
5077 let key = string_arg(&args[2], name, 3)?;
5078 if key != "IgnoreCase" {
5079 return Err(format!(
5080 "contains: unknown option '{key}'; expected 'IgnoreCase'"
5081 ));
5082 }
5083 let ignore = match &args[3] {
5084 Value::Scalar(n) => *n != 0.0,
5085 _ => return Err("contains: 'IgnoreCase' value must be a scalar".to_string()),
5086 };
5087 if ignore {
5088 Ok(Value::Scalar(bool_to_f64(
5089 s.to_lowercase().contains(&pat.to_lowercase()),
5090 )))
5091 } else {
5092 Ok(Value::Scalar(bool_to_f64(s.contains(pat))))
5093 }
5094 }
5095 ("startsWith", 2) => {
5097 let s = string_arg(&args[0], name, 1)?;
5098 let pat = string_arg(&args[1], name, 2)?;
5099 Ok(Value::Scalar(bool_to_f64(s.starts_with(pat))))
5100 }
5101 ("endsWith", 2) => {
5103 let s = string_arg(&args[0], name, 1)?;
5104 let pat = string_arg(&args[1], name, 2)?;
5105 Ok(Value::Scalar(bool_to_f64(s.ends_with(pat))))
5106 }
5107 ("regexp", 2) => {
5109 let s = string_arg(&args[0], name, 1)?.to_string();
5110 let pat = string_arg(&args[1], name, 2)?.to_string();
5111 regexp_impl("regexp", &s, &pat, false, false)
5112 }
5113 ("regexp", 3) => {
5114 let s = string_arg(&args[0], name, 1)?.to_string();
5115 let pat = string_arg(&args[1], name, 2)?.to_string();
5116 let opt = string_arg(&args[2], name, 3)?;
5117 if opt != "match" {
5118 return Err(format!("regexp: unknown option '{opt}'; expected 'match'"));
5119 }
5120 regexp_impl("regexp", &s, &pat, false, true)
5121 }
5122 ("regexpi", 2) => {
5124 let s = string_arg(&args[0], name, 1)?.to_string();
5125 let pat = string_arg(&args[1], name, 2)?.to_string();
5126 regexp_impl("regexpi", &s, &pat, true, false)
5127 }
5128 ("regexpi", 3) => {
5129 let s = string_arg(&args[0], name, 1)?.to_string();
5130 let pat = string_arg(&args[1], name, 2)?.to_string();
5131 let opt = string_arg(&args[2], name, 3)?;
5132 if opt != "match" {
5133 return Err(format!("regexpi: unknown option '{opt}'; expected 'match'"));
5134 }
5135 regexp_impl("regexpi", &s, &pat, true, true)
5136 }
5137 ("regexprep", 3) => {
5139 let s = string_arg(&args[0], name, 1)?.to_string();
5140 let pat = string_arg(&args[1], name, 2)?.to_string();
5141 let rep = string_arg(&args[2], name, 3)?.to_string();
5142 regexprep_impl(&s, &pat, &rep)
5143 }
5144 ("error", _) if !args.is_empty() => {
5146 let fmt_str = match &args[0] {
5147 Value::Str(s) | Value::StringObj(s) => s.clone(),
5148 _ => return Err("error: first argument must be a format string".to_string()),
5149 };
5150 let msg = format_printf(&fmt_str, &args[1..])?;
5151 Err(msg)
5152 }
5153 ("warning", _) if !args.is_empty() => {
5155 let fmt_str = match &args[0] {
5156 Value::Str(s) | Value::StringObj(s) => s.clone(),
5157 _ => return Err("warning: first argument must be a format string".to_string()),
5158 };
5159 let msg = format_printf(&fmt_str, &args[1..])?;
5160 eprintln!("warning: {msg}");
5161 Ok(Value::Void)
5162 }
5163 ("lasterr", 0) => Ok(Value::Str(get_last_err())),
5165 ("lasterr", 1) => {
5166 let prev = get_last_err();
5167 let new_msg = match &args[0] {
5168 Value::Str(s) | Value::StringObj(s) => s.clone(),
5169 _ => return Err("lasterr: argument must be a string".to_string()),
5170 };
5171 set_last_err(&new_msg);
5172 Ok(Value::Str(prev))
5173 }
5174 ("pcall", _) if !args.is_empty() => {
5176 let callable = args[0].clone();
5177 let call_args = &args[1..];
5178 let result = match &callable {
5179 Value::Lambda(f) => {
5180 let f = f.clone();
5181 f.0(call_args, io)
5182 }
5183 Value::Function { .. } => match io {
5184 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
5185 Some(hook) => hook("<pcall>", &callable, call_args, env, io_ref),
5186 None => Err("pcall: function execution not initialized".to_string()),
5187 }),
5188 None => {
5189 let mut tmp_io = IoContext::new();
5190 FN_CALL_HOOK.with(|c| match c.get() {
5191 Some(hook) => hook("<pcall>", &callable, call_args, env, &mut tmp_io),
5192 None => Err("pcall: function execution not initialized".to_string()),
5193 })
5194 }
5195 },
5196 _ => {
5197 return Err(
5198 "pcall: first argument must be a function handle (@func)".to_string()
5199 );
5200 }
5201 };
5202 match result {
5203 Ok(v) => Ok(Value::Tuple(vec![Value::Scalar(1.0), v])),
5204 Err(msg) => {
5205 set_last_err(&msg);
5206 Ok(Value::Tuple(vec![Value::Scalar(0.0), Value::Str(msg)]))
5207 }
5208 }
5209 }
5210 ("eig", 1) => match &args[0] {
5214 Value::Scalar(n) => {
5215 if get_nargout() <= 1 {
5216 Ok(Value::Matrix(
5217 Array2::from_shape_vec((1, 1), vec![*n]).unwrap(),
5218 ))
5219 } else {
5220 Ok(Value::Tuple(vec![
5221 Value::Matrix(Array2::eye(1)),
5222 Value::Matrix(Array2::from_elem((1, 1), *n)),
5223 ]))
5224 }
5225 }
5226 Value::Matrix(m) => {
5227 let (evals, evecs) = eig_compute(m)?;
5228 let nn = evals.len();
5229 let has_imag = evals.iter().any(|c| c.im.abs() > 1e-14);
5230 if get_nargout() <= 1 {
5231 if has_imag {
5232 Ok(Value::ComplexMatrix(
5233 Array2::from_shape_vec((nn, 1), evals).unwrap(),
5234 ))
5235 } else {
5236 let reals: Vec<f64> = evals.iter().map(|c| c.re).collect();
5237 Ok(Value::Matrix(
5238 Array2::from_shape_vec((nn, 1), reals).unwrap(),
5239 ))
5240 }
5241 } else if has_imag {
5242 Err("eig: [V,D] form not supported when eigenvalues are complex".to_string())
5243 } else {
5244 let reals: Vec<f64> = evals.iter().map(|c| c.re).collect();
5245 let mut d = Array2::<f64>::zeros((nn, nn));
5246 for (i, &e) in reals.iter().enumerate() {
5247 d[[i, i]] = e;
5248 }
5249 Ok(Value::Tuple(vec![Value::Matrix(evecs), Value::Matrix(d)]))
5250 }
5251 }
5252 _ => Err("eig: argument must be a real numeric matrix".to_string()),
5253 },
5254
5255 ("svd", 1) => match &args[0] {
5258 Value::Scalar(n) => {
5259 let sv = n.abs();
5260 if get_nargout() <= 1 {
5261 Ok(Value::Matrix(
5262 Array2::from_shape_vec((1, 1), vec![sv]).unwrap(),
5263 ))
5264 } else {
5265 Ok(Value::Tuple(vec![
5266 Value::Matrix(Array2::eye(1)),
5267 Value::Matrix(Array2::from_elem((1, 1), sv)),
5268 Value::Matrix(Array2::eye(1)),
5269 ]))
5270 }
5271 }
5272 Value::Matrix(m) => {
5273 let mm = m.nrows();
5274 let nn = m.ncols();
5275 let (u_c, s_v, v_c) = svd_compute(m)?;
5276 let k = s_v.len();
5277 if get_nargout() <= 1 {
5278 let col: Vec<f64> = s_v;
5279 Ok(Value::Matrix(Array2::from_shape_vec((k, 1), col).unwrap()))
5280 } else {
5281 let u_full = complete_orthonormal_basis(&u_c);
5283 let mut s_mat = Array2::<f64>::zeros((mm, nn));
5284 for (i, &sv) in s_v.iter().enumerate() {
5285 s_mat[[i, i]] = sv;
5286 }
5287 Ok(Value::Tuple(vec![
5288 Value::Matrix(u_full),
5289 Value::Matrix(s_mat),
5290 Value::Matrix(v_c),
5291 ]))
5292 }
5293 }
5294 _ => Err("svd: argument must be a real numeric matrix".to_string()),
5295 },
5296 ("svd", 2) => match (&args[0], &args[1]) {
5297 (Value::Matrix(m), Value::Str(opt) | Value::StringObj(opt)) if opt == "econ" => {
5298 let (u_c, s_v, v_c) = svd_compute(m)?;
5299 let k = s_v.len();
5300 let mut s_mat = Array2::<f64>::zeros((k, k));
5301 for (i, &sv) in s_v.iter().enumerate() {
5302 s_mat[[i, i]] = sv;
5303 }
5304 Ok(Value::Tuple(vec![
5305 Value::Matrix(u_c),
5306 Value::Matrix(s_mat),
5307 Value::Matrix(v_c),
5308 ]))
5309 }
5310 _ => Err("svd: expected svd(A, 'econ')".to_string()),
5311 },
5312
5313 ("lu", 1) => match &args[0] {
5315 Value::Scalar(n) => {
5316 if get_nargout() <= 1 {
5317 Ok(Value::Scalar(*n))
5318 } else {
5319 Ok(Value::Tuple(vec![
5320 Value::Matrix(Array2::eye(1)),
5321 Value::Matrix(Array2::from_elem((1, 1), *n)),
5322 Value::Matrix(Array2::eye(1)),
5323 ]))
5324 }
5325 }
5326 Value::Matrix(m) => {
5327 let (l, u, p) = lu_decompose(m)?;
5328 if get_nargout() <= 1 {
5329 Ok(Value::Matrix(u))
5330 } else {
5331 Ok(Value::Tuple(vec![
5332 Value::Matrix(l),
5333 Value::Matrix(u),
5334 Value::Matrix(p),
5335 ]))
5336 }
5337 }
5338 _ => Err("lu: argument must be a real numeric matrix".to_string()),
5339 },
5340
5341 ("qr", 1) => match &args[0] {
5343 Value::Scalar(n) => {
5344 if get_nargout() <= 1 {
5345 Ok(Value::Scalar(*n))
5346 } else {
5347 Ok(Value::Tuple(vec![
5348 Value::Matrix(Array2::from_elem(
5349 (1, 1),
5350 if *n >= 0.0 { 1.0 } else { -1.0 },
5351 )),
5352 Value::Matrix(Array2::from_elem((1, 1), n.abs())),
5353 ]))
5354 }
5355 }
5356 Value::Matrix(m) => {
5357 let (q, r) = qr_decompose(m)?;
5358 if get_nargout() <= 1 {
5359 Ok(Value::Matrix(r))
5360 } else {
5361 Ok(Value::Tuple(vec![Value::Matrix(q), Value::Matrix(r)]))
5362 }
5363 }
5364 _ => Err("qr: argument must be a real numeric matrix".to_string()),
5365 },
5366
5367 ("chol", 1) => match &args[0] {
5369 Value::Scalar(n) => {
5370 if *n < 0.0 {
5371 Err("chol: value is not positive definite".to_string())
5372 } else {
5373 Ok(Value::Scalar(n.sqrt()))
5374 }
5375 }
5376 Value::Matrix(m) => Ok(Value::Matrix(chol_decompose(m)?)),
5377 _ => Err("chol: argument must be a real numeric matrix".to_string()),
5378 },
5379
5380 ("rank", 1) => match &args[0] {
5382 Value::Scalar(x) => Ok(Value::Scalar(if x.abs() > 1e-15 { 1.0 } else { 0.0 })),
5383 Value::Matrix(m) => {
5384 let (_, s_v, _) = svd_compute(m)?;
5385 let tol = (m.nrows().max(m.ncols())) as f64
5386 * s_v.first().copied().unwrap_or(0.0)
5387 * f64::EPSILON
5388 * 2.0;
5389 let r = s_v.iter().filter(|&&s| s > tol).count();
5390 Ok(Value::Scalar(r as f64))
5391 }
5392 _ => Err("rank: argument must be a real numeric matrix".to_string()),
5393 },
5394
5395 ("null", 1) => match &args[0] {
5397 Value::Scalar(_) => Ok(Value::Matrix(Array2::zeros((1, 0)))),
5398 Value::Matrix(m) => {
5399 let nn = m.ncols();
5400 let (_, s_v, v_c) = svd_compute(m)?;
5401 let tol = (m.nrows().max(nn)) as f64
5402 * s_v.first().copied().unwrap_or(0.0)
5403 * f64::EPSILON
5404 * 2.0;
5405 let r = s_v.iter().filter(|&&s| s > tol).count();
5406 let null_k = nn.saturating_sub(r);
5407 if null_k == 0 {
5408 return Ok(Value::Matrix(Array2::zeros((nn, 0))));
5409 }
5410 let mut result = Array2::<f64>::zeros((nn, null_k));
5411 for j in 0..null_k {
5412 let col_idx = r + j;
5413 if col_idx < v_c.ncols() {
5414 for i in 0..nn {
5415 result[[i, j]] = v_c[[i, col_idx]];
5416 }
5417 }
5418 }
5419 Ok(Value::Matrix(result))
5420 }
5421 _ => Err("null: argument must be a real numeric matrix".to_string()),
5422 },
5423
5424 ("orth", 1) => match &args[0] {
5426 Value::Scalar(x) => {
5427 if x.abs() > 1e-15 {
5428 Ok(Value::Matrix(Array2::from_elem((1, 1), 1.0)))
5429 } else {
5430 Ok(Value::Matrix(Array2::zeros((1, 0))))
5431 }
5432 }
5433 Value::Matrix(m) => {
5434 let mm = m.nrows();
5435 let (u_c, s_v, _) = svd_compute(m)?;
5436 let tol = (mm.max(m.ncols())) as f64
5437 * s_v.first().copied().unwrap_or(0.0)
5438 * f64::EPSILON
5439 * 2.0;
5440 let r = s_v.iter().filter(|&&s| s > tol).count();
5441 if r == 0 {
5442 return Ok(Value::Matrix(Array2::zeros((mm, 0))));
5443 }
5444 let mut result = Array2::<f64>::zeros((mm, r));
5445 for j in 0..r {
5446 if j < u_c.ncols() {
5447 for i in 0..mm {
5448 result[[i, j]] = u_c[[i, j]];
5449 }
5450 }
5451 }
5452 Ok(Value::Matrix(result))
5453 }
5454 _ => Err("orth: argument must be a real numeric matrix".to_string()),
5455 },
5456
5457 ("cond", 1) => match &args[0] {
5459 Value::Scalar(x) => {
5460 if x.abs() < 1e-15 {
5461 Ok(Value::Scalar(f64::INFINITY))
5462 } else {
5463 Ok(Value::Scalar(1.0))
5464 }
5465 }
5466 Value::Matrix(m) => {
5467 let (_, s_v, _) = svd_compute(m)?;
5468 if s_v.is_empty() {
5469 return Ok(Value::Scalar(1.0));
5470 }
5471 let s_max = s_v[0];
5472 let s_min = *s_v.last().unwrap();
5473 Ok(Value::Scalar(if s_min < 1e-15 {
5474 f64::INFINITY
5475 } else {
5476 s_max / s_min
5477 }))
5478 }
5479 _ => Err("cond: argument must be a real numeric matrix".to_string()),
5480 },
5481
5482 ("pinv", 1) => match &args[0] {
5484 Value::Scalar(x) => Ok(Value::Scalar(if x.abs() < 1e-15 { 0.0 } else { 1.0 / x })),
5485 Value::Matrix(m) => {
5486 let mm = m.nrows();
5487 let nn = m.ncols();
5488 let (u_c, s_v, v_c) = svd_compute(m)?;
5489 let k = s_v.len();
5490 let tol =
5491 (mm.max(nn)) as f64 * s_v.first().copied().unwrap_or(0.0) * f64::EPSILON * 2.0;
5492 let mut result = Array2::<f64>::zeros((nn, mm));
5494 for j in 0..k {
5495 if s_v[j] > tol {
5496 let inv_s = 1.0 / s_v[j];
5497 for r in 0..nn {
5498 for c in 0..mm {
5499 result[[r, c]] += v_c[[r, j]] * inv_s * u_c[[c, j]];
5500 }
5501 }
5502 }
5503 }
5504 Ok(Value::Matrix(result))
5505 }
5506 _ => Err("pinv: argument must be a real numeric matrix".to_string()),
5507 },
5508
5509 ("fft", 1) => fft_call(&args[0], None),
5511 ("fft", 2) => {
5512 let n = scalar_arg(&args[1], "fft", 2)?;
5513 let n = n as usize;
5514 if n == 0 {
5515 return Err("fft: length must be positive".to_string());
5516 }
5517 fft_call(&args[0], Some(n))
5518 }
5519 ("ifft", 1) => ifft_call(&args[0]),
5520
5521 ("fftshift", 1) => match &args[0] {
5523 Value::Scalar(s) => Ok(Value::Scalar(*s)),
5524 Value::Matrix(m) => {
5525 let (nrows, ncols) = (m.nrows(), m.ncols());
5526 if nrows == 1 {
5527 let n = ncols;
5528 let shift = n / 2;
5529 let data: Vec<f64> = m.iter().copied().collect();
5530 let mut out = vec![0.0f64; n];
5531 for (i, &x) in data.iter().enumerate() {
5532 out[(i + shift) % n] = x;
5533 }
5534 Ok(Value::Matrix(Array2::from_shape_vec((1, n), out).unwrap()))
5535 } else if ncols == 1 {
5536 let n = nrows;
5537 let shift = n / 2;
5538 let data: Vec<f64> = m.iter().copied().collect();
5539 let mut out = vec![0.0f64; n];
5540 for (i, &x) in data.iter().enumerate() {
5541 out[(i + shift) % n] = x;
5542 }
5543 Ok(Value::Matrix(Array2::from_shape_vec((n, 1), out).unwrap()))
5544 } else {
5545 let row_shift = nrows / 2;
5546 let col_shift = ncols / 2;
5547 let mut out = Array2::<f64>::zeros((nrows, ncols));
5548 for i in 0..nrows {
5549 for j in 0..ncols {
5550 out[[(i + row_shift) % nrows, (j + col_shift) % ncols]] = m[[i, j]];
5551 }
5552 }
5553 Ok(Value::Matrix(out))
5554 }
5555 }
5556 _ => Err("fftshift: argument must be a numeric matrix".to_string()),
5557 },
5558
5559 ("ifftshift", 1) => match &args[0] {
5561 Value::Scalar(s) => Ok(Value::Scalar(*s)),
5562 Value::Matrix(m) => {
5563 let (nrows, ncols) = (m.nrows(), m.ncols());
5564 if nrows == 1 {
5565 let n = ncols;
5566 let shift = n.div_ceil(2);
5567 let data: Vec<f64> = m.iter().copied().collect();
5568 let mut out = vec![0.0f64; n];
5569 for (i, &x) in data.iter().enumerate() {
5570 out[(i + shift) % n] = x;
5571 }
5572 Ok(Value::Matrix(Array2::from_shape_vec((1, n), out).unwrap()))
5573 } else if ncols == 1 {
5574 let n = nrows;
5575 let shift = n.div_ceil(2);
5576 let data: Vec<f64> = m.iter().copied().collect();
5577 let mut out = vec![0.0f64; n];
5578 for (i, &x) in data.iter().enumerate() {
5579 out[(i + shift) % n] = x;
5580 }
5581 Ok(Value::Matrix(Array2::from_shape_vec((n, 1), out).unwrap()))
5582 } else {
5583 let row_shift = nrows.div_ceil(2);
5584 let col_shift = ncols.div_ceil(2);
5585 let mut out = Array2::<f64>::zeros((nrows, ncols));
5586 for i in 0..nrows {
5587 for j in 0..ncols {
5588 out[[(i + row_shift) % nrows, (j + col_shift) % ncols]] = m[[i, j]];
5589 }
5590 }
5591 Ok(Value::Matrix(out))
5592 }
5593 }
5594 _ => Err("ifftshift: argument must be a numeric matrix".to_string()),
5595 },
5596
5597 ("fftfreq", 2) => {
5599 let n = match &args[0] {
5600 Value::Scalar(s) => {
5601 let n = *s as usize;
5602 if *s < 1.0 || (*s - n as f64).abs() > 1e-9 {
5603 return Err("fftfreq: n must be a positive integer".to_string());
5604 }
5605 n
5606 }
5607 _ => return Err("fftfreq: first argument must be a scalar integer".to_string()),
5608 };
5609 let d = scalar_arg(&args[1], "fftfreq", 2)?;
5610 if d == 0.0 {
5611 return Err("fftfreq: sample spacing d must be nonzero".to_string());
5612 }
5613 let pos_count = (n - 1) / 2 + 1;
5615 let neg_count = n / 2;
5616 let factor = 1.0 / (n as f64 * d);
5617 let mut freqs = Vec::with_capacity(n);
5618 for k in 0..pos_count as i64 {
5619 freqs.push(k as f64 * factor);
5620 }
5621 let neg_start = -(neg_count as i64);
5622 for k in neg_start..0 {
5623 freqs.push(k as f64 * factor);
5624 }
5625 Ok(Value::Matrix(
5626 Array2::from_shape_vec((1, n), freqs).unwrap(),
5627 ))
5628 }
5629
5630 ("jsondecode", 1) => jsondecode_impl(&args[0]),
5632 ("jsonencode", 1) => jsonencode_impl(&args[0]),
5633
5634 ("load", 1) => {
5636 let path = match &args[0] {
5637 Value::Str(s) | Value::StringObj(s) => s.clone(),
5638 _ => return Err("load: argument must be a string path".to_string()),
5639 };
5640 if !path.ends_with(".mat") {
5641 return Err("load: use bare 'load path' syntax for non-.mat files".to_string());
5642 }
5643 load_mat_file(&path)
5644 }
5645
5646 ("assert", 1) => {
5648 let truthy = match &args[0] {
5649 Value::Scalar(n) => *n != 0.0 && !n.is_nan(),
5650 Value::Matrix(m) => m.iter().all(|&x| x != 0.0 && !x.is_nan()),
5651 Value::Complex(re, im) => *re != 0.0 || *im != 0.0,
5652 Value::Str(s) | Value::StringObj(s) => !s.is_empty(),
5653 _ => false,
5654 };
5655 if truthy {
5656 Ok(Value::Void)
5657 } else {
5658 Err("assert: condition is false".to_string())
5659 }
5660 }
5661
5662 ("assert", 2) => assert_values_equal(&args[0], &args[1], None),
5664
5665 ("assert", 3) => {
5667 let tol = match &args[2] {
5668 Value::Scalar(t) => *t,
5669 _ => return Err("assert: tolerance must be a scalar".to_string()),
5670 };
5671 assert_values_equal(&args[0], &args[1], Some(tol))
5672 }
5673
5674 ("datetime", 1) => match &args[0] {
5676 Value::Str(s) | Value::StringObj(s) => {
5677 let s = s.as_str();
5678 if s == "now" {
5679 return Ok(Value::DateTime(crate::datetime::now_timestamp()));
5680 }
5681 if s == "today" {
5682 return Ok(Value::DateTime(crate::datetime::today_timestamp()));
5683 }
5684 crate::datetime::parse_iso8601(s).map(Value::DateTime)
5685 }
5686 _ => Err("datetime: expected a string or numeric constructor arguments".to_string()),
5687 },
5688 ("datetime", 3) if matches!(&args[1], Value::Str(_) | Value::StringObj(_)) => {
5690 let ts = scalar_arg(&args[0], "datetime", 1)?;
5691 match (&args[1], &args[2]) {
5692 (Value::Str(k) | Value::StringObj(k), Value::Str(v) | Value::StringObj(v))
5693 if k.eq_ignore_ascii_case("convertfrom")
5694 && v.eq_ignore_ascii_case("posixtime") =>
5695 {
5696 Ok(Value::DateTime(ts))
5697 }
5698 _ => Err("datetime: unsupported arguments".to_string()),
5699 }
5700 }
5701 ("datetime", 3) => {
5702 let y = scalar_arg(&args[0], "datetime", 1)? as i64;
5703 let mo = scalar_arg(&args[1], "datetime", 2)? as u32;
5704 let d = scalar_arg(&args[2], "datetime", 3)? as u32;
5705 Ok(Value::DateTime(crate::datetime::civil_to_timestamp(
5706 y, mo, d, 0, 0, 0.0,
5707 )))
5708 }
5709 ("datetime", 6) => {
5710 let y = scalar_arg(&args[0], "datetime", 1)? as i64;
5711 let mo = scalar_arg(&args[1], "datetime", 2)? as u32;
5712 let d = scalar_arg(&args[2], "datetime", 3)? as u32;
5713 let h = scalar_arg(&args[3], "datetime", 4)? as u32;
5714 let mi = scalar_arg(&args[4], "datetime", 5)? as u32;
5715 let s = scalar_arg(&args[5], "datetime", 6)?;
5716 Ok(Value::DateTime(crate::datetime::civil_to_timestamp(
5717 y, mo, d, h, mi, s,
5718 )))
5719 }
5720
5721 ("year", 1) => match &args[0] {
5723 Value::DateTime(ts) => {
5724 let (y, ..) = crate::datetime::timestamp_to_civil(*ts);
5725 Ok(Value::Scalar(y as f64))
5726 }
5727 Value::DateTimeArray(v) => {
5728 let rows: Vec<f64> = v
5729 .iter()
5730 .map(|ts| {
5731 let (y, ..) = crate::datetime::timestamp_to_civil(*ts);
5732 y as f64
5733 })
5734 .collect();
5735 Ok(Value::Matrix(
5736 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5737 .map_err(|e| e.to_string())?,
5738 ))
5739 }
5740 _ => Err("year: argument must be a datetime".to_string()),
5741 },
5742 ("month", 1) => match &args[0] {
5743 Value::DateTime(ts) => {
5744 let (_, mo, ..) = crate::datetime::timestamp_to_civil(*ts);
5745 Ok(Value::Scalar(mo as f64))
5746 }
5747 Value::DateTimeArray(v) => {
5748 let rows: Vec<f64> = v
5749 .iter()
5750 .map(|ts| {
5751 let (_, mo, ..) = crate::datetime::timestamp_to_civil(*ts);
5752 mo as f64
5753 })
5754 .collect();
5755 Ok(Value::Matrix(
5756 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5757 .map_err(|e| e.to_string())?,
5758 ))
5759 }
5760 _ => Err("month: argument must be a datetime".to_string()),
5761 },
5762 ("day", 1) => match &args[0] {
5763 Value::DateTime(ts) => {
5764 let (_, _, d, ..) = crate::datetime::timestamp_to_civil(*ts);
5765 Ok(Value::Scalar(d as f64))
5766 }
5767 Value::DateTimeArray(v) => {
5768 let rows: Vec<f64> = v
5769 .iter()
5770 .map(|ts| {
5771 let (_, _, d, ..) = crate::datetime::timestamp_to_civil(*ts);
5772 d as f64
5773 })
5774 .collect();
5775 Ok(Value::Matrix(
5776 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5777 .map_err(|e| e.to_string())?,
5778 ))
5779 }
5780 _ => Err("day: argument must be a datetime".to_string()),
5781 },
5782 ("hour", 1) => match &args[0] {
5783 Value::DateTime(ts) => {
5784 let (_, _, _, h, ..) = crate::datetime::timestamp_to_civil(*ts);
5785 Ok(Value::Scalar(h as f64))
5786 }
5787 Value::DateTimeArray(v) => {
5788 let rows: Vec<f64> = v
5789 .iter()
5790 .map(|ts| {
5791 let (_, _, _, h, ..) = crate::datetime::timestamp_to_civil(*ts);
5792 h as f64
5793 })
5794 .collect();
5795 Ok(Value::Matrix(
5796 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5797 .map_err(|e| e.to_string())?,
5798 ))
5799 }
5800 _ => Err("hour: argument must be a datetime or duration".to_string()),
5801 },
5802 ("minute", 1) => match &args[0] {
5803 Value::DateTime(ts) => {
5804 let (_, _, _, _, mi, ..) = crate::datetime::timestamp_to_civil(*ts);
5805 Ok(Value::Scalar(mi as f64))
5806 }
5807 Value::DateTimeArray(v) => {
5808 let rows: Vec<f64> = v
5809 .iter()
5810 .map(|ts| {
5811 let (_, _, _, _, mi, ..) = crate::datetime::timestamp_to_civil(*ts);
5812 mi as f64
5813 })
5814 .collect();
5815 Ok(Value::Matrix(
5816 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5817 .map_err(|e| e.to_string())?,
5818 ))
5819 }
5820 _ => Err("minute: argument must be a datetime or duration".to_string()),
5821 },
5822 ("second", 1) => match &args[0] {
5823 Value::DateTime(ts) => {
5824 let (_, _, _, _, _, s) = crate::datetime::timestamp_to_civil(*ts);
5825 Ok(Value::Scalar(s))
5826 }
5827 Value::DateTimeArray(v) => {
5828 let rows: Vec<f64> = v
5829 .iter()
5830 .map(|ts| {
5831 let (_, _, _, _, _, s) = crate::datetime::timestamp_to_civil(*ts);
5832 s
5833 })
5834 .collect();
5835 Ok(Value::Matrix(
5836 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5837 .map_err(|e| e.to_string())?,
5838 ))
5839 }
5840 _ => Err("second: argument must be a datetime or duration".to_string()),
5841 },
5842
5843 ("isdatetime", 1) => Ok(Value::Scalar(bool_to_f64(matches!(
5845 &args[0],
5846 Value::DateTime(_) | Value::DateTimeArray(_)
5847 )))),
5848 ("isduration", 1) => Ok(Value::Scalar(bool_to_f64(matches!(
5849 &args[0],
5850 Value::Duration(_) | Value::DurationArray(_)
5851 )))),
5852 ("isnat", 1) => match &args[0] {
5853 Value::DateTime(ts) => Ok(Value::Scalar(bool_to_f64(ts.is_nan()))),
5854 Value::DateTimeArray(v) => {
5855 let rows: Vec<f64> = v
5856 .iter()
5857 .map(|ts| if ts.is_nan() { 1.0 } else { 0.0 })
5858 .collect();
5859 Ok(Value::Matrix(
5860 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5861 .map_err(|e| e.to_string())?,
5862 ))
5863 }
5864 _ => Ok(Value::Scalar(0.0)),
5865 },
5866
5867 ("hours", 1) => match &args[0] {
5869 Value::Duration(s) => Ok(Value::Scalar(*s / 3600.0)),
5870 Value::DurationArray(v) => {
5871 let rows: Vec<f64> = v.iter().map(|s| s / 3600.0).collect();
5872 Ok(Value::Matrix(
5873 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5874 .map_err(|e| e.to_string())?,
5875 ))
5876 }
5877 _ => {
5878 let s = scalar_arg(&args[0], "hours", 1)?;
5879 Ok(Value::Duration(s * 3600.0))
5880 }
5881 },
5882 ("minutes", 1) => match &args[0] {
5883 Value::Duration(s) => Ok(Value::Scalar(*s / 60.0)),
5884 Value::DurationArray(v) => {
5885 let rows: Vec<f64> = v.iter().map(|s| s / 60.0).collect();
5886 Ok(Value::Matrix(
5887 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5888 .map_err(|e| e.to_string())?,
5889 ))
5890 }
5891 _ => {
5892 let s = scalar_arg(&args[0], "minutes", 1)?;
5893 Ok(Value::Duration(s * 60.0))
5894 }
5895 },
5896 ("seconds", 1) => match &args[0] {
5897 Value::Duration(s) => Ok(Value::Scalar(*s)),
5898 Value::DurationArray(v) => {
5899 let rows = v.to_vec();
5900 Ok(Value::Matrix(
5901 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5902 .map_err(|e| e.to_string())?,
5903 ))
5904 }
5905 _ => {
5906 let s = scalar_arg(&args[0], "seconds", 1)?;
5907 Ok(Value::Duration(s))
5908 }
5909 },
5910 ("days", 1) => match &args[0] {
5911 Value::Duration(s) => Ok(Value::Scalar(*s / 86400.0)),
5912 Value::DurationArray(v) => {
5913 let rows: Vec<f64> = v.iter().map(|s| s / 86400.0).collect();
5914 Ok(Value::Matrix(
5915 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5916 .map_err(|e| e.to_string())?,
5917 ))
5918 }
5919 _ => {
5920 let s = scalar_arg(&args[0], "days", 1)?;
5921 Ok(Value::Duration(s * 86400.0))
5922 }
5923 },
5924 ("milliseconds", 1) => match &args[0] {
5925 Value::Duration(s) => Ok(Value::Scalar(*s * 1000.0)),
5926 Value::DurationArray(v) => {
5927 let rows: Vec<f64> = v.iter().map(|s| s * 1000.0).collect();
5928 Ok(Value::Matrix(
5929 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5930 .map_err(|e| e.to_string())?,
5931 ))
5932 }
5933 _ => {
5934 let s = scalar_arg(&args[0], "milliseconds", 1)?;
5935 Ok(Value::Duration(s / 1000.0))
5936 }
5937 },
5938 ("years", 1) => match &args[0] {
5939 Value::Duration(s) => Ok(Value::Scalar(*s / (365.2425 * 86400.0))),
5940 Value::DurationArray(v) => {
5941 let rows: Vec<f64> = v.iter().map(|s| s / (365.2425 * 86400.0)).collect();
5942 Ok(Value::Matrix(
5943 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5944 .map_err(|e| e.to_string())?,
5945 ))
5946 }
5947 _ => {
5948 let s = scalar_arg(&args[0], "years", 1)?;
5949 Ok(Value::Duration(s * 365.2425 * 86400.0))
5950 }
5951 },
5952 ("duration", 3) => {
5954 let h = scalar_arg(&args[0], "duration", 1)?;
5955 let m = scalar_arg(&args[1], "duration", 2)?;
5956 let s = scalar_arg(&args[2], "duration", 3)?;
5957 Ok(Value::Duration(h * 3600.0 + m * 60.0 + s))
5958 }
5959
5960 ("datestr", 1) => match &args[0] {
5962 Value::DateTime(ts) => {
5963 let s = crate::datetime::format_datestr(*ts, "dd-MMM-yyyy HH:mm:ss");
5964 Ok(Value::Str(s))
5965 }
5966 Value::DateTimeArray(v) => Ok(Value::Cell(
5967 v.iter()
5968 .map(|ts| {
5969 Value::Str(crate::datetime::format_datestr(*ts, "dd-MMM-yyyy HH:mm:ss"))
5970 })
5971 .collect(),
5972 )),
5973 _ => Err("datestr: argument must be a datetime".to_string()),
5974 },
5975 ("datestr", 2) => {
5976 let fmt_str = match &args[1] {
5977 Value::Str(s) | Value::StringObj(s) => s.clone(),
5978 _ => return Err("datestr: second argument must be a format string".to_string()),
5979 };
5980 match &args[0] {
5981 Value::DateTime(ts) => {
5982 Ok(Value::Str(crate::datetime::format_datestr(*ts, &fmt_str)))
5983 }
5984 Value::DateTimeArray(v) => Ok(Value::Cell(
5985 v.iter()
5986 .map(|ts| Value::Str(crate::datetime::format_datestr(*ts, &fmt_str)))
5987 .collect(),
5988 )),
5989 _ => Err("datestr: first argument must be a datetime".to_string()),
5990 }
5991 }
5992 ("datevec", 1) => match &args[0] {
5993 Value::DateTime(ts) => {
5994 let (y, mo, d, h, mi, s) = crate::datetime::timestamp_to_civil(*ts);
5995 let sec_i = s.floor() as u32;
5996 let data = vec![
5997 y as f64,
5998 mo as f64,
5999 d as f64,
6000 h as f64,
6001 mi as f64,
6002 sec_i as f64,
6003 ];
6004 Ok(Value::Matrix(
6005 ndarray::Array2::from_shape_vec((1, 6), data).map_err(|e| e.to_string())?,
6006 ))
6007 }
6008 _ => Err("datevec: argument must be a datetime".to_string()),
6009 },
6010 ("datenum", 1) => match &args[0] {
6011 Value::DateTime(ts) => Ok(Value::Scalar(crate::datetime::to_datenum(*ts))),
6012 _ => Err("datenum: argument must be a datetime".to_string()),
6013 },
6014 ("datenum", 3) => {
6015 let y = scalar_arg(&args[0], "datenum", 1)? as i64;
6016 let mo = scalar_arg(&args[1], "datenum", 2)? as u32;
6017 let d = scalar_arg(&args[2], "datenum", 3)? as u32;
6018 let ts = crate::datetime::civil_to_timestamp(y, mo, d, 0, 0, 0.0);
6019 Ok(Value::Scalar(crate::datetime::to_datenum(ts)))
6020 }
6021 ("posixtime", 1) => match &args[0] {
6022 Value::DateTime(ts) => Ok(Value::Scalar(*ts)),
6023 _ => Err("posixtime: argument must be a datetime".to_string()),
6024 },
6025
6026 ("diff", 1) => match &args[0] {
6028 Value::DateTimeArray(v) if v.len() >= 2 => {
6029 let diffs: Vec<f64> = v.windows(2).map(|w| w[1] - w[0]).collect();
6030 Ok(Value::DurationArray(diffs))
6031 }
6032 Value::DurationArray(v) if v.len() >= 2 => {
6033 let diffs: Vec<f64> = v.windows(2).map(|w| w[1] - w[0]).collect();
6034 Ok(Value::DurationArray(diffs))
6035 }
6036 Value::Matrix(m) => {
6037 let (nrows, ncols) = (m.nrows(), m.ncols());
6039 if ncols > 1 && nrows == 1 {
6040 let data: Vec<f64> =
6042 (0..ncols - 1).map(|j| m[[0, j + 1]] - m[[0, j]]).collect();
6043 Ok(Value::Matrix(
6044 ndarray::Array2::from_shape_vec((1, data.len()), data)
6045 .map_err(|e| e.to_string())?,
6046 ))
6047 } else if nrows > 1 {
6048 let data: Vec<f64> = (0..nrows - 1)
6050 .flat_map(|i| (0..ncols).map(move |j| m[[i + 1, j]] - m[[i, j]]))
6051 .collect();
6052 Ok(Value::Matrix(
6053 ndarray::Array2::from_shape_vec((nrows - 1, ncols), data)
6054 .map_err(|e| e.to_string())?,
6055 ))
6056 } else {
6057 Err("diff: input must have at least 2 elements".to_string())
6058 }
6059 }
6060 _ => Err("diff: unsupported argument type".to_string()),
6061 },
6062
6063 ("triu", 1) => match &args[0] {
6065 Value::Matrix(m) => {
6066 let mut r = m.clone();
6067 for i in 0..m.nrows() {
6068 for j in 0..m.ncols() {
6069 if (j as isize) < (i as isize) {
6070 r[[i, j]] = 0.0;
6071 }
6072 }
6073 }
6074 Ok(Value::Matrix(r))
6075 }
6076 Value::Scalar(n) => Ok(Value::Scalar(*n)),
6077 _ => Err("triu: argument must be a numeric matrix".to_string()),
6078 },
6079 ("triu", 2) => match (&args[0], &args[1]) {
6080 (Value::Matrix(m), Value::Scalar(k)) => {
6081 let k = *k as isize;
6082 let mut r = m.clone();
6083 for i in 0..m.nrows() {
6084 for j in 0..m.ncols() {
6085 if (j as isize) - (i as isize) < k {
6086 r[[i, j]] = 0.0;
6087 }
6088 }
6089 }
6090 Ok(Value::Matrix(r))
6091 }
6092 _ => Err("triu: expects (matrix, scalar)".to_string()),
6093 },
6094
6095 ("tril", 1) => match &args[0] {
6096 Value::Matrix(m) => {
6097 let mut r = m.clone();
6098 for i in 0..m.nrows() {
6099 for j in 0..m.ncols() {
6100 if (j as isize) > (i as isize) {
6101 r[[i, j]] = 0.0;
6102 }
6103 }
6104 }
6105 Ok(Value::Matrix(r))
6106 }
6107 Value::Scalar(n) => Ok(Value::Scalar(*n)),
6108 _ => Err("tril: argument must be a numeric matrix".to_string()),
6109 },
6110 ("tril", 2) => match (&args[0], &args[1]) {
6111 (Value::Matrix(m), Value::Scalar(k)) => {
6112 let k = *k as isize;
6113 let mut r = m.clone();
6114 for i in 0..m.nrows() {
6115 for j in 0..m.ncols() {
6116 if (j as isize) - (i as isize) > k {
6117 r[[i, j]] = 0.0;
6118 }
6119 }
6120 }
6121 Ok(Value::Matrix(r))
6122 }
6123 _ => Err("tril: expects (matrix, scalar)".to_string()),
6124 },
6125
6126 ("repmat", 3) => match (&args[0], &args[1], &args[2]) {
6127 (Value::Matrix(a), Value::Scalar(rm), Value::Scalar(cn)) => {
6128 let rm = *rm as usize;
6129 let cn = *cn as usize;
6130 if rm == 0 || cn == 0 {
6131 return Ok(Value::Matrix(Array2::zeros((0, 0))));
6132 }
6133 let row_tile: Vec<Array2<f64>> = std::iter::repeat_n(a.view(), cn)
6134 .map(|v| v.to_owned())
6135 .collect();
6136 let row_block = ndarray::concatenate(
6137 ndarray::Axis(1),
6138 &row_tile.iter().map(|m| m.view()).collect::<Vec<_>>(),
6139 )
6140 .map_err(|e| e.to_string())?;
6141 let col_tiles: Vec<Array2<f64>> = std::iter::repeat_n(row_block.view(), rm)
6142 .map(|v| v.to_owned())
6143 .collect();
6144 let result = ndarray::concatenate(
6145 ndarray::Axis(0),
6146 &col_tiles.iter().map(|m| m.view()).collect::<Vec<_>>(),
6147 )
6148 .map_err(|e| e.to_string())?;
6149 Ok(Value::Matrix(result))
6150 }
6151 (Value::Scalar(s), Value::Scalar(rm), Value::Scalar(cn)) => {
6152 let rm = *rm as usize;
6153 let cn = *cn as usize;
6154 Ok(Value::Matrix(Array2::from_elem((rm, cn), *s)))
6155 }
6156 _ => Err("repmat: expects (matrix, m, n)".to_string()),
6157 },
6158
6159 ("kron", 2) => match (&args[0], &args[1]) {
6160 (Value::Matrix(a), Value::Matrix(b)) => {
6161 let (ra, ca) = (a.nrows(), a.ncols());
6162 let (rb, cb) = (b.nrows(), b.ncols());
6163 let mut result = Array2::<f64>::zeros((ra * rb, ca * cb));
6164 for i in 0..ra {
6165 for j in 0..ca {
6166 let aij = a[[i, j]];
6167 for p in 0..rb {
6168 for q in 0..cb {
6169 result[[i * rb + p, j * cb + q]] = aij * b[[p, q]];
6170 }
6171 }
6172 }
6173 }
6174 Ok(Value::Matrix(result))
6175 }
6176 (Value::Scalar(s), Value::Matrix(b)) => Ok(Value::Matrix(b.mapv(|x| x * s))),
6177 (Value::Matrix(a), Value::Scalar(s)) => Ok(Value::Matrix(a.mapv(|x| x * s))),
6178 (Value::Scalar(a), Value::Scalar(b)) => Ok(Value::Scalar(a * b)),
6179 _ => Err("kron: arguments must be numeric matrices".to_string()),
6180 },
6181
6182 ("cross", 2) => {
6184 fn to_vec3(v: &Value, argn: usize) -> Result<[f64; 3], String> {
6185 match v {
6186 Value::Matrix(m) => {
6187 let flat: Vec<f64> = m.iter().copied().collect();
6188 if flat.len() != 3 {
6189 Err(format!(
6190 "cross: argument {} must have exactly 3 elements",
6191 argn
6192 ))
6193 } else {
6194 Ok([flat[0], flat[1], flat[2]])
6195 }
6196 }
6197 _ => Err(format!(
6198 "cross: argument {} must be a 3-element vector",
6199 argn
6200 )),
6201 }
6202 }
6203 let a = to_vec3(&args[0], 1)?;
6204 let b = to_vec3(&args[1], 2)?;
6205 let cx = a[1] * b[2] - a[2] * b[1];
6206 let cy = a[2] * b[0] - a[0] * b[2];
6207 let cz = a[0] * b[1] - a[1] * b[0];
6208 let result = match &args[0] {
6210 Value::Matrix(m) if m.nrows() == 1 => {
6211 Array2::from_shape_vec((1, 3), vec![cx, cy, cz]).unwrap()
6212 }
6213 _ => Array2::from_shape_vec((3, 1), vec![cx, cy, cz]).unwrap(),
6214 };
6215 Ok(Value::Matrix(result))
6216 }
6217
6218 ("dot", 2) => {
6219 fn to_flat(v: &Value, argn: usize) -> Result<Vec<f64>, String> {
6220 match v {
6221 Value::Matrix(m) => Ok(m.iter().copied().collect()),
6222 Value::Scalar(s) => Ok(vec![*s]),
6223 _ => Err(format!("dot: argument {} must be a numeric vector", argn)),
6224 }
6225 }
6226 let a = to_flat(&args[0], 1)?;
6227 let b = to_flat(&args[1], 2)?;
6228 if a.len() != b.len() {
6229 return Err(format!(
6230 "dot: vectors must have the same length ({} vs {})",
6231 a.len(),
6232 b.len()
6233 ));
6234 }
6235 let s: f64 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
6236 Ok(Value::Scalar(s))
6237 }
6238
6239 ("intersect", 2) => {
6241 fn to_sorted_vec(v: &Value, fname: &str) -> Result<Vec<f64>, String> {
6242 match v {
6243 Value::Matrix(m) => {
6244 let mut vals: Vec<f64> = m.iter().copied().collect();
6245 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
6246 Ok(vals)
6247 }
6248 Value::Scalar(s) => Ok(vec![*s]),
6249 _ => Err(format!("{fname}: arguments must be numeric vectors")),
6250 }
6251 }
6252 let a = to_sorted_vec(&args[0], "intersect")?;
6253 let b = to_sorted_vec(&args[1], "intersect")?;
6254 let b_set: std::collections::HashSet<u64> = b
6255 .iter()
6256 .filter(|x| !x.is_nan())
6257 .map(|x| x.to_bits())
6258 .collect();
6259 let mut result: Vec<f64> = Vec::new();
6260 for x in &a {
6261 if !x.is_nan()
6262 && b_set.contains(&x.to_bits())
6263 && result.last().is_none_or(|&last| last != *x)
6264 {
6265 result.push(*x);
6266 }
6267 }
6268 let n = result.len();
6269 if n == 0 {
6270 Ok(Value::Matrix(Array2::zeros((1, 0))))
6271 } else {
6272 Ok(Value::Matrix(
6273 Array2::from_shape_vec((1, n), result).unwrap(),
6274 ))
6275 }
6276 }
6277
6278 ("union", 2) => {
6279 fn collect_vals(v: &Value, fname: &str) -> Result<Vec<f64>, String> {
6280 match v {
6281 Value::Matrix(m) => Ok(m.iter().copied().collect()),
6282 Value::Scalar(s) => Ok(vec![*s]),
6283 _ => Err(format!("{fname}: arguments must be numeric vectors")),
6284 }
6285 }
6286 let mut combined = collect_vals(&args[0], "union")?;
6287 combined.extend(collect_vals(&args[1], "union")?);
6288 combined.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
6289 let mut result: Vec<f64> = Vec::new();
6290 for x in combined {
6291 if result.last().is_none_or(|&last| last != x) {
6292 result.push(x);
6293 }
6294 }
6295 let n = result.len();
6296 if n == 0 {
6297 Ok(Value::Matrix(Array2::zeros((1, 0))))
6298 } else {
6299 Ok(Value::Matrix(
6300 Array2::from_shape_vec((1, n), result).unwrap(),
6301 ))
6302 }
6303 }
6304
6305 ("setdiff", 2) => {
6306 fn collect_vals2(v: &Value, fname: &str) -> Result<Vec<f64>, String> {
6307 match v {
6308 Value::Matrix(m) => Ok(m.iter().copied().collect()),
6309 Value::Scalar(s) => Ok(vec![*s]),
6310 _ => Err(format!("{fname}: arguments must be numeric vectors")),
6311 }
6312 }
6313 let a = collect_vals2(&args[0], "setdiff")?;
6314 let b = collect_vals2(&args[1], "setdiff")?;
6315 let b_set: std::collections::HashSet<u64> = b
6316 .iter()
6317 .filter(|x| !x.is_nan())
6318 .map(|x| x.to_bits())
6319 .collect();
6320 let mut a_sorted = a.clone();
6321 a_sorted.sort_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal));
6322 let mut result: Vec<f64> = Vec::new();
6323 for x in a_sorted {
6324 if !x.is_nan()
6325 && !b_set.contains(&x.to_bits())
6326 && result.last().is_none_or(|&last| last != x)
6327 {
6328 result.push(x);
6329 }
6330 }
6331 let n = result.len();
6332 if n == 0 {
6333 Ok(Value::Matrix(Array2::zeros((1, 0))))
6334 } else {
6335 Ok(Value::Matrix(
6336 Array2::from_shape_vec((1, n), result).unwrap(),
6337 ))
6338 }
6339 }
6340
6341 ("ismember", 2) => {
6342 fn collect_vals3(v: &Value, fname: &str) -> Result<Vec<f64>, String> {
6343 match v {
6344 Value::Matrix(m) => Ok(m.iter().copied().collect()),
6345 Value::Scalar(s) => Ok(vec![*s]),
6346 _ => Err(format!("{fname}: arguments must be numeric")),
6347 }
6348 }
6349 let set: std::collections::HashSet<u64> = collect_vals3(&args[1], "ismember")?
6350 .into_iter()
6351 .filter(|x| !x.is_nan())
6352 .map(|x| x.to_bits())
6353 .collect();
6354 match &args[0] {
6355 Value::Scalar(s) => {
6356 let found = !s.is_nan() && set.contains(&s.to_bits());
6357 Ok(Value::Scalar(if found { 1.0 } else { 0.0 }))
6358 }
6359 Value::Matrix(m) => {
6360 let result: Vec<f64> = m
6361 .iter()
6362 .map(|x| {
6363 if !x.is_nan() && set.contains(&x.to_bits()) {
6364 1.0
6365 } else {
6366 0.0
6367 }
6368 })
6369 .collect();
6370 let shape = m.raw_dim();
6371 Ok(Value::Matrix(
6372 Array2::from_shape_vec(shape, result).unwrap(),
6373 ))
6374 }
6375 _ => Err("ismember: first argument must be numeric".to_string()),
6376 }
6377 }
6378
6379 ("sub2ind", 3) => {
6381 let sz = match &args[0] {
6382 Value::Matrix(m) if m.len() == 2 => (m[[0, 0]] as usize, m[[0, 1]] as usize),
6383 _ => return Err("sub2ind: first argument must be [rows cols]".to_string()),
6384 };
6385 let rows = sz.0;
6386 fn idx_vals(v: &Value, argn: usize) -> Result<Vec<f64>, String> {
6387 match v {
6388 Value::Scalar(s) => Ok(vec![*s]),
6389 Value::Matrix(m) => Ok(m.iter().copied().collect()),
6390 _ => Err(format!("sub2ind: argument {} must be numeric", argn)),
6391 }
6392 }
6393 let r = idx_vals(&args[1], 2)?;
6394 let c = idx_vals(&args[2], 3)?;
6395 if r.len() != c.len() {
6396 return Err(
6397 "sub2ind: row and column index vectors must have the same length".to_string(),
6398 );
6399 }
6400 if r.len() == 1 {
6401 let idx = (c[0] as usize - 1) * rows + r[0] as usize;
6402 Ok(Value::Scalar(idx as f64))
6403 } else {
6404 let vals: Vec<f64> = r
6405 .iter()
6406 .zip(c.iter())
6407 .map(|(&ri, &ci)| ((ci as usize - 1) * rows + ri as usize) as f64)
6408 .collect();
6409 let n = vals.len();
6410 Ok(Value::Matrix(Array2::from_shape_vec((1, n), vals).unwrap()))
6411 }
6412 }
6413
6414 ("ind2sub", 2) => {
6415 let sz = match &args[0] {
6416 Value::Matrix(m) if m.len() == 2 => (m[[0, 0]] as usize, m[[0, 1]] as usize),
6417 _ => return Err("ind2sub: first argument must be [rows cols]".to_string()),
6418 };
6419 let rows = sz.0;
6420 fn idx_vals2(v: &Value, argn: usize) -> Result<Vec<f64>, String> {
6421 match v {
6422 Value::Scalar(s) => Ok(vec![*s]),
6423 Value::Matrix(m) => Ok(m.iter().copied().collect()),
6424 _ => Err(format!("ind2sub: argument {} must be numeric", argn)),
6425 }
6426 }
6427 let indices = idx_vals2(&args[1], 2)?;
6428 if indices.len() == 1 {
6429 let idx = indices[0] as usize;
6430 let r = ((idx - 1) % rows + 1) as f64;
6431 let c = ((idx - 1) / rows + 1) as f64;
6432 Ok(Value::Tuple(vec![Value::Scalar(r), Value::Scalar(c)]))
6433 } else {
6434 let n = indices.len();
6435 let rs: Vec<f64> = indices
6436 .iter()
6437 .map(|&idx| ((idx as usize - 1) % rows + 1) as f64)
6438 .collect();
6439 let cs: Vec<f64> = indices
6440 .iter()
6441 .map(|&idx| ((idx as usize - 1) / rows + 1) as f64)
6442 .collect();
6443 let rm = Value::Matrix(Array2::from_shape_vec((1, n), rs).unwrap());
6444 let cm = Value::Matrix(Array2::from_shape_vec((1, n), cs).unwrap());
6445 Ok(Value::Tuple(vec![rm, cm]))
6446 }
6447 }
6448
6449 ("repelem", 2) => match (&args[0], &args[1]) {
6450 (Value::Matrix(a), Value::Scalar(n)) => {
6451 let n = *n as usize;
6452 let flat: Vec<f64> = a.iter().flat_map(|&x| std::iter::repeat_n(x, n)).collect();
6453 let total = flat.len();
6454 Ok(Value::Matrix(
6455 Array2::from_shape_vec((1, total), flat).unwrap(),
6456 ))
6457 }
6458 (Value::Matrix(a), Value::Matrix(ns)) => {
6459 let av: Vec<f64> = a.iter().copied().collect();
6460 let nv: Vec<f64> = ns.iter().copied().collect();
6461 if av.len() != nv.len() {
6462 return Err(
6463 "repelem: element count vector must match source vector length".to_string(),
6464 );
6465 }
6466 let flat: Vec<f64> = av
6467 .iter()
6468 .zip(nv.iter())
6469 .flat_map(|(&x, &n)| std::iter::repeat_n(x, n as usize))
6470 .collect();
6471 let total = flat.len();
6472 Ok(Value::Matrix(
6473 Array2::from_shape_vec((1, total), flat).unwrap(),
6474 ))
6475 }
6476 (Value::Scalar(s), Value::Scalar(n)) => {
6477 let n = *n as usize;
6478 Ok(Value::Matrix(Array2::from_elem((1, n), *s)))
6479 }
6480 _ => Err("repelem: unsupported argument types".to_string()),
6481 },
6482 ("repelem", 3) => match (&args[0], &args[1], &args[2]) {
6483 (Value::Matrix(a), Value::Scalar(rm), Value::Scalar(cn)) => {
6484 let rm = *rm as usize;
6485 let cn = *cn as usize;
6486 let (nrows, ncols) = (a.nrows(), a.ncols());
6487 let mut result = Array2::<f64>::zeros((nrows * rm, ncols * cn));
6488 for i in 0..nrows {
6489 for j in 0..ncols {
6490 let v = a[[i, j]];
6491 for di in 0..rm {
6492 for dj in 0..cn {
6493 result[[i * rm + di, j * cn + dj]] = v;
6494 }
6495 }
6496 }
6497 }
6498 Ok(Value::Matrix(result))
6499 }
6500 (Value::Scalar(s), Value::Scalar(rm), Value::Scalar(cn)) => Ok(Value::Matrix(
6501 Array2::from_elem((*rm as usize, *cn as usize), *s),
6502 )),
6503 _ => Err("repelem: expects (matrix, m, n) for 2D repetition".to_string()),
6504 },
6505
6506 ("polyval", 2) => {
6508 let coeffs = poly_coeffs(&args[0], "polyval")?;
6509 if coeffs.is_empty() {
6510 return Err("polyval: polynomial vector is empty".to_string());
6511 }
6512 match &args[1] {
6513 Value::Scalar(x) => Ok(Value::Scalar(horner(&coeffs, *x))),
6514 Value::Matrix(m) => Ok(Value::Matrix(m.mapv(|x| horner(&coeffs, x)))),
6515 _ => Err("polyval: second argument must be a real numeric value".to_string()),
6516 }
6517 }
6518
6519 ("polyfit", 3) => {
6520 let xv = poly_coeffs(&args[0], "polyfit")?;
6521 let yv = poly_coeffs(&args[1], "polyfit")?;
6522 let deg = match &args[2] {
6523 Value::Scalar(n) => {
6524 let d = *n as usize;
6525 if *n < 0.0 || (*n - d as f64).abs() > 1e-9 {
6526 return Err("polyfit: degree must be a non-negative integer".to_string());
6527 }
6528 d
6529 }
6530 _ => return Err("polyfit: degree must be a scalar".to_string()),
6531 };
6532 if xv.len() != yv.len() {
6533 return Err("polyfit: x and y must have the same length".to_string());
6534 }
6535 let m = xv.len();
6536 let ncols = deg + 1;
6537 if ncols > m {
6538 return Err(format!(
6539 "polyfit: not enough data points ({m}) for degree-{deg} fit"
6540 ));
6541 }
6542 let mut vander = Array2::<f64>::zeros((m, ncols));
6544 for (i, &xi) in xv.iter().enumerate() {
6545 for j in 0..ncols {
6546 vander[[i, j]] = xi.powi((deg - j) as i32);
6547 }
6548 }
6549 let (q, r) = qr_decompose(&vander)?;
6551 let qty: Vec<f64> = (0..ncols)
6552 .map(|i| (0..m).map(|k| q[[k, i]] * yv[k]).sum())
6553 .collect();
6554 let mut r_sq = Array2::<f64>::zeros((ncols, ncols));
6556 for i in 0..ncols {
6557 for j in 0..ncols {
6558 r_sq[[i, j]] = r[[i, j]];
6559 }
6560 }
6561 let coeffs = poly_back_sub(&r_sq, &qty)?;
6562 let result = Array2::from_shape_vec((1, ncols), coeffs)
6563 .map_err(|e| format!("polyfit: internal error: {e}"))?;
6564 Ok(Value::Matrix(result))
6565 }
6566
6567 ("roots", 1) => {
6568 let raw = poly_coeffs(&args[0], "roots")?;
6569 let start = raw.iter().position(|&c| c != 0.0).unwrap_or(raw.len());
6571 let coeffs = &raw[start..];
6572 if coeffs.len() <= 1 {
6573 return Ok(Value::Matrix(Array2::zeros((0, 1))));
6574 }
6575 let roots = durand_kerner(coeffs)?;
6576 Ok(roots_to_value(&roots))
6577 }
6578
6579 ("poly", 1) => match &args[0] {
6580 Value::Scalar(r) => {
6581 let data = vec![1.0, -*r];
6582 Ok(Value::Matrix(Array2::from_shape_vec((1, 2), data).unwrap()))
6583 }
6584 Value::Matrix(m) => {
6585 if m.nrows() == 1 || m.ncols() == 1 {
6586 let roots: Vec<f64> = if m.nrows() == 1 {
6588 m.row(0).iter().copied().collect()
6589 } else {
6590 m.column(0).iter().copied().collect()
6591 };
6592 let mut p = vec![1.0_f64];
6593 for &r in &roots {
6594 p = poly_conv(&p, &[1.0, -r]);
6595 }
6596 let ncols = p.len();
6597 Ok(Value::Matrix(
6598 Array2::from_shape_vec((1, ncols), p).unwrap(),
6599 ))
6600 } else {
6601 let coeffs = characteristic_poly(m)?;
6603 let ncols = coeffs.len();
6604 Ok(Value::Matrix(
6605 Array2::from_shape_vec((1, ncols), coeffs).unwrap(),
6606 ))
6607 }
6608 }
6609 _ => Err("poly: argument must be a numeric vector or square matrix".to_string()),
6610 },
6611
6612 ("conv", 2) => {
6614 let a = poly_coeffs(&args[0], "conv")?;
6615 let b = poly_coeffs(&args[1], "conv")?;
6616 if a.is_empty() || b.is_empty() {
6617 return Ok(Value::Matrix(Array2::zeros((1, 0))));
6618 }
6619 let c = poly_conv(&a, &b);
6620 let len = c.len();
6621 Ok(Value::Matrix(Array2::from_shape_vec((1, len), c).unwrap()))
6622 }
6623
6624 ("deconv", 2) => {
6625 let c = poly_coeffs(&args[0], "deconv")?;
6626 let b = poly_coeffs(&args[1], "deconv")?;
6627 let (q, r) = poly_deconv(&c, &b)?;
6628 let qn = q.len();
6629 let rn = r.len();
6630 let q_val = Value::Matrix(Array2::from_shape_vec((1, qn), q).unwrap());
6631 let r_val = Value::Matrix(Array2::from_shape_vec((1, rn), r).unwrap());
6632 Ok(Value::Tuple(vec![q_val, r_val]))
6633 }
6634
6635 ("interp1", 3) => {
6636 let xv = poly_coeffs(&args[0], "interp1")?;
6637 let yv = poly_coeffs(&args[1], "interp1")?;
6638 if xv.len() != yv.len() {
6639 return Err("interp1: x and y must have the same length".to_string());
6640 }
6641 if xv.len() < 2 {
6642 return Err("interp1: requires at least two knot points".to_string());
6643 }
6644 match &args[2] {
6645 Value::Scalar(xi) => Ok(Value::Scalar(interp1_at(&xv, &yv, *xi, "linear"))),
6646 Value::Matrix(xi_m) => Ok(Value::Matrix(
6647 xi_m.mapv(|xi| interp1_at(&xv, &yv, xi, "linear")),
6648 )),
6649 _ => Err("interp1: query points must be numeric".to_string()),
6650 }
6651 }
6652
6653 ("interp1", 4) => {
6654 let xv = poly_coeffs(&args[0], "interp1")?;
6655 let yv = poly_coeffs(&args[1], "interp1")?;
6656 let method = match &args[3] {
6657 Value::Str(s) | Value::StringObj(s) => s.clone(),
6658 _ => return Err("interp1: method argument must be a string".to_string()),
6659 };
6660 if !matches!(method.as_str(), "linear" | "nearest" | "previous" | "next") {
6661 return Err(format!(
6662 "interp1: unknown method '{method}'; supported: linear nearest previous next"
6663 ));
6664 }
6665 if xv.len() != yv.len() {
6666 return Err("interp1: x and y must have the same length".to_string());
6667 }
6668 if xv.len() < 2 {
6669 return Err("interp1: requires at least two knot points".to_string());
6670 }
6671 match &args[2] {
6672 Value::Scalar(xi) => Ok(Value::Scalar(interp1_at(&xv, &yv, *xi, &method))),
6673 Value::Matrix(xi_m) => {
6674 let m_str = method.as_str();
6675 Ok(Value::Matrix(
6676 xi_m.mapv(|xi| interp1_at(&xv, &yv, xi, m_str)),
6677 ))
6678 }
6679 _ => Err("interp1: query points must be numeric".to_string()),
6680 }
6681 }
6682
6683 ("tic", 0) => {
6685 TIC_TIME.with(|t| t.set(Some(std::time::Instant::now())));
6686 Ok(Value::Void)
6687 }
6688 ("toc", 0) => {
6689 let elapsed = TIC_TIME.with(|t| t.get().map(|s| s.elapsed().as_secs_f64()));
6690 match elapsed {
6691 Some(t) => Ok(Value::Scalar(t)),
6692 None => Err("toc: tic must be called before toc".to_string()),
6693 }
6694 }
6695
6696 ("eval", 1) => {
6698 let code = match &args[0] {
6699 Value::Str(s) | Value::StringObj(s) => s.clone(),
6700 _ => return Err("eval: argument must be a string".to_string()),
6701 };
6702 call_eval_str_hook(&code, env)
6703 }
6704 ("eval", 2) => {
6705 let code = match &args[0] {
6706 Value::Str(s) | Value::StringObj(s) => s.clone(),
6707 _ => return Err("eval: argument must be a string".to_string()),
6708 };
6709 match call_eval_str_hook(&code, env) {
6710 Err(e) => {
6711 set_last_err(&e);
6712 let catch = match &args[1] {
6713 Value::Str(s) | Value::StringObj(s) => s.clone(),
6714 _ => return Err("eval: catch argument must be a string".to_string()),
6715 };
6716 call_eval_str_hook(&catch, env)
6717 }
6718 ok => ok,
6719 }
6720 }
6721
6722 _ => {
6723 let hint = suggest_similar(name, env);
6724 match hint {
6725 Some(s) => Err(format!("Unknown function '{name}'; did you mean '{s}'?")),
6726 None => Err(format!("Unknown function: '{name}'")),
6727 }
6728 }
6729 }
6730}
6731
6732fn interpret_delim(s: &str) -> String {
6735 match s {
6736 r"\t" => "\t".to_string(),
6737 r"\n" => "\n".to_string(),
6738 other => other.to_string(),
6739 }
6740}
6741
6742fn delim_consistent(lines: &[&str], delim: char) -> bool {
6744 let counts: Vec<usize> = lines.iter().map(|l| l.split(delim).count()).collect();
6745 counts.iter().all(|&c| c > 1) && counts.windows(2).all(|w| w[0] == w[1])
6746}
6747
6748fn dlmread_impl(path: &str, explicit_delim: Option<String>) -> Result<Value, String> {
6750 let content =
6751 std::fs::read_to_string(path).map_err(|e| format!("dlmread: cannot read '{path}': {e}"))?;
6752
6753 let lines: Vec<&str> = content.lines().filter(|l| !l.trim().is_empty()).collect();
6754
6755 if lines.is_empty() {
6756 return Ok(Value::Matrix(Array2::zeros((0, 0))));
6757 }
6758
6759 let delim: Option<String> = match explicit_delim {
6761 Some(d) => Some(d),
6762 None => {
6763 if delim_consistent(&lines, ',') {
6764 Some(",".to_string())
6765 } else if delim_consistent(&lines, '\t') {
6766 Some("\t".to_string())
6767 } else {
6768 None }
6770 }
6771 };
6772
6773 let mut rows: Vec<Vec<f64>> = Vec::new();
6774 for (line_num, line) in lines.iter().enumerate() {
6775 let fields: Vec<&str> = match &delim {
6776 Some(d) => line.split(d.as_str()).collect(),
6777 None => line.split_whitespace().collect(),
6778 };
6779 let mut row_vals: Vec<f64> = Vec::with_capacity(fields.len());
6780 for field in &fields {
6781 let trimmed = field.trim();
6782 if trimmed.is_empty() {
6783 row_vals.push(0.0);
6784 } else {
6785 row_vals.push(trimmed.parse::<f64>().map_err(|_| {
6786 format!(
6787 "dlmread: non-numeric value '{trimmed}' on line {}",
6788 line_num + 1
6789 )
6790 })?);
6791 }
6792 }
6793 if !row_vals.is_empty() {
6794 rows.push(row_vals);
6795 }
6796 }
6797
6798 if rows.is_empty() {
6799 return Ok(Value::Matrix(Array2::zeros((0, 0))));
6800 }
6801
6802 let ncols = rows[0].len();
6803 for (i, row) in rows.iter().enumerate() {
6804 if row.len() != ncols {
6805 return Err(format!(
6806 "dlmread: row {} has {} fields, expected {ncols}",
6807 i + 1,
6808 row.len()
6809 ));
6810 }
6811 }
6812
6813 let nrows = rows.len();
6814 let flat: Vec<f64> = rows.into_iter().flatten().collect();
6815 Array2::from_shape_vec((nrows, ncols), flat)
6816 .map_err(|e| format!("dlmread: shape error: {e}"))
6817 .map(Value::Matrix)
6818}
6819
6820fn fmt_dlm_number(n: f64) -> String {
6823 if n.is_finite() && n == n.trunc() && n.abs() < 1e15 {
6824 format!("{}", n as i64)
6825 } else {
6826 format!("{n}")
6827 }
6828}
6829
6830fn dlmwrite_impl(path: &str, val: &Value, explicit_delim: Option<String>) -> Result<Value, String> {
6832 let delim = explicit_delim.unwrap_or_else(|| ",".to_string());
6833
6834 let content = match val {
6835 Value::Scalar(n) => format!("{}\n", fmt_dlm_number(*n)),
6836 Value::Matrix(m) => {
6837 let mut out = String::new();
6838 for row in m.rows() {
6839 let parts: Vec<String> = row.iter().map(|n| fmt_dlm_number(*n)).collect();
6840 out.push_str(&parts.join(&delim));
6841 out.push('\n');
6842 }
6843 out
6844 }
6845 _ => {
6846 return Err("dlmwrite: second argument must be a numeric scalar or matrix".to_string());
6847 }
6848 };
6849
6850 std::fs::write(path, content).map_err(|e| format!("dlmwrite: cannot write '{path}': {e}"))?;
6851 Ok(Value::Void)
6852}
6853
6854fn auto_detect_delim(lines: &[&str]) -> Option<String> {
6860 let comma_counts: Vec<usize> = lines.iter().map(|l| split_csv_row(l, ",").len()).collect();
6862 if comma_counts.iter().all(|&c| c > 1) && comma_counts.windows(2).all(|w| w[0] == w[1]) {
6863 return Some(",".to_string());
6864 }
6865 if delim_consistent(lines, '\t') {
6866 Some("\t".to_string())
6867 } else {
6868 None
6869 }
6870}
6871
6872fn split_csv_row(line: &str, delim: &str) -> Vec<String> {
6876 if delim.chars().count() != 1 {
6877 return line.split(delim).map(str::to_string).collect();
6878 }
6879 let delim_char = delim.chars().next().unwrap();
6880 let chars: Vec<char> = line.chars().collect();
6881 let mut fields: Vec<String> = Vec::new();
6882 let mut field = String::new();
6883 let mut i = 0;
6884 let mut in_quotes = false;
6885 while i < chars.len() {
6886 let c = chars[i];
6887 if in_quotes {
6888 if c == '"' && i + 1 < chars.len() && chars[i + 1] == '"' {
6889 field.push('"');
6890 i += 2;
6891 continue;
6892 } else if c == '"' {
6893 in_quotes = false;
6894 } else {
6895 field.push(c);
6896 }
6897 } else if c == '"' {
6898 in_quotes = true;
6899 } else if c == delim_char {
6900 fields.push(std::mem::take(&mut field));
6901 } else {
6902 field.push(c);
6903 }
6904 i += 1;
6905 }
6906 fields.push(field);
6907 fields
6908}
6909
6910fn split_csv_row_opt(line: &str, delim: &Option<String>) -> Vec<String> {
6912 match delim {
6913 None => line.split_whitespace().map(str::to_string).collect(),
6914 Some(d) => split_csv_row(line, d),
6915 }
6916}
6917
6918fn row_is_header(fields: &[String]) -> bool {
6920 fields
6921 .iter()
6922 .any(|f| !f.trim().is_empty() && f.trim().parse::<f64>().is_err())
6923}
6924
6925fn sanitize_header(s: &str, col_1based: usize) -> String {
6929 let s = s.trim();
6930 if s.is_empty() {
6931 return format!("x{col_1based}");
6932 }
6933 let mut out = String::new();
6934 for c in s.chars() {
6935 if c.is_alphanumeric() || c == '_' {
6936 out.push(c);
6937 } else if !out.ends_with('_') {
6938 out.push('_');
6939 }
6940 }
6941 let out = out.trim_end_matches('_').to_string();
6942 if out.is_empty() {
6943 return format!("x{col_1based}");
6944 }
6945 if out.chars().next().unwrap().is_ascii_digit() {
6946 format!("x{out}")
6947 } else {
6948 out
6949 }
6950}
6951
6952fn deduplicate_headers(headers: Vec<String>) -> Vec<String> {
6955 let mut count: HashMap<String, usize> = HashMap::new();
6956 for h in &headers {
6957 *count.entry(h.clone()).or_insert(0) += 1;
6958 }
6959 let mut seen: HashMap<String, usize> = HashMap::new();
6960 headers
6961 .into_iter()
6962 .map(|h| {
6963 if *count.get(&h).unwrap() == 1 {
6964 h
6965 } else {
6966 let idx = seen.entry(h.clone()).or_insert(0);
6967 *idx += 1;
6968 format!("{h}_{idx}")
6969 }
6970 })
6971 .collect()
6972}
6973
6974fn parse_delimiter_opt(
6977 fn_name: &str,
6978 args: &[Value],
6979 start: usize,
6980) -> Result<Option<String>, String> {
6981 if args.len() <= start {
6982 return Ok(None);
6983 }
6984 let key = string_arg(&args[start], fn_name, start + 1)?;
6985 if !key.eq_ignore_ascii_case("delimiter") {
6986 return Err(format!(
6987 "{fn_name}: expected 'Delimiter' option at argument {}, got '{key}'",
6988 start + 1
6989 ));
6990 }
6991 if args.len() <= start + 1 {
6992 return Err(format!("{fn_name}: 'Delimiter' option requires a value"));
6993 }
6994 let val = interpret_delim(string_arg(&args[start + 1], fn_name, start + 2)?);
6995 Ok(Some(val))
6996}
6997
6998fn readmatrix_impl(path: &str, explicit_delim: Option<String>) -> Result<Value, String> {
7004 let content = std::fs::read_to_string(path)
7005 .map_err(|e| format!("readmatrix: cannot read '{path}': {e}"))?;
7006
7007 let lines: Vec<&str> = content.lines().filter(|l| !l.trim().is_empty()).collect();
7008 if lines.is_empty() {
7009 return Ok(Value::Matrix(Array2::<f64>::zeros((0, 0))));
7010 }
7011
7012 let delim = match explicit_delim {
7013 Some(d) => Some(d),
7014 None => auto_detect_delim(&lines),
7015 };
7016
7017 let first_fields = split_csv_row_opt(lines[0], &delim);
7018 let skip_header = row_is_header(&first_fields);
7019 let data_lines = if skip_header { &lines[1..] } else { &lines[..] };
7020
7021 if data_lines.is_empty() {
7022 return Ok(Value::Matrix(Array2::<f64>::zeros((0, 0))));
7023 }
7024
7025 let mut rows: Vec<Vec<f64>> = Vec::new();
7026 for (i, line) in data_lines.iter().enumerate() {
7027 let fields = split_csv_row_opt(line, &delim);
7028 let mut row: Vec<f64> = Vec::with_capacity(fields.len());
7029 for f in &fields {
7030 let t = f.trim();
7031 if t.is_empty() {
7032 row.push(f64::NAN);
7033 } else {
7034 row.push(t.parse::<f64>().map_err(|_| {
7035 format!(
7036 "readmatrix: non-numeric value '{t}' on line {}",
7037 i + 1 + usize::from(skip_header)
7038 )
7039 })?);
7040 }
7041 }
7042 rows.push(row);
7043 }
7044
7045 if rows.is_empty() {
7046 return Ok(Value::Matrix(Array2::<f64>::zeros((0, 0))));
7047 }
7048
7049 let ncols = rows[0].len();
7050 for (i, row) in rows.iter().enumerate() {
7051 if row.len() != ncols {
7052 return Err(format!(
7053 "readmatrix: row {} has {} fields, expected {ncols}",
7054 i + 1,
7055 row.len()
7056 ));
7057 }
7058 }
7059
7060 let nrows = rows.len();
7061 let flat: Vec<f64> = rows.into_iter().flatten().collect();
7062 Array2::from_shape_vec((nrows, ncols), flat)
7063 .map_err(|e| format!("readmatrix: shape error: {e}"))
7064 .map(Value::Matrix)
7065}
7066
7067fn readtable_impl(path: &str, explicit_delim: Option<String>) -> Result<Value, String> {
7073 let content = std::fs::read_to_string(path)
7074 .map_err(|e| format!("readtable: cannot read '{path}': {e}"))?;
7075
7076 let lines: Vec<&str> = content.lines().filter(|l| !l.trim().is_empty()).collect();
7077 if lines.is_empty() {
7078 return Ok(Value::Struct(IndexMap::new()));
7079 }
7080
7081 let delim = match explicit_delim {
7082 Some(d) => Some(d),
7083 None => auto_detect_delim(&lines),
7084 };
7085
7086 let raw_headers = split_csv_row_opt(lines[0], &delim);
7087 let ncols = raw_headers.len();
7088 let headers: Vec<String> = deduplicate_headers(
7089 raw_headers
7090 .iter()
7091 .enumerate()
7092 .map(|(i, h)| sanitize_header(h.trim(), i + 1))
7093 .collect(),
7094 );
7095
7096 let data_lines = &lines[1..];
7097 if data_lines.is_empty() {
7098 let mut s: IndexMap<String, Value> = IndexMap::new();
7099 for h in &headers {
7100 s.insert(h.clone(), Value::Matrix(Array2::<f64>::zeros((0, 1))));
7101 }
7102 return Ok(Value::Struct(s));
7103 }
7104
7105 let mut all_rows: Vec<Vec<String>> = Vec::new();
7106 for (i, line) in data_lines.iter().enumerate() {
7107 let fields = split_csv_row_opt(line, &delim);
7108 if fields.len() != ncols {
7109 return Err(format!(
7110 "readtable: row {} has {} fields, expected {ncols}",
7111 i + 2,
7112 fields.len()
7113 ));
7114 }
7115 all_rows.push(fields.into_iter().map(|f| f.trim().to_string()).collect());
7116 }
7117
7118 let nrows = all_rows.len();
7119 let mut s: IndexMap<String, Value> = IndexMap::new();
7120 for col in 0..ncols {
7121 let all_numeric = all_rows.iter().all(|row| {
7122 let t = row[col].as_str();
7123 t.is_empty() || t.parse::<f64>().is_ok()
7124 });
7125 if all_numeric {
7126 let vals: Vec<f64> = all_rows
7127 .iter()
7128 .map(|row| {
7129 let t = row[col].as_str();
7130 if t.is_empty() {
7131 f64::NAN
7132 } else {
7133 t.parse::<f64>().unwrap()
7134 }
7135 })
7136 .collect();
7137 let col_mat = Array2::from_shape_vec((nrows, 1), vals)
7138 .map_err(|e| format!("readtable: shape error: {e}"))?;
7139 s.insert(headers[col].clone(), Value::Matrix(col_mat));
7140 } else {
7141 let vals: Vec<Value> = all_rows
7142 .iter()
7143 .map(|row| Value::Str(row[col].clone()))
7144 .collect();
7145 s.insert(headers[col].clone(), Value::Cell(vals));
7146 }
7147 }
7148 Ok(Value::Struct(s))
7149}
7150
7151fn csv_quote_cell(s: &str, delim: &str) -> String {
7153 if s.contains('"') || s.contains('\n') || s.contains(delim) {
7154 let escaped = s.replace('"', "\"\"");
7155 format!("\"{escaped}\"")
7156 } else {
7157 s.to_string()
7158 }
7159}
7160
7161fn col_nrows(v: &Value) -> Option<usize> {
7166 match v {
7167 Value::Matrix(m) if m.ncols() == 1 || m.nrows() == 0 => Some(m.nrows()),
7168 Value::Cell(c) => Some(c.len()),
7169 Value::Scalar(_) => Some(1),
7170 Value::Str(_) | Value::StringObj(_) => Some(1),
7171 _ => None,
7172 }
7173}
7174
7175fn col_cell_str(v: &Value, row: usize, delim: &str) -> Result<String, String> {
7177 match v {
7178 Value::Matrix(m) => Ok(csv_quote_cell(&fmt_dlm_number(m[[row, 0]]), delim)),
7179 Value::Cell(c) => match &c[row] {
7180 Value::Str(s) | Value::StringObj(s) => Ok(csv_quote_cell(s, delim)),
7181 Value::Scalar(n) => Ok(csv_quote_cell(&fmt_dlm_number(*n), delim)),
7182 _ => Err(format!(
7183 "writetable: cell element at row {} has unsupported type",
7184 row + 1
7185 )),
7186 },
7187 Value::Scalar(n) => Ok(csv_quote_cell(&fmt_dlm_number(*n), delim)),
7188 Value::Str(s) | Value::StringObj(s) => Ok(csv_quote_cell(s, delim)),
7189 _ => Err(format!(
7190 "writetable: unsupported column type at row {}",
7191 row + 1
7192 )),
7193 }
7194}
7195
7196fn writetable_impl(
7202 tbl: &Value,
7203 path: &str,
7204 explicit_delim: Option<String>,
7205) -> Result<Value, String> {
7206 let delim = explicit_delim.unwrap_or_else(|| ",".to_string());
7207 let fields = match tbl {
7208 Value::Struct(m) => m,
7209 _ => return Err("writetable: first argument must be a struct".to_string()),
7210 };
7211 if fields.is_empty() {
7212 std::fs::write(path, "").map_err(|e| format!("writetable: cannot write '{path}': {e}"))?;
7213 return Ok(Value::Void);
7214 }
7215
7216 let nrows = {
7217 let (first_name, first_val) = fields.iter().next().unwrap();
7218 col_nrows(first_val).ok_or_else(|| {
7219 format!("writetable: column '{first_name}' must be a Matrix (N×1), Cell, or scalar")
7220 })?
7221 };
7222 for (cname, cval) in fields.iter() {
7223 let n = col_nrows(cval).ok_or_else(|| {
7224 format!("writetable: column '{cname}' must be a Matrix (N×1), Cell, or scalar")
7225 })?;
7226 if n != nrows {
7227 return Err(format!(
7228 "writetable: column '{cname}' has {n} rows, expected {nrows}"
7229 ));
7230 }
7231 }
7232
7233 let mut out = String::new();
7234 let header_parts: Vec<String> = fields.keys().map(|k| csv_quote_cell(k, &delim)).collect();
7235 out.push_str(&header_parts.join(&delim));
7236 out.push('\n');
7237
7238 for row in 0..nrows {
7239 let mut parts: Vec<String> = Vec::with_capacity(fields.len());
7240 for cval in fields.values() {
7241 parts.push(col_cell_str(cval, row, &delim)?);
7242 }
7243 out.push_str(&parts.join(&delim));
7244 out.push('\n');
7245 }
7246
7247 std::fs::write(path, out).map_err(|e| format!("writetable: cannot write '{path}': {e}"))?;
7248 Ok(Value::Void)
7249}
7250
7251fn to_bits(v: f64, fname: &str, pos: usize) -> Result<u64, String> {
7254 if v < 0.0 {
7255 return Err(format!(
7256 "{fname}: argument {pos} must be non-negative, got {v}"
7257 ));
7258 }
7259 if v.fract() != 0.0 {
7260 return Err(format!(
7261 "{fname}: argument {pos} must be an integer, got {v}"
7262 ));
7263 }
7264 if v > u64::MAX as f64 {
7265 return Err(format!(
7266 "{fname}: argument {pos} is too large for bitwise operations"
7267 ));
7268 }
7269 Ok(v as u64)
7270}
7271
7272fn det_matrix(m: &Array2<f64>) -> Result<f64, String> {
7276 let n = m.nrows();
7277 if m.ncols() != n {
7278 return Err("det: matrix must be square".to_string());
7279 }
7280 if n == 0 {
7281 return Ok(1.0);
7282 }
7283 let mut a = m.clone();
7284 let mut sign: f64 = 1.0;
7285 for col in 0..n {
7286 let pivot = (col..n)
7288 .max_by(|&r1, &r2| a[[r1, col]].abs().partial_cmp(&a[[r2, col]].abs()).unwrap())
7289 .unwrap();
7290 if a[[pivot, col]].abs() < 1e-15 {
7291 return Ok(0.0); }
7293 if pivot != col {
7294 for j in 0..n {
7295 let tmp = a[[pivot, j]];
7296 a[[pivot, j]] = a[[col, j]];
7297 a[[col, j]] = tmp;
7298 }
7299 sign = -sign;
7300 }
7301 let pv = a[[col, col]];
7302 for row in (col + 1)..n {
7303 let factor = a[[row, col]] / pv;
7304 for j in col..n {
7305 let val = a[[col, j]] * factor;
7306 a[[row, j]] -= val;
7307 }
7308 }
7309 }
7310 Ok(sign * (0..n).map(|i| a[[i, i]]).product::<f64>())
7311}
7312
7313fn inv_matrix(m: &Array2<f64>) -> Result<Array2<f64>, String> {
7316 let n = m.nrows();
7317 if m.ncols() != n {
7318 return Err("inv: matrix must be square".to_string());
7319 }
7320 let cols = 2 * n;
7321 let mut aug = vec![0.0f64; n * cols];
7322 for i in 0..n {
7323 for j in 0..n {
7324 aug[i * cols + j] = m[[i, j]];
7325 }
7326 aug[i * cols + n + i] = 1.0;
7327 }
7328 for col in 0..n {
7329 let pivot = (col..n)
7331 .max_by(|&r1, &r2| {
7332 aug[r1 * cols + col]
7333 .abs()
7334 .partial_cmp(&aug[r2 * cols + col].abs())
7335 .unwrap()
7336 })
7337 .filter(|&r| aug[r * cols + col].abs() > 1e-12)
7338 .ok_or_else(|| "inv: matrix is singular".to_string())?;
7339 if pivot != col {
7340 for j in 0..cols {
7341 aug.swap(col * cols + j, pivot * cols + j);
7342 }
7343 }
7344 let pv = aug[col * cols + col];
7345 for j in 0..cols {
7346 aug[col * cols + j] /= pv;
7347 }
7348 for row in 0..n {
7349 if row == col {
7350 continue;
7351 }
7352 let factor = aug[row * cols + col];
7353 for j in 0..cols {
7354 let val = aug[col * cols + j] * factor;
7355 aug[row * cols + j] -= val;
7356 }
7357 }
7358 }
7359 let mut result = Array2::<f64>::zeros((n, n));
7360 for i in 0..n {
7361 for j in 0..n {
7362 result[[i, j]] = aug[i * cols + n + j];
7363 }
7364 }
7365 Ok(result)
7366}
7367
7368fn solve_linear(a: &Array2<f64>, b: &Array2<f64>) -> Result<Array2<f64>, String> {
7373 let n = a.nrows();
7374 if a.ncols() != n {
7375 return Err(format!(
7376 "\\: coefficient matrix must be square, got {}×{}",
7377 n,
7378 a.ncols()
7379 ));
7380 }
7381 let k = b.ncols();
7382 if b.nrows() != n {
7383 return Err(format!(
7384 "\\: size mismatch — A is {}×{} but b has {} rows",
7385 n,
7386 n,
7387 b.nrows()
7388 ));
7389 }
7390 if n == 0 {
7391 return Ok(Array2::zeros((0, k)));
7392 }
7393 let cols = n + k;
7394 let mut aug = vec![0.0f64; n * cols];
7395 for i in 0..n {
7396 for j in 0..n {
7397 aug[i * cols + j] = a[[i, j]];
7398 }
7399 for j in 0..k {
7400 aug[i * cols + n + j] = b[[i, j]];
7401 }
7402 }
7403 for col in 0..n {
7404 let pivot = (col..n)
7405 .max_by(|&r1, &r2| {
7406 aug[r1 * cols + col]
7407 .abs()
7408 .partial_cmp(&aug[r2 * cols + col].abs())
7409 .unwrap()
7410 })
7411 .filter(|&r| aug[r * cols + col].abs() > 1e-12)
7412 .ok_or_else(|| "\\: matrix is singular or nearly singular".to_string())?;
7413 if pivot != col {
7414 for j in 0..cols {
7415 aug.swap(col * cols + j, pivot * cols + j);
7416 }
7417 }
7418 let pv = aug[col * cols + col];
7419 for j in col..cols {
7420 aug[col * cols + j] /= pv;
7421 }
7422 for row in 0..n {
7423 if row == col {
7424 continue;
7425 }
7426 let factor = aug[row * cols + col];
7427 if factor == 0.0 {
7428 continue;
7429 }
7430 for j in col..cols {
7431 let val = aug[col * cols + j] * factor;
7432 aug[row * cols + j] -= val;
7433 }
7434 }
7435 }
7436 let mut result = Array2::<f64>::zeros((n, k));
7437 for i in 0..n {
7438 for j in 0..k {
7439 result[[i, j]] = aug[i * cols + n + j];
7440 }
7441 }
7442 Ok(result)
7443}
7444
7445fn qr_decompose(a: &Array2<f64>) -> Result<(Array2<f64>, Array2<f64>), String> {
7454 let m = a.nrows();
7455 let n = a.ncols();
7456 let k = m.min(n);
7457 let mut r = a.clone();
7458 let mut q = Array2::<f64>::eye(m);
7459
7460 for j in 0..k {
7461 let col_len = m - j;
7462 let mut v: Vec<f64> = (j..m).map(|i| r[[i, j]]).collect();
7463
7464 let norm_x = v.iter().map(|&x| x * x).sum::<f64>().sqrt();
7465 if norm_x < 1e-14 {
7466 continue;
7467 }
7468 v[0] += if v[0] >= 0.0 { norm_x } else { -norm_x };
7470 let v_sq: f64 = v.iter().map(|&x| x * x).sum();
7471 if v_sq < 1e-28 {
7472 continue;
7473 }
7474
7475 for col in j..n {
7477 let dot: f64 = (0..col_len).map(|i| v[i] * r[[j + i, col]]).sum();
7478 let fac = 2.0 * dot / v_sq;
7479 for i in 0..col_len {
7480 r[[j + i, col]] -= fac * v[i];
7481 }
7482 }
7483 for row in 0..m {
7485 let dot: f64 = (0..col_len).map(|i| q[[row, j + i]] * v[i]).sum();
7486 let fac = 2.0 * dot / v_sq;
7487 for i in 0..col_len {
7488 q[[row, j + i]] -= fac * v[i];
7489 }
7490 }
7491 }
7492
7493 Ok((q, r))
7494}
7495
7496type LuResult = Result<(Array2<f64>, Array2<f64>, Array2<f64>), String>;
7502fn lu_decompose(a: &Array2<f64>) -> LuResult {
7503 let n = a.nrows();
7504 if a.ncols() != n {
7505 return Err("lu: matrix must be square".to_string());
7506 }
7507 let mut u = a.clone();
7508 let mut l = Array2::<f64>::eye(n);
7509 let mut perm: Vec<usize> = (0..n).collect();
7510
7511 for j in 0..n {
7512 let pivot = (j..n)
7513 .max_by(|&r1, &r2| {
7514 u[[r1, j]]
7515 .abs()
7516 .partial_cmp(&u[[r2, j]].abs())
7517 .unwrap_or(std::cmp::Ordering::Equal)
7518 })
7519 .unwrap();
7520
7521 if pivot != j {
7522 for col in 0..n {
7523 let tmp = u[[j, col]];
7524 u[[j, col]] = u[[pivot, col]];
7525 u[[pivot, col]] = tmp;
7526 }
7527 for col in 0..j {
7528 let tmp = l[[j, col]];
7529 l[[j, col]] = l[[pivot, col]];
7530 l[[pivot, col]] = tmp;
7531 }
7532 perm.swap(j, pivot);
7533 }
7534
7535 if u[[j, j]].abs() < 1e-15 {
7536 continue;
7537 }
7538 for i in (j + 1)..n {
7539 l[[i, j]] = u[[i, j]] / u[[j, j]];
7540 for k in j..n {
7541 let val = l[[i, j]] * u[[j, k]];
7542 u[[i, k]] -= val;
7543 }
7544 }
7545 }
7546
7547 let mut p = Array2::<f64>::zeros((n, n));
7548 for (i, &j) in perm.iter().enumerate() {
7549 p[[i, j]] = 1.0;
7550 }
7551 Ok((l, u, p))
7552}
7553
7554fn chol_decompose(a: &Array2<f64>) -> Result<Array2<f64>, String> {
7559 let n = a.nrows();
7560 if a.ncols() != n {
7561 return Err("chol: matrix must be square".to_string());
7562 }
7563 let mut r = Array2::<f64>::zeros((n, n));
7564 for j in 0..n {
7565 let mut s = a[[j, j]];
7566 for k in 0..j {
7567 s -= r[[k, j]] * r[[k, j]];
7568 }
7569 if s <= 0.0 {
7570 return Err("chol: matrix is not positive definite".to_string());
7571 }
7572 r[[j, j]] = s.sqrt();
7573 for i in (j + 1)..n {
7574 let mut t = a[[j, i]];
7575 for k in 0..j {
7576 t -= r[[k, j]] * r[[k, i]];
7577 }
7578 r[[j, i]] = t / r[[j, j]];
7579 }
7580 }
7581 Ok(r)
7582}
7583
7584type SvdResult = Result<(Array2<f64>, Vec<f64>, Array2<f64>), String>;
7593fn svd_compute(a: &Array2<f64>) -> SvdResult {
7594 let m = a.nrows();
7595 let n = a.ncols();
7596 if m < n {
7597 let (v, s, u) = svd_compute(&a.t().to_owned())?;
7598 return Ok((u, s, v));
7599 }
7600 let k = n;
7602 let mut b = a.clone();
7603 let mut v = Array2::<f64>::eye(k);
7604
7605 const MAX_ITER: usize = 200;
7606 const EPS: f64 = 1e-14;
7607
7608 'outer: for _ in 0..MAX_ITER {
7609 let mut changed = false;
7610 for p in 0..k {
7611 for q in (p + 1)..k {
7612 let alpha: f64 = (0..m).map(|i| b[[i, p]] * b[[i, p]]).sum();
7613 let beta: f64 = (0..m).map(|i| b[[i, q]] * b[[i, q]]).sum();
7614 let gamma: f64 = (0..m).map(|i| b[[i, p]] * b[[i, q]]).sum();
7615
7616 if gamma.abs() <= EPS * (alpha * beta).sqrt() {
7617 continue;
7618 }
7619 changed = true;
7620
7621 let zeta = (beta - alpha) / (2.0 * gamma);
7622 let t = zeta.signum() / (zeta.abs() + (1.0 + zeta * zeta).sqrt());
7623 let c = 1.0 / (1.0 + t * t).sqrt();
7624 let s = c * t;
7625
7626 for i in 0..m {
7627 let bp = b[[i, p]];
7628 let bq = b[[i, q]];
7629 b[[i, p]] = c * bp - s * bq;
7630 b[[i, q]] = s * bp + c * bq;
7631 }
7632 for i in 0..k {
7633 let vp = v[[i, p]];
7634 let vq = v[[i, q]];
7635 v[[i, p]] = c * vp - s * vq;
7636 v[[i, q]] = s * vp + c * vq;
7637 }
7638 }
7639 }
7640 if !changed {
7641 break 'outer;
7642 }
7643 }
7644
7645 let mut sigma: Vec<f64> = (0..k)
7646 .map(|j| (0..m).map(|i| b[[i, j]] * b[[i, j]]).sum::<f64>().sqrt())
7647 .collect();
7648 let mut u_mat = Array2::<f64>::zeros((m, k));
7649 for j in 0..k {
7650 if sigma[j] > EPS {
7651 for i in 0..m {
7652 u_mat[[i, j]] = b[[i, j]] / sigma[j];
7653 }
7654 }
7655 }
7656
7657 let mut order: Vec<usize> = (0..k).collect();
7659 order.sort_by(|&a, &b| {
7660 sigma[b]
7661 .partial_cmp(&sigma[a])
7662 .unwrap_or(std::cmp::Ordering::Equal)
7663 });
7664 let sigma_s: Vec<f64> = order.iter().map(|&i| sigma[i]).collect();
7665 let mut u_s = Array2::<f64>::zeros((m, k));
7666 let mut v_s = Array2::<f64>::zeros((n, k));
7667 for (ni, &oi) in order.iter().enumerate() {
7668 for r in 0..m {
7669 u_s[[r, ni]] = u_mat[[r, oi]];
7670 }
7671 for r in 0..k {
7672 v_s[[r, ni]] = v[[r, oi]];
7673 }
7674 }
7675 sigma = sigma_s;
7676
7677 Ok((u_s, sigma, v_s))
7678}
7679
7680fn complete_orthonormal_basis(u: &Array2<f64>) -> Array2<f64> {
7685 let m = u.nrows();
7686 let k = u.ncols();
7687 let mut basis: Vec<Vec<f64>> = (0..k).map(|j| u.column(j).to_vec()).collect();
7688
7689 let mut ei = 0usize;
7690 while basis.len() < m && ei < m {
7691 let mut v: Vec<f64> = vec![0.0; m];
7692 v[ei] = 1.0;
7693 ei += 1;
7694 for b in &basis {
7695 let dot: f64 = v.iter().zip(b.iter()).map(|(&a, &b)| a * b).sum();
7696 for (vi, &bi) in v.iter_mut().zip(b.iter()) {
7697 *vi -= dot * bi;
7698 }
7699 }
7700 let norm = v.iter().map(|&x| x * x).sum::<f64>().sqrt();
7701 if norm > 1e-10 {
7702 for vi in &mut v {
7703 *vi /= norm;
7704 }
7705 basis.push(v);
7706 }
7707 }
7708
7709 let mut result = Array2::<f64>::zeros((m, m));
7710 for (j, b) in basis.iter().enumerate() {
7711 for (i, &val) in b.iter().enumerate() {
7712 result[[i, j]] = val;
7713 }
7714 }
7715 result
7716}
7717
7718fn eig_compute(a: &Array2<f64>) -> Result<(Vec<Complex<f64>>, Array2<f64>), String> {
7726 let n = a.nrows();
7727 if a.ncols() != n {
7728 return Err("eig: matrix must be square".to_string());
7729 }
7730 if n == 0 {
7731 return Ok((vec![], Array2::zeros((0, 0))));
7732 }
7733 if n == 1 {
7734 return Ok((vec![Complex::new(a[[0, 0]], 0.0)], Array2::eye(1)));
7735 }
7736
7737 let mut ak = a.clone();
7738 let mut evecs = Array2::<f64>::eye(n);
7739
7740 const MAX_ITER: usize = 2000;
7741 const EPS: f64 = 1e-12;
7742
7743 for _ in 0..MAX_ITER {
7744 let mu = {
7746 let d = ak[[n - 1, n - 1]];
7747 if n >= 2 {
7748 let a = ak[[n - 2, n - 2]];
7749 let b = ak[[n - 2, n - 1]];
7750 let delta = (a - d) / 2.0;
7751 if delta.abs() < 1e-30 {
7752 d - b.abs()
7753 } else {
7754 d - b * b / (delta + delta.signum() * (delta * delta + b * b).sqrt())
7755 }
7756 } else {
7757 d
7758 }
7759 };
7760
7761 for i in 0..n {
7762 ak[[i, i]] -= mu;
7763 }
7764 let (q, r) = qr_decompose(&ak)?;
7765 ak = r.dot(&q);
7766 for i in 0..n {
7767 ak[[i, i]] += mu;
7768 }
7769 evecs = evecs.dot(&q);
7770
7771 let max_sub = (0..(n - 1))
7773 .map(|i| ak[[i + 1, i]].abs())
7774 .fold(0.0_f64, f64::max);
7775 if max_sub < EPS {
7776 break;
7777 }
7778 }
7779
7780 const EPS_BLOCK: f64 = 1e-8;
7783 let mut evals: Vec<Complex<f64>> = Vec::with_capacity(n);
7784 let mut i = 0;
7785 while i < n {
7786 if i + 1 < n && ak[[i + 1, i]].abs() > EPS_BLOCK {
7787 let (a_ii, b, c, d_ii) = (
7788 ak[[i, i]],
7789 ak[[i, i + 1]],
7790 ak[[i + 1, i]],
7791 ak[[i + 1, i + 1]],
7792 );
7793 let p = (a_ii + d_ii) / 2.0;
7794 let disc = ((a_ii - d_ii) / 2.0).powi(2) + b * c;
7795 if disc < 0.0 {
7796 let q = (-disc).sqrt();
7797 evals.push(Complex::new(p, q));
7798 evals.push(Complex::new(p, -q));
7799 } else {
7800 let q = disc.sqrt();
7801 evals.push(Complex::new(p + q, 0.0));
7802 evals.push(Complex::new(p - q, 0.0));
7803 }
7804 i += 2;
7805 } else {
7806 evals.push(Complex::new(ak[[i, i]], 0.0));
7807 i += 1;
7808 }
7809 }
7810
7811 Ok((evals, evecs))
7812}
7813
7814fn env_with_end(env: &Env, dim_size: usize) -> Env {
7821 let mut e = env.clone();
7822 e.insert("end".to_string(), Value::Scalar(dim_size as f64));
7823 e
7824}
7825
7826pub(crate) fn contains_end(expr: &Expr) -> bool {
7830 match expr {
7831 Expr::Var(s) => s == "end",
7832 Expr::Number(_)
7833 | Expr::Colon
7834 | Expr::StrLiteral(_)
7835 | Expr::StringObjLiteral(_)
7836 | Expr::NaT
7837 | Expr::FuncHandle(_) => false,
7838 Expr::UnaryMinus(e)
7839 | Expr::UnaryNot(e)
7840 | Expr::Transpose(e)
7841 | Expr::PlainTranspose(e)
7842 | Expr::FieldGet(e, _) => contains_end(e),
7843 Expr::BinOp(l, _, r) => contains_end(l) || contains_end(r),
7844 Expr::Call(_, args) | Expr::DotCall(_, args) => args.iter().any(contains_end),
7845 Expr::Matrix(rows) => rows.iter().flat_map(|r| r.iter()).any(contains_end),
7846 Expr::Range(a, step, b) => {
7847 contains_end(a) || step.as_deref().is_some_and(contains_end) || contains_end(b)
7848 }
7849 Expr::Lambda { body, .. } => contains_end(body),
7850 Expr::CellLiteral(elems) => elems.iter().any(contains_end),
7851 Expr::CellIndex(a, b) => contains_end(a) || contains_end(b),
7852 }
7853}
7854
7855fn eval_index(val: &Value, args: &[Expr], env: &Env) -> Result<Value, String> {
7860 match args.len() {
7861 0 => Err("Indexing requires at least one index".to_string()),
7862 1 => {
7863 match val {
7865 Value::Void => Err("Cannot index into void".to_string()),
7866 Value::Lambda(_) | Value::Function { .. } | Value::Tuple(_) => {
7867 Err("Cannot index into a function value".to_string())
7868 }
7869 Value::Cell(_) => Err("Use c{i} to index into a cell array, not c(i)".to_string()),
7870 Value::Struct(_) => {
7871 Err("Use s.field to access struct fields, not s(i)".to_string())
7872 }
7873 Value::StructArray(arr) => {
7874 let total = arr.len();
7875 let _owned_env;
7876 let env1: &Env = if contains_end(&args[0]) {
7877 _owned_env = env_with_end(env, total);
7878 &_owned_env
7879 } else {
7880 env
7881 };
7882 match resolve_dim(&args[0], total, env1)? {
7883 DimIdx::All => {
7884 Ok(Value::StructArray(arr.clone()))
7886 }
7887 DimIdx::Indices(idxs) => {
7888 if idxs.len() == 1 {
7889 let i = idxs[0];
7890 if i >= total {
7891 return Err(format!(
7892 "Index {} out of range (1..{})",
7893 i + 1,
7894 total
7895 ));
7896 }
7897 Ok(Value::Struct(arr[i].clone()))
7898 } else {
7899 let mut selected = Vec::with_capacity(idxs.len());
7900 for &i in &idxs {
7901 if i >= total {
7902 return Err(format!(
7903 "Index {} out of range (1..{})",
7904 i + 1,
7905 total
7906 ));
7907 }
7908 selected.push(arr[i].clone());
7909 }
7910 Ok(Value::StructArray(selected))
7911 }
7912 }
7913 }
7914 }
7915 Value::Scalar(n) => {
7916 let _owned_env;
7917 let env1: &Env = if contains_end(&args[0]) {
7918 _owned_env = env_with_end(env, 1);
7919 &_owned_env
7920 } else {
7921 env
7922 };
7923 match resolve_dim(&args[0], 1, env1)? {
7924 DimIdx::All | DimIdx::Indices(_) => Ok(Value::Scalar(*n)),
7925 }
7926 }
7927 Value::Complex(re, im) => {
7928 let _owned_env;
7929 let env1: &Env = if contains_end(&args[0]) {
7930 _owned_env = env_with_end(env, 1);
7931 &_owned_env
7932 } else {
7933 env
7934 };
7935 match resolve_dim(&args[0], 1, env1)? {
7936 DimIdx::All | DimIdx::Indices(_) => Ok(Value::Complex(*re, *im)),
7937 }
7938 }
7939 Value::ComplexMatrix(m) => {
7940 let total = m.nrows() * m.ncols();
7941 let _owned_env;
7942 let env1: &Env = if contains_end(&args[0]) {
7943 _owned_env = env_with_end(env, total);
7944 &_owned_env
7945 } else {
7946 env
7947 };
7948 match resolve_dim(&args[0], total, env1)? {
7949 DimIdx::All => {
7950 let mut flat: Vec<Complex<f64>> = Vec::with_capacity(total);
7952 for col in 0..m.ncols() {
7953 for row in 0..m.nrows() {
7954 flat.push(m[[row, col]]);
7955 }
7956 }
7957 Ok(Value::ComplexMatrix(
7958 Array2::from_shape_vec((total, 1), flat).unwrap(),
7959 ))
7960 }
7961 DimIdx::Indices(idxs) => {
7962 let nrows = m.nrows();
7963 let ncols_m = m.ncols();
7964 let vals: Result<Vec<Complex<f64>>, String> = idxs
7965 .iter()
7966 .map(|&i| {
7967 let row = i % nrows;
7968 let col = i / nrows;
7969 if col >= ncols_m {
7970 Err(format!("Index {} out of range (1..{})", i + 1, total))
7971 } else {
7972 Ok(m[[row, col]])
7973 }
7974 })
7975 .collect();
7976 let vals = vals?;
7977 if vals.len() == 1 {
7978 let c = vals[0];
7979 Ok(make_complex(c.re, c.im))
7980 } else {
7981 let n = vals.len();
7982 Ok(Value::ComplexMatrix(
7983 Array2::from_shape_vec((1, n), vals).unwrap(),
7984 ))
7985 }
7986 }
7987 }
7988 }
7989 Value::Matrix(m) => {
7990 let total = m.nrows() * m.ncols();
7991 let _owned_env;
7992 let env1: &Env = if contains_end(&args[0]) {
7993 _owned_env = env_with_end(env, total);
7994 &_owned_env
7995 } else {
7996 env
7997 };
7998 match resolve_dim(&args[0], total, env1)? {
7999 DimIdx::All => {
8000 let mut flat = Vec::with_capacity(total);
8002 for col in 0..m.ncols() {
8003 for row in 0..m.nrows() {
8004 flat.push(m[[row, col]]);
8005 }
8006 }
8007 Ok(Value::Matrix(
8008 Array2::from_shape_vec((total, 1), flat).unwrap(),
8009 ))
8010 }
8011 DimIdx::Indices(idxs) => {
8012 let nrows = m.nrows();
8014 let ncols_m = m.ncols();
8015 let vals: Result<Vec<f64>, String> = idxs
8016 .iter()
8017 .map(|&i| {
8018 let row = i % nrows;
8020 let col = i / nrows;
8021 if col >= ncols_m {
8022 Err(format!("Index {} out of range (1..{})", i + 1, total))
8023 } else {
8024 Ok(m[[row, col]])
8025 }
8026 })
8027 .collect();
8028 let vals = vals?;
8029 if vals.len() == 1 {
8030 Ok(Value::Scalar(vals[0]))
8031 } else {
8032 let n = vals.len();
8033 Ok(Value::Matrix(Array2::from_shape_vec((1, n), vals).unwrap()))
8034 }
8035 }
8036 }
8037 }
8038 Value::Str(s) => {
8039 let chars: Vec<char> = s.chars().collect();
8041 let total = chars.len();
8042 let _owned_env;
8043 let env1: &Env = if contains_end(&args[0]) {
8044 _owned_env = env_with_end(env, total);
8045 &_owned_env
8046 } else {
8047 env
8048 };
8049 match resolve_dim(&args[0], total, env1)? {
8050 DimIdx::All => {
8051 let codes: Vec<f64> = chars.iter().map(|&c| c as u32 as f64).collect();
8052 if codes.len() == 1 {
8053 Ok(Value::Scalar(codes[0]))
8054 } else {
8055 let n = codes.len();
8056 Ok(Value::Matrix(
8057 Array2::from_shape_vec((1, n), codes).unwrap(),
8058 ))
8059 }
8060 }
8061 DimIdx::Indices(idxs) => {
8062 let mut selected = String::new();
8063 for &i in &idxs {
8064 if i >= chars.len() {
8065 return Err(format!("Index {} out of range", i + 1));
8066 }
8067 selected.push(chars[i]);
8068 }
8069 if selected.chars().count() == 1 {
8070 Ok(Value::Scalar(selected.chars().next().unwrap() as u32 as f64))
8071 } else {
8072 Ok(Value::Str(selected))
8073 }
8074 }
8075 }
8076 }
8077 Value::StringObj(s) => {
8078 let _owned_env;
8080 let env1: &Env = if contains_end(&args[0]) {
8081 _owned_env = env_with_end(env, 1);
8082 &_owned_env
8083 } else {
8084 env
8085 };
8086 match resolve_dim(&args[0], 1, env1)? {
8087 DimIdx::All | DimIdx::Indices(_) => Ok(Value::StringObj(s.clone())),
8088 }
8089 }
8090 Value::DateTimeArray(v) => {
8091 let total = v.len();
8092 let _owned_env;
8093 let env1: &Env = if contains_end(&args[0]) {
8094 _owned_env = env_with_end(env, total);
8095 &_owned_env
8096 } else {
8097 env
8098 };
8099 match resolve_dim(&args[0], total, env1)? {
8100 DimIdx::All => Ok(Value::DateTimeArray(v.clone())),
8101 DimIdx::Indices(idxs) => {
8102 if idxs.len() == 1 {
8103 let i = idxs[0];
8104 if i >= total {
8105 return Err(format!(
8106 "Index {} out of range (1..{})",
8107 i + 1,
8108 total
8109 ));
8110 }
8111 Ok(Value::DateTime(v[i]))
8112 } else {
8113 let mut sel = Vec::with_capacity(idxs.len());
8114 for &i in &idxs {
8115 if i >= total {
8116 return Err(format!(
8117 "Index {} out of range (1..{})",
8118 i + 1,
8119 total
8120 ));
8121 }
8122 sel.push(v[i]);
8123 }
8124 Ok(Value::DateTimeArray(sel))
8125 }
8126 }
8127 }
8128 }
8129 Value::DurationArray(v) => {
8130 let total = v.len();
8131 let _owned_env;
8132 let env1: &Env = if contains_end(&args[0]) {
8133 _owned_env = env_with_end(env, total);
8134 &_owned_env
8135 } else {
8136 env
8137 };
8138 match resolve_dim(&args[0], total, env1)? {
8139 DimIdx::All => Ok(Value::DurationArray(v.clone())),
8140 DimIdx::Indices(idxs) => {
8141 if idxs.len() == 1 {
8142 let i = idxs[0];
8143 if i >= total {
8144 return Err(format!(
8145 "Index {} out of range (1..{})",
8146 i + 1,
8147 total
8148 ));
8149 }
8150 Ok(Value::Duration(v[i]))
8151 } else {
8152 let mut sel = Vec::with_capacity(idxs.len());
8153 for &i in &idxs {
8154 if i >= total {
8155 return Err(format!(
8156 "Index {} out of range (1..{})",
8157 i + 1,
8158 total
8159 ));
8160 }
8161 sel.push(v[i]);
8162 }
8163 Ok(Value::DurationArray(sel))
8164 }
8165 }
8166 }
8167 }
8168 Value::DateTime(_) | Value::Duration(_) => {
8169 let _owned_env;
8171 let env1: &Env = if contains_end(&args[0]) {
8172 _owned_env = env_with_end(env, 1);
8173 &_owned_env
8174 } else {
8175 env
8176 };
8177 match resolve_dim(&args[0], 1, env1)? {
8178 DimIdx::All | DimIdx::Indices(_) => Ok(val.clone()),
8179 }
8180 }
8181 }
8182 }
8183 2 => {
8184 if matches!(
8186 val,
8187 Value::Void
8188 | Value::Str(_)
8189 | Value::StringObj(_)
8190 | Value::Lambda(_)
8191 | Value::Function { .. }
8192 | Value::Tuple(_)
8193 | Value::Cell(_)
8194 | Value::Struct(_)
8195 | Value::StructArray(_)
8196 | Value::DateTime(_)
8197 | Value::Duration(_)
8198 | Value::DateTimeArray(_)
8199 | Value::DurationArray(_)
8200 ) {
8201 return Err("2D indexing not supported for this type".to_string());
8202 }
8203 let (nrows, ncols) = match val {
8205 Value::Scalar(_) | Value::Complex(_, _) => (1, 1),
8206 Value::Matrix(m) => (m.nrows(), m.ncols()),
8207 Value::ComplexMatrix(m) => (m.nrows(), m.ncols()),
8208 _ => unreachable!(),
8209 };
8210 let _owned_r;
8211 let env_r: &Env = if contains_end(&args[0]) {
8212 _owned_r = env_with_end(env, nrows);
8213 &_owned_r
8214 } else {
8215 env
8216 };
8217 let _owned_c;
8218 let env_c: &Env = if contains_end(&args[1]) {
8219 _owned_c = env_with_end(env, ncols);
8220 &_owned_c
8221 } else {
8222 env
8223 };
8224 let row_idx = resolve_dim(&args[0], nrows, env_r)?;
8225 let col_idx = resolve_dim(&args[1], ncols, env_c)?;
8226
8227 let rows: Vec<usize> = match row_idx {
8228 DimIdx::All => (0..nrows).collect(),
8229 DimIdx::Indices(v) => v,
8230 };
8231 let cols: Vec<usize> = match col_idx {
8232 DimIdx::All => (0..ncols).collect(),
8233 DimIdx::Indices(v) => v,
8234 };
8235
8236 if rows.len() == 1 && cols.len() == 1 {
8237 match val {
8238 Value::Scalar(n) => Ok(Value::Scalar(*n)),
8239 Value::Complex(re, im) => Ok(Value::Complex(*re, *im)),
8240 Value::Matrix(m) => Ok(Value::Scalar(m[[rows[0], cols[0]]])),
8241 Value::ComplexMatrix(m) => {
8242 let c = m[[rows[0], cols[0]]];
8243 Ok(make_complex(c.re, c.im))
8244 }
8245 _ => unreachable!(),
8246 }
8247 } else {
8248 let out_r = rows.len();
8249 let out_c = cols.len();
8250 match val {
8251 Value::ComplexMatrix(m) => {
8252 let flat: Vec<Complex<f64>> = rows
8253 .iter()
8254 .flat_map(|&r| cols.iter().map(move |&c| m[[r, c]]))
8255 .collect();
8256 Ok(Value::ComplexMatrix(
8257 Array2::from_shape_vec((out_r, out_c), flat).unwrap(),
8258 ))
8259 }
8260 _ => {
8261 let flat: Vec<f64> = rows
8262 .iter()
8263 .flat_map(|&r| {
8264 cols.iter().map(move |&c| match val {
8265 Value::Scalar(n) => *n,
8266 Value::Complex(re, _) => *re,
8267 Value::Matrix(m) => m[[r, c]],
8268 _ => unreachable!(),
8269 })
8270 })
8271 .collect();
8272 Ok(Value::Matrix(
8273 Array2::from_shape_vec((out_r, out_c), flat).unwrap(),
8274 ))
8275 }
8276 }
8277 }
8278 }
8279 n => Err(format!(
8280 "Indexing with {n} indices is not supported (max 2)"
8281 )),
8282 }
8283}
8284
8285enum DimIdx {
8287 All,
8288 Indices(Vec<usize>),
8289}
8290
8291fn resolve_dim(expr: &Expr, dim_size: usize, env: &Env) -> Result<DimIdx, String> {
8297 if matches!(expr, Expr::Colon) {
8298 return Ok(DimIdx::All);
8299 }
8300 let val = eval(expr, env)?;
8301 let floats: Vec<f64> = match val {
8302 Value::Void => {
8303 return Err("Index must be numeric, not void".to_string());
8304 }
8305 Value::Scalar(n) => vec![n],
8306 Value::Complex(re, im) => {
8307 if im != 0.0 {
8308 return Err("Index must be real, not complex".to_string());
8309 }
8310 vec![re]
8311 }
8312 Value::Matrix(m) => {
8313 let total = m.nrows() * m.ncols();
8315 if m.nrows() > 1 && m.ncols() > 1 && total != dim_size {
8316 return Err("Index must be a scalar or vector, not a matrix".to_string());
8317 }
8318 if m.nrows() > 1 && m.ncols() > 1 {
8320 let mut v = Vec::with_capacity(total);
8321 for col in 0..m.ncols() {
8322 for row in 0..m.nrows() {
8323 v.push(m[[row, col]]);
8324 }
8325 }
8326 v
8327 } else {
8328 m.iter().copied().collect()
8329 }
8330 }
8331 Value::Str(_) | Value::StringObj(_) => {
8332 return Err("Index must be numeric, not a string".to_string());
8333 }
8334 Value::ComplexMatrix(_) => {
8335 return Err("Index must be real, not a complex matrix".to_string());
8336 }
8337 Value::Lambda(_)
8338 | Value::Function { .. }
8339 | Value::Tuple(_)
8340 | Value::Cell(_)
8341 | Value::Struct(_)
8342 | Value::StructArray(_)
8343 | Value::DateTime(_)
8344 | Value::Duration(_)
8345 | Value::DateTimeArray(_)
8346 | Value::DurationArray(_) => {
8347 return Err("Index must be numeric, not a function or datetime".to_string());
8348 }
8349 };
8350 if dim_size > 0 && floats.len() == dim_size && floats.iter().all(|&f| f == 0.0 || f == 1.0) {
8352 let idxs: Vec<usize> = floats
8353 .iter()
8354 .enumerate()
8355 .filter(|&(_, &f)| f == 1.0)
8356 .map(|(i, _)| i)
8357 .collect();
8358 return Ok(DimIdx::Indices(idxs));
8359 }
8360 let mut idxs = Vec::with_capacity(floats.len());
8361 for n in floats {
8362 let i = n.round() as i64;
8363 if i < 1 || i as usize > dim_size {
8364 return Err(format!("Index {i} out of range (1..{dim_size})"));
8365 }
8366 idxs.push(i as usize - 1);
8367 }
8368 Ok(DimIdx::Indices(idxs))
8369}
8370
8371pub fn format_number(n: f64) -> String {
8375 if n.fract() == 0.0 && n.abs() < 1e15 {
8376 format!("{}", n as i64)
8377 } else if n != 0.0 && (n.abs() >= 1e15 || n.abs() < 1e-9) {
8378 trim_sci(&format!("{:.15e}", n))
8379 } else {
8380 let s = format!("{:.10}", n);
8381 s.trim_end_matches('0').trim_end_matches('.').to_string()
8382 }
8383}
8384
8385pub fn format_scalar(n: f64, base: Base, mode: &FormatMode) -> String {
8387 if matches!(mode, FormatMode::Hex) {
8389 return format_decimal(n, mode);
8390 }
8391 match base {
8392 Base::Dec => format_decimal(n, mode),
8393 _ => format_non_dec(n, base),
8394 }
8395}
8396
8397pub fn format_complex(re: f64, im: f64, mode: &FormatMode) -> String {
8403 if im == 0.0 {
8404 return format_decimal(re, mode);
8405 }
8406 let im_abs = im.abs();
8407 let im_str = if im_abs == 1.0 {
8408 String::new()
8409 } else {
8410 format_decimal(im_abs, mode)
8411 };
8412 if re == 0.0 {
8413 if im < 0.0 {
8414 format!("-{}i", im_str)
8415 } else {
8416 format!("{}i", im_str)
8417 }
8418 } else {
8419 let re_str = format_decimal(re, mode);
8420 if im < 0.0 {
8421 format!("{} - {}i", re_str, im_str)
8422 } else {
8423 format!("{} + {}i", re_str, im_str)
8424 }
8425 }
8426}
8427
8428pub fn expr_to_string(e: &Expr) -> String {
8433 match e {
8434 Expr::Number(n) => {
8435 if n.is_nan() {
8436 "nan".to_string()
8437 } else if n.is_infinite() {
8438 if *n > 0.0 {
8439 "inf".to_string()
8440 } else {
8441 "-inf".to_string()
8442 }
8443 } else {
8444 format!("{n}")
8445 }
8446 }
8447 Expr::Var(name) => name.clone(),
8448 Expr::UnaryMinus(e) => format!("-{}", expr_to_string(e)),
8449 Expr::UnaryNot(e) => format!("~{}", expr_to_string(e)),
8450 Expr::BinOp(l, op, r) => {
8451 let op_str = match op {
8452 Op::Add => "+",
8453 Op::Sub => "-",
8454 Op::Mul => "*",
8455 Op::Div => "/",
8456 Op::Pow => "^",
8457 Op::ElemMul => ".*",
8458 Op::ElemDiv => "./",
8459 Op::ElemPow => ".^",
8460 Op::Eq => "==",
8461 Op::NotEq => "~=",
8462 Op::Lt => "<",
8463 Op::Gt => ">",
8464 Op::LtEq => "<=",
8465 Op::GtEq => ">=",
8466 Op::And => "&&",
8467 Op::Or => "||",
8468 Op::ElemAnd => "&",
8469 Op::ElemOr => "|",
8470 Op::LDiv => "\\",
8471 };
8472 format!("{} {op_str} {}", expr_to_string(l), expr_to_string(r))
8473 }
8474 Expr::Call(name, args) => {
8475 let args_str = args
8476 .iter()
8477 .map(expr_to_string)
8478 .collect::<Vec<_>>()
8479 .join(", ");
8480 format!("{name}({args_str})")
8481 }
8482 Expr::Transpose(e) => format!("{}'", expr_to_string(e)),
8483 Expr::PlainTranspose(e) => format!("{}.'", expr_to_string(e)),
8484 Expr::Range(start, step, stop) => {
8485 if let Some(step) = step {
8486 format!(
8487 "{}:{}:{}",
8488 expr_to_string(start),
8489 expr_to_string(step),
8490 expr_to_string(stop)
8491 )
8492 } else {
8493 format!("{}:{}", expr_to_string(start), expr_to_string(stop))
8494 }
8495 }
8496 Expr::StrLiteral(s) => format!("'{s}'"),
8497 Expr::StringObjLiteral(s) => format!("\"{s}\""),
8498 Expr::Lambda { params, body, .. } => {
8499 format!("@({}) {}", params.join(", "), expr_to_string(body))
8500 }
8501 Expr::FuncHandle(name) => format!("@{name}"),
8502 Expr::Matrix(_) => "[...]".to_string(),
8503 Expr::CellLiteral(_) => "{...}".to_string(),
8504 Expr::CellIndex(e, i) => format!("{}{{{}}}", expr_to_string(e), expr_to_string(i)),
8505 Expr::Colon => ":".to_string(),
8506 Expr::NaT => "NaT".to_string(),
8507 Expr::FieldGet(base, field) => format!("{}.{field}", expr_to_string(base)),
8508 Expr::DotCall(segs, args) => {
8509 let args_str = args
8510 .iter()
8511 .map(expr_to_string)
8512 .collect::<Vec<_>>()
8513 .join(", ");
8514 format!("{}({args_str})", segs.join("."))
8515 }
8516 }
8517}
8518
8519pub fn format_value(v: &Value, base: Base, mode: &FormatMode) -> String {
8521 match v {
8522 Value::Void => String::new(),
8523 Value::Scalar(n) => format_scalar(*n, base, mode),
8524 Value::Matrix(m) => format!("[{}x{} double]", m.nrows(), m.ncols()),
8525 Value::ComplexMatrix(m) => format!("[{}×{} complex]", m.nrows(), m.ncols()),
8526 Value::Complex(re, im) => format_complex(*re, *im, mode),
8527 Value::Str(s) => s.clone(),
8528 Value::StringObj(s) => s.clone(),
8529 Value::Lambda(lf) => lf.1.clone(),
8530 Value::Function {
8531 params, outputs, ..
8532 } => {
8533 let params_str = params.join(", ");
8534 let out_str = match outputs.len() {
8535 0 => String::new(),
8536 1 => format!("{} = ", outputs[0]),
8537 _ => format!("[{}] = ", outputs.join(", ")),
8538 };
8539 format!("@function {out_str}f({params_str})")
8540 }
8541 Value::Tuple(vals) => {
8542 let parts: Vec<String> = vals.iter().map(|v| format_value(v, base, mode)).collect();
8543 format!("({})", parts.join(", "))
8544 }
8545 Value::Cell(v) => format!("{{1×{} cell}}", v.len()),
8546 Value::Struct(_) => "[1×1 struct]".to_string(),
8547 Value::StructArray(arr) => format!("[1×{} struct]", arr.len()),
8548 Value::DateTime(ts) => crate::datetime::format_datetime(*ts),
8549 Value::Duration(s) => crate::datetime::format_duration(*s),
8550 Value::DateTimeArray(v) => format!("[{}×1 datetime]", v.len()),
8551 Value::DurationArray(v) => format!("[{}×1 duration]", v.len()),
8552 }
8553}
8554
8555pub fn format_value_full(v: &Value, mode: &FormatMode) -> Option<String> {
8558 match v {
8559 Value::Void
8560 | Value::Scalar(_)
8561 | Value::Complex(_, _)
8562 | Value::Str(_)
8563 | Value::StringObj(_)
8564 | Value::Lambda(_)
8565 | Value::Function { .. }
8566 | Value::Tuple(_)
8567 | Value::DateTime(_)
8568 | Value::Duration(_) => None,
8569 Value::Matrix(m) => Some(format_matrix(m, mode)),
8570 Value::ComplexMatrix(m) => Some(format_complex_matrix(m, mode)),
8571 Value::Cell(elems) => Some(format_cell(elems, mode)),
8572 Value::Struct(map) => Some(format_struct(map, mode)),
8573 Value::StructArray(arr) => Some(format_struct_array(arr, mode)),
8574 Value::DateTimeArray(v) => Some(format_datetime_array(v)),
8575 Value::DurationArray(v) => Some(format_duration_array(v)),
8576 }
8577}
8578
8579fn format_cell(elems: &[Value], mode: &FormatMode) -> String {
8581 if elems.is_empty() {
8582 return " {}".to_string();
8583 }
8584 let mut lines = vec![" {".to_string()];
8585 for (i, val) in elems.iter().enumerate() {
8586 let label = format!(" [1,{}]", i + 1);
8587 match val {
8588 Value::Matrix(_) => {
8589 lines.push(format!("{label}:"));
8590 if let Some(full) = format_value_full(val, mode) {
8591 for line in full.lines() {
8592 lines.push(format!(" {line}"));
8593 }
8594 }
8595 }
8596 Value::Cell(_) => {
8597 lines.push(format!("{label}: {}", format_value(val, Base::Dec, mode)));
8598 }
8599 _ => {
8600 lines.push(format!("{label}: {}", format_value(val, Base::Dec, mode)));
8601 }
8602 }
8603 }
8604 lines.push(" }".to_string());
8605 lines.join("\n")
8606}
8607
8608fn format_struct(map: &IndexMap<String, Value>, mode: &FormatMode) -> String {
8610 let mut lines = vec![
8611 String::new(),
8612 " struct with fields:".to_string(),
8613 String::new(),
8614 ];
8615 for (key, val) in map {
8616 let val_str = match val {
8617 Value::Struct(_) => "[1×1 struct]".to_string(),
8618 Value::StructArray(arr) => format!("[1×{} struct]", arr.len()),
8619 Value::Matrix(m) => format!("[{}×{} double]", m.nrows(), m.ncols()),
8620 Value::Cell(v) => format!("{{1×{} cell}}", v.len()),
8621 _ => format_value(val, Base::Dec, mode),
8622 };
8623 lines.push(format!(" {key}: {val_str}"));
8624 }
8625 lines.join("\n")
8626}
8627
8628fn format_struct_array(arr: &[IndexMap<String, Value>], mode: &FormatMode) -> String {
8630 let n = arr.len();
8631 let mut lines = vec![
8632 String::new(),
8633 format!(" 1×{n} struct array with fields:"),
8634 String::new(),
8635 ];
8636 if let Some(first) = arr.first() {
8638 for key in first.keys() {
8639 lines.push(format!(" {key}"));
8640 }
8641 }
8642 if n == 1
8644 && let Some(first) = arr.first()
8645 {
8646 lines.clear();
8647 lines.push(String::new());
8648 lines.push(" struct with fields:".to_string());
8649 lines.push(String::new());
8650 for (key, val) in first {
8651 let val_str = match val {
8652 Value::Struct(_) => "[1×1 struct]".to_string(),
8653 Value::StructArray(a) => format!("[1×{} struct]", a.len()),
8654 Value::Matrix(m) => format!("[{}×{} double]", m.nrows(), m.ncols()),
8655 Value::Cell(v) => format!("{{1×{} cell}}", v.len()),
8656 _ => format_value(val, Base::Dec, mode),
8657 };
8658 lines.push(format!(" {key}: {val_str}"));
8659 }
8660 }
8661 lines.join("\n")
8662}
8663
8664fn format_datetime_array(v: &[f64]) -> String {
8665 let mut lines = Vec::with_capacity(v.len());
8666 for ts in v {
8667 lines.push(format!(" {}", crate::datetime::format_datetime(*ts)));
8668 }
8669 lines.join("\n")
8670}
8671
8672fn format_duration_array(v: &[f64]) -> String {
8673 let mut lines = Vec::with_capacity(v.len());
8674 for secs in v {
8675 lines.push(format!(" {}", crate::datetime::format_duration(*secs)));
8676 }
8677 lines.join("\n")
8678}
8679
8680fn format_complex_matrix(m: &Array2<Complex<f64>>, mode: &FormatMode) -> String {
8683 if m.nrows() == 0 || m.ncols() == 0 {
8684 return " []".to_string();
8685 }
8686 let ncols = m.ncols();
8687
8688 let parts: Vec<Vec<(String, &'static str, String)>> = m
8692 .rows()
8693 .into_iter()
8694 .map(|row| {
8695 row.iter()
8696 .map(|c| {
8697 let re_str = format_decimal(c.re, mode);
8698 let im_abs = format_decimal(c.im.abs(), mode);
8699 let sign = if c.im < 0.0 { " - " } else { " + " };
8700 (re_str, sign, im_abs)
8701 })
8702 .collect()
8703 })
8704 .collect();
8705
8706 let re_widths: Vec<usize> = (0..ncols)
8708 .map(|c| parts.iter().map(|row| row[c].0.len()).max().unwrap_or(0))
8709 .collect();
8710 let im_widths: Vec<usize> = (0..ncols)
8711 .map(|c| parts.iter().map(|row| row[c].2.len()).max().unwrap_or(0))
8712 .collect();
8713
8714 let mut lines = Vec::new();
8715 for row in &parts {
8716 let mut line = String::from(" ");
8717 for (c, (re_str, sign, im_str)) in row.iter().enumerate() {
8718 if c > 0 {
8719 let prev_im_pad = im_widths[c - 1].saturating_sub(row[c - 1].2.len());
8722 for _ in 0..prev_im_pad {
8723 line.push(' ');
8724 }
8725 line.push_str(" ");
8726 }
8727 let re_pad = re_widths[c].saturating_sub(re_str.len());
8728 for _ in 0..re_pad {
8729 line.push(' ');
8730 }
8731 line.push_str(re_str);
8732 line.push_str(sign);
8733 line.push_str(im_str);
8734 line.push('i');
8735 }
8736 lines.push(line);
8737 }
8738 lines.join("\n")
8739}
8740
8741fn format_matrix(m: &Array2<f64>, mode: &FormatMode) -> String {
8744 if m.nrows() == 0 || m.ncols() == 0 {
8745 return " []".to_string();
8746 }
8747 if matches!(mode, FormatMode::Plus) {
8749 let lines: Vec<String> = m
8750 .rows()
8751 .into_iter()
8752 .map(|row| {
8753 let chars: String = row
8754 .iter()
8755 .map(|&x| {
8756 if x > 0.0 {
8757 '+'
8758 } else if x < 0.0 {
8759 '-'
8760 } else {
8761 '0'
8762 }
8763 })
8764 .collect();
8765 format!(" {}", chars)
8766 })
8767 .collect();
8768 return lines.join("\n");
8769 }
8770 let ncols = m.ncols();
8771 let cells: Vec<Vec<String>> = m
8772 .rows()
8773 .into_iter()
8774 .map(|row| row.iter().map(|&x| format_decimal(x, mode)).collect())
8775 .collect();
8776 let col_widths: Vec<usize> = (0..ncols)
8777 .map(|c| cells.iter().map(|row| row[c].len()).max().unwrap_or(0))
8778 .collect();
8779 let mut lines = Vec::new();
8780 for row in &cells {
8781 let mut line = String::from(" ");
8782 for (c, cell) in row.iter().enumerate() {
8783 if c > 0 {
8784 line.push_str(" ");
8785 }
8786 let pad = col_widths[c].saturating_sub(cell.len());
8787 for _ in 0..pad {
8788 line.push(' ');
8789 }
8790 line.push_str(cell);
8791 }
8792 lines.push(line);
8793 }
8794 lines.join("\n")
8795}
8796
8797pub fn format_non_dec(n: f64, base: Base) -> String {
8800 let i = n.round() as i64;
8801 let u = i.unsigned_abs();
8802 let sign = if i < 0 { "-" } else { "" };
8803 match base {
8804 Base::Hex => format!("{}0x{:X}", sign, u),
8805 Base::Bin => format!("{}0b{:b}", sign, u),
8806 Base::Oct => format!("{}0o{:o}", sign, u),
8807 Base::Dec => format_decimal(n, &FormatMode::default()),
8808 }
8809}
8810
8811fn format_decimal(n: f64, mode: &FormatMode) -> String {
8816 if n.is_nan() {
8817 return "NaN".to_string();
8818 }
8819 if n.is_infinite() {
8820 return if n > 0.0 { "Inf" } else { "-Inf" }.to_string();
8821 }
8822 match mode {
8823 FormatMode::Short | FormatMode::ShortG => fmt_auto_sig(n, 5),
8824 FormatMode::Long | FormatMode::LongG => fmt_auto_sig(n, 15),
8825 FormatMode::ShortE => fmt_sci_dp(n, 4),
8826 FormatMode::LongE => fmt_sci_dp(n, 14),
8827 FormatMode::Bank => format!("{:.2}", n),
8828 FormatMode::Rat => fmt_rat(n),
8829 FormatMode::Hex => fmt_hex_ieee754(n),
8830 FormatMode::Plus => fmt_plus_sign(n),
8831 FormatMode::Custom(prec) => fmt_custom_prec(n, *prec),
8832 }
8833}
8834
8835#[inline]
8837fn is_exact_int(n: f64) -> bool {
8838 n.fract() == 0.0 && n.abs() < 1e15
8839}
8840
8841fn fmt_auto_sig(n: f64, sig: usize) -> String {
8845 if is_exact_int(n) {
8846 return format!("{}", n as i64);
8847 }
8848 let abs_n = n.abs();
8849 let exp = if abs_n == 0.0 {
8850 0i32
8851 } else {
8852 abs_n.log10().floor() as i32
8853 };
8854 if exp >= -3 && exp < sig as i32 {
8855 let dp = (sig as i32 - 1 - exp) as usize;
8856 let s = format!("{:.prec$}", n, prec = dp);
8857 if s.contains('.') {
8859 s.trim_end_matches('0').trim_end_matches('.').to_string()
8860 } else {
8861 s
8862 }
8863 } else {
8864 let s = format!("{:.prec$e}", n, prec = sig - 1);
8865 trim_sci(&s)
8866 }
8867}
8868
8869fn fmt_sci_dp(n: f64, dp: usize) -> String {
8871 let s = format!("{:.prec$e}", n, prec = dp);
8872 trim_sci(&s)
8873}
8874
8875fn fmt_custom_prec(n: f64, prec: usize) -> String {
8877 if is_exact_int(n) {
8878 return format!("{}", n as i64);
8879 }
8880 if n.abs() >= 1e15 || (n != 0.0 && n.abs() < 1e-9) {
8881 let s = format!("{:.prec$e}", n, prec = prec);
8882 trim_sci(&s)
8883 } else {
8884 let s = format!("{:.prec$}", n, prec = prec);
8885 s.trim_end_matches('0').trim_end_matches('.').to_string()
8886 }
8887}
8888
8889fn fmt_rat(n: f64) -> String {
8891 if is_exact_int(n) {
8892 return format!("{}", n as i64);
8893 }
8894 let sign = if n < 0.0 { -1i64 } else { 1i64 };
8895 let x = n.abs();
8896 let (mut h1, mut h2): (i64, i64) = (1, 0);
8897 let (mut k1, mut k2): (i64, i64) = (0, 1);
8898 let mut b = x;
8899 for _ in 0..64 {
8900 let a = b.floor() as i64;
8901 let (nh, nk) = (a * h1 + h2, a * k1 + k2);
8902 if nk > 10_000 {
8903 break;
8904 }
8905 h2 = h1;
8906 h1 = nh;
8907 k2 = k1;
8908 k1 = nk;
8909 let frac = b - a as f64;
8910 if frac < 1e-12 || (h1 as f64 / k1 as f64 - x).abs() < 1e-6 {
8911 break;
8912 }
8913 b = 1.0 / frac;
8914 }
8915 let p = sign * h1;
8916 if k1 == 1 {
8917 format!("{}", p)
8918 } else {
8919 format!("{}/{}", p, k1)
8920 }
8921}
8922
8923fn fmt_hex_ieee754(n: f64) -> String {
8925 format!("{:016X}", n.to_bits())
8926}
8927
8928fn fmt_plus_sign(n: f64) -> String {
8930 if n > 0.0 {
8931 "+".to_string()
8932 } else if n < 0.0 {
8933 "-".to_string()
8934 } else {
8935 " ".to_string()
8936 }
8937}
8938
8939fn trim_sci(s: &str) -> String {
8940 if let Some(e_pos) = s.find('e') {
8941 let mantissa = s[..e_pos].trim_end_matches('0').trim_end_matches('.');
8942 let exp_str = &s[e_pos + 1..];
8943 let (sign, digits) = if let Some(d) = exp_str.strip_prefix('-') {
8944 ("-", d)
8945 } else if let Some(d) = exp_str.strip_prefix('+') {
8946 ("+", d)
8947 } else {
8948 ("+", exp_str)
8949 };
8950 let exp_num: i32 = digits.parse().unwrap_or(0);
8951 format!("{}e{}{:02}", mantissa, sign, exp_num)
8952 } else {
8953 s.to_string()
8954 }
8955}
8956
8957pub fn load_mat_file(path: &str) -> Result<Value, String> {
8963 load_mat_file_impl(path)
8964}
8965
8966#[cfg(feature = "mat")]
8967fn load_mat_file_impl(path: &str) -> Result<Value, String> {
8968 crate::mat::mat_load(path)
8969}
8970
8971#[cfg(not(feature = "mat"))]
8972fn load_mat_file_impl(_path: &str) -> Result<Value, String> {
8973 Err("load: .mat support not available — rebuild with --features mat".to_string())
8974}
8975
8976#[cfg(feature = "regex")]
8979fn regexp_impl(
8980 fname: &str,
8981 s: &str,
8982 pat: &str,
8983 ignore_case: bool,
8984 return_match: bool,
8985) -> Result<Value, String> {
8986 use ndarray::Array2;
8987 let full_pat = if ignore_case {
8988 format!("(?i){pat}")
8989 } else {
8990 pat.to_string()
8991 };
8992 let re = regex::Regex::new(&full_pat).map_err(|e| format!("{fname}: invalid pattern: {e}"))?;
8993 if return_match {
8994 let matches: Vec<Value> = re
8995 .find_iter(s)
8996 .map(|m| Value::Str(m.as_str().to_string()))
8997 .collect();
8998 Ok(Value::Cell(matches))
8999 } else {
9000 match re.find(s) {
9001 Some(m) => Ok(Value::Scalar((s[..m.start()].chars().count() + 1) as f64)),
9002 None => Ok(Value::Matrix(Array2::zeros((0, 0)))),
9003 }
9004 }
9005}
9006
9007#[cfg(not(feature = "regex"))]
9008fn regexp_impl(
9009 fname: &str,
9010 _s: &str,
9011 _pat: &str,
9012 _ignore_case: bool,
9013 _return_match: bool,
9014) -> Result<Value, String> {
9015 Err(format!(
9016 "{fname}: not available — rebuild with --features regex"
9017 ))
9018}
9019
9020#[cfg(feature = "regex")]
9021fn regexprep_impl(s: &str, pat: &str, rep: &str) -> Result<Value, String> {
9022 let re = regex::Regex::new(pat).map_err(|e| format!("regexprep: invalid pattern: {e}"))?;
9023 let result = re.replace_all(s, regex::NoExpand(rep));
9024 Ok(Value::Str(result.into_owned()))
9025}
9026
9027#[cfg(not(feature = "regex"))]
9028fn regexprep_impl(_s: &str, _pat: &str, _rep: &str) -> Result<Value, String> {
9029 Err("regexprep: not available — rebuild with --features regex".to_string())
9030}
9031
9032#[cfg(feature = "fft")]
9036fn extract_real_vec(v: &Value, name: &str) -> Result<Vec<f64>, String> {
9037 match v {
9038 Value::Scalar(s) => Ok(vec![*s]),
9039 Value::Matrix(m) if m.nrows() == 1 || m.ncols() == 1 => Ok(m.iter().copied().collect()),
9040 Value::Matrix(m) => Err(format!(
9041 "{name}: input must be a vector (got {}×{} matrix)",
9042 m.nrows(),
9043 m.ncols()
9044 )),
9045 _ => Err(format!("{name}: input must be a real numeric vector")),
9046 }
9047}
9048
9049#[cfg(feature = "fft")]
9051fn complex_pairs_to_complex_matrix(data: Vec<(f64, f64)>) -> Value {
9052 let n = data.len();
9053 if n == 0 {
9054 return Value::ComplexMatrix(Array2::zeros((1, 0)));
9055 }
9056 let elems: Vec<Complex<f64>> = data
9057 .into_iter()
9058 .map(|(re, im)| Complex::new(re, im))
9059 .collect();
9060 Value::ComplexMatrix(Array2::from_shape_vec((1, n), elems).unwrap())
9061}
9062
9063#[cfg(feature = "fft")]
9065fn extract_complex_vec(v: &Value, name: &str) -> Result<Vec<(f64, f64)>, String> {
9066 match v {
9067 Value::Scalar(s) => Ok(vec![(*s, 0.0)]),
9068 Value::Matrix(m) => Ok(m.iter().copied().map(|x| (x, 0.0)).collect()),
9069 Value::ComplexMatrix(m) => Ok(m.iter().map(|c| (c.re, c.im)).collect()),
9070 Value::Cell(elems) => elems
9071 .iter()
9072 .enumerate()
9073 .map(|(i, e)| match e {
9074 Value::Complex(re, im) => Ok((*re, *im)),
9075 Value::Scalar(s) => Ok((*s, 0.0)),
9076 _ => Err(format!(
9077 "{name}: cell element {} must be a complex or real number",
9078 i + 1
9079 )),
9080 })
9081 .collect(),
9082 _ => Err(format!(
9083 "{name}: input must be a complex matrix, cell array, or numeric vector"
9084 )),
9085 }
9086}
9087
9088#[cfg(feature = "fft")]
9089fn fft_call(v: &Value, n_opt: Option<usize>) -> Result<Value, String> {
9090 let real = extract_real_vec(v, "fft")?;
9091 let n = n_opt.unwrap_or(real.len());
9092 if n == 0 {
9093 return Err("fft: length must be positive".to_string());
9094 }
9095 let out = crate::fft::fft_forward(&real, n);
9096 Ok(complex_pairs_to_complex_matrix(out))
9097}
9098
9099#[cfg(not(feature = "fft"))]
9100fn fft_call(_v: &Value, _n_opt: Option<usize>) -> Result<Value, String> {
9101 Err("fft: not available — rebuild with --features fft".to_string())
9102}
9103
9104#[cfg(feature = "fft")]
9105fn ifft_call(v: &Value) -> Result<Value, String> {
9106 let complex = extract_complex_vec(v, "ifft")?;
9107 if complex.is_empty() {
9108 return Ok(Value::Matrix(ndarray::Array2::zeros((1, 0))));
9109 }
9110 let out = crate::fft::fft_inverse(&complex);
9111 if out.iter().all(|(_, im)| im.abs() < 1e-12) {
9112 let real: Vec<f64> = out.iter().map(|(re, _)| *re).collect();
9113 let n = real.len();
9114 Ok(Value::Matrix(
9115 ndarray::Array2::from_shape_vec((1, n), real).unwrap(),
9116 ))
9117 } else {
9118 Ok(complex_pairs_to_complex_matrix(out))
9119 }
9120}
9121
9122#[cfg(not(feature = "fft"))]
9123fn ifft_call(_v: &Value) -> Result<Value, String> {
9124 Err("ifft: not available — rebuild with --features fft".to_string())
9125}
9126
9127#[cfg(feature = "json")]
9130fn jsondecode_impl(arg: &Value) -> Result<Value, String> {
9131 let s = match arg {
9132 Value::Str(s) | Value::StringObj(s) => s.as_str(),
9133 _ => return Err("jsondecode: argument must be a string".to_string()),
9134 };
9135 let jval: serde_json::Value =
9136 serde_json::from_str(s).map_err(|e| format!("jsondecode: invalid JSON: {e}"))?;
9137 Ok(crate::json::json_to_value(&jval))
9138}
9139
9140#[cfg(not(feature = "json"))]
9141fn jsondecode_impl(_arg: &Value) -> Result<Value, String> {
9142 Err("jsondecode: not available — rebuild with --features json".to_string())
9143}
9144
9145#[cfg(feature = "json")]
9146fn jsonencode_impl(arg: &Value) -> Result<Value, String> {
9147 let jval = crate::json::value_to_json(arg)?;
9148 let s = serde_json::to_string(&jval)
9149 .map_err(|e| format!("jsonencode: serialization error: {e}"))?;
9150 Ok(Value::Str(s))
9151}
9152
9153#[cfg(not(feature = "json"))]
9154fn jsonencode_impl(_arg: &Value) -> Result<Value, String> {
9155 Err("jsonencode: not available — rebuild with --features json".to_string())
9156}
9157
9158fn cpoly_eval(coeffs: &[f64], z: (f64, f64)) -> (f64, f64) {
9164 let mut acc = (0.0_f64, 0.0_f64);
9165 for &c in coeffs {
9166 acc = (acc.0 * z.0 - acc.1 * z.1 + c, acc.0 * z.1 + acc.1 * z.0);
9168 }
9169 acc
9170}
9171
9172fn horner(coeffs: &[f64], x: f64) -> f64 {
9174 coeffs.iter().fold(0.0, |acc, &c| acc * x + c)
9175}
9176
9177fn poly_coeffs(v: &Value, fname: &str) -> Result<Vec<f64>, String> {
9179 match v {
9180 Value::Scalar(s) => Ok(vec![*s]),
9181 Value::Matrix(m) => {
9182 if m.nrows() == 1 {
9183 Ok(m.row(0).iter().copied().collect())
9184 } else if m.ncols() == 1 {
9185 Ok(m.column(0).iter().copied().collect())
9186 } else {
9187 Err(format!(
9188 "{fname}: argument must be a vector, got {}×{}",
9189 m.nrows(),
9190 m.ncols()
9191 ))
9192 }
9193 }
9194 _ => Err(format!("{fname}: argument must be a real numeric vector")),
9195 }
9196}
9197
9198fn poly_conv(a: &[f64], b: &[f64]) -> Vec<f64> {
9200 if a.is_empty() || b.is_empty() {
9201 return vec![];
9202 }
9203 let mut result = vec![0.0_f64; a.len() + b.len() - 1];
9204 for (i, &ai) in a.iter().enumerate() {
9205 for (j, &bj) in b.iter().enumerate() {
9206 result[i + j] += ai * bj;
9207 }
9208 }
9209 result
9210}
9211
9212fn poly_deconv(c: &[f64], b: &[f64]) -> Result<(Vec<f64>, Vec<f64>), String> {
9217 if b.is_empty() || b.iter().all(|&x| x == 0.0) {
9218 return Err("deconv: divisor polynomial must not be zero".to_string());
9219 }
9220 let mc = c.len();
9221 let mb = b.len();
9222 if mb > mc {
9223 return Ok((vec![0.0], c.to_vec()));
9224 }
9225 let q_len = mc - mb + 1;
9226 let mut remainder = c.to_vec();
9227 let mut q = vec![0.0_f64; q_len];
9228 for i in 0..q_len {
9229 let coeff = remainder[i] / b[0];
9230 q[i] = coeff;
9231 for j in 0..mb {
9232 remainder[i + j] -= coeff * b[j];
9233 }
9234 }
9235 let scale = c.iter().map(|v| v.abs()).fold(0.0_f64, f64::max).max(1.0);
9237 for x in &mut remainder {
9238 if x.abs() < 1e-10 * scale {
9239 *x = 0.0;
9240 }
9241 }
9242 Ok((q, remainder))
9243}
9244
9245fn durand_kerner(coeffs: &[f64]) -> Result<Vec<(f64, f64)>, String> {
9251 let n = coeffs.len() - 1; if n == 0 {
9253 return Ok(vec![]);
9254 }
9255 let lc = coeffs[0];
9256 if lc == 0.0 {
9257 return Err("roots: leading coefficient must not be zero".to_string());
9258 }
9259 let monic: Vec<f64> = coeffs.iter().map(|&c| c / lc).collect();
9261
9262 let r = 1.0 + monic[1..].iter().map(|c| c.abs()).fold(0.0_f64, f64::max);
9264
9265 let mut z: Vec<(f64, f64)> = (0..n)
9268 .map(|k| {
9269 let angle = 2.0 * std::f64::consts::PI * (k as f64 + 0.25) / n as f64;
9270 (r * angle.cos(), r * angle.sin())
9271 })
9272 .collect();
9273
9274 const MAX_ITER: usize = 2000;
9275 const EPS: f64 = 1e-12;
9276
9277 for _ in 0..MAX_ITER {
9278 let z_old = z.clone();
9279 let mut max_corr = 0.0_f64;
9280 for i in 0..n {
9281 let (pre, pim) = cpoly_eval(&monic, z_old[i]);
9282 let mut dre = 1.0_f64;
9284 let mut dim = 0.0_f64;
9285 for j in 0..n {
9286 if j == i {
9287 continue;
9288 }
9289 let (dr, di) = (z_old[i].0 - z_old[j].0, z_old[i].1 - z_old[j].1);
9290 (dre, dim) = (dre * dr - dim * di, dre * di + dim * dr);
9291 }
9292 let d2 = dre * dre + dim * dim;
9294 let (cre, cim) = if d2 > 0.0 {
9295 ((pre * dre + pim * dim) / d2, (pim * dre - pre * dim) / d2)
9296 } else {
9297 (pre, pim)
9298 };
9299 let corr_abs = (cre * cre + cim * cim).sqrt();
9300 max_corr = max_corr.max(corr_abs);
9301 z[i] = (z_old[i].0 - cre, z_old[i].1 - cim);
9302 }
9303 if max_corr < EPS {
9304 break;
9305 }
9306 }
9307
9308 z.sort_by(|a, b| {
9310 b.0.partial_cmp(&a.0)
9311 .unwrap_or(std::cmp::Ordering::Equal)
9312 .then(b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal))
9313 });
9314
9315 Ok(z)
9316}
9317
9318fn roots_to_value(roots: &[(f64, f64)]) -> Value {
9323 const IMAG_TOL: f64 = 1e-9;
9324 let all_real = roots.iter().all(|(_, im)| im.abs() < IMAG_TOL);
9325 if all_real {
9326 let data: Vec<f64> = roots.iter().map(|(re, _)| *re).collect();
9327 let n = data.len();
9328 Value::Matrix(Array2::from_shape_vec((n, 1), data).unwrap())
9329 } else {
9330 let vals: Vec<Value> = roots
9331 .iter()
9332 .map(|&(re, im)| {
9333 if im.abs() < IMAG_TOL {
9334 Value::Scalar(re)
9335 } else {
9336 Value::Complex(re, im)
9337 }
9338 })
9339 .collect();
9340 Value::Cell(vals)
9341 }
9342}
9343
9344fn characteristic_poly(a: &Array2<f64>) -> Result<Vec<f64>, String> {
9349 let n = a.nrows();
9350 if a.ncols() != n {
9351 return Err("poly: matrix must be square".to_string());
9352 }
9353 if n == 0 {
9354 return Ok(vec![1.0]);
9355 }
9356 let mut coeffs = vec![0.0_f64; n + 1];
9357 coeffs[0] = 1.0;
9358 let mut nk = Array2::<f64>::eye(n); for (k, coeff) in coeffs.iter_mut().enumerate().skip(1) {
9360 let ank = a.dot(&nk); let tr: f64 = (0..n).map(|i| ank[[i, i]]).sum();
9362 let ak = -tr / k as f64;
9363 *coeff = ak;
9364 nk = ank; for i in 0..n {
9366 nk[[i, i]] += ak;
9367 }
9368 }
9369 Ok(coeffs)
9370}
9371
9372fn poly_back_sub(r: &Array2<f64>, b: &[f64]) -> Result<Vec<f64>, String> {
9374 let n = r.nrows();
9375 let mut x = vec![0.0_f64; n];
9376 for i in (0..n).rev() {
9377 let mut s = b[i];
9378 for j in (i + 1)..n {
9379 s -= r[[i, j]] * x[j];
9380 }
9381 if r[[i, i]].abs() < 1e-14 {
9382 return Err(
9383 "polyfit: Vandermonde matrix is rank-deficient; reduce polynomial degree"
9384 .to_string(),
9385 );
9386 }
9387 x[i] = s / r[[i, i]];
9388 }
9389 Ok(x)
9390}
9391
9392fn interp1_at(x: &[f64], y: &[f64], xi: f64, method: &str) -> f64 {
9396 let n = x.len();
9397 if xi < x[0] || xi > x[n - 1] {
9398 return f64::NAN;
9399 }
9400 let lo = x.partition_point(|&xk| xk <= xi).saturating_sub(1);
9402 let lo2 = lo.min(n - 2);
9404 match method {
9405 "nearest" => {
9406 if lo == n - 1 {
9407 return y[n - 1];
9408 }
9409 if (xi - x[lo2]) <= (x[lo2 + 1] - xi) {
9410 y[lo2]
9411 } else {
9412 y[lo2 + 1]
9413 }
9414 }
9415 "previous" => y[lo],
9416 "next" => {
9417 if lo == n - 1 || xi == x[lo] {
9418 y[lo]
9419 } else {
9420 y[lo2 + 1]
9421 }
9422 }
9423 _ => {
9424 if lo == n - 1 {
9426 return y[n - 1];
9427 }
9428 let t = (xi - x[lo2]) / (x[lo2 + 1] - x[lo2]);
9429 y[lo2] + t * (y[lo2 + 1] - y[lo2])
9430 }
9431 }
9432}
9433
9434#[cfg(test)]
9435#[path = "eval_tests.rs"]
9436mod tests;