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