1#![allow(clippy::needless_range_loop)]
7
8use std::fmt::Write;
9
10fn is_prime(n: u64) -> bool {
13 if n < 2 {
14 return false;
15 }
16 if n < 4 {
17 return true;
18 }
19 if n % 2 == 0 || n % 3 == 0 {
20 return false;
21 }
22 let mut i = 5u64;
23 while i * i <= n {
24 if n % i == 0 || n % (i + 2) == 0 {
25 return false;
26 }
27 i += 6;
28 }
29 true
30}
31
32fn factorize(mut n: u64) -> Vec<(u64, u32)> {
33 let mut factors: Vec<(u64, u32)> = Vec::new();
34 if n < 2 {
35 return factors;
36 }
37 for p in [2u64, 3] {
38 if n % p == 0 {
39 let mut exp = 0u32;
40 while n % p == 0 {
41 n /= p;
42 exp += 1;
43 }
44 factors.push((p, exp));
45 }
46 }
47 let mut i = 5u64;
48 while i * i <= n {
49 if n % i == 0 {
50 let mut exp = 0u32;
51 while n % i == 0 {
52 n /= i;
53 exp += 1;
54 }
55 factors.push((i, exp));
56 }
57 if n % (i + 2) == 0 {
58 let mut exp = 0u32;
59 while n % (i + 2) == 0 {
60 n /= i + 2;
61 exp += 1;
62 }
63 factors.push((i + 2, exp));
64 }
65 i += 6;
66 }
67 if n > 1 {
68 factors.push((n, 1));
69 }
70 factors
71}
72
73fn next_prime(n: u64) -> u64 {
74 let mut c = n + 1;
75 while !is_prime(c) {
76 c += 1;
77 }
78 c
79}
80
81fn prev_prime(n: u64) -> Option<u64> {
82 if n <= 2 {
83 return None;
84 }
85 let mut c = n - 1;
86 while c >= 2 {
87 if is_prime(c) {
88 return Some(c);
89 }
90 if c == 2 {
91 break;
92 }
93 c -= 1;
94 }
95 None
96}
97
98pub fn prime_info(n: u64) -> String {
99 let mut out = String::new();
100 let _ = writeln!(out, "Number: {}", n);
101 let _ = writeln!(out, "Prime: {}", if is_prime(n) { "Yes" } else { "No" });
102
103 let factors = factorize(n);
104 if factors.is_empty() {
105 let _ = writeln!(out, "Factors: 1 (or 0)");
106 } else {
107 let expr: Vec<String> = factors
108 .iter()
109 .map(|(p, e)| {
110 if *e == 1 {
111 format!("{}", p)
112 } else {
113 format!("{}^{}", p, e)
114 }
115 })
116 .collect();
117 let _ = writeln!(out, "Factors: {}", expr.join(" × "));
118
119 let mut divisors = vec![1u64];
121 for (p, e) in &factors {
122 let len = divisors.len();
123 let mut pw = 1u64;
124 for _ in 0..*e {
125 pw *= p;
126 for i in 0..len {
127 divisors.push(divisors[i] * pw);
128 }
129 }
130 }
131 divisors.sort_unstable();
132 let _ = writeln!(
133 out,
134 "Divisors ({} total): {}",
135 divisors.len(),
136 if divisors.len() <= 24 {
137 divisors
138 .iter()
139 .map(|d| d.to_string())
140 .collect::<Vec<_>>()
141 .join(", ")
142 } else {
143 format!(
144 "{} ... {} [first 12 + last 12]",
145 divisors[..12]
146 .iter()
147 .map(|d| d.to_string())
148 .collect::<Vec<_>>()
149 .join(", "),
150 divisors[divisors.len() - 12..]
151 .iter()
152 .map(|d| d.to_string())
153 .collect::<Vec<_>>()
154 .join(", ")
155 )
156 }
157 );
158 let phi = factors.iter().fold(n, |acc, (p, _)| acc / p * (p - 1));
160 let _ = writeln!(out, "φ(n): {}", phi);
161 let sigma: u64 = factors
163 .iter()
164 .map(|(p, e)| (p.pow(e + 1) - 1) / (p - 1))
165 .product();
166 let _ = writeln!(out, "σ(n): {}", sigma);
167 if sigma == 2 * n {
169 let _ = writeln!(out, "✓ Perfect number");
170 }
171 }
172
173 if let Some(pp) = prev_prime(n) {
174 let _ = writeln!(out, "Prev prime: {}", pp);
175 } else {
176 let _ = writeln!(out, "Prev prime: (none)");
177 }
178 let np = next_prime(n);
179 let _ = writeln!(out, "Next prime: {}", np);
180 out
181}
182
183pub fn generate_sequence(kind: &str, count: usize, start: f64, step: f64) -> String {
186 let count = count.clamp(1, 10_000);
187 let kind = kind.trim().to_lowercase();
188 let mut out = String::new();
189
190 let nums: Vec<f64> = match kind.as_str() {
191 "arithmetic" | "arith" | "linear" => (0..count).map(|i| start + i as f64 * step).collect(),
192 "geometric" | "geo" | "geom" => {
193 let ratio = if step == 0.0 { 2.0 } else { step };
194 let mut v = start;
195 (0..count)
196 .map(|_| {
197 let x = v;
198 v *= ratio;
199 x
200 })
201 .collect()
202 }
203 "fibonacci" | "fib" => {
204 let (mut a, mut b) = (start as u64, (start + step) as u64);
205 let mut seq = vec![a as f64, b as f64];
206 for _ in 2..count {
207 let c = a.saturating_add(b);
208 seq.push(c as f64);
209 a = b;
210 b = c;
211 }
212 seq.truncate(count);
213 seq
214 }
215 "prime" | "primes" => {
216 let mut seq = Vec::with_capacity(count);
217 let mut n: u64 = start.max(2.0) as u64;
218 if !is_prime(n) {
219 n = next_prime(n - 1);
220 }
221 while seq.len() < count {
222 seq.push(n as f64);
223 n = next_prime(n);
224 }
225 seq
226 }
227 "square" | "squares" => {
228 let s = start.max(0.0) as u64;
229 (s..s + count as u64).map(|i| (i * i) as f64).collect()
230 }
231 "triangular" | "triangle" => {
232 let s = start.max(0.0) as u64;
233 (s..s + count as u64)
234 .map(|i| (i * (i + 1) / 2) as f64)
235 .collect()
236 }
237 "cube" | "cubes" => {
238 let s = start.max(0.0) as u64;
239 (s..s + count as u64).map(|i| (i * i * i) as f64).collect()
240 }
241 "power2" | "powers-of-2" | "powers_of_2" => {
242 (0..count).map(|i| (1u64 << i.min(62)) as f64).collect()
243 }
244 _ => {
245 return format!(
246 "Unknown sequence type: '{}'\n\
247 Available: arithmetic geometric fibonacci prime square triangular cube power2\n\
248 Defaults: --seq-start 1 --seq-step 1 --seq-count 10",
249 kind
250 );
251 }
252 };
253
254 let label = match kind.as_str() {
255 "arithmetic" | "arith" | "linear" => format!("Arithmetic (start={}, step={})", start, step),
256 "geometric" | "geo" | "geom" => format!(
257 "Geometric (start={}, ratio={})",
258 start,
259 if step == 0.0 { 2.0 } else { step }
260 ),
261 _ => kind[..1].to_uppercase() + &kind[1..],
262 };
263 let _ = writeln!(out, "{}: {} terms", label, nums.len());
264 let strs: Vec<String> = nums
265 .iter()
266 .map(|x| {
267 if x.fract() == 0.0 && x.abs() < 1e15 {
268 format!("{}", *x as i64)
269 } else {
270 let s = format!("{:.6e}", x);
271 s
273 }
274 })
275 .collect();
276 let mut line = String::new();
278 for (i, s) in strs.iter().enumerate() {
279 let piece = if i == 0 {
280 s.clone()
281 } else {
282 format!(", {}", s)
283 };
284 if line.len() + piece.len() > 72 {
285 let _ = writeln!(out, "{}", line);
286 line = s.clone();
287 } else {
288 line.push_str(&piece);
289 }
290 }
291 if !line.is_empty() {
292 let _ = writeln!(out, "{}", line);
293 }
294 out
295}
296
297pub fn combinatorics(n: u64, k: u64) -> String {
300 let mut out = String::new();
301
302 let binom = if k > n {
304 0u128
305 } else {
306 let k = k.min(n - k);
307 (0..k).fold(1u128, |acc, i| acc * (n - i) as u128 / (i + 1) as u128)
308 };
309
310 let perm: u128 = if k > n {
312 0
313 } else {
314 (n - k + 1..=n).fold(1u128, |acc, i| acc.saturating_mul(i as u128))
315 };
316
317 let _ = writeln!(out, "n = {} k = {}", n, k);
318 let _ = writeln!(
319 out,
320 "C(n,k) = n! / (k!(n-k)!) = {} (combinations — order does not matter)",
321 binom
322 );
323 let _ = writeln!(
324 out,
325 "P(n,k) = n! / (n-k)! = {} (permutations — order matters)",
326 perm
327 );
328
329 if n <= 20 {
331 let row: Vec<u128> = (0..=n)
332 .map(|j| {
333 let j = j.min(n - j);
334 (0..j).fold(1u128, |acc, i| acc * (n - i) as u128 / (i + 1) as u128)
335 })
336 .collect();
337 let _ = writeln!(
338 out,
339 "Pascal row {}: {}",
340 n,
341 row.iter()
342 .map(|x| x.to_string())
343 .collect::<Vec<_>>()
344 .join(", ")
345 );
346 }
347 out
348}
349
350pub fn truth_table(expr: &str) -> String {
354 let mut vars: Vec<char> = expr
356 .chars()
357 .filter(|c| c.is_ascii_alphabetic())
358 .collect::<std::collections::HashSet<char>>()
359 .into_iter()
360 .collect();
361 vars.sort_unstable();
362
363 if vars.is_empty() {
364 return format!(
365 "No variables found in: {}\nUse single letters (A, B, C, ...) as variables.",
366 expr
367 );
368 }
369 if vars.len() > 6 {
370 return format!(
371 "Too many variables ({}). Limit: 6 (for 2^6 = 64 rows).",
372 vars.len()
373 );
374 }
375
376 let n_vars = vars.len();
377 let n_rows = 1usize << n_vars;
378 let mut out = String::new();
379 let expr_display = expr
380 .trim()
381 .replace("AND", "∧")
382 .replace("OR", "∨")
383 .replace("NOT", "¬")
384 .replace("XOR", "⊕")
385 .replace("NAND", "⊼")
386 .replace("NOR", "⊽");
387
388 for v in &vars {
390 let _ = write!(out, " {} ", v);
391 }
392 let _ = writeln!(out, "| {}", expr_display);
393 let sep: String =
394 vars.iter().map(|_| "----").collect::<String>() + "+--" + &"-".repeat(expr_display.len());
395 let _ = writeln!(out, "{}", sep);
396
397 let mut true_rows = 0usize;
398 for row in 0..n_rows {
399 let vals: Vec<bool> = (0..n_vars)
400 .map(|i| (row >> (n_vars - 1 - i)) & 1 == 1)
401 .collect();
402 for &v in &vals {
403 let _ = write!(out, " {} ", if v { 'T' } else { 'F' });
404 }
405 let result = eval_bool(expr, &vars, &vals);
406 match result {
407 Ok(r) => {
408 if r {
409 true_rows += 1;
410 }
411 let _ = writeln!(out, "| {}", if r { 'T' } else { 'F' });
412 }
413 Err(e) => {
414 let _ = writeln!(out, "| Error: {}", e);
415 }
416 }
417 }
418 let _ = writeln!(out, "{}", sep);
419 let _ = writeln!(
420 out,
421 "True rows: {} / {} ({}%)",
422 true_rows,
423 n_rows,
424 100 * true_rows / n_rows
425 );
426 if true_rows == 0 {
427 let _ = writeln!(out, "Classification: Contradiction (always false)");
428 } else if true_rows == n_rows {
429 let _ = writeln!(out, "Classification: Tautology (always true)");
430 } else {
431 let _ = writeln!(out, "Classification: Contingency");
432 }
433 out
434}
435
436fn eval_bool(expr: &str, vars: &[char], vals: &[bool]) -> Result<bool, String> {
437 let tokens = tokenize_bool(expr)?;
438 let (result, _) = parse_bool_or(&tokens, 0, vars, vals)?;
439 Ok(result)
440}
441
442#[derive(Debug, Clone, PartialEq)]
443enum BoolToken {
444 Var(char),
445 True,
446 False,
447 Not,
448 And,
449 Or,
450 Xor,
451 Nand,
452 Nor,
453 LParen,
454 RParen,
455}
456
457fn tokenize_bool(s: &str) -> Result<Vec<BoolToken>, String> {
458 let mut tokens = Vec::new();
459 let s = s
460 .replace("NAND", "⊼")
461 .replace("NOR", "⊽")
462 .replace("XOR", "⊕")
463 .replace("AND", "∧")
464 .replace("OR", "∨")
465 .replace("NOT", "¬")
466 .replace("&&", "∧")
467 .replace("||", "∨")
468 .replace("!", "¬")
469 .replace('&', "∧")
470 .replace('|', "∨")
471 .replace('^', "⊕");
472 let chars = s.chars().peekable();
473 for c in chars {
474 match c {
475 ' ' | '\t' | '\n' => {}
476 '(' => tokens.push(BoolToken::LParen),
477 ')' => tokens.push(BoolToken::RParen),
478 '¬' => tokens.push(BoolToken::Not),
479 '∧' => tokens.push(BoolToken::And),
480 '∨' => tokens.push(BoolToken::Or),
481 '⊕' => tokens.push(BoolToken::Xor),
482 '⊼' => tokens.push(BoolToken::Nand),
483 '⊽' => tokens.push(BoolToken::Nor),
484 '1' | 'T' => tokens.push(BoolToken::True),
485 '0' | 'F' => tokens.push(BoolToken::False),
486 c if c.is_ascii_alphabetic() => tokens.push(BoolToken::Var(c)),
487 other => return Err(format!("Unknown token: '{}'", other)),
488 }
489 }
490 Ok(tokens)
491}
492
493fn parse_bool_or(
494 tokens: &[BoolToken],
495 pos: usize,
496 vars: &[char],
497 vals: &[bool],
498) -> Result<(bool, usize), String> {
499 let (mut lhs, mut pos) = parse_bool_and(tokens, pos, vars, vals)?;
500 while pos < tokens.len() {
501 match &tokens[pos] {
502 BoolToken::Or => {
503 let (rhs, np) = parse_bool_and(tokens, pos + 1, vars, vals)?;
504 lhs = lhs || rhs;
505 pos = np;
506 }
507 BoolToken::Nor => {
508 let (rhs, np) = parse_bool_and(tokens, pos + 1, vars, vals)?;
509 lhs = !(lhs || rhs);
510 pos = np;
511 }
512 _ => break,
513 }
514 }
515 Ok((lhs, pos))
516}
517
518fn parse_bool_and(
519 tokens: &[BoolToken],
520 pos: usize,
521 vars: &[char],
522 vals: &[bool],
523) -> Result<(bool, usize), String> {
524 let (mut lhs, mut pos) = parse_bool_xor(tokens, pos, vars, vals)?;
525 while pos < tokens.len() {
526 match &tokens[pos] {
527 BoolToken::And => {
528 let (rhs, np) = parse_bool_xor(tokens, pos + 1, vars, vals)?;
529 lhs = lhs && rhs;
530 pos = np;
531 }
532 BoolToken::Nand => {
533 let (rhs, np) = parse_bool_xor(tokens, pos + 1, vars, vals)?;
534 lhs = !(lhs && rhs);
535 pos = np;
536 }
537 _ => break,
538 }
539 }
540 Ok((lhs, pos))
541}
542
543fn parse_bool_xor(
544 tokens: &[BoolToken],
545 pos: usize,
546 vars: &[char],
547 vals: &[bool],
548) -> Result<(bool, usize), String> {
549 let (mut lhs, mut pos) = parse_bool_not(tokens, pos, vars, vals)?;
550 while pos < tokens.len() && tokens[pos] == BoolToken::Xor {
551 let (rhs, np) = parse_bool_not(tokens, pos + 1, vars, vals)?;
552 lhs ^= rhs;
553 pos = np;
554 }
555 Ok((lhs, pos))
556}
557
558fn parse_bool_not(
559 tokens: &[BoolToken],
560 pos: usize,
561 vars: &[char],
562 vals: &[bool],
563) -> Result<(bool, usize), String> {
564 if pos < tokens.len() && tokens[pos] == BoolToken::Not {
565 let (inner, np) = parse_bool_not(tokens, pos + 1, vars, vals)?;
566 return Ok((!inner, np));
567 }
568 parse_bool_atom(tokens, pos, vars, vals)
569}
570
571fn parse_bool_atom(
572 tokens: &[BoolToken],
573 pos: usize,
574 vars: &[char],
575 vals: &[bool],
576) -> Result<(bool, usize), String> {
577 if pos >= tokens.len() {
578 return Err("Unexpected end of expression".into());
579 }
580 match &tokens[pos] {
581 BoolToken::LParen => {
582 let (inner, np) = parse_bool_or(tokens, pos + 1, vars, vals)?;
583 if np >= tokens.len() || tokens[np] != BoolToken::RParen {
584 return Err("Missing closing ')'".into());
585 }
586 Ok((inner, np + 1))
587 }
588 BoolToken::Var(c) => {
589 let idx = vars
590 .iter()
591 .position(|v| v == c)
592 .ok_or_else(|| format!("Unknown variable '{}'", c))?;
593 Ok((vals[idx], pos + 1))
594 }
595 BoolToken::True => Ok((true, pos + 1)),
596 BoolToken::False => Ok((false, pos + 1)),
597 other => Err(format!("Unexpected token: {:?}", other)),
598 }
599}
600
601fn gcd(a: u128, b: u128) -> u128 {
604 if b == 0 {
605 a
606 } else {
607 gcd(b, a % b)
608 }
609}
610
611pub fn gcd_lcm(a: u128, b: u128) -> String {
612 let g = gcd(a, b);
613 let l = if g == 0 { 0 } else { a / g * b };
614 format!("GCD({a}, {b}) = {g}\nLCM({a}, {b}) = {l}")
615}
616
617pub fn number_theory(query: &str) -> String {
631 let q = query.trim();
632 let tokens: Vec<&str> = q.split_whitespace().collect();
633 if tokens.is_empty() {
634 return nt_usage();
635 }
636 match tokens[0].to_lowercase().as_str() {
637 "extgcd" | "xgcd" => {
638 if tokens.len() < 3 {
639 return "Usage: extgcd A B".into();
640 }
641 let a: i128 = match tokens[1].parse() {
642 Ok(v) => v,
643 Err(_) => return format!("Not a number: {}", tokens[1]),
644 };
645 let b: i128 = match tokens[2].parse() {
646 Ok(v) => v,
647 Err(_) => return format!("Not a number: {}", tokens[2]),
648 };
649 let (g, x, y) = ext_gcd(a, b);
650 format!(
651 "Extended GCD({}, {}):\n GCD = {}\n Bézout: {}×{} + {}×{} = {}\n (verify: {}×{} + {}×{} = {})",
652 a, b, g, x, a, y, b, g, x, a, y, b, x*a + y*b
653 )
654 }
655 "crt" => {
656 if tokens.len() < 5 || tokens.len() % 2 == 0 {
658 return "Usage: crt r1 m1 r2 m2 [r3 m3 ...]\n Example: crt 2 3 3 5 (x ≡ 2 mod 3 and x ≡ 3 mod 5)".into();
659 }
660 let pairs: Vec<(i128, i128)> = tokens[1..]
661 .chunks(2)
662 .filter_map(|c| {
663 let r = c[0].parse::<i128>().ok()?;
664 let m = c[1].parse::<i128>().ok()?;
665 Some((r, m))
666 })
667 .collect();
668 if pairs.len() < 2 {
669 return "Need at least 2 remainder-modulus pairs.".into();
670 }
671 match crt(&pairs) {
672 Some((x, m)) => {
673 let mut out = "Chinese Remainder Theorem:\n".to_string();
674 for (r, mo) in &pairs {
675 out.push_str(&format!(" x ≡ {} (mod {})\n", r, mo));
676 }
677 out.push_str(&format!(
678 "Solution: x ≡ {} (mod {}) [smallest positive: {}]",
679 x,
680 m,
681 ((x % m) + m) % m
682 ));
683 out
684 }
685 None => "No solution — moduli are not pairwise coprime.".into(),
686 }
687 }
688 "mobius" | "möbius" | "mu" => {
689 if tokens.len() < 2 {
690 return "Usage: mobius N".into();
691 }
692 let n: u64 = match tokens[1].parse() {
693 Ok(v) => v,
694 Err(_) => return format!("Not a number: {}", tokens[1]),
695 };
696 let mu = mobius(n);
697 let explanation = match mu {
698 0 => "n has a squared prime factor → μ(n) = 0",
699 1 => "n is squarefree with even number of prime factors → μ(n) = 1",
700 -1 => "n is squarefree with odd number of prime factors → μ(n) = -1",
701 _ => "",
702 };
703 format!("Möbius function μ({}) = {}\n {}", n, mu, explanation)
704 }
705 "modinv" => {
706 if tokens.len() < 3 {
707 return "Usage: modinv A MOD".into();
708 }
709 let a: i128 = match tokens[1].parse() {
710 Ok(v) => v,
711 Err(_) => return format!("Not a number: {}", tokens[1]),
712 };
713 let m: i128 = match tokens[2].parse() {
714 Ok(v) => v,
715 Err(_) => return format!("Not a number: {}", tokens[2]),
716 };
717 match mod_inv(a, m) {
718 Some(inv) => format!(
719 "Modular inverse: {}⁻¹ ≡ {} (mod {})\nVerify: {} × {} = {} ≡ 1 (mod {})",
720 a,
721 inv,
722 m,
723 a,
724 inv,
725 a * inv,
726 m
727 ),
728 None => format!("No modular inverse: gcd({}, {}) ≠ 1 (not coprime)", a, m),
729 }
730 }
731 "modpow" | "powmod" => {
732 if tokens.len() < 4 {
733 return "Usage: modpow BASE EXP MOD".into();
734 }
735 let base: u128 = match tokens[1].parse() {
736 Ok(v) => v,
737 Err(_) => return format!("Not a number: {}", tokens[1]),
738 };
739 let exp: u128 = match tokens[2].parse() {
740 Ok(v) => v,
741 Err(_) => return format!("Not a number: {}", tokens[2]),
742 };
743 let modu: u128 = match tokens[3].parse() {
744 Ok(v) => v,
745 Err(_) => return format!("Not a number: {}", tokens[3]),
746 };
747 if modu == 0 {
748 return "Modulus cannot be zero.".into();
749 }
750 let result = mod_pow(base, exp, modu);
751 format!("{}^{} mod {} = {}", base, exp, modu, result)
752 }
753 "cf" | "cfrac" | "continued_fraction" => {
754 if tokens.len() < 2 {
755 return "Usage: cf N/D or cf DECIMAL".into();
756 }
757 let input = tokens[1];
758 let (num, den) = if input.contains('/') {
759 let parts: Vec<&str> = input.splitn(2, '/').collect();
760 let n: i64 = parts[0].parse().unwrap_or(0);
761 let d: i64 = parts[1].parse().unwrap_or(1);
762 (n, d)
763 } else if let Ok(f) = input.parse::<f64>() {
764 let scale = 1_000_000i64;
766 ((f * scale as f64).round() as i64, scale)
767 } else {
768 return format!("Cannot parse: {}", input);
769 };
770 if den == 0 {
771 return "Denominator cannot be zero.".into();
772 }
773 let coeffs = cf_expansion(num, den, 20);
774 let convergents = cf_convergents(&coeffs);
775 let mut out = format!(
776 "Continued fraction of {}/{} = {}:\n",
777 num,
778 den,
779 num as f64 / den as f64
780 );
781 out.push_str(&format!(
782 " CF = [{}]\n",
783 coeffs
784 .iter()
785 .map(|x| x.to_string())
786 .collect::<Vec<_>>()
787 .join("; ")
788 ));
789 out.push_str(" Convergents:\n");
790 for (p, q) in &convergents {
791 out.push_str(&format!(" {}/{} = {:.8}\n", p, q, *p as f64 / *q as f64));
792 }
793 out
794 }
795 "goldbach" => {
796 if tokens.len() < 2 {
797 return "Usage: goldbach N (must be even, > 2)".into();
798 }
799 let n: u64 = match tokens[1].parse() {
800 Ok(v) => v,
801 Err(_) => return format!("Not a number: {}", tokens[1]),
802 };
803 if n <= 2 || n % 2 != 0 {
804 return format!("{} must be even and > 2 for Goldbach's conjecture.", n);
805 }
806 let pairs: Vec<(u64, u64)> = (2..=n / 2)
807 .filter(|&p| is_prime(p) && is_prime(n - p))
808 .map(|p| (p, n - p))
809 .collect();
810 let mut out = format!("Goldbach decompositions of {}:\n", n);
811 if pairs.is_empty() {
812 out.push_str(" No decompositions found (unexpected for n > 2).\n");
813 } else {
814 for (p, q) in pairs.iter().take(10) {
815 out.push_str(&format!(" {} = {} + {}\n", n, p, q));
816 }
817 if pairs.len() > 10 {
818 out.push_str(&format!(" ... ({} total decompositions)\n", pairs.len()));
819 }
820 }
821 out
822 }
823 "totient" | "phi" | "euler" => {
824 if tokens.len() < 2 {
825 return "Usage: totient N".into();
826 }
827 let n: u64 = match tokens[1].parse() {
828 Ok(v) => v,
829 Err(_) => return format!("Not a number: {}", tokens[1]),
830 };
831 let phi = euler_totient(n);
832 format!(
833 "Euler's totient φ({}) = {}\n (count of integers 1..{} coprime to {})",
834 n, phi, n, n
835 )
836 }
837 "jacobi" => {
838 if tokens.len() < 3 {
839 return "Usage: jacobi A N (N must be odd)".into();
840 }
841 let a: i64 = match tokens[1].parse() {
842 Ok(v) => v,
843 Err(_) => return format!("Not a number: {}", tokens[1]),
844 };
845 let n: i64 = match tokens[2].parse() {
846 Ok(v) => v,
847 Err(_) => return format!("Not a number: {}", tokens[2]),
848 };
849 if n <= 0 || n % 2 == 0 {
850 return "N must be a positive odd integer.".into();
851 }
852 let j = jacobi_symbol(a, n);
853 let meaning = match j {
854 0 => "a is not coprime to n",
855 1 => "a is a quadratic residue mod n (or n is composite)",
856 -1 => "a is a quadratic non-residue mod n",
857 _ => "",
858 };
859 format!("Jacobi symbol ({}/{}) = {}\n {}", a, n, j, meaning)
860 }
861 _ => {
862 if let Ok(n) = tokens[0].parse::<u64>() {
864 nt_report(n)
865 } else {
866 nt_usage()
867 }
868 }
869 }
870}
871
872fn nt_usage() -> String {
873 "Number theory operations:\n\
874 hematite --number-theory 'extgcd 35 15'\n\
875 hematite --number-theory 'crt 2 3 3 5'\n\
876 hematite --number-theory 'mobius 30'\n\
877 hematite --number-theory 'modinv 7 13'\n\
878 hematite --number-theory 'modpow 3 10 1000'\n\
879 hematite --number-theory 'cf 355/113'\n\
880 hematite --number-theory 'goldbach 28'\n\
881 hematite --number-theory 'totient 36'\n\
882 hematite --number-theory 'jacobi 5 15'\n\
883 hematite --number-theory '42' (full report for a number)"
884 .into()
885}
886
887fn nt_report(n: u64) -> String {
888 let mut out = String::new();
889 let _ = writeln!(out, "Number theory report for {}", n);
890 let _ = writeln!(out, " Euler's totient φ(n) = {}", euler_totient(n));
891 let _ = writeln!(out, " Möbius μ(n) = {}", mobius(n));
892 if n < 1_000_000 {
893 let sigma: u64 = (1..=n).filter(|d| n % d == 0).sum();
894 let _ = writeln!(out, " Sum of divisors σ(n) = {}", sigma);
895 if sigma == 2 * n {
896 let _ = writeln!(out, " → Perfect number!");
897 } else if sigma > 2 * n {
898 let _ = writeln!(out, " → Abundant number");
899 } else {
900 let _ = writeln!(out, " → Deficient number");
901 }
902 }
903 if n >= 4 && n % 2 == 0 {
904 if let Some((p, q)) = (2..=n / 2)
905 .filter(|&p| is_prime(p) && is_prime(n - p))
906 .map(|p| (p, n - p))
907 .next()
908 {
909 let _ = writeln!(out, " Goldbach: {} = {} + {}", n, p, q);
910 }
911 }
912 out
913}
914
915fn ext_gcd(a: i128, b: i128) -> (i128, i128, i128) {
916 if b == 0 {
917 return (a, 1, 0);
918 }
919 let (g, x1, y1) = ext_gcd(b, a % b);
920 (g, y1, x1 - (a / b) * y1)
921}
922
923fn mod_inv(a: i128, m: i128) -> Option<i128> {
924 let (g, x, _) = ext_gcd(a.rem_euclid(m), m);
925 if g != 1 {
926 return None;
927 }
928 Some(x.rem_euclid(m))
929}
930
931fn mod_pow(mut base: u128, mut exp: u128, modu: u128) -> u128 {
932 if modu == 1 {
933 return 0;
934 }
935 let mut result = 1u128;
936 base %= modu;
937 while exp > 0 {
938 if exp % 2 == 1 {
939 result = result.wrapping_mul(base) % modu;
940 }
941 exp /= 2;
942 base = base.wrapping_mul(base) % modu;
943 }
944 result
945}
946
947fn crt(pairs: &[(i128, i128)]) -> Option<(i128, i128)> {
948 let mut x = pairs[0].0;
949 let mut m = pairs[0].1;
950 for &(r, mi) in &pairs[1..] {
951 let g = gcd(m as u128, mi.unsigned_abs()) as i128;
952 if (r - x) % g != 0 {
953 return None;
954 }
955 let lcm = m / g * mi;
956 let inv = mod_inv(m / g, mi / g)?;
957 x = x + m * ((r - x) / g % (mi / g) * inv % (mi / g));
958 m = lcm;
959 x = x.rem_euclid(m);
960 }
961 Some((x, m))
962}
963
964fn mobius(n: u64) -> i32 {
965 if n == 1 {
966 return 1;
967 }
968 let factors = factorize(n);
969 for (_, exp) in &factors {
970 if *exp > 1 {
971 return 0;
972 }
973 }
974 if factors.len() % 2 == 0 {
975 1
976 } else {
977 -1
978 }
979}
980
981fn euler_totient(n: u64) -> u64 {
982 if n == 0 {
983 return 0;
984 }
985 let factors = factorize(n);
986 let mut phi = n;
987 for (p, _) in factors {
988 phi = phi / p * (p - 1);
989 }
990 phi
991}
992
993fn cf_expansion(mut num: i64, mut den: i64, max_terms: usize) -> Vec<i64> {
994 let mut coeffs = Vec::new();
995 for _ in 0..max_terms {
996 coeffs.push(num / den);
997 let rem = num % den;
998 if rem == 0 {
999 break;
1000 }
1001 num = den;
1002 den = rem;
1003 }
1004 coeffs
1005}
1006
1007fn cf_convergents(coeffs: &[i64]) -> Vec<(i64, i64)> {
1008 let mut result = Vec::new();
1009 let (mut p_prev, mut q_prev) = (1i64, 0i64);
1010 let (mut p_curr, mut q_curr) = (coeffs[0], 1i64);
1011 result.push((p_curr, q_curr));
1012 for &a in &coeffs[1..] {
1013 let p_next = a * p_curr + p_prev;
1014 let q_next = a * q_curr + q_prev;
1015 result.push((p_next, q_next));
1016 p_prev = p_curr;
1017 q_prev = q_curr;
1018 p_curr = p_next;
1019 q_curr = q_next;
1020 }
1021 result
1022}
1023
1024fn jacobi_symbol(mut a: i64, mut n: i64) -> i32 {
1025 if n <= 0 || n % 2 == 0 {
1026 return 0;
1027 }
1028 let mut result = 1i32;
1029 a = a.rem_euclid(n);
1030 while a != 0 {
1031 while a % 2 == 0 {
1032 a /= 2;
1033 if n % 8 == 3 || n % 8 == 5 {
1034 result = -result;
1035 }
1036 }
1037 std::mem::swap(&mut a, &mut n);
1038 if a % 4 == 3 && n % 4 == 3 {
1039 result = -result;
1040 }
1041 a %= n;
1042 }
1043 if n == 1 {
1044 result
1045 } else {
1046 0
1047 }
1048}
1049
1050pub fn to_roman(mut n: u64) -> String {
1053 if n == 0 {
1054 return "Roman numerals start at 1.".into();
1055 }
1056 if n > 3_999_999 {
1057 return format!("{n} is too large for standard Roman numerals (max 3,999,999).");
1058 }
1059 const TABLE: &[(u64, &str)] = &[
1060 (1_000_000, "M̄"),
1061 (900_000, "C̄M̄"),
1062 (500_000, "D̄"),
1063 (400_000, "C̄D̄"),
1064 (100_000, "C̄"),
1065 (90_000, "X̄C̄"),
1066 (50_000, "L̄"),
1067 (40_000, "X̄L̄"),
1068 (10_000, "X̄"),
1069 (9_000, "MX̄"),
1070 (5_000, "V̄"),
1071 (4_000, "MV̄"),
1072 (1000, "M"),
1073 (900, "CM"),
1074 (500, "D"),
1075 (400, "CD"),
1076 (100, "C"),
1077 (90, "XC"),
1078 (50, "L"),
1079 (40, "XL"),
1080 (10, "X"),
1081 (9, "IX"),
1082 (5, "V"),
1083 (4, "IV"),
1084 (1, "I"),
1085 ];
1086 let mut out = String::new();
1087 for &(val, sym) in TABLE {
1088 while n >= val {
1089 out.push_str(sym);
1090 n -= val;
1091 }
1092 }
1093 out
1094}
1095
1096pub fn from_roman(s: &str) -> String {
1097 let s = s.trim().to_uppercase();
1098 let map = [
1099 ("M̄", 1_000_000u64),
1100 ("C̄M̄", 900_000),
1101 ("D̄", 500_000),
1102 ("C̄D̄", 400_000),
1103 ("C̄", 100_000),
1104 ("X̄C̄", 90_000),
1105 ("L̄", 50_000),
1106 ("X̄L̄", 40_000),
1107 ("X̄", 10_000),
1108 ("MX̄", 9_000),
1109 ("V̄", 5_000),
1110 ("MV̄", 4_000),
1111 ("M", 1000),
1112 ("CM", 900),
1113 ("D", 500),
1114 ("CD", 400),
1115 ("C", 100),
1116 ("XC", 90),
1117 ("L", 50),
1118 ("XL", 40),
1119 ("X", 10),
1120 ("IX", 9),
1121 ("V", 5),
1122 ("IV", 4),
1123 ("I", 1),
1124 ];
1125 let mut pos = 0usize;
1126 let chars: Vec<char> = s.chars().collect();
1127 let mut total = 0u64;
1128 'outer: while pos < chars.len() {
1129 for &(sym, val) in &map {
1130 let sc: Vec<char> = sym.chars().collect();
1131 if chars[pos..].starts_with(&sc) {
1132 total += val;
1133 pos += sc.len();
1134 continue 'outer;
1135 }
1136 }
1137 return format!(
1138 "Unrecognized Roman numeral character at position {}: '{}'",
1139 pos, chars[pos]
1140 );
1141 }
1142 format!("{} = {}", s, total)
1143}
1144
1145pub fn roman_info(input: &str) -> String {
1146 let t = input.trim();
1147 if let Ok(n) = t.parse::<u64>() {
1148 let r = to_roman(n);
1149 format!("{n} = {r}")
1150 } else {
1151 from_roman(t)
1152 }
1153}
1154
1155pub fn base_convert(input: &str, from_base: u32, to_base: u32) -> String {
1158 if !(2..=36).contains(&from_base) || !(2..=36).contains(&to_base) {
1159 return "Base must be between 2 and 36.".into();
1160 }
1161 let s = input.trim().to_ascii_uppercase();
1162 let value = u128::from_str_radix(&s, from_base).unwrap_or(0);
1163 if u128::from_str_radix(&s, from_base).is_err() {
1165 return format!("'{}' is not a valid base-{} number.", s, from_base);
1166 }
1167 let to_str = |mut v: u128, base: u32| -> String {
1168 if v == 0 {
1169 return "0".into();
1170 }
1171 let digits: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1172 let mut result = Vec::new();
1173 while v > 0 {
1174 result.push(digits[(v % base as u128) as usize] as char);
1175 v /= base as u128;
1176 }
1177 result.into_iter().rev().collect()
1178 };
1179 let _ = value; let value2 = u128::from_str_radix(&s, from_base).unwrap();
1181 let mut out = String::new();
1182 let _ = writeln!(out, "Input (base {}): {}", from_base, s);
1183 let _ = writeln!(out, "Decimal: {}", value2);
1184 let _ = writeln!(
1185 out,
1186 "Output (base {}): {}",
1187 to_base,
1188 to_str(value2, to_base)
1189 );
1190 if to_base != 2 && from_base != 2 {
1191 let _ = writeln!(out, "Binary: {}", to_str(value2, 2));
1192 }
1193 if to_base != 16 && from_base != 16 {
1194 let _ = writeln!(out, "Hex: {}", to_str(value2, 16));
1195 }
1196 if to_base != 8 && from_base != 8 {
1197 let _ = writeln!(out, "Octal: {}", to_str(value2, 8));
1198 }
1199 out
1200}
1201
1202fn days_in_month(year: i32, month: u32) -> u32 {
1205 match month {
1206 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
1207 4 | 6 | 9 | 11 => 30,
1208 2 => {
1209 if year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) {
1210 29
1211 } else {
1212 28
1213 }
1214 }
1215 _ => 30,
1216 }
1217}
1218
1219fn to_jdn(y: i32, m: u32, d: u32) -> i64 {
1220 let a = (14 - m as i32) / 12;
1221 let yr = y + 4800 - a;
1222 let mo = m as i32 + 12 * a - 3;
1223 d as i64 + (153 * mo + 2) as i64 / 5 + 365 * yr as i64 + yr as i64 / 4 - yr as i64 / 100
1224 + yr as i64 / 400
1225 - 32045
1226}
1227
1228fn from_jdn(jdn: i64) -> (i32, u32, u32) {
1229 let l = jdn + 68569;
1230 let n = 4 * l / 146097;
1231 let l = l - (146097 * n + 3) / 4;
1232 let i = 4000 * (l + 1) / 1461001;
1233 let l = l - 1461 * i / 4 + 31;
1234 let j = 80 * l / 2447;
1235 let d = l - 2447 * j / 80;
1236 let l = j / 11;
1237 let m = j + 2 - 12 * l;
1238 let y = 100 * (n - 49) + i + l;
1239 (y as i32, m as u32, d as u32)
1240}
1241
1242fn parse_date(s: &str) -> Option<(i32, u32, u32)> {
1243 let s = s.trim();
1244 let parts: Vec<&str> = s
1246 .splitn(3, |c: char| !c.is_ascii_digit())
1247 .filter(|p| !p.is_empty())
1248 .collect();
1249 if parts.len() == 3 {
1250 let y = parts[0].parse::<i32>().ok()?;
1251 let m = parts[1].parse::<u32>().ok()?;
1252 let d = parts[2].parse::<u32>().ok()?;
1253 if (1..=12).contains(&m) && d >= 1 && d <= days_in_month(y, m) {
1254 return Some((y, m, d));
1255 }
1256 }
1257 None
1258}
1259
1260const WEEKDAYS: &[&str] = &[
1261 "Monday",
1262 "Tuesday",
1263 "Wednesday",
1264 "Thursday",
1265 "Friday",
1266 "Saturday",
1267 "Sunday",
1268];
1269
1270pub fn date_calc(input: &str) -> String {
1271 let s = input.trim();
1272 let mut out = String::new();
1273
1274 if s.to_lowercase().starts_with("unix ") {
1281 let ts: i64 = match s[5..].trim().parse() {
1282 Ok(v) => v,
1283 Err(_) => return "Usage: --date 'unix 1700000000'".into(),
1284 };
1285 let jdn = ts / 86400 + 2440588;
1286 let (y, m, d) = from_jdn(jdn);
1287 let dow = ((jdn + 1) % 7) as usize;
1288 let _ = writeln!(out, "Unix: {}", ts);
1289 let _ = writeln!(out, "Date: {}-{:02}-{:02} ({})", y, m, d, WEEKDAYS[dow]);
1290 return out;
1291 }
1292
1293 if s.to_lowercase().starts_with("timestamp ") {
1294 let date_str = &s["timestamp ".len()..];
1295 if let Some((y, m, d)) = parse_date(date_str) {
1296 let jdn = to_jdn(y, m, d);
1297 let ts = (jdn - 2440588) * 86400;
1298 let _ = writeln!(out, "Date: {}-{:02}-{:02}", y, m, d);
1299 let _ = writeln!(out, "Unix timestamp (midnight UTC): {}", ts);
1300 } else {
1301 out.push_str("Could not parse date. Use YYYY-MM-DD format.");
1302 }
1303 return out;
1304 }
1305
1306 let (a_str, b_str) = if let Some(pos) = s.to_lowercase().find(" to ") {
1308 (s[..pos].trim(), Some(s[pos + 4..].trim()))
1309 } else if s.contains(',') {
1310 let mut it = s.splitn(2, ',');
1311 (
1312 it.next().unwrap_or("").trim(),
1313 Some(it.next().unwrap_or("").trim()),
1314 )
1315 } else {
1316 (s, None)
1317 };
1318
1319 let plus_re = {
1321 let a = a_str.trim_end();
1322 if let Some(idx) = a.rfind(['+', '-']) {
1323 let (date_part, offset_part) = a.split_at(idx);
1324 if let Ok(n) = offset_part.trim().parse::<i64>() {
1325 Some((date_part.trim(), n))
1326 } else {
1327 None
1328 }
1329 } else {
1330 None
1331 }
1332 };
1333
1334 if let Some((date_part, offset)) = plus_re {
1335 if let Some((y, m, d)) = parse_date(date_part) {
1336 let jdn = to_jdn(y, m, d) + offset;
1337 let (y2, m2, d2) = from_jdn(jdn);
1338 let dow = ((jdn + 1) % 7) as usize;
1339 let _ = writeln!(
1340 out,
1341 "{}-{:02}-{:02} {} {} days → {}-{:02}-{:02} ({})",
1342 y,
1343 m,
1344 d,
1345 if offset >= 0 { "+" } else { "" },
1346 offset,
1347 y2,
1348 m2,
1349 d2,
1350 WEEKDAYS[dow]
1351 );
1352 } else {
1353 out.push_str(
1354 "Could not parse date. Use: --date '2024-03-15 +90' or '2024-01-01 to 2024-12-31'",
1355 );
1356 }
1357 return out;
1358 }
1359
1360 if let (Some((y1, m1, d1)), Some(b)) = (parse_date(a_str), b_str) {
1361 if let Some((y2, m2, d2)) = parse_date(b) {
1362 let jdn1 = to_jdn(y1, m1, d1);
1363 let jdn2 = to_jdn(y2, m2, d2);
1364 let diff = jdn2 - jdn1;
1365 let dow1 = ((jdn1 + 1) % 7) as usize;
1366 let dow2 = ((jdn2 + 1) % 7) as usize;
1367 let _ = writeln!(out, "From: {}-{:02}-{:02} ({})", y1, m1, d1, WEEKDAYS[dow1]);
1368 let _ = writeln!(out, "To: {}-{:02}-{:02} ({})", y2, m2, d2, WEEKDAYS[dow2]);
1369 let _ = writeln!(
1370 out,
1371 "Difference: {} days ({} weeks {} days)",
1372 diff.abs(),
1373 diff.abs() / 7,
1374 diff.abs() % 7
1375 );
1376 let _ = writeln!(
1377 out,
1378 " ≈ {:.2} months ≈ {:.3} years",
1379 diff.abs() as f64 / 30.4375,
1380 diff.abs() as f64 / 365.25
1381 );
1382 if diff < 0 {
1383 let _ = writeln!(out, "(B is before A — {} days ago)", diff.abs());
1384 }
1385 } else {
1386 out.push_str("Could not parse second date.");
1387 }
1388 return out;
1389 }
1390
1391 if let Some((y, m, d)) = parse_date(a_str) {
1393 let jdn = to_jdn(y, m, d);
1394 let dow = ((jdn + 1) % 7) as usize;
1395 let ts = (jdn - 2440588) * 86400;
1396 let day_of_year: u32 = (1..m).map(|mo| days_in_month(y, mo)).sum::<u32>() + d;
1397 let is_leap = y % 400 == 0 || (y % 4 == 0 && y % 100 != 0);
1398 let days_left = (if is_leap { 366 } else { 365 }) - day_of_year;
1399 let _ = writeln!(out, "Date: {}-{:02}-{:02}", y, m, d);
1400 let _ = writeln!(
1401 out,
1402 "Day: {} (day {} of {}, {} remaining)",
1403 WEEKDAYS[dow],
1404 day_of_year,
1405 if is_leap { 366 } else { 365 },
1406 days_left
1407 );
1408 let _ = writeln!(out, "Leap year: {}", if is_leap { "Yes" } else { "No" });
1409 let _ = writeln!(out, "Unix stamp: {} (midnight UTC)", ts);
1410 let _ = writeln!(out, "Julian day: {}", jdn);
1411 } else {
1412 out.push_str("Could not parse date. Examples:\n --date '2024-06-15'\n --date '2024-01-01 to 2024-12-31'\n --date '2024-03-15 +90'\n --date 'unix 1700000000'");
1413 }
1414 out
1415}
1416
1417pub fn subnet_calc(cidr: &str) -> String {
1420 let cidr = cidr.trim();
1421 let mut out = String::new();
1422
1423 let (ip_str, prefix) = if let Some(idx) = cidr.find('/') {
1425 let prefix: u8 = match cidr[idx + 1..].trim().parse() {
1426 Ok(v) => v,
1427 Err(_) => return "Invalid prefix length. Use CIDR format: 192.168.1.0/24".into(),
1428 };
1429 (&cidr[..idx], prefix)
1430 } else {
1431 return "Use CIDR notation: 192.168.1.0/24".into();
1432 };
1433
1434 let parse_ip = |s: &str| -> Option<u32> {
1435 let parts: Vec<u8> = s.trim().split('.').filter_map(|x| x.parse().ok()).collect();
1436 if parts.len() == 4 {
1437 Some(
1438 ((parts[0] as u32) << 24)
1439 | ((parts[1] as u32) << 16)
1440 | ((parts[2] as u32) << 8)
1441 | parts[3] as u32,
1442 )
1443 } else {
1444 None
1445 }
1446 };
1447
1448 let ip = match parse_ip(ip_str) {
1449 Some(v) => v,
1450 None => return format!("Invalid IP address: '{}'", ip_str),
1451 };
1452
1453 if prefix > 32 {
1454 return "Prefix must be 0–32.".into();
1455 }
1456
1457 let mask: u32 = if prefix == 0 {
1458 0
1459 } else {
1460 !0u32 << (32 - prefix)
1461 };
1462 let network = ip & mask;
1463 let broadcast = network | !mask;
1464 let first_host = if prefix >= 31 { network } else { network + 1 };
1465 let last_host = if prefix >= 31 {
1466 broadcast
1467 } else {
1468 broadcast - 1
1469 };
1470 let host_count: u64 = if prefix >= 32 {
1471 1
1472 } else if prefix == 31 {
1473 2
1474 } else {
1475 (1u64 << (32 - prefix)) - 2
1476 };
1477
1478 let fmt_ip = |v: u32| {
1479 format!(
1480 "{}.{}.{}.{}",
1481 v >> 24,
1482 (v >> 16) & 0xff,
1483 (v >> 8) & 0xff,
1484 v & 0xff
1485 )
1486 };
1487
1488 let class = match ip >> 24 {
1489 0..=127 => "A",
1490 128..=191 => "B",
1491 192..=223 => "C",
1492 224..=239 => "D (Multicast)",
1493 _ => "E (Reserved)",
1494 };
1495 let private = (ip >> 24) == 10
1496 || ((ip >> 24) == 172 && ((ip >> 20) & 0xf) == 1)
1497 || ((ip >> 24) == 192 && ((ip >> 16) & 0xff) == 168);
1498
1499 let _ = writeln!(out, "CIDR: {}/{}", fmt_ip(ip), prefix);
1500 let _ = writeln!(out, "Network: {}/{}", fmt_ip(network), prefix);
1501 let _ = writeln!(out, "Broadcast: {}", fmt_ip(broadcast));
1502 let _ = writeln!(out, "Subnet mask:{}", fmt_ip(mask));
1503 let _ = writeln!(out, "First host: {}", fmt_ip(first_host));
1504 let _ = writeln!(out, "Last host: {}", fmt_ip(last_host));
1505 let _ = writeln!(out, "Hosts: {}", host_count);
1506 let _ = writeln!(
1507 out,
1508 "Class: {} | Private: {}",
1509 class,
1510 if private { "Yes" } else { "No" }
1511 );
1512 out
1513}
1514
1515pub fn color_convert(input: &str) -> String {
1518 let s = input.trim().to_ascii_lowercase();
1519 let mut out = String::new();
1520
1521 let hex_input = s.trim_start_matches('#');
1523 let (r8, g8, b8) = if hex_input.len() == 6 {
1524 if let (Ok(r), Ok(g), Ok(b)) = (
1525 u8::from_str_radix(&hex_input[0..2], 16),
1526 u8::from_str_radix(&hex_input[2..4], 16),
1527 u8::from_str_radix(&hex_input[4..6], 16),
1528 ) {
1529 (r, g, b)
1530 } else {
1531 return format!("Invalid hex: '{}'", input);
1532 }
1533 } else if hex_input.len() == 3 {
1534 if let (Ok(r), Ok(g), Ok(b)) = (
1535 u8::from_str_radix(&hex_input[0..1].repeat(2), 16),
1536 u8::from_str_radix(&hex_input[1..2].repeat(2), 16),
1537 u8::from_str_radix(&hex_input[2..3].repeat(2), 16),
1538 ) {
1539 (r, g, b)
1540 } else {
1541 return format!("Invalid hex: '{}'", input);
1542 }
1543 } else if s.starts_with("rgb(") || s.starts_with("rgb ") {
1544 let nums: Vec<u8> = s
1545 .chars()
1546 .filter(|c| c.is_ascii_digit() || *c == ' ' || *c == ',')
1547 .collect::<String>()
1548 .split(|c: char| !c.is_ascii_digit())
1549 .filter_map(|x| x.parse().ok())
1550 .collect();
1551 if nums.len() >= 3 {
1552 (nums[0], nums[1], nums[2])
1553 } else {
1554 return "Usage: --color '#ff8800' or --color 'rgb(255,136,0)'".into();
1555 }
1556 } else {
1557 return "Usage: --color '#ff8800' or --color 'rgb(255,136,0)' or --color '3f8'".into();
1558 };
1559
1560 let rf = r8 as f64 / 255.0;
1561 let gf = g8 as f64 / 255.0;
1562 let bf = b8 as f64 / 255.0;
1563
1564 let cmax = rf.max(gf).max(bf);
1566 let cmin = rf.min(gf).min(bf);
1567 let delta = cmax - cmin;
1568 let l = (cmax + cmin) / 2.0;
1569 let s_hsl = if delta == 0.0 {
1570 0.0
1571 } else {
1572 delta / (1.0 - (2.0 * l - 1.0).abs())
1573 };
1574 let h_hsl = if delta == 0.0 {
1575 0.0
1576 } else if cmax == rf {
1577 60.0 * (((gf - bf) / delta) % 6.0)
1578 } else if cmax == gf {
1579 60.0 * ((bf - rf) / delta + 2.0)
1580 } else {
1581 60.0 * ((rf - gf) / delta + 4.0)
1582 };
1583 let h_hsl = if h_hsl < 0.0 { h_hsl + 360.0 } else { h_hsl };
1584
1585 let v_hsv = cmax;
1587 let s_hsv = if cmax == 0.0 { 0.0 } else { delta / cmax };
1588
1589 let k_cmyk = 1.0 - cmax;
1591 let (c_cmyk, m_cmyk, y_cmyk) = if k_cmyk == 1.0 {
1592 (0.0, 0.0, 0.0)
1593 } else {
1594 (
1595 (1.0 - rf - k_cmyk) / (1.0 - k_cmyk),
1596 (1.0 - gf - k_cmyk) / (1.0 - k_cmyk),
1597 (1.0 - bf - k_cmyk) / (1.0 - k_cmyk),
1598 )
1599 };
1600
1601 let _ = writeln!(out, "Hex: #{:02X}{:02X}{:02X}", r8, g8, b8);
1602 let _ = writeln!(out, "RGB: rgb({}, {}, {})", r8, g8, b8);
1603 let _ = writeln!(
1604 out,
1605 "HSL: hsl({:.1}°, {:.1}%, {:.1}%)",
1606 h_hsl,
1607 s_hsl * 100.0,
1608 l * 100.0
1609 );
1610 let _ = writeln!(
1611 out,
1612 "HSV: hsv({:.1}°, {:.1}%, {:.1}%)",
1613 h_hsl,
1614 s_hsv * 100.0,
1615 v_hsv * 100.0
1616 );
1617 let _ = writeln!(
1618 out,
1619 "CMYK: cmyk({:.0}%, {:.0}%, {:.0}%, {:.0}%)",
1620 c_cmyk * 100.0,
1621 m_cmyk * 100.0,
1622 y_cmyk * 100.0,
1623 k_cmyk * 100.0
1624 );
1625 let lum = 0.2126 * rf + 0.7152 * gf + 0.0722 * bf;
1627 let _ = writeln!(
1628 out,
1629 "Luminance: {:.4} (WCAG relative, 0=black 1=white)",
1630 lum
1631 );
1632 let contrast_white = (1.0 + 0.05) / (lum + 0.05);
1633 let _ = writeln!(
1634 out,
1635 "Contrast vs white: {:.2}:1 (WCAG AA needs 4.5:1)",
1636 contrast_white
1637 );
1638 out
1639}
1640
1641fn atomic_masses() -> &'static [(&'static str, f64)] {
1646 &[
1647 ("H", 1.008),
1648 ("He", 4.0026),
1649 ("Li", 6.94),
1650 ("Be", 9.0122),
1651 ("B", 10.81),
1652 ("C", 12.011),
1653 ("N", 14.007),
1654 ("O", 15.999),
1655 ("F", 18.998),
1656 ("Ne", 20.180),
1657 ("Na", 22.990),
1658 ("Mg", 24.305),
1659 ("Al", 26.982),
1660 ("Si", 28.085),
1661 ("P", 30.974),
1662 ("S", 32.06),
1663 ("Cl", 35.45),
1664 ("Ar", 39.948),
1665 ("K", 39.098),
1666 ("Ca", 40.078),
1667 ("Sc", 44.956),
1668 ("Ti", 47.867),
1669 ("V", 50.942),
1670 ("Cr", 51.996),
1671 ("Mn", 54.938),
1672 ("Fe", 55.845),
1673 ("Co", 58.933),
1674 ("Ni", 58.693),
1675 ("Cu", 63.546),
1676 ("Zn", 65.38),
1677 ("Ga", 69.723),
1678 ("Ge", 72.630),
1679 ("As", 74.922),
1680 ("Se", 78.971),
1681 ("Br", 79.904),
1682 ("Kr", 83.798),
1683 ("Rb", 85.468),
1684 ("Sr", 87.62),
1685 ("Y", 88.906),
1686 ("Zr", 91.224),
1687 ("Nb", 92.906),
1688 ("Mo", 95.95),
1689 ("Tc", 98.0),
1690 ("Ru", 101.07),
1691 ("Rh", 102.906),
1692 ("Pd", 106.42),
1693 ("Ag", 107.868),
1694 ("Cd", 112.414),
1695 ("In", 114.818),
1696 ("Sn", 118.710),
1697 ("Sb", 121.760),
1698 ("Te", 127.60),
1699 ("I", 126.904),
1700 ("Xe", 131.293),
1701 ("Cs", 132.905),
1702 ("Ba", 137.327),
1703 ("La", 138.905),
1704 ("Ce", 140.116),
1705 ("Pr", 140.908),
1706 ("Nd", 144.242),
1707 ("Pm", 145.0),
1708 ("Sm", 150.36),
1709 ("Eu", 151.964),
1710 ("Gd", 157.25),
1711 ("Tb", 158.925),
1712 ("Dy", 162.500),
1713 ("Ho", 164.930),
1714 ("Er", 167.259),
1715 ("Tm", 168.934),
1716 ("Yb", 173.045),
1717 ("Lu", 174.967),
1718 ("Hf", 178.49),
1719 ("Ta", 180.948),
1720 ("W", 183.84),
1721 ("Re", 186.207),
1722 ("Os", 190.23),
1723 ("Ir", 192.217),
1724 ("Pt", 195.084),
1725 ("Au", 196.967),
1726 ("Hg", 200.592),
1727 ("Tl", 204.38),
1728 ("Pb", 207.2),
1729 ("Bi", 208.980),
1730 ("Po", 209.0),
1731 ("At", 210.0),
1732 ("Rn", 222.0),
1733 ("Fr", 223.0),
1734 ("Ra", 226.0),
1735 ("Ac", 227.0),
1736 ("Th", 232.038),
1737 ("Pa", 231.036),
1738 ("U", 238.029),
1739 ("Np", 237.0),
1740 ("Pu", 244.0),
1741 ("Am", 243.0),
1742 ("Cm", 247.0),
1743 ("Bk", 247.0),
1744 ("Cf", 251.0),
1745 ("Es", 252.0),
1746 ("Fm", 257.0),
1747 ("Md", 258.0),
1748 ("No", 259.0),
1749 ("Lr", 266.0),
1750 ("Rf", 267.0),
1751 ("Db", 268.0),
1752 ("Sg", 271.0),
1753 ("Bh", 270.0),
1754 ("Hs", 277.0),
1755 ("Mt", 278.0),
1756 ("Ds", 281.0),
1757 ("Rg", 282.0),
1758 ("Cn", 285.0),
1759 ("Nh", 286.0),
1760 ("Fl", 289.0),
1761 ("Mc", 290.0),
1762 ("Lv", 293.0),
1763 ("Ts", 294.0),
1764 ("Og", 294.0),
1765 ]
1766}
1767
1768fn lookup_mass(sym: &str) -> Option<f64> {
1769 atomic_masses()
1770 .iter()
1771 .find(|(s, _)| *s == sym)
1772 .map(|(_, m)| *m)
1773}
1774
1775fn parse_formula(chars: &[char], pos: &mut usize) -> Result<Vec<(String, u32)>, String> {
1776 let mut items: Vec<(String, u32)> = Vec::new();
1777 while *pos < chars.len() {
1778 match chars[*pos] {
1779 '(' => {
1780 *pos += 1;
1781 let inner = parse_formula(chars, pos)?;
1782 if *pos >= chars.len() || chars[*pos] != ')' {
1783 return Err("Missing closing ')'".into());
1784 }
1785 *pos += 1;
1786 let count = read_number(chars, pos).unwrap_or(1);
1787 for (sym, n) in inner {
1788 items.push((sym, n * count));
1789 }
1790 }
1791 '[' => {
1792 *pos += 1;
1793 let inner = parse_formula(chars, pos)?;
1794 if *pos >= chars.len() || chars[*pos] != ']' {
1795 return Err("Missing closing ']'".into());
1796 }
1797 *pos += 1;
1798 let count = read_number(chars, pos).unwrap_or(1);
1799 for (sym, n) in inner {
1800 items.push((sym, n * count));
1801 }
1802 }
1803 ')' | ']' => break,
1804 c if c.is_ascii_uppercase() => {
1805 let mut sym = c.to_string();
1806 *pos += 1;
1807 while *pos < chars.len() && chars[*pos].is_ascii_lowercase() {
1808 sym.push(chars[*pos]);
1809 *pos += 1;
1810 }
1811 let count = read_number(chars, pos).unwrap_or(1);
1812 items.push((sym, count));
1813 }
1814 '·' | '•' | '.' => {
1815 *pos += 1;
1816 } ' ' | '\t' => {
1818 *pos += 1;
1819 }
1820 other => return Err(format!("Unexpected character: '{}'", other)),
1821 }
1822 }
1823 Ok(items)
1824}
1825
1826fn read_number(chars: &[char], pos: &mut usize) -> Option<u32> {
1827 let start = *pos;
1828 while *pos < chars.len() && chars[*pos].is_ascii_digit() {
1829 *pos += 1;
1830 }
1831 if *pos == start {
1832 None
1833 } else {
1834 chars[start..*pos].iter().collect::<String>().parse().ok()
1835 }
1836}
1837
1838pub fn molecular_weight(formula: &str) -> String {
1839 let chars: Vec<char> = formula.chars().collect();
1840 let mut pos = 0usize;
1841 let items = match parse_formula(&chars, &mut pos) {
1842 Ok(v) => v,
1843 Err(e) => return format!("Parse error: {}. Example: H2O, C6H12O6, Ca(NO3)2", e),
1844 };
1845
1846 let mut counts: std::collections::HashMap<String, u32> = std::collections::HashMap::new();
1848 for (sym, n) in &items {
1849 *counts.entry(sym.clone()).or_insert(0) += n;
1850 }
1851
1852 let mut mw = 0.0f64;
1853 let mut breakdown: Vec<(String, u32, f64)> = Vec::new();
1854 let mut unknown: Vec<String> = Vec::new();
1855 let mut syms: Vec<String> = counts.keys().cloned().collect();
1856 syms.sort();
1857 for sym in &syms {
1858 let n = counts[sym];
1859 if let Some(mass) = lookup_mass(sym) {
1860 let contrib = mass * n as f64;
1861 mw += contrib;
1862 breakdown.push((sym.clone(), n, contrib));
1863 } else {
1864 unknown.push(sym.clone());
1865 }
1866 }
1867
1868 if !unknown.is_empty() {
1869 return format!(
1870 "Unknown element(s): {}. Check your formula.",
1871 unknown.join(", ")
1872 );
1873 }
1874
1875 let mut out = String::new();
1876 let _ = writeln!(out, "Formula: {}", formula.trim());
1877 let _ = writeln!(out, "Molecular weight: {:.4} g/mol", mw);
1878 let _ = writeln!(out);
1879 let _ = writeln!(out, "Composition:");
1880 for (sym, n, contrib) in &breakdown {
1881 let pct = 100.0 * contrib / mw;
1882 let _ = writeln!(
1883 out,
1884 " {:2} × {:2} {:8.4} g/mol ({:.2}%)",
1885 n, sym, contrib, pct
1886 );
1887 }
1888 let _ = writeln!(out);
1890 let _ = writeln!(out, "1 mole = {:.4} g", mw);
1891 let _ = writeln!(out, "1 g = {:.6} mol", 1.0 / mw);
1892 out
1893}
1894
1895const CONSTANTS: &[(&str, &str, &str, &str)] = &[
1898 (
1900 "Speed of light",
1901 "299792458",
1902 "m/s",
1903 "c,light,speed of light,c0",
1904 ),
1905 (
1906 "Planck constant",
1907 "6.62607015e-34",
1908 "J·s",
1909 "h,planck,planck constant",
1910 ),
1911 (
1912 "Reduced Planck (ℏ)",
1913 "1.054571817e-34",
1914 "J·s",
1915 "hbar,h-bar,reduced planck,hbar",
1916 ),
1917 (
1918 "Gravitational constant",
1919 "6.67430e-11",
1920 "N·m²/kg²",
1921 "G,gravity,gravitational,newton gravity",
1922 ),
1923 (
1924 "Elementary charge",
1925 "1.602176634e-19",
1926 "C",
1927 "e,electron charge,elementary charge,charge",
1928 ),
1929 (
1930 "Electron mass",
1931 "9.1093837015e-31",
1932 "kg",
1933 "me,electron mass,m_e",
1934 ),
1935 (
1936 "Proton mass",
1937 "1.67262192369e-27",
1938 "kg",
1939 "mp,proton mass,m_p",
1940 ),
1941 (
1942 "Neutron mass",
1943 "1.67492749804e-27",
1944 "kg",
1945 "mn,neutron mass,m_n",
1946 ),
1947 (
1948 "Avogadro constant",
1949 "6.02214076e23",
1950 "mol⁻¹",
1951 "NA,avogadro,avogadro constant,N_A",
1952 ),
1953 (
1954 "Boltzmann constant",
1955 "1.380649e-23",
1956 "J/K",
1957 "k,kb,boltzmann,boltzmann constant,k_B",
1958 ),
1959 (
1960 "Gas constant",
1961 "8.314462618",
1962 "J/(mol·K)",
1963 "R,gas constant,universal gas constant,molar gas",
1964 ),
1965 (
1966 "Stefan-Boltzmann",
1967 "5.670374419e-8",
1968 "W/(m²·K⁴)",
1969 "sigma,stefan,stefan-boltzmann,σ",
1970 ),
1971 (
1972 "Vacuum permittivity",
1973 "8.8541878128e-12",
1974 "F/m",
1975 "eps0,epsilon0,vacuum permittivity,ε₀",
1976 ),
1977 (
1978 "Vacuum permeability",
1979 "1.25663706212e-6",
1980 "N/A²",
1981 "mu0,mu_0,vacuum permeability,μ₀",
1982 ),
1983 (
1984 "Bohr radius",
1985 "5.29177210903e-11",
1986 "m",
1987 "a0,bohr,bohr radius,a_0",
1988 ),
1989 (
1990 "Fine structure constant",
1991 "7.2973525693e-3",
1992 "dimensionless",
1993 "alpha,fine structure,α",
1994 ),
1995 (
1996 "Rydberg constant",
1997 "10973731.568160",
1998 "m⁻¹",
1999 "Ry,rydberg,rydberg constant",
2000 ),
2001 (
2002 "Faraday constant",
2003 "96485.33212",
2004 "C/mol",
2005 "F,faraday,faraday constant",
2006 ),
2007 (
2008 "Standard gravity",
2009 "9.80665",
2010 "m/s²",
2011 "g,grav,standard gravity,g0,g_n",
2012 ),
2013 (
2014 "Atomic mass unit",
2015 "1.66053906660e-27",
2016 "kg",
2017 "amu,u,dalton,atomic mass unit",
2018 ),
2019 (
2020 "Standard atmosphere",
2021 "101325",
2022 "Pa",
2023 "atm,atmosphere,standard atmosphere",
2024 ),
2025 (
2026 "Electron volt",
2027 "1.602176634e-19",
2028 "J",
2029 "eV,electronvolt,electron volt",
2030 ),
2031 (
2032 "Speed of sound (20°C)",
2033 "343",
2034 "m/s",
2035 "sound,speed of sound,vsound",
2036 ),
2037 (
2038 "Molar volume (STP)",
2039 "22.414",
2040 "L/mol",
2041 "molar volume,vm,STP volume",
2042 ),
2043 (
2044 "Wien displacement",
2045 "2.897771955e-3",
2046 "m·K",
2047 "wien,wien displacement,b",
2048 ),
2049];
2050
2051pub fn physical_const(query: &str) -> String {
2052 let q = query.trim().to_lowercase();
2053 let mut out = String::new();
2054
2055 if q.is_empty() || q == "list" || q == "all" {
2056 let _ = writeln!(out, "Physical Constants (use --const NAME to look up)");
2057 let _ = writeln!(out, "{}", "─".repeat(60));
2058 for (name, val, unit, _) in CONSTANTS {
2059 let _ = writeln!(out, " {:<30} {} {}", name, val, unit);
2060 }
2061 return out;
2062 }
2063
2064 let matches: Vec<_> = CONSTANTS
2065 .iter()
2066 .filter(|(name, _, _, aliases)| {
2067 name.to_lowercase().contains(&q) || aliases.to_lowercase().contains(&q)
2068 })
2069 .collect();
2070
2071 if matches.is_empty() {
2072 let _ = writeln!(
2073 out,
2074 "No constant found for '{}'. Use --const list to see all.",
2075 query.trim()
2076 );
2077 return out;
2078 }
2079
2080 for (name, val, unit, aliases) in matches {
2081 let _ = writeln!(out, "{}", name);
2082 let _ = writeln!(out, " Value: {}", val);
2083 let _ = writeln!(out, " Unit: {}", unit);
2084 let _ = writeln!(out, " Aliases: {}", aliases);
2085 if let Ok(v) = val.parse::<f64>() {
2087 if v.abs() > 1e6 || v.abs() < 1e-4 {
2088 let _ = writeln!(out, " ≈ {:.6e}", v);
2089 }
2090 }
2091 let _ = writeln!(out);
2092 }
2093 out
2094}
2095
2096fn erf_approx(x: f64) -> f64 {
2100 let sign = if x < 0.0 { -1.0 } else { 1.0 };
2102 let x = x.abs();
2103 let t = 1.0 / (1.0 + 0.3275911 * x);
2104 let y = 1.0
2105 - (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t - 0.284496736) * t
2106 + 0.254829592)
2107 * t
2108 * (-x * x).exp();
2109 sign * y
2110}
2111
2112fn normal_cdf(x: f64, mu: f64, sigma: f64) -> f64 {
2113 0.5 * (1.0 + erf_approx((x - mu) / (sigma * 2.0f64.sqrt())))
2114}
2115
2116fn normal_pdf(x: f64, mu: f64, sigma: f64) -> f64 {
2117 let z = (x - mu) / sigma;
2118 (-0.5 * z * z).exp() / (sigma * (2.0 * std::f64::consts::PI).sqrt())
2119}
2120
2121fn normal_inv_cdf(p: f64) -> f64 {
2122 let p = p.clamp(1e-10, 1.0 - 1e-10);
2124 let (a, b) = if p < 0.5 {
2125 let t = (-2.0 * p.ln()).sqrt();
2126 let c = [2.515517, 0.802853, 0.010328];
2127 let d = [1.432788, 0.189269, 0.001308];
2128 let num = c[0] + c[1] * t + c[2] * t * t;
2129 let den = 1.0 + d[0] * t + d[1] * t * t + d[2] * t * t * t;
2130 (-(t - num / den), p)
2131 } else {
2132 let t = (-2.0 * (1.0 - p).ln()).sqrt();
2133 let c = [2.515517, 0.802853, 0.010328];
2134 let d = [1.432788, 0.189269, 0.001308];
2135 let num = c[0] + c[1] * t + c[2] * t * t;
2136 let den = 1.0 + d[0] * t + d[1] * t * t + d[2] * t * t * t;
2137 (t - num / den, p)
2138 };
2139 let _ = b;
2140 a
2141}
2142
2143pub fn stat_normal(query: &str) -> String {
2144 let q = query.trim().to_lowercase();
2145 let mut out = String::new();
2146
2147 let parts: Vec<&str> = q.split_whitespace().collect();
2149 if parts.is_empty() {
2150 out.push_str("Usage:\n --normal 'cdf 1.96' P(Z ≤ 1.96) for standard normal\n --normal 'cdf 70 60 10' P(X ≤ 70) for N(60, 10)\n --normal 'pdf 1.96' Standard normal PDF at x=1.96\n --normal 'inv 0.975' z-score for 97.5th percentile\n --normal 'between -1.96 1.96' P(-1.96 ≤ Z ≤ 1.96)");
2151 return out;
2152 }
2153
2154 let parse_f = |s: &str| s.parse::<f64>().ok();
2155
2156 match parts[0] {
2157 "cdf" if parts.len() >= 2 => {
2158 let x = parse_f(parts[1]).unwrap_or(0.0);
2159 let mu = if parts.len() > 2 {
2160 parse_f(parts[2]).unwrap_or(0.0)
2161 } else {
2162 0.0
2163 };
2164 let sg = if parts.len() > 3 {
2165 parse_f(parts[3]).unwrap_or(1.0)
2166 } else {
2167 1.0
2168 };
2169 let p = normal_cdf(x, mu, sg);
2170 let z = (x - mu) / sg;
2171 let _ = writeln!(out, "Distribution: N({}, {})", mu, sg);
2172 let _ = writeln!(out, "x = {}", x);
2173 let _ = writeln!(out, "z-score = {:.6}", z);
2174 let _ = writeln!(out, "P(X ≤ {}) = {:.8} ({:.4}%)", x, p, p * 100.0);
2175 let _ = writeln!(
2176 out,
2177 "P(X > {}) = {:.8} ({:.4}%)",
2178 x,
2179 1.0 - p,
2180 (1.0 - p) * 100.0
2181 );
2182 }
2183 "pdf" if parts.len() >= 2 => {
2184 let x = parse_f(parts[1]).unwrap_or(0.0);
2185 let mu = if parts.len() > 2 {
2186 parse_f(parts[2]).unwrap_or(0.0)
2187 } else {
2188 0.0
2189 };
2190 let sg = if parts.len() > 3 {
2191 parse_f(parts[3]).unwrap_or(1.0)
2192 } else {
2193 1.0
2194 };
2195 let p = normal_pdf(x, mu, sg);
2196 let _ = writeln!(out, "PDF at x={}: {:.8}", x, p);
2197 }
2198 "inv" | "quantile" | "ppf" if parts.len() >= 2 => {
2199 let p = parse_f(parts[1]).unwrap_or(0.5);
2200 let mu = if parts.len() > 2 {
2201 parse_f(parts[2]).unwrap_or(0.0)
2202 } else {
2203 0.0
2204 };
2205 let sg = if parts.len() > 3 {
2206 parse_f(parts[3]).unwrap_or(1.0)
2207 } else {
2208 1.0
2209 };
2210 let z = normal_inv_cdf(p);
2211 let x = mu + sg * z;
2212 let _ = writeln!(out, "P = {} → z-score = {:.6} → x = {:.6}", p, z, x);
2213 let _ = writeln!(out, "Interpretation: P(X ≤ {:.6}) = {:.4}%", x, p * 100.0);
2214 }
2215 "between" | "interval" if parts.len() >= 3 => {
2216 let a = parse_f(parts[1]).unwrap_or(-1.96);
2217 let b = parse_f(parts[2]).unwrap_or(1.96);
2218 let mu = if parts.len() > 3 {
2219 parse_f(parts[3]).unwrap_or(0.0)
2220 } else {
2221 0.0
2222 };
2223 let sg = if parts.len() > 4 {
2224 parse_f(parts[4]).unwrap_or(1.0)
2225 } else {
2226 1.0
2227 };
2228 let p = normal_cdf(b, mu, sg) - normal_cdf(a, mu, sg);
2229 let _ = writeln!(out, "P({} ≤ X ≤ {}) = {:.8} ({:.4}%)", a, b, p, p * 100.0);
2230 }
2231 "table" | "z-table" => {
2232 let _ = writeln!(out, "Standard Normal CDF P(Z ≤ z)");
2233 let _ = writeln!(out, " z P(Z ≤ z) P(Z > z)");
2234 let _ = writeln!(out, " ─────────────────────────────");
2235 for &z in &[
2236 -3.0f64, -2.576, -2.326, -1.960, -1.645, -1.282, -0.842, 0.0, 0.842, 1.282, 1.645,
2237 1.960, 2.326, 2.576, 3.0,
2238 ] {
2239 let p = normal_cdf(z, 0.0, 1.0);
2240 let _ = writeln!(out, " {:6.3} {:.6} {:.6}", z, p, 1.0 - p);
2241 }
2242 }
2243 _ => {
2244 if let Some(z) = parse_f(parts[0]) {
2246 let p = normal_cdf(z, 0.0, 1.0);
2247 let _ = writeln!(out, "Standard normal CDF at z={}: {:.8}", z, p);
2248 } else {
2249 out.push_str("Usage: --normal 'cdf 1.96' --normal 'inv 0.975' --normal 'table'\n --normal 'between -1.96 1.96' --normal 'pdf 0'");
2250 }
2251 }
2252 }
2253 out
2254}
2255
2256pub fn prob_calc(query: &str) -> String {
2272 use std::f64::consts::PI;
2273 let q = query.trim().to_lowercase();
2274 if q.is_empty() || q == "help" || q == "?" {
2275 return prob_usage();
2276 }
2277
2278 let parts: Vec<&str> = q.split_whitespace().collect();
2279 if parts.is_empty() {
2280 return prob_usage();
2281 }
2282
2283 let get_param = |parts: &[&str], name: &str, default: f64| -> f64 {
2285 parts
2286 .iter()
2287 .find(|s| s.starts_with(name))
2288 .and_then(|s| s.split_once('=').map(|x| x.1))
2289 .and_then(|v| v.parse().ok())
2290 .unwrap_or(default)
2291 };
2292 let get_positional = |parts: &[&str], idx: usize| -> Option<f64> {
2293 parts
2294 .iter()
2295 .filter(|s| !s.contains('='))
2296 .nth(idx)
2297 .and_then(|s| s.parse().ok())
2298 };
2299
2300 let dist = parts[0];
2301 let op_candidate = parts.get(1).copied().unwrap_or("all");
2303 let (op, data_start) = if matches!(
2304 op_candidate,
2305 "cdf" | "pdf" | "pmf" | "inv" | "quantile" | "between" | "all"
2306 ) {
2307 (op_candidate, 2usize)
2308 } else {
2309 ("all", 1usize)
2310 };
2311 let data: &[&str] = &parts[data_start..];
2312
2313 let mut out = String::new();
2314 let sep = "=".repeat(64);
2315
2316 match dist {
2317 "normal" | "norm" | "gaussian" => {
2318 let x = get_positional(data, 0).unwrap_or(0.0);
2319 let mu = get_param(data, "mu", get_param(data, "mean", 0.0));
2320 let sd = get_param(
2321 data,
2322 "sd",
2323 get_param(data, "sigma", get_param(data, "std", 1.0)),
2324 );
2325 let _ = writeln!(out, "{}", sep);
2326 let _ = writeln!(out, " Normal Distribution N(μ={}, σ={})", mu, sd);
2327 let _ = writeln!(out, "{}", sep);
2328 if op == "pdf" || op == "all" {
2329 let _ = writeln!(out, " PDF f({}) = {:.8}", x, normal_pdf(x, mu, sd));
2330 }
2331 if op == "cdf" || op == "all" {
2332 let p = normal_cdf(x, mu, sd);
2333 let _ = writeln!(out, " CDF P(X ≤ {}) = {:.8}", x, p);
2334 let _ = writeln!(out, " P(X > {}) = {:.8}", x, 1.0 - p);
2335 let z = (x - mu) / sd;
2336 let _ = writeln!(out, " z-score = {:.4}", z);
2337 }
2338 if op == "inv" || op == "quantile" {
2339 let p = get_positional(data, 0).unwrap_or(0.975);
2340 let z = normal_inv_cdf(p);
2341 let _ = writeln!(out, " Quantile p={:.4} → x = {:.6}", p, mu + sd * z);
2342 }
2343 if op == "between" {
2344 let a = get_positional(data, 0).unwrap_or(-1.0);
2345 let b = get_positional(data, 1).unwrap_or(1.0);
2346 let p = normal_cdf(b, mu, sd) - normal_cdf(a, mu, sd);
2347 let _ = writeln!(out, " P({} ≤ X ≤ {}) = {:.8}", a, b, p);
2348 }
2349 if op == "all" {
2350 let _ = writeln!(
2352 out,
2353 " Quantiles: p=.025 → {:.4} p=.975 → {:.4} p=.5 → {:.4}",
2354 mu + sd * normal_inv_cdf(0.025),
2355 mu + sd * normal_inv_cdf(0.975),
2356 mu
2357 );
2358 }
2359 }
2360 "binomial" | "binom" | "bin" => {
2361 let k = get_positional(data, 0).unwrap_or(0.0) as i64;
2362 let n = get_param(data, "n", 10.0) as u64;
2363 let p = get_param(data, "p", 0.5);
2364 let _ = writeln!(out, "{}", sep);
2365 let _ = writeln!(out, " Binomial Distribution Bin(n={}, p={})", n, p);
2366 let _ = writeln!(out, "{}", sep);
2367 let mean = n as f64 * p;
2368 let var = n as f64 * p * (1.0 - p);
2369 let _ = writeln!(
2370 out,
2371 " Mean={:.4} Var={:.4} StdDev={:.4}",
2372 mean,
2373 var,
2374 var.sqrt()
2375 );
2376 let binom_pmf = |k: i64, n: u64, p: f64| -> f64 {
2377 if k < 0 || k > n as i64 {
2378 return 0.0;
2379 }
2380 let k = k as u64;
2381 let log_binom: f64 = log_factorial(n) - log_factorial(k) - log_factorial(n - k);
2383 (log_binom + k as f64 * p.ln() + (n - k) as f64 * (1.0 - p).ln()).exp()
2384 };
2385 if op == "pmf" || op == "all" {
2386 let pmf = binom_pmf(k, n, p);
2387 let _ = writeln!(out, " PMF P(X={}) = {:.8}", k, pmf);
2388 }
2389 if op == "cdf" || op == "all" {
2390 let cdf: f64 = (0..=k).map(|i| binom_pmf(i, n, p)).sum();
2391 let _ = writeln!(out, " CDF P(X≤{}) = {:.8}", k, cdf);
2392 let _ = writeln!(out, " P(X>{}) = {:.8}", k, 1.0 - cdf);
2393 }
2394 if op == "all" {
2395 if n <= 20 {
2397 let _ = writeln!(out, " PMF table:");
2398 for i in 0..=n {
2399 let pmf = binom_pmf(i as i64, n, p);
2400 let bar = (pmf * 50.0) as usize;
2401 let _ = writeln!(out, " k={:>3} {:.6} {}", i, pmf, "#".repeat(bar));
2402 }
2403 }
2404 }
2405 }
2406 "poisson" | "pois" => {
2407 let k = get_positional(data, 0).unwrap_or(0.0) as i64;
2408 let lam = get_param(data, "lam", get_param(data, "lambda", 1.0));
2409 let _ = writeln!(out, "{}", sep);
2410 let _ = writeln!(out, " Poisson Distribution Poi(λ={})", lam);
2411 let _ = writeln!(out, "{}", sep);
2412 let _ = writeln!(
2413 out,
2414 " Mean={:.4} Var={:.4} StdDev={:.4}",
2415 lam,
2416 lam,
2417 lam.sqrt()
2418 );
2419 let pois_pmf = |k: i64, lam: f64| -> f64 {
2420 if k < 0 {
2421 return 0.0;
2422 }
2423 (-lam + k as f64 * lam.ln() - log_factorial(k as u64)).exp()
2424 };
2425 if op == "pmf" || op == "all" {
2426 let _ = writeln!(out, " PMF P(X={}) = {:.8}", k, pois_pmf(k, lam));
2427 }
2428 if op == "cdf" || op == "all" {
2429 let cdf: f64 = (0..=k).map(|i| pois_pmf(i, lam)).sum();
2430 let _ = writeln!(out, " CDF P(X≤{}) = {:.8}", k, cdf);
2431 let _ = writeln!(out, " P(X>{}) = {:.8}", k, 1.0 - cdf);
2432 }
2433 if op == "all" && lam <= 30.0 {
2434 let hi = (lam + 4.0 * lam.sqrt()).ceil() as i64;
2435 let _ = writeln!(out, " PMF table (up to k={}):", hi);
2436 for i in 0..=hi.min(40) {
2437 let pmf = pois_pmf(i, lam);
2438 let bar = (pmf * 60.0) as usize;
2439 let _ = writeln!(out, " k={:>3} {:.6} {}", i, pmf, "#".repeat(bar));
2440 }
2441 }
2442 }
2443 "t" | "student" | "t-dist" => {
2444 let x = get_positional(data, 0).unwrap_or(0.0);
2445 let df = get_param(data, "df", get_param(data, "dof", 1.0));
2446 let _ = writeln!(out, "{}", sep);
2447 let _ = writeln!(out, " Student's t Distribution t(df={})", df);
2448 let _ = writeln!(out, "{}", sep);
2449 let t_pdf = |x: f64, df: f64| -> f64 {
2450 let lg_n = lgamma((df + 1.0) / 2.0);
2451 let lg_d = lgamma(df / 2.0);
2452 let coef = (lg_n - lg_d - 0.5 * (df * PI).ln()).exp();
2453 coef * (1.0 + x * x / df).powf(-(df + 1.0) / 2.0)
2454 };
2455 let t_cdf = |x: f64, df: f64| -> f64 {
2457 let t2 = x * x;
2458 let z = df / (df + t2);
2459 let ib = reg_inc_beta(df / 2.0, 0.5, z);
2460 if x >= 0.0 {
2461 1.0 - 0.5 * ib
2462 } else {
2463 0.5 * ib
2464 }
2465 };
2466 if op == "pdf" || op == "all" {
2467 let _ = writeln!(out, " PDF f({:.4}) = {:.8}", x, t_pdf(x, df));
2468 }
2469 if op == "cdf" || op == "all" {
2470 let p = t_cdf(x, df);
2471 let _ = writeln!(out, " CDF P(T≤{:.4}) = {:.8}", x, p);
2472 let _ = writeln!(
2473 out,
2474 " Two-tailed P = {:.8} (p-value for |T|≥{:.4})",
2475 2.0 * (1.0 - t_cdf(x.abs(), df)),
2476 x.abs()
2477 );
2478 }
2479 if op == "all" {
2480 let _ = writeln!(out, " Critical values (two-tailed):");
2482 for alpha in [0.10, 0.05, 0.01, 0.001] {
2483 let target = 1.0 - alpha / 2.0;
2485 let cv = bisect(|t| t_cdf(t, df) - target, 0.0, 100.0, 60);
2486 let _ = writeln!(out, " α={:.3} t* = {:.4}", alpha, cv);
2487 }
2488 }
2489 }
2490 "chi2" | "chisquare" | "chi-square" | "chi_sq" => {
2491 let x = get_positional(data, 0).unwrap_or(1.0);
2492 let df = get_param(data, "df", get_param(data, "dof", 1.0));
2493 let _ = writeln!(out, "{}", sep);
2494 let _ = writeln!(out, " Chi-Square Distribution χ²(df={})", df);
2495 let _ = writeln!(out, "{}", sep);
2496 let _ = writeln!(
2497 out,
2498 " Mean={:.4} Var={:.4} StdDev={:.4}",
2499 df,
2500 2.0 * df,
2501 (2.0 * df).sqrt()
2502 );
2503 let chi2_pdf = |x: f64, k: f64| -> f64 {
2504 if x <= 0.0 {
2505 return 0.0;
2506 }
2507 let lg = lgamma(k / 2.0);
2508 ((k / 2.0 - 1.0) * x.ln() - x / 2.0 - (k / 2.0) * 2.0f64.ln() - lg).exp()
2509 };
2510 let chi2_cdf = |x: f64, k: f64| -> f64 {
2512 if x <= 0.0 {
2513 return 0.0;
2514 }
2515 reg_inc_gamma(k / 2.0, x / 2.0)
2516 };
2517 if op == "pdf" || op == "all" {
2518 let _ = writeln!(out, " PDF f({:.4}) = {:.8}", x, chi2_pdf(x, df));
2519 }
2520 if op == "cdf" || op == "all" {
2521 let p = chi2_cdf(x, df);
2522 let _ = writeln!(out, " CDF P(X≤{:.4}) = {:.8}", x, p);
2523 let _ = writeln!(out, " P-value (upper) = {:.8}", 1.0 - p);
2524 }
2525 if op == "all" {
2526 let _ = writeln!(out, " Critical values (upper tail):");
2527 for alpha in [0.10, 0.05, 0.025, 0.01] {
2528 let cv = bisect(|t| chi2_cdf(t, df) - (1.0 - alpha), 0.0, df + 100.0, 80);
2529 let _ = writeln!(out, " α={:.3} χ² = {:.4}", alpha, cv);
2530 }
2531 }
2532 }
2533 "exponential" | "exp" | "expon" => {
2534 let x = get_positional(data, 0).unwrap_or(1.0);
2535 let lam = get_param(
2536 data,
2537 "lam",
2538 get_param(data, "lambda", get_param(data, "rate", 1.0)),
2539 );
2540 let _ = writeln!(out, "{}", sep);
2541 let _ = writeln!(out, " Exponential Distribution Exp(λ={})", lam);
2542 let _ = writeln!(out, "{}", sep);
2543 let _ = writeln!(
2544 out,
2545 " Mean={:.4} StdDev={:.4} Median={:.4}",
2546 1.0 / lam,
2547 1.0 / lam,
2548 2.0f64.ln() / lam
2549 );
2550 if op == "pdf" || op == "all" {
2551 let pdf = if x >= 0.0 {
2552 lam * (-lam * x).exp()
2553 } else {
2554 0.0
2555 };
2556 let _ = writeln!(out, " PDF f({:.4}) = {:.8}", x, pdf);
2557 }
2558 if op == "cdf" || op == "all" {
2559 let p = if x >= 0.0 {
2560 1.0 - (-lam * x).exp()
2561 } else {
2562 0.0
2563 };
2564 let _ = writeln!(out, " CDF P(X≤{:.4}) = {:.8}", x, p);
2565 let _ = writeln!(out, " P(X>{:.4}) = {:.8}", x, 1.0 - p);
2566 let _ = writeln!(out, " Quantile p=.5 → {:.6} (median)", 2.0f64.ln() / lam);
2567 }
2568 }
2569 "uniform" | "unif" => {
2570 let x = get_positional(data, 0).unwrap_or(0.5);
2571 let a = get_param(data, "a", get_param(data, "lo", 0.0));
2572 let b = get_param(data, "b", get_param(data, "hi", 1.0));
2573 let _ = writeln!(out, "{}", sep);
2574 let _ = writeln!(out, " Uniform Distribution U({}, {})", a, b);
2575 let _ = writeln!(out, "{}", sep);
2576 let rng = b - a;
2577 let _ = writeln!(
2578 out,
2579 " Mean={:.4} Var={:.6} StdDev={:.4}",
2580 (a + b) / 2.0,
2581 rng * rng / 12.0,
2582 (rng * rng / 12.0).sqrt()
2583 );
2584 if op == "pdf" || op == "all" {
2585 let pdf = if x >= a && x <= b { 1.0 / rng } else { 0.0 };
2586 let _ = writeln!(out, " PDF f({:.4}) = {:.8}", x, pdf);
2587 }
2588 if op == "cdf" || op == "all" {
2589 let p = if x < a {
2590 0.0
2591 } else if x > b {
2592 1.0
2593 } else {
2594 (x - a) / rng
2595 };
2596 let _ = writeln!(out, " CDF P(X≤{:.4}) = {:.8}", x, p);
2597 }
2598 }
2599 "geometric" | "geo" | "geom" => {
2600 let k = get_positional(data, 0).unwrap_or(1.0) as i64;
2601 let p = get_param(data, "p", 0.5);
2602 let _ = writeln!(out, "{}", sep);
2603 let _ = writeln!(
2604 out,
2605 " Geometric Distribution Geo(p={}) [trials until first success]",
2606 p
2607 );
2608 let _ = writeln!(out, "{}", sep);
2609 let _ = writeln!(
2610 out,
2611 " Mean={:.4} Var={:.4} StdDev={:.4}",
2612 1.0 / p,
2613 (1.0 - p) / (p * p),
2614 ((1.0 - p) / (p * p)).sqrt()
2615 );
2616 let geo_pmf = |k: i64, p: f64| -> f64 {
2617 if k < 1 {
2618 return 0.0;
2619 }
2620 (1.0 - p).powi((k - 1) as i32) * p
2621 };
2622 if op == "pmf" || op == "all" {
2623 let _ = writeln!(out, " PMF P(X={}) = {:.8}", k, geo_pmf(k, p));
2624 }
2625 if op == "cdf" || op == "all" {
2626 let cdf = 1.0 - (1.0 - p).powi(k as i32);
2627 let _ = writeln!(out, " CDF P(X≤{}) = {:.8}", k, cdf);
2628 let _ = writeln!(out, " P(X>{}) = {:.8}", k, 1.0 - cdf);
2629 }
2630 if op == "all" {
2631 let _ = writeln!(out, " PMF table:");
2632 for i in 1..=10i64.min((10.0 / p) as i64) {
2633 let pmf = geo_pmf(i, p);
2634 let bar = (pmf * 50.0) as usize;
2635 let _ = writeln!(out, " k={:>3} {:.6} {}", i, pmf, "#".repeat(bar));
2636 }
2637 }
2638 }
2639 _ => {
2640 out.push_str(&prob_usage());
2641 }
2642 }
2643
2644 if out.trim().is_empty() || out.starts_with("Probability") {
2645 return out;
2646 }
2647 let _ = writeln!(out, "{}", sep);
2648 out
2649}
2650
2651fn log_factorial(n: u64) -> f64 {
2652 (1..=n).map(|i| (i as f64).ln()).sum::<f64>()
2653}
2654
2655fn lgamma(x: f64) -> f64 {
2656 let g = 7.0;
2658 let c: [f64; 9] = [
2659 0.999_999_999_999_809_9,
2660 676.5203681218851,
2661 -1259.1392167224028,
2662 771.323_428_777_653_1,
2663 -176.615_029_162_140_6,
2664 12.507343278686905,
2665 -0.13857109526572012,
2666 9.984_369_578_019_572e-6,
2667 1.5056327351493116e-7,
2668 ];
2669 if x < 0.5 {
2670 PI.ln() - (PI * x).sin().ln() - lgamma(1.0 - x)
2671 } else {
2672 let x = x - 1.0;
2673 let mut a = c[0];
2674 for i in 1..9 {
2675 a += c[i] / (x + i as f64);
2676 }
2677 let t = x + g + 0.5;
2678 0.5 * (2.0 * PI).ln() + (x + 0.5) * t.ln() - t + a.ln()
2679 }
2680}
2681
2682fn reg_inc_beta(a: f64, b: f64, x: f64) -> f64 {
2683 if x <= 0.0 {
2685 return 0.0;
2686 }
2687 if x >= 1.0 {
2688 return 1.0;
2689 }
2690 if x > (a + 1.0) / (a + b + 2.0) {
2692 return 1.0 - reg_inc_beta(b, a, 1.0 - x);
2693 }
2694 let lbeta = lgamma(a) + lgamma(b) - lgamma(a + b);
2695 let front = (a * x.ln() + b * (1.0 - x).ln() - lbeta).exp() / a;
2696 let mut c_cf = 1.0;
2698 let mut d_cf = 1.0 - (a + b) * x / (a + 1.0);
2699 if d_cf.abs() < 1e-30 {
2700 d_cf = 1e-30;
2701 }
2702 d_cf = 1.0 / d_cf;
2703 let mut f = d_cf;
2704 for m in 1..200usize {
2705 let mf = m as f64;
2706 let nm = mf * (b - mf) * x / ((a + 2.0 * mf - 1.0) * (a + 2.0 * mf));
2708 d_cf = 1.0 + nm * d_cf;
2709 if d_cf.abs() < 1e-30 {
2710 d_cf = 1e-30;
2711 }
2712 c_cf = 1.0 + nm / c_cf;
2713 if c_cf.abs() < 1e-30 {
2714 c_cf = 1e-30;
2715 }
2716 d_cf = 1.0 / d_cf;
2717 f *= d_cf * c_cf;
2718 let nm2 = -(a + mf) * (a + b + mf) * x / ((a + 2.0 * mf) * (a + 2.0 * mf + 1.0));
2720 d_cf = 1.0 + nm2 * d_cf;
2721 if d_cf.abs() < 1e-30 {
2722 d_cf = 1e-30;
2723 }
2724 c_cf = 1.0 + nm2 / c_cf;
2725 if c_cf.abs() < 1e-30 {
2726 c_cf = 1e-30;
2727 }
2728 d_cf = 1.0 / d_cf;
2729 let delta = d_cf * c_cf;
2730 f *= delta;
2731 if (delta - 1.0).abs() < 3e-7 {
2732 break;
2733 }
2734 }
2735 front * f
2736}
2737
2738fn reg_inc_gamma(a: f64, x: f64) -> f64 {
2739 if x <= 0.0 {
2741 return 0.0;
2742 }
2743 if x > a + 1.0 {
2744 return 1.0 - reg_inc_gamma_upper(a, x);
2746 }
2747 let mut sum = 1.0 / a;
2748 let mut term = 1.0 / a;
2749 for n in 1..200u64 {
2750 term *= x / (a + n as f64);
2751 sum += term;
2752 if term.abs() < 1e-10 * sum.abs() {
2753 break;
2754 }
2755 }
2756 sum * (-x + a * x.ln() - lgamma(a)).exp()
2757}
2758
2759fn reg_inc_gamma_upper(a: f64, x: f64) -> f64 {
2760 let mut c_cf = 1.0;
2762 let b0 = x + 1.0 - a;
2763 let mut d_cf = if b0.abs() < 1e-30 { 1e30 } else { 1.0 / b0 };
2764 let mut f = d_cf;
2765 for i in 1..200u64 {
2766 let ai = -(i as f64) * (i as f64 - a);
2767 let bi = x + (2 * i + 1) as f64 - a;
2768 d_cf = bi + ai * d_cf;
2769 if d_cf.abs() < 1e-30 {
2770 d_cf = 1e-30;
2771 }
2772 c_cf = bi + ai / c_cf;
2773 if c_cf.abs() < 1e-30 {
2774 c_cf = 1e-30;
2775 }
2776 d_cf = 1.0 / d_cf;
2777 let delta = d_cf * c_cf;
2778 f *= delta;
2779 if (delta - 1.0).abs() < 3e-7 {
2780 break;
2781 }
2782 }
2783 f * (-x + a * x.ln() - lgamma(a)).exp()
2784}
2785
2786fn bisect<F: Fn(f64) -> f64>(f: F, mut lo: f64, mut hi: f64, iters: usize) -> f64 {
2787 for _ in 0..iters {
2788 let mid = (lo + hi) / 2.0;
2789 if f(mid) < 0.0 {
2790 lo = mid;
2791 } else {
2792 hi = mid;
2793 }
2794 }
2795 (lo + hi) / 2.0
2796}
2797
2798fn prob_usage() -> String {
2799 "Probability distributions — instant, no model, no cloud:\n\
2800 \n\
2801 hematite --probability 'normal cdf 1.96' P(Z ≤ 1.96) std normal\n\
2802 hematite --probability 'normal cdf 70 mu=60 sd=10' P(X ≤ 70) for N(60,10)\n\
2803 hematite --probability 'binomial pmf 3 n=10 p=0.4' P(X=3) Bin(10,0.4)\n\
2804 hematite --probability 'binomial cdf 5 n=10 p=0.4' P(X≤5) Bin(10,0.4)\n\
2805 hematite --probability 'poisson pmf 2 lam=3' P(X=2) Poi(3)\n\
2806 hematite --probability 't cdf 2.0 df=9' P(T≤2.0) t(9)\n\
2807 hematite --probability 'chi2 cdf 5.99 df=2' P(X≤5.99) chi²(2)\n\
2808 hematite --probability 'exponential cdf 1.0 lam=0.5' P(X≤1) Exp(0.5)\n\
2809 hematite --probability 'uniform cdf 0.7 a=0 b=1' P(X≤0.7) U(0,1)\n\
2810 hematite --probability 'geometric pmf 3 p=0.5' P(X=3) Geo(0.5)\n\
2811 \n\
2812 Operations: pdf/pmf cdf inv/quantile between (default: show all)\n\
2813 Distributions: normal binomial poisson t chi2 exponential uniform geometric"
2814 .into()
2815}
2816
2817pub fn unit_convert(query: &str) -> String {
2823 let q = query.trim();
2824 if q.eq_ignore_ascii_case("list")
2825 || q.eq_ignore_ascii_case("units")
2826 || q.eq_ignore_ascii_case("help")
2827 {
2828 return unit_convert_list();
2829 }
2830
2831 let lower = q.to_lowercase();
2834 let sep = if lower.contains(" to ") {
2835 " to "
2836 } else if lower.contains(" in ") {
2837 " in "
2838 } else {
2839 ""
2840 };
2841
2842 if sep.is_empty() {
2843 return "Usage: hematite --convert '5 km to miles'\n\
2844 Common examples:\n\
2845 hematite --convert '100 f to c'\n\
2846 hematite --convert '1 atm to Pa'\n\
2847 hematite --convert '60 mph to km/h'\n\
2848 hematite --convert '1 GiB to MB'\n\
2849 hematite --convert '1 cal to J'\n\
2850 hematite --convert 'list' (show all units)"
2851 .to_string();
2852 }
2853
2854 let parts: Vec<&str> = q
2855 .splitn(2, &sep.to_uppercase().as_str().to_string())
2856 .collect();
2857 let parts: Vec<&str> = if parts.len() < 2 {
2858 q.splitn(2, sep).collect()
2859 } else {
2860 parts
2861 };
2862
2863 if parts.len() < 2 {
2864 return format!("Could not parse: '{}'. Try: '5 km to miles'", q);
2865 }
2866
2867 let lhs = parts[0].trim();
2868 let to_unit = parts[1].trim();
2869
2870 let (value_str, from_unit) = split_value_unit(lhs);
2872 let value: f64 = match value_str.parse() {
2873 Ok(v) => v,
2874 Err(_) => return format!("Cannot parse value: '{}'", value_str),
2875 };
2876
2877 match convert(value, from_unit.trim(), to_unit.trim()) {
2878 Ok((result, category)) => {
2879 let result_str = if result.abs() >= 1e-3 && result.abs() < 1e7 {
2880 format!("{:.8}", result)
2881 .trim_end_matches('0')
2882 .trim_end_matches('.')
2883 .to_string()
2884 } else {
2885 format!("{:.6e}", result)
2886 };
2887 format!(
2888 "{} {} = {} {}\n({})",
2889 value_str.trim(),
2890 from_unit.trim(),
2891 result_str,
2892 to_unit.trim(),
2893 category
2894 )
2895 }
2896 Err(e) => e,
2897 }
2898}
2899
2900fn split_value_unit(s: &str) -> (&str, &str) {
2901 let s = s.trim();
2903 let mut end = 0;
2904 for (i, c) in s.char_indices() {
2905 if c.is_ascii_digit() || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E' {
2906 end = i + c.len_utf8();
2907 } else if i == 0 && (c == '-' || c == '+') {
2908 end = 1;
2909 } else if i > 0 {
2910 break;
2911 }
2912 }
2913 if end == 0 {
2914 end = s.len();
2915 }
2916 (&s[..end], s[end..].trim())
2917}
2918
2919fn convert(value: f64, from: &str, to: &str) -> Result<(f64, &'static str), String> {
2921 let from_n = norm_unit(from);
2923 let to_n = norm_unit(to);
2924
2925 for (cat_name, units) in UNIT_CATEGORIES {
2927 let from_entry = units
2929 .iter()
2930 .find(|(names, _)| names.contains(&from_n.as_str()));
2931 let to_entry = units
2932 .iter()
2933 .find(|(names, _)| names.contains(&to_n.as_str()));
2934
2935 if let (Some(fe), Some(te)) = (from_entry, to_entry) {
2936 if *cat_name == "Temperature" {
2938 return Ok((
2939 convert_temperature(value, from_n.as_str(), to_n.as_str()),
2940 "Temperature",
2941 ));
2942 }
2943 let si = value * fe.1;
2945 let result = si / te.1;
2946 return Ok((result, cat_name));
2947 }
2948 }
2949
2950 let known: Vec<&str> = UNIT_CATEGORIES
2952 .iter()
2953 .flat_map(|(_, units)| units.iter().flat_map(|(names, _)| names.iter().copied()))
2954 .collect();
2955 let mut close: Vec<&str> = known
2956 .iter()
2957 .filter(|n| levenshtein(n, from_n.as_str()) <= 2)
2958 .copied()
2959 .collect();
2960 close.extend(
2961 known
2962 .iter()
2963 .filter(|n| levenshtein(n, to_n.as_str()) <= 2)
2964 .copied(),
2965 );
2966 close.dedup();
2967
2968 if close.is_empty() {
2969 Err(format!(
2970 "Unknown units: '{}' or '{}'. Run 'hematite --convert list' to see all.",
2971 from, to
2972 ))
2973 } else {
2974 Err(format!("Unknown unit(s). Did you mean: {}?\nRun 'hematite --convert list' for all supported units.", close.join(", ")))
2975 }
2976}
2977
2978fn norm_unit(s: &str) -> String {
2979 s.trim()
2980 .to_lowercase()
2981 .replace("°", "")
2982 .replace("²", "2")
2983 .replace("³", "3")
2984 .replace("/s", "_per_s")
2985 .replace("per second", "_per_s")
2986}
2987
2988fn convert_temperature(value: f64, from: &str, to: &str) -> f64 {
2989 let kelvin = match from {
2991 "c" | "celsius" => value + 273.15,
2992 "f" | "fahrenheit" => (value - 32.0) * 5.0 / 9.0 + 273.15,
2993 "k" | "kelvin" => value,
2994 "r" | "rankine" => value * 5.0 / 9.0,
2995 _ => value,
2996 };
2997 match to {
2998 "c" | "celsius" => kelvin - 273.15,
2999 "f" | "fahrenheit" => (kelvin - 273.15) * 9.0 / 5.0 + 32.0,
3000 "k" | "kelvin" => kelvin,
3001 "r" | "rankine" => kelvin * 9.0 / 5.0,
3002 _ => kelvin,
3003 }
3004}
3005
3006fn levenshtein(a: &str, b: &str) -> usize {
3007 let a: Vec<char> = a.chars().collect();
3008 let b: Vec<char> = b.chars().collect();
3009 let (m, n) = (a.len(), b.len());
3010 let mut dp = vec![vec![0usize; n + 1]; m + 1];
3011 for i in 0..=m {
3012 dp[i][0] = i;
3013 }
3014 for j in 0..=n {
3015 dp[0][j] = j;
3016 }
3017 for i in 1..=m {
3018 for j in 1..=n {
3019 dp[i][j] = if a[i - 1] == b[j - 1] {
3020 dp[i - 1][j - 1]
3021 } else {
3022 1 + dp[i - 1][j].min(dp[i][j - 1]).min(dp[i - 1][j - 1])
3023 };
3024 }
3025 }
3026 dp[m][n]
3027}
3028
3029type UnitEntry = (&'static [&'static str], f64);
3032type UnitCategory = (&'static str, &'static [UnitEntry]);
3033
3034static UNIT_CATEGORIES: &[UnitCategory] = &[
3035 (
3036 "Length",
3037 &[
3038 (&["m", "meter", "meters", "metre", "metres"], 1.0),
3039 (
3040 &["km", "kilometer", "kilometers", "kilometre", "kilometres"],
3041 1000.0,
3042 ),
3043 (
3044 &[
3045 "cm",
3046 "centimeter",
3047 "centimeters",
3048 "centimetre",
3049 "centimetres",
3050 ],
3051 0.01,
3052 ),
3053 (
3054 &[
3055 "mm",
3056 "millimeter",
3057 "millimeters",
3058 "millimetre",
3059 "millimetres",
3060 ],
3061 0.001,
3062 ),
3063 (
3064 &["um", "micrometer", "micrometers", "micron", "microns"],
3065 1e-6,
3066 ),
3067 (
3068 &["nm", "nanometer", "nanometers", "nanometre", "nanometres"],
3069 1e-9,
3070 ),
3071 (&["mi", "mile", "miles"], 1609.344),
3072 (&["yd", "yard", "yards"], 0.9144),
3073 (&["ft", "foot", "feet"], 0.3048),
3074 (&["in", "inch", "inches"], 0.0254),
3075 (&["nmi", "nautical_mile", "nautical_miles"], 1852.0),
3076 (
3077 &["ly", "light_year", "light_years", "lightyear", "lightyears"],
3078 9.460_730_472_580_8e15,
3079 ),
3080 (
3081 &["au", "astronomical_unit", "astronomical_units"],
3082 1.495978707e11,
3083 ),
3084 (&["pc", "parsec", "parsecs"], 3.085677581e16),
3085 (&["ang", "angstrom", "angstroms"], 1e-10),
3086 ],
3087 ),
3088 (
3089 "Mass",
3090 &[
3091 (&["kg", "kilogram", "kilograms", "kilogramme"], 1.0),
3092 (&["g", "gram", "grams", "gramme"], 0.001),
3093 (&["mg", "milligram", "milligrams", "milligramme"], 1e-6),
3094 (&["ug", "microgram", "micrograms"], 1e-9),
3095 (
3096 &["t", "tonne", "tonnes", "metric_ton", "metric_tons"],
3097 1000.0,
3098 ),
3099 (&["lb", "lbs", "pound", "pounds"], 0.45359237),
3100 (&["oz", "ounce", "ounces"], 0.028349523125),
3101 (&["st", "stone", "stones"], 6.35029318),
3102 (&["ton", "short_ton", "short_tons"], 907.18474),
3103 (&["long_ton", "long_tons"], 1016.0469088),
3104 (&["gr", "grain", "grains"], 6.479891e-5),
3105 (&["u", "amu", "dalton", "daltons", "da"], 1.66053906660e-27),
3106 ],
3107 ),
3108 (
3109 "Temperature",
3110 &[
3111 (&["c", "celsius"], 1.0), (&["f", "fahrenheit"], 1.0),
3113 (&["k", "kelvin"], 1.0),
3114 (&["r", "rankine"], 1.0),
3115 ],
3116 ),
3117 (
3118 "Time",
3119 &[
3120 (&["s", "sec", "second", "seconds"], 1.0),
3121 (&["ms", "millisecond", "milliseconds"], 0.001),
3122 (&["us", "microsecond", "microseconds"], 1e-6),
3123 (&["ns", "nanosecond", "nanoseconds"], 1e-9),
3124 (&["min", "minute", "minutes"], 60.0),
3125 (&["h", "hr", "hour", "hours"], 3600.0),
3126 (&["d", "day", "days"], 86400.0),
3127 (&["wk", "week", "weeks"], 604800.0),
3128 (&["mo", "month", "months"], 2629800.0),
3129 (&["yr", "year", "years"], 31557600.0),
3130 ],
3131 ),
3132 (
3133 "Area",
3134 &[
3135 (
3136 &["m2", "sqm", "square_meter", "square_meters", "square_metre"],
3137 1.0,
3138 ),
3139 (&["km2", "sqkm", "square_kilometer", "square_km"], 1e6),
3140 (
3141 &["cm2", "sqcm", "square_centimeter", "square_centimeters"],
3142 1e-4,
3143 ),
3144 (
3145 &["mm2", "sqmm", "square_millimeter", "square_millimeters"],
3146 1e-6,
3147 ),
3148 (&["ha", "hectare", "hectares"], 1e4),
3149 (&["ac", "acre", "acres"], 4046.8564224),
3150 (&["sqft", "sq_ft", "square_foot", "square_feet"], 0.09290304),
3151 (
3152 &["sqin", "sq_in", "square_inch", "square_inches"],
3153 6.4516e-4,
3154 ),
3155 (
3156 &["sqmi", "sq_mi", "square_mile", "square_miles"],
3157 2589988.110336,
3158 ),
3159 (
3160 &["sqyd", "sq_yd", "square_yard", "square_yards"],
3161 0.83612736,
3162 ),
3163 ],
3164 ),
3165 (
3166 "Volume",
3167 &[
3168 (&["m3", "cubic_meter", "cubic_meters", "cubic_metre"], 1.0),
3169 (&["l", "liter", "liters", "litre", "litres"], 0.001),
3170 (&["ml", "milliliter", "milliliters", "millilitre"], 1e-6),
3171 (&["cl", "centiliter", "centiliters"], 1e-5),
3172 (&["dl", "deciliter", "deciliters"], 1e-4),
3173 (&["ul", "microliter", "microliters"], 1e-9),
3174 (
3175 &["cm3", "cc", "cubic_centimeter", "cubic_centimeters"],
3176 1e-6,
3177 ),
3178 (&["mm3", "cubic_millimeter", "cubic_millimeters"], 1e-9),
3179 (&["km3", "cubic_kilometer", "cubic_kilometers"], 1e9),
3180 (&["ft3", "cubic_foot", "cubic_feet"], 0.0283168466),
3181 (&["in3", "cubic_inch", "cubic_inches"], 1.6387064e-5),
3182 (&["yd3", "cubic_yard", "cubic_yards"], 0.764554858),
3183 (&["gal", "gallon", "gallons"], 0.003785411784),
3184 (&["qt", "quart", "quarts"], 9.46352946e-4),
3185 (&["pt", "pint", "pints"], 4.73176473e-4),
3186 (&["cup", "cups"], 2.36588237e-4),
3187 (
3188 &["floz", "fl_oz", "fluid_ounce", "fluid_ounces"],
3189 2.95735296e-5,
3190 ),
3191 (&["tbsp", "tablespoon", "tablespoons"], 1.47867648e-5),
3192 (&["tsp", "teaspoon", "teaspoons"], 4.92892159e-6),
3193 (&["bbl", "barrel", "barrels"], 0.158987295),
3194 (
3195 &["gal_uk", "uk_gallon", "imperial_gallon", "imperial_gallons"],
3196 0.00454609,
3197 ),
3198 ],
3199 ),
3200 (
3201 "Speed",
3202 &[
3203 (&["m_per_s", "m/s", "mps"], 1.0),
3204 (&["km_per_s", "km/s", "kmps"], 1000.0),
3205 (
3206 &["km/h", "kmh", "kph", "km_per_h", "km_per_hour"],
3207 1.0 / 3.6,
3208 ),
3209 (&["mph", "mi/h", "mi_per_h", "miles_per_hour"], 0.44704),
3210 (&["knot", "knots", "kn"], 0.514444),
3211 (&["ft_per_s", "ft/s", "fps"], 0.3048),
3212 (&["c_speed", "speed_of_light"], 299792458.0),
3213 (&["mach"], 340.29),
3214 ],
3215 ),
3216 (
3217 "Force",
3218 &[
3219 (&["n", "newton", "newtons"], 1.0),
3220 (&["kn", "kilonewton", "kilonewtons"], 1000.0),
3221 (&["mn", "meganewton", "meganewtons"], 1e6),
3222 (&["lbf", "pound_force", "pound-force"], 4.44822162),
3223 (&["kgf", "kilogram_force", "kilogram-force"], 9.80665),
3224 (&["dyn", "dyne", "dynes"], 1e-5),
3225 (&["ozf", "ounce_force"], 0.278013851),
3226 ],
3227 ),
3228 (
3229 "Pressure",
3230 &[
3231 (&["pa", "pascal", "pascals"], 1.0),
3232 (&["kpa", "kilopascal", "kilopascals"], 1000.0),
3233 (&["mpa", "megapascal", "megapascals"], 1e6),
3234 (&["gpa", "gigapascal", "gigapascals"], 1e9),
3235 (
3236 &[
3237 "hpa",
3238 "hectopascal",
3239 "hectopascals",
3240 "mbar",
3241 "millibar",
3242 "millibars",
3243 ],
3244 100.0,
3245 ),
3246 (&["bar", "bars"], 1e5),
3247 (&["atm", "atmosphere", "atmospheres"], 101325.0),
3248 (&["torr"], 133.322368),
3249 (&["mmhg", "mm_hg", "millimeter_of_mercury"], 133.322368),
3250 (&["psi", "pound_per_square_inch"], 6894.75729),
3251 (&["inhg", "in_hg", "inch_of_mercury"], 3386.389),
3252 ],
3253 ),
3254 (
3255 "Energy",
3256 &[
3257 (&["j", "joule", "joules"], 1.0),
3258 (&["kj", "kilojoule", "kilojoules"], 1000.0),
3259 (&["mj", "megajoule", "megajoules"], 1e6),
3260 (&["gj", "gigajoule", "gigajoules"], 1e9),
3261 (
3262 &["cal", "calorie", "calories", "thermochemical_calorie"],
3263 4.184,
3264 ),
3265 (
3266 &["kcal", "kilocalorie", "kilocalories", "food_calorie"],
3267 4184.0,
3268 ),
3269 (&["wh", "watt_hour", "watt_hours"], 3600.0),
3270 (&["kwh", "kilowatt_hour", "kilowatt_hours"], 3.6e6),
3271 (&["mwh", "megawatt_hour", "megawatt_hours"], 3.6e9),
3272 (&["ev", "electronvolt", "electronvolts"], 1.602176634e-19),
3273 (
3274 &["kev", "kiloelectronvolt", "kiloelectronvolts"],
3275 1.602176634e-16,
3276 ),
3277 (
3278 &["mev", "megaelectronvolt", "megaelectronvolts"],
3279 1.602176634e-13,
3280 ),
3281 (
3282 &["gev", "gigaelectronvolt", "gigaelectronvolts"],
3283 1.602176634e-10,
3284 ),
3285 (
3286 &["tev", "teraelectronvolt", "teraelectronvolts"],
3287 1.602176634e-7,
3288 ),
3289 (&["btu", "british_thermal_unit"], 1055.05585),
3290 (&["erg", "ergs"], 1e-7),
3291 (&["ft_lb", "foot_pound", "foot_pounds"], 1.35581795),
3292 (&["therm", "therms"], 1.05480400e8),
3293 ],
3294 ),
3295 (
3296 "Power",
3297 &[
3298 (&["w", "watt", "watts"], 1.0),
3299 (&["kw", "kilowatt", "kilowatts"], 1000.0),
3300 (&["mw", "megawatt", "megawatts"], 1e6),
3301 (&["gw", "gigawatt", "gigawatts"], 1e9),
3302 (&["tw", "terawatt", "terawatts"], 1e12),
3303 (&["mw_milli", "milliwatt", "milliwatts"], 0.001),
3304 (&["hp", "horsepower"], 745.69987),
3305 (&["ps", "metric_horsepower"], 735.49875),
3306 (&["btu_h", "btu/h", "btu_per_hour"], 0.29307107),
3307 (&["erg_s", "erg/s", "erg_per_second"], 1e-7),
3308 (&["ft_lb_s", "ft_lb/s"], 1.35581795),
3309 ],
3310 ),
3311 (
3312 "Data",
3313 &[
3314 (&["bit", "bits"], 1.0),
3315 (&["byte", "bytes", "b"], 8.0),
3316 (&["kb", "kilobit", "kilobits"], 1e3),
3317 (&["kib", "kibibit", "kibibits"], 1024.0),
3318 (&["mb", "megabit", "megabits"], 1e6),
3319 (&["mib", "mebibit", "mebibits"], 1024.0 * 1024.0),
3320 (&["gb", "gigabit", "gigabits"], 1e9),
3321 (&["gib", "gibibit", "gibibits"], 1024.0 * 1024.0 * 1024.0),
3322 (&["tb", "terabit", "terabits"], 1e12),
3323 (
3324 &["tib", "tebibit", "tebibits"],
3325 1024.0 * 1024.0 * 1024.0 * 1024.0,
3326 ),
3327 (&["pb", "petabit", "petabits"], 1e15),
3328 (&["kb_byte", "kilobyte", "kilobytes"], 8e3),
3329 (&["kib_byte", "kibibyte", "kibibytes"], 8.0 * 1024.0),
3330 (&["mb_byte", "megabyte", "megabytes"], 8e6),
3331 (
3332 &["mib_byte", "mebibyte", "mebibytes"],
3333 8.0 * 1024.0 * 1024.0,
3334 ),
3335 (&["gb_byte", "gigabyte", "gigabytes"], 8e9),
3336 (
3337 &["gib_byte", "gibibyte", "gibibytes"],
3338 8.0 * 1024.0 * 1024.0 * 1024.0,
3339 ),
3340 (&["tb_byte", "terabyte", "terabytes"], 8e12),
3341 (
3342 &["tib_byte", "tebibyte", "tebibytes"],
3343 8.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0,
3344 ),
3345 ],
3346 ),
3347 (
3348 "Angle",
3349 &[
3350 (&["rad", "radian", "radians"], 1.0),
3351 (&["deg", "degree", "degrees"], std::f64::consts::PI / 180.0),
3352 (
3353 &["grad", "gradian", "gradians", "gon", "gons"],
3354 std::f64::consts::PI / 200.0,
3355 ),
3356 (
3357 &["arcmin", "arc_minute", "arc_minutes", "minute_of_arc"],
3358 std::f64::consts::PI / 10800.0,
3359 ),
3360 (
3361 &["arcsec", "arc_second", "arc_seconds", "second_of_arc"],
3362 std::f64::consts::PI / 648000.0,
3363 ),
3364 (
3365 &["rev", "revolution", "revolutions", "turn", "turns"],
3366 2.0 * std::f64::consts::PI,
3367 ),
3368 ],
3369 ),
3370 (
3371 "Frequency",
3372 &[
3373 (&["hz", "hertz"], 1.0),
3374 (&["khz", "kilohertz"], 1e3),
3375 (&["mhz", "megahertz"], 1e6),
3376 (&["ghz", "gigahertz"], 1e9),
3377 (&["thz", "terahertz"], 1e12),
3378 (&["rpm", "revolutions_per_minute"], 1.0 / 60.0),
3379 (&["rad_per_s", "rad/s"], 1.0 / (2.0 * std::f64::consts::PI)),
3380 ],
3381 ),
3382 (
3383 "Illuminance",
3384 &[
3385 (&["lx", "lux"], 1.0),
3386 (&["fc", "footcandle", "footcandles"], 10.7639),
3387 (&["phot", "phots"], 1e4),
3388 ],
3389 ),
3390 (
3391 "Fuel Economy",
3392 &[
3393 (&["mpg", "miles_per_gallon"], 1.0),
3394 (&["mpg_uk", "miles_per_gallon_uk", "imperial_mpg"], 1.20095),
3395 (&["l_per_100km", "l/100km", "liters_per_100km"], 235.214583),
3396 (&["km_per_l", "km/l", "km_per_liter"], 2.35214583),
3397 ],
3398 ),
3399];
3400
3401fn unit_convert_list() -> String {
3402 let mut out = String::new();
3403 let _ = writeln!(out, "Supported unit categories and aliases:");
3404 let _ = writeln!(out, "Usage: hematite --convert '<value> <from> to <to>'");
3405 let _ = writeln!(out);
3406 for (cat, units) in UNIT_CATEGORIES {
3407 let _ = writeln!(out, " {}:", cat);
3408 for (names, _) in *units {
3409 let _ = writeln!(out, " {}", names.join(", "));
3410 }
3411 let _ = writeln!(out);
3412 }
3413 out
3414}
3415
3416pub fn vector_calc(query: &str) -> String {
3433 let q = query.trim();
3434
3435 if let Some(rest) = strip_prefix_ci(q, "mag") {
3437 if let Some(v) = parse_vec(rest.trim()) {
3438 return format_vec_result("Magnitude", &[], vec_mag(&v));
3439 }
3440 }
3441 if let Some(rest) = strip_prefix_ci(q, "magnitude") {
3442 if let Some(v) = parse_vec(rest.trim()) {
3443 return format_vec_result("Magnitude", &[], vec_mag(&v));
3444 }
3445 }
3446 if let Some(rest) = strip_prefix_ci(q, "norm") {
3447 if let Some(v) = parse_vec(rest.trim()) {
3448 let mag = vec_mag(&v);
3449 if mag == 0.0 {
3450 return "Zero vector has no unit direction.".into();
3451 }
3452 let n: Vec<f64> = v.iter().map(|x| x / mag).collect();
3453 return format_vec_display("Unit vector (normalized)", &n);
3454 }
3455 }
3456 if let Some(rest) = strip_prefix_ci(q, "normalize") {
3457 if let Some(v) = parse_vec(rest.trim()) {
3458 let mag = vec_mag(&v);
3459 if mag == 0.0 {
3460 return "Zero vector has no unit direction.".into();
3461 }
3462 let n: Vec<f64> = v.iter().map(|x| x / mag).collect();
3463 return format_vec_display("Unit vector (normalized)", &n);
3464 }
3465 }
3466
3467 if let Some(rest) = strip_prefix_ci(q, "angle") {
3469 let vecs = find_all_vecs(rest.trim());
3470 if vecs.len() >= 2 {
3471 let a = &vecs[0];
3472 let b = &vecs[1];
3473 if a.len() != b.len() {
3474 return "Vectors must have the same dimension for angle.".into();
3475 }
3476 let dot = vec_dot(a, b);
3477 let ma = vec_mag(a);
3478 let mb = vec_mag(b);
3479 if ma == 0.0 || mb == 0.0 {
3480 return "Cannot compute angle involving a zero vector.".into();
3481 }
3482 let cos_theta = (dot / (ma * mb)).clamp(-1.0, 1.0);
3483 let deg = cos_theta.acos().to_degrees();
3484 let rad = cos_theta.acos();
3485 return format!(
3486 "Angle between {} and {}:\n {:.6}° ({:.6} radians)\n cos θ = {:.6}",
3487 fmt_vec(a),
3488 fmt_vec(b),
3489 deg,
3490 rad,
3491 cos_theta
3492 );
3493 }
3494 }
3495
3496 if q.to_lowercase().contains("proj") && q.to_lowercase().contains("onto") {
3498 let vecs = find_all_vecs(q);
3499 if vecs.len() >= 2 {
3500 let a = &vecs[0];
3501 let b = &vecs[1];
3502 if a.len() != b.len() {
3503 return "Vectors must have the same dimension for projection.".into();
3504 }
3505 let b_mag2: f64 = b.iter().map(|x| x * x).sum();
3506 if b_mag2 == 0.0 {
3507 return "Cannot project onto a zero vector.".into();
3508 }
3509 let scalar = vec_dot(a, b) / b_mag2;
3510 let proj: Vec<f64> = b.iter().map(|x| x * scalar).collect();
3511 let mut out = String::new();
3512 let _ = writeln!(out, "Projection of {} onto {}:", fmt_vec(a), fmt_vec(b));
3513 let _ = writeln!(out, " proj = {}", fmt_vec(&proj));
3514 let _ = writeln!(out, " scalar factor = {:.6}", scalar);
3515 return out;
3516 }
3517 }
3518
3519 let lower = q.to_lowercase();
3521
3522 if lower.contains(" dot ") {
3524 if let Some(idx) = lower.find(" dot ") {
3525 let left = &q[..idx];
3526 let right = &q[idx + 5..];
3527 if let (Some(a), Some(b)) = (parse_vec(left.trim()), parse_vec(right.trim())) {
3528 if a.len() != b.len() {
3529 return format!("Dimension mismatch: {} vs {}", a.len(), b.len());
3530 }
3531 let d = vec_dot(&a, &b);
3532 return format!("{} · {} = {}", fmt_vec(&a), fmt_vec(&b), fmt_scalar(d));
3533 }
3534 }
3535 }
3536
3537 if lower.contains(" cross ") {
3539 if let Some(idx) = lower.find(" cross ") {
3540 let left = &q[..idx];
3541 let right = &q[idx + 7..];
3542 if let (Some(a), Some(b)) = (parse_vec(left.trim()), parse_vec(right.trim())) {
3543 if a.len() != 3 || b.len() != 3 {
3544 return "Cross product requires two 3D vectors.".into();
3545 }
3546 let c = vec_cross(&a, &b);
3547 return format!(
3548 "{} × {} = {}\n |result| = {}",
3549 fmt_vec(&a),
3550 fmt_vec(&b),
3551 fmt_vec(&c),
3552 fmt_scalar(vec_mag(&c))
3553 );
3554 }
3555 }
3556 }
3557
3558 if lower.contains(" * ") {
3560 if let Some(idx) = q.find(" * ") {
3561 let left = q[..idx].trim();
3562 let right = q[idx + 3..].trim();
3563 if let (Ok(s), Some(v)) = (left.parse::<f64>(), parse_vec(right)) {
3565 let result: Vec<f64> = v.iter().map(|x| x * s).collect();
3566 return format!("{} × {} = {}", s, fmt_vec(&v), fmt_vec(&result));
3567 }
3568 if let (Some(v), Ok(s)) = (parse_vec(left), right.parse::<f64>()) {
3570 let result: Vec<f64> = v.iter().map(|x| x * s).collect();
3571 return format!("{} × {} = {}", fmt_vec(&v), s, fmt_vec(&result));
3572 }
3573 }
3574 }
3575
3576 if let Some(idx) = q.find(" + ") {
3578 let left = q[..idx].trim();
3579 let right = q[idx + 3..].trim();
3580 if let (Some(a), Some(b)) = (parse_vec(left), parse_vec(right)) {
3581 if a.len() != b.len() {
3582 return format!("Dimension mismatch: {} vs {}", a.len(), b.len());
3583 }
3584 let c: Vec<f64> = a.iter().zip(b.iter()).map(|(x, y)| x + y).collect();
3585 return format!("{} + {} = {}", fmt_vec(&a), fmt_vec(&b), fmt_vec(&c));
3586 }
3587 }
3588
3589 if let Some(idx) = q.rfind(" - ") {
3591 let left = q[..idx].trim();
3592 let right = q[idx + 3..].trim();
3593 if let (Some(a), Some(b)) = (parse_vec(left), parse_vec(right)) {
3594 if a.len() != b.len() {
3595 return format!("Dimension mismatch: {} vs {}", a.len(), b.len());
3596 }
3597 let c: Vec<f64> = a.iter().zip(b.iter()).map(|(x, y)| x - y).collect();
3598 return format!("{} - {} = {}", fmt_vec(&a), fmt_vec(&b), fmt_vec(&c));
3599 }
3600 }
3601
3602 if let Some(v) = parse_vec(q) {
3604 let mut out = String::new();
3605 let mag = vec_mag(&v);
3606 let _ = writeln!(out, "Vector: {}", fmt_vec(&v));
3607 let _ = writeln!(out, "Dimension: {}", v.len());
3608 let _ = writeln!(out, "Magnitude: {}", fmt_scalar(mag));
3609 if mag > 0.0 {
3610 let unit: Vec<f64> = v.iter().map(|x| x / mag).collect();
3611 let _ = writeln!(out, "Unit vec: {}", fmt_vec(&unit));
3612 }
3613 if v.len() == 2 {
3614 let angle = v[1].atan2(v[0]).to_degrees();
3615 let _ = writeln!(out, "Angle (from +x): {:.4}°", angle);
3616 }
3617 return out;
3618 }
3619
3620 format!(
3621 "Could not parse: '{}'\n\
3622 Examples:\n\
3623 hematite --vectors '[1,2,3] dot [4,5,6]'\n\
3624 hematite --vectors '[1,2,3] cross [4,5,6]'\n\
3625 hematite --vectors '[1,2,3] + [4,5,6]'\n\
3626 hematite --vectors 'mag [3,4]'\n\
3627 hematite --vectors 'norm [1,2,3]'\n\
3628 hematite --vectors 'angle [1,0] [0,1]'\n\
3629 hematite --vectors 'proj [1,2] onto [3,4]'\n\
3630 hematite --vectors '3 * [1,2,3]'",
3631 q
3632 )
3633}
3634
3635fn strip_prefix_ci<'a>(s: &'a str, prefix: &str) -> Option<&'a str> {
3636 if s.len() >= prefix.len() && s[..prefix.len()].eq_ignore_ascii_case(prefix) {
3637 Some(&s[prefix.len()..])
3638 } else {
3639 None
3640 }
3641}
3642
3643fn parse_vec(s: &str) -> Option<Vec<f64>> {
3644 let s = s
3646 .trim()
3647 .trim_start_matches(['[', '('])
3648 .trim_end_matches([']', ')']);
3649 let parts: Vec<&str> = if s.contains(',') {
3650 s.split(',').collect()
3651 } else {
3652 s.split_whitespace().collect()
3653 };
3654 if parts.is_empty() {
3655 return None;
3656 }
3657 let nums: Vec<f64> = parts
3658 .iter()
3659 .filter_map(|p| p.trim().parse::<f64>().ok())
3660 .collect();
3661 if nums.len() == parts.len() && !nums.is_empty() {
3662 Some(nums)
3663 } else {
3664 None
3665 }
3666}
3667
3668fn find_all_vecs(s: &str) -> Vec<Vec<f64>> {
3669 let mut result = Vec::new();
3671 let mut i = 0;
3672 let chars: Vec<char> = s.chars().collect();
3673 while i < chars.len() {
3674 if chars[i] == '[' || chars[i] == '(' {
3675 let close = if chars[i] == '[' { ']' } else { ')' };
3676 if let Some(j) = chars[i + 1..].iter().position(|&c| c == close) {
3677 let inner: String = chars[i + 1..i + 1 + j].iter().collect();
3678 if let Some(v) = parse_vec(&inner) {
3679 result.push(v);
3680 }
3681 i += j + 2;
3682 continue;
3683 }
3684 }
3685 i += 1;
3686 }
3687 result
3688}
3689
3690fn vec_dot(a: &[f64], b: &[f64]) -> f64 {
3691 a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
3692}
3693
3694fn vec_mag(v: &[f64]) -> f64 {
3695 v.iter().map(|x| x * x).sum::<f64>().sqrt()
3696}
3697
3698fn vec_cross(a: &[f64], b: &[f64]) -> Vec<f64> {
3699 vec![
3700 a[1] * b[2] - a[2] * b[1],
3701 a[2] * b[0] - a[0] * b[2],
3702 a[0] * b[1] - a[1] * b[0],
3703 ]
3704}
3705
3706fn fmt_vec(v: &[f64]) -> String {
3707 let inner: Vec<String> = v.iter().map(|x| fmt_scalar(*x)).collect();
3708 format!("[{}]", inner.join(", "))
3709}
3710
3711fn fmt_scalar(x: f64) -> String {
3712 if x.fract() == 0.0 && x.abs() < 1e12 {
3713 format!("{}", x as i64)
3714 } else if x.abs() >= 1e-3 && x.abs() < 1e7 {
3715 format!("{:.6}", x)
3716 .trim_end_matches('0')
3717 .trim_end_matches('.')
3718 .to_string()
3719 } else {
3720 format!("{:.6e}", x)
3721 }
3722}
3723
3724fn format_vec_result(label: &str, _v: &[f64], val: f64) -> String {
3725 format!("{}: {}", label, fmt_scalar(val))
3726}
3727
3728fn format_vec_display(label: &str, v: &[f64]) -> String {
3729 format!("{}: {}", label, fmt_vec(v))
3730}
3731
3732pub fn simulate(query: &str) -> String {
3743 let q = query.trim();
3744 let tokens: Vec<&str> = q.split_whitespace().collect();
3745 if tokens.is_empty() {
3746 return simulate_usage();
3747 }
3748
3749 match tokens[0].to_lowercase().as_str() {
3750 "pi" => {
3751 let n: u64 = tokens
3752 .get(1)
3753 .and_then(|s| s.parse().ok())
3754 .unwrap_or(1_000_000);
3755 let n = n.min(100_000_000);
3756 let mut inside = 0u64;
3757 let mut rng = Lcg64::new(0xdeadbeef_12345678);
3758 for _ in 0..n {
3759 let x = rng.next_f64() * 2.0 - 1.0;
3760 let y = rng.next_f64() * 2.0 - 1.0;
3761 if x * x + y * y <= 1.0 {
3762 inside += 1;
3763 }
3764 }
3765 let pi_est = 4.0 * inside as f64 / n as f64;
3766 let error = (pi_est - std::f64::consts::PI).abs();
3767 format!(
3768 "Monte Carlo π estimate ({} trials):\n π ≈ {:.8}\n True π = {:.8}\n Error: {:.6e}\n Inside circle: {} / {}",
3769 n, pi_est, std::f64::consts::PI, error, inside, n
3770 )
3771 }
3772 "birthday" => {
3773 let n: u32 = tokens.get(1).and_then(|s| s.parse().ok()).unwrap_or(23);
3774 let p_no_match = (0..n as u64).fold(1.0f64, |acc, i| acc * (365 - i) as f64 / 365.0);
3776 let p_match = 1.0 - p_no_match;
3777 let mut out = format!("Birthday problem — room of {} people:\n", n);
3778 out.push_str(&format!(
3779 " P(at least 2 share a birthday) = {:.6} ({:.2}%)\n",
3780 p_match,
3781 p_match * 100.0
3782 ));
3783 out.push_str(&format!(
3784 " P(all different birthdays) = {:.6} ({:.2}%)\n",
3785 p_no_match,
3786 p_no_match * 100.0
3787 ));
3788 let n50 = (1..366u32)
3790 .find(|&k| {
3791 let p = 1.0 - (0..k as u64).fold(1.0f64, |a, i| a * (365 - i) as f64 / 365.0);
3792 p >= 0.5
3793 })
3794 .unwrap_or(23);
3795 out.push_str(&format!(
3796 " Minimum group for ≥50% chance: {} people\n",
3797 n50
3798 ));
3799 out
3800 }
3801 "dice" => {
3802 let spec = tokens.get(1).copied().unwrap_or("1d6");
3804 let rolls: u64 = tokens.get(2).and_then(|s| s.parse().ok()).unwrap_or(1000);
3805 let rolls = rolls.min(1_000_000);
3806 let (n_dice, sides, bonus) = parse_dice_spec(spec);
3808 let mut counts: std::collections::HashMap<i64, u64> = std::collections::HashMap::new();
3809 let mut rng = Lcg64::new(0xcafe_babe_dead_beef);
3810 for _ in 0..rolls {
3811 let total: i64 = (0..n_dice)
3812 .map(|_| (rng.next_u64() % sides as u64) as i64 + 1)
3813 .sum::<i64>()
3814 + bonus;
3815 *counts.entry(total).or_insert(0) += 1;
3816 }
3817 let mut sorted_keys: Vec<i64> = counts.keys().copied().collect();
3818 sorted_keys.sort();
3819 let mean: f64 = sorted_keys
3820 .iter()
3821 .map(|&k| k as f64 * counts[&k] as f64)
3822 .sum::<f64>()
3823 / rolls as f64;
3824 let mut out = format!("Dice simulation: {} × {} rolls\n", rolls, spec);
3825 let _ = writeln!(
3826 out,
3827 " Mean: {:.3} Range: {}–{}",
3828 mean,
3829 sorted_keys.first().unwrap_or(&0),
3830 sorted_keys.last().unwrap_or(&0)
3831 );
3832 out.push_str(" Distribution:\n");
3833 let max_count = counts.values().copied().max().unwrap_or(1);
3834 for k in &sorted_keys {
3835 let c = counts[k];
3836 let pct = 100.0 * c as f64 / rolls as f64;
3837 let bar_len = (c as f64 / max_count as f64 * 30.0) as usize;
3838 let _ = writeln!(out, " {:4} {:6.2}% {}", k, pct, "█".repeat(bar_len));
3839 }
3840 out
3841 }
3842 "ruin" | "gambler" => {
3843 let p: f64 = tokens.get(1).and_then(|s| s.parse().ok()).unwrap_or(0.5);
3844 let a: i64 = tokens.get(2).and_then(|s| s.parse().ok()).unwrap_or(10);
3845 let b: i64 = tokens.get(3).and_then(|s| s.parse().ok()).unwrap_or(20);
3846 let n: u64 = tokens.get(4).and_then(|s| s.parse().ok()).unwrap_or(10_000);
3847 let n = n.min(100_000);
3848 if a <= 0 || b <= a {
3849 return "Usage: ruin PROB START GOAL N_SIM (GOAL > START > 0)".into();
3850 }
3851
3852 let mut wins = 0u64;
3853 let mut steps_total = 0u64;
3854 let mut rng = Lcg64::new(0x1234_5678_9abc_def0);
3855 for _ in 0..n {
3856 let mut money = a;
3857 let mut steps = 0u64;
3858 while money > 0 && money < b {
3859 let r = rng.next_f64();
3860 money += if r < p { 1 } else { -1 };
3861 steps += 1;
3862 if steps > 100_000 {
3863 break;
3864 }
3865 }
3866 if money >= b {
3867 wins += 1;
3868 }
3869 steps_total += steps;
3870 }
3871 let win_rate = wins as f64 / n as f64;
3872 let avg_steps = steps_total as f64 / n as f64;
3873 let exact = if (p - 0.5).abs() < 1e-10 {
3875 a as f64 / b as f64
3876 } else {
3877 let q = 1.0 - p;
3878 let r = q / p;
3879 (1.0 - r.powi(a as i32)) / (1.0 - r.powi(b as i32))
3880 };
3881 format!(
3882 "Gambler's Ruin ({} simulations):\n Win prob p={:.4} Start=${} → Goal=${}\n\
3883 \n Simulated win rate: {:.4} ({:.2}%)\n Exact formula: {:.4} ({:.2}%)\n\
3884 \n Average steps to finish: {:.1}",
3885 n,
3886 p,
3887 a,
3888 b,
3889 win_rate,
3890 win_rate * 100.0,
3891 exact,
3892 exact * 100.0,
3893 avg_steps
3894 )
3895 }
3896 "walk" | "random_walk" => {
3897 let n_walks: u64 = tokens.get(1).and_then(|s| s.parse().ok()).unwrap_or(1000);
3898 let steps: u64 = tokens.get(2).and_then(|s| s.parse().ok()).unwrap_or(100);
3899 let n_walks = n_walks.min(100_000);
3900 let steps = steps.min(100_000);
3901 let mut final_positions: Vec<f64> = Vec::with_capacity(n_walks as usize);
3902 let mut max_deviation: f64 = 0.0;
3903 let mut rng = Lcg64::new(0xabcdef01_23456789);
3904 for _ in 0..n_walks {
3905 let mut pos = 0.0f64;
3906 for _ in 0..steps {
3907 pos += if rng.next_f64() < 0.5 { 1.0 } else { -1.0 };
3908 }
3909 final_positions.push(pos);
3910 if pos.abs() > max_deviation {
3911 max_deviation = pos.abs();
3912 }
3913 }
3914 let mean = final_positions.iter().sum::<f64>() / n_walks as f64;
3915 let variance: f64 = final_positions
3916 .iter()
3917 .map(|x| (x - mean).powi(2))
3918 .sum::<f64>()
3919 / n_walks as f64;
3920 let std_dev = variance.sqrt();
3921 let theoretical_std = (steps as f64).sqrt();
3922 format!(
3923 "Random Walk simulation ({} walks × {} steps):\n Mean final position: {:.4}\n Std deviation: {:.4} (theoretical √N = {:.4})\n Max |deviation|: {:.0}\n Expected: walk ends within ±{:.1} of origin with 95% probability",
3924 n_walks, steps, mean, std_dev, theoretical_std, max_deviation, 1.96 * theoretical_std
3925 )
3926 }
3927 _ => {
3928 if let Ok(n) = tokens[0].parse::<u64>() {
3930 return simulate(&format!("pi {}", n));
3932 }
3933 simulate_usage()
3934 }
3935 }
3936}
3937
3938fn simulate_usage() -> String {
3939 "Monte Carlo simulation:\n\
3940 hematite --simulate 'pi 1000000' estimate π with N darts\n\
3941 hematite --simulate 'birthday 23' birthday problem\n\
3942 hematite --simulate 'dice 2d6 10000' roll 2d6 × 10000\n\
3943 hematite --simulate 'ruin 0.48 10 20 5000' gambler's ruin\n\
3944 hematite --simulate 'walk 1000 200' random walk simulation"
3945 .into()
3946}
3947
3948fn parse_dice_spec(spec: &str) -> (i64, i64, i64) {
3949 let lower = spec.to_lowercase();
3951 let (dice_part, bonus) = if let Some(idx) = lower.rfind('+') {
3952 let b: i64 = spec[idx + 1..].parse().unwrap_or(0);
3953 (&spec[..idx], b)
3954 } else if let Some(idx) = lower[1..].rfind('-').map(|i| i + 1) {
3955 let b: i64 = spec[idx + 1..].parse().unwrap_or(0);
3956 (&spec[..idx], -b)
3957 } else {
3958 (spec, 0i64)
3959 };
3960 if let Some(d_pos) = dice_part.to_lowercase().find('d') {
3961 let n: i64 = dice_part[..d_pos].parse().unwrap_or(1).max(1);
3962 let s: i64 = dice_part[d_pos + 1..].parse().unwrap_or(6).max(2);
3963 (n, s, bonus)
3964 } else {
3965 (1, 6, 0)
3966 }
3967}
3968
3969struct Lcg64 {
3971 state: u64,
3972}
3973impl Lcg64 {
3974 fn new(seed: u64) -> Self {
3975 Self {
3976 state: seed.wrapping_add(1),
3977 }
3978 }
3979 fn next_u64(&mut self) -> u64 {
3980 self.state = self
3981 .state
3982 .wrapping_mul(6_364_136_223_846_793_005)
3983 .wrapping_add(1_442_695_040_888_963_407);
3984 self.state
3985 }
3986 fn next_f64(&mut self) -> f64 {
3987 (self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
3988 }
3989}
3990
3991#[derive(Clone, Debug, PartialEq)]
4010#[allow(dead_code)]
4011enum BExpr {
4012 Var(String),
4013 Not(Box<BExpr>),
4014 And(Box<BExpr>, Box<BExpr>),
4015 Or(Box<BExpr>, Box<BExpr>),
4016 Xor(Box<BExpr>, Box<BExpr>),
4017 Implies(Box<BExpr>, Box<BExpr>),
4018 Iff(Box<BExpr>, Box<BExpr>),
4019 Nand(Box<BExpr>, Box<BExpr>),
4020 Nor(Box<BExpr>, Box<BExpr>),
4021 Xnor(Box<BExpr>, Box<BExpr>),
4022 Const(bool),
4023}
4024
4025struct BParser<'a> {
4026 chars: &'a [char],
4027 pos: usize,
4028}
4029
4030impl<'a> BParser<'a> {
4031 fn new(chars: &'a [char]) -> Self {
4032 Self { chars, pos: 0 }
4033 }
4034 fn peek(&self) -> Option<char> {
4035 self.chars.get(self.pos).copied()
4036 }
4037 fn consume(&mut self) -> Option<char> {
4038 let c = self.peek();
4039 self.pos += 1;
4040 c
4041 }
4042 fn skip_ws(&mut self) {
4043 while matches!(self.peek(), Some(' ') | Some('\t')) {
4044 self.pos += 1;
4045 }
4046 }
4047
4048 fn parse_iff(&mut self) -> Result<BExpr, String> {
4049 let mut left = self.parse_implies()?;
4050 loop {
4051 self.skip_ws();
4052 if self.try_keyword("iff") || self.try_str("<->") || self.try_str("<=>") {
4053 let right = self.parse_implies()?;
4054 left = BExpr::Iff(Box::new(left), Box::new(right));
4055 } else {
4056 break;
4057 }
4058 }
4059 Ok(left)
4060 }
4061
4062 fn parse_implies(&mut self) -> Result<BExpr, String> {
4063 let left = self.parse_or()?;
4064 self.skip_ws();
4065 if self.try_str("->") || self.try_str("=>") || self.try_keyword("implies") {
4066 let right = self.parse_implies()?;
4067 return Ok(BExpr::Implies(Box::new(left), Box::new(right)));
4068 }
4069 Ok(left)
4070 }
4071
4072 fn parse_or(&mut self) -> Result<BExpr, String> {
4073 let mut left = self.parse_xor()?;
4074 loop {
4075 self.skip_ws();
4076 if self.try_str("||")
4077 || self.try_str("|")
4078 || self.try_keyword("or")
4079 || self.try_keyword("nor")
4080 {
4081 let right = self.parse_xor()?;
4082 left = BExpr::Or(Box::new(left), Box::new(right));
4083 } else {
4084 break;
4085 }
4086 }
4087 Ok(left)
4088 }
4089
4090 fn parse_xor(&mut self) -> Result<BExpr, String> {
4091 let mut left = self.parse_and()?;
4092 loop {
4093 self.skip_ws();
4094 if self.try_keyword("xor") || self.try_keyword("xnor") || self.try_str("^") {
4095 let right = self.parse_and()?;
4096 left = BExpr::Xor(Box::new(left), Box::new(right));
4097 } else {
4098 break;
4099 }
4100 }
4101 Ok(left)
4102 }
4103
4104 fn parse_and(&mut self) -> Result<BExpr, String> {
4105 let mut left = self.parse_not()?;
4106 loop {
4107 self.skip_ws();
4108 if self.try_str("&&")
4109 || self.try_str("&")
4110 || self.try_keyword("and")
4111 || self.try_keyword("nand")
4112 || self.try_str("*")
4113 {
4114 let right = self.parse_not()?;
4115 left = BExpr::And(Box::new(left), Box::new(right));
4116 } else {
4117 break;
4118 }
4119 }
4120 Ok(left)
4121 }
4122
4123 fn parse_not(&mut self) -> Result<BExpr, String> {
4124 self.skip_ws();
4125 if self.peek() == Some('!') || self.peek() == Some('~') {
4126 self.consume();
4127 let inner = self.parse_not()?;
4128 return Ok(BExpr::Not(Box::new(inner)));
4129 }
4130 if self.try_keyword("not") {
4131 let inner = self.parse_not()?;
4132 return Ok(BExpr::Not(Box::new(inner)));
4133 }
4134 self.parse_atom()
4135 }
4136
4137 fn parse_atom(&mut self) -> Result<BExpr, String> {
4138 self.skip_ws();
4139 if self.peek() == Some('(') {
4140 self.consume();
4141 let inner = self.parse_iff()?;
4142 self.skip_ws();
4143 if self.peek() == Some(')') {
4144 self.consume();
4145 }
4146 return Ok(inner);
4147 }
4148 if self.try_keyword("true") || self.try_keyword("1") {
4150 return Ok(BExpr::Const(true));
4151 }
4152 if self.try_keyword("false") || self.try_keyword("0") {
4153 return Ok(BExpr::Const(false));
4154 }
4155 if matches!(self.peek(), Some(c) if c.is_alphabetic() || c == '_') {
4157 let start = self.pos;
4158 while matches!(self.peek(), Some(c) if c.is_alphanumeric() || c == '_') {
4159 self.pos += 1;
4160 }
4161 let name: String = self.chars[start..self.pos].iter().collect();
4162 return Ok(BExpr::Var(name));
4163 }
4164 Err(format!(
4165 "unexpected char '{}'",
4166 self.peek().map(|c| c.to_string()).unwrap_or("EOF".into())
4167 ))
4168 }
4169
4170 fn try_str(&mut self, s: &str) -> bool {
4171 let chars: Vec<char> = s.chars().collect();
4172 let remaining = &self.chars[self.pos..];
4173 if remaining.len() >= chars.len() && remaining[..chars.len()] == chars[..] {
4174 self.pos += chars.len();
4175 return true;
4176 }
4177 false
4178 }
4179
4180 fn try_keyword(&mut self, kw: &str) -> bool {
4181 let saved = self.pos;
4182 self.skip_ws();
4183 let chars: Vec<char> = kw.chars().collect();
4184 let remaining = &self.chars[self.pos..];
4185 if remaining.len() >= chars.len()
4186 && remaining[..chars.len()]
4187 .iter()
4188 .map(|c| c.to_lowercase().next().unwrap())
4189 .collect::<Vec<_>>()
4190 == chars
4191 && !matches!(remaining.get(chars.len()), Some(c) if c.is_alphanumeric() || *c == '_')
4192 {
4193 self.pos += chars.len();
4194 return true;
4195 }
4196 self.pos = saved;
4197 false
4198 }
4199}
4200
4201fn parse_bexpr(s: &str) -> Result<BExpr, String> {
4202 let chars: Vec<char> = s.chars().collect();
4203 let mut p = BParser::new(&chars);
4204 let e = p.parse_iff()?;
4205 p.skip_ws();
4206 if p.pos < p.chars.len() {
4207 let rest: String = p.chars[p.pos..].iter().collect();
4208 if !rest.trim().is_empty() {
4209 return Err(format!("unexpected trailing: '{}'", rest.trim()));
4210 }
4211 }
4212 Ok(e)
4213}
4214
4215fn collect_vars(e: &BExpr, vars: &mut Vec<String>) {
4217 match e {
4218 BExpr::Var(v) => {
4219 if !vars.contains(v) {
4220 vars.push(v.clone());
4221 }
4222 }
4223 BExpr::Not(a) => collect_vars(a, vars),
4224 BExpr::And(a, b)
4225 | BExpr::Or(a, b)
4226 | BExpr::Xor(a, b)
4227 | BExpr::Implies(a, b)
4228 | BExpr::Iff(a, b)
4229 | BExpr::Nand(a, b)
4230 | BExpr::Nor(a, b)
4231 | BExpr::Xnor(a, b) => {
4232 collect_vars(a, vars);
4233 collect_vars(b, vars);
4234 }
4235 BExpr::Const(_) => {}
4236 }
4237}
4238
4239fn eval_bexpr(e: &BExpr, assignment: &[(&str, bool)]) -> bool {
4240 match e {
4241 BExpr::Const(b) => *b,
4242 BExpr::Var(v) => assignment
4243 .iter()
4244 .find(|(n, _)| n == v)
4245 .map(|(_, b)| *b)
4246 .unwrap_or(false),
4247 BExpr::Not(a) => !eval_bexpr(a, assignment),
4248 BExpr::And(a, b) => eval_bexpr(a, assignment) && eval_bexpr(b, assignment),
4249 BExpr::Or(a, b) => eval_bexpr(a, assignment) || eval_bexpr(b, assignment),
4250 BExpr::Xor(a, b) => eval_bexpr(a, assignment) ^ eval_bexpr(b, assignment),
4251 BExpr::Xnor(a, b) => !(eval_bexpr(a, assignment) ^ eval_bexpr(b, assignment)),
4252 BExpr::Nand(a, b) => !(eval_bexpr(a, assignment) && eval_bexpr(b, assignment)),
4253 BExpr::Nor(a, b) => !(eval_bexpr(a, assignment) || eval_bexpr(b, assignment)),
4254 BExpr::Implies(a, b) => !eval_bexpr(a, assignment) || eval_bexpr(b, assignment),
4255 BExpr::Iff(a, b) => eval_bexpr(a, assignment) == eval_bexpr(b, assignment),
4256 }
4257}
4258
4259fn bexpr_to_str(e: &BExpr) -> String {
4260 match e {
4261 BExpr::Const(true) => "true".into(),
4262 BExpr::Const(false) => "false".into(),
4263 BExpr::Var(v) => v.clone(),
4264 BExpr::Not(a) => format!("¬{}", bexpr_atom_str(a)),
4265 BExpr::And(a, b) => format!("({} ∧ {})", bexpr_to_str(a), bexpr_to_str(b)),
4266 BExpr::Or(a, b) => format!("({} ∨ {})", bexpr_to_str(a), bexpr_to_str(b)),
4267 BExpr::Xor(a, b) => format!("({} ⊕ {})", bexpr_to_str(a), bexpr_to_str(b)),
4268 BExpr::Implies(a, b) => format!("({} → {})", bexpr_to_str(a), bexpr_to_str(b)),
4269 BExpr::Iff(a, b) => format!("({} ↔ {})", bexpr_to_str(a), bexpr_to_str(b)),
4270 BExpr::Nand(a, b) => format!("({}↑{})", bexpr_to_str(a), bexpr_to_str(b)),
4271 BExpr::Nor(a, b) => format!("({}↓{})", bexpr_to_str(a), bexpr_to_str(b)),
4272 BExpr::Xnor(a, b) => format!("({}⊙{})", bexpr_to_str(a), bexpr_to_str(b)),
4273 }
4274}
4275
4276fn bexpr_atom_str(e: &BExpr) -> String {
4277 match e {
4278 BExpr::Var(v) => v.clone(),
4279 BExpr::Const(b) => b.to_string(),
4280 _ => format!("({})", bexpr_to_str(e)),
4281 }
4282}
4283
4284fn simplify_bexpr(e: BExpr) -> BExpr {
4285 match e {
4286 BExpr::Not(a) => {
4287 let a = simplify_bexpr(*a);
4288 match a {
4289 BExpr::Const(b) => BExpr::Const(!b),
4290 BExpr::Not(inner) => *inner,
4291 _ => BExpr::Not(Box::new(a)),
4292 }
4293 }
4294 BExpr::And(a, b) => {
4295 let a = simplify_bexpr(*a);
4296 let b = simplify_bexpr(*b);
4297 match (&a, &b) {
4298 (BExpr::Const(false), _) | (_, BExpr::Const(false)) => BExpr::Const(false),
4299 (BExpr::Const(true), _) => b,
4300 (_, BExpr::Const(true)) => a,
4301 _ if a == b => a,
4302 _ => BExpr::And(Box::new(a), Box::new(b)),
4303 }
4304 }
4305 BExpr::Or(a, b) => {
4306 let a = simplify_bexpr(*a);
4307 let b = simplify_bexpr(*b);
4308 match (&a, &b) {
4309 (BExpr::Const(true), _) | (_, BExpr::Const(true)) => BExpr::Const(true),
4310 (BExpr::Const(false), _) => b,
4311 (_, BExpr::Const(false)) => a,
4312 _ if a == b => a,
4313 _ => BExpr::Or(Box::new(a), Box::new(b)),
4314 }
4315 }
4316 BExpr::Xor(a, b) => {
4317 let a = simplify_bexpr(*a);
4318 let b = simplify_bexpr(*b);
4319 match (&a, &b) {
4320 (BExpr::Const(false), _) => b,
4321 (_, BExpr::Const(false)) => a,
4322 (BExpr::Const(true), _) => BExpr::Not(Box::new(b)),
4323 (_, BExpr::Const(true)) => BExpr::Not(Box::new(a)),
4324 _ if a == b => BExpr::Const(false),
4325 _ => BExpr::Xor(Box::new(a), Box::new(b)),
4326 }
4327 }
4328 BExpr::Implies(a, b) => {
4329 let a = simplify_bexpr(*a);
4330 let b = simplify_bexpr(*b);
4331 match (&a, &b) {
4332 (BExpr::Const(false), _) => BExpr::Const(true),
4333 (BExpr::Const(true), _) => b,
4334 (_, BExpr::Const(true)) => BExpr::Const(true),
4335 _ if a == b => BExpr::Const(true),
4336 _ => BExpr::Implies(Box::new(a), Box::new(b)),
4337 }
4338 }
4339 BExpr::Iff(a, b) => {
4340 let a = simplify_bexpr(*a);
4341 let b = simplify_bexpr(*b);
4342 match (&a, &b) {
4343 _ if a == b => BExpr::Const(true),
4344 _ => BExpr::Iff(Box::new(a), Box::new(b)),
4345 }
4346 }
4347 other => other,
4348 }
4349}
4350
4351pub fn logic_calc(query: &str) -> String {
4352 let q = query.trim();
4353 let q_lower = q.to_lowercase();
4354
4355 let (mode, expr_str, expr2_str) =
4357 if q_lower.starts_with("table ") || q_lower.starts_with("truth ") {
4358 (
4359 "table",
4360 q.split_once(' ').map(|x| x.1).unwrap_or("").trim(),
4361 "",
4362 )
4363 } else if q_lower.starts_with("sat ") {
4364 (
4365 "sat",
4366 q.split_once(' ').map(|x| x.1).unwrap_or("").trim(),
4367 "",
4368 )
4369 } else if q_lower.starts_with("taut ") {
4370 (
4371 "taut",
4372 q.split_once(' ').map(|x| x.1).unwrap_or("").trim(),
4373 "",
4374 )
4375 } else if q_lower.starts_with("cnf ") {
4376 (
4377 "cnf",
4378 q.split_once(' ').map(|x| x.1).unwrap_or("").trim(),
4379 "",
4380 )
4381 } else if q_lower.starts_with("dnf ") {
4382 (
4383 "dnf",
4384 q.split_once(' ').map(|x| x.1).unwrap_or("").trim(),
4385 "",
4386 )
4387 } else if q_lower.starts_with("simplify ") {
4388 (
4389 "simplify",
4390 q.split_once(' ').map(|x| x.1).unwrap_or("").trim(),
4391 "",
4392 )
4393 } else if q_lower.starts_with("equiv ") {
4394 let rest = q.split_once(' ').map(|x| x.1).unwrap_or("").trim();
4395 if let Some(semi) = rest.find(';') {
4396 ("equiv", rest[..semi].trim(), rest[semi + 1..].trim())
4397 } else {
4398 ("equiv", rest, "")
4399 }
4400 } else {
4401 ("info", q, "")
4402 };
4403
4404 let mut out = String::new();
4405 let w = 64usize;
4406 let _ = writeln!(out, "{}", "=".repeat(w));
4407
4408 let expr = match parse_bexpr(expr_str) {
4409 Ok(e) => e,
4410 Err(e) => {
4411 let _ = writeln!(out, " Logic — parse error: {}", e);
4412 let _ = writeln!(
4413 out,
4414 " Input: {}",
4415 expr_str.chars().take(60).collect::<String>()
4416 );
4417 let _ = writeln!(out, " Usage: hematite --logic 'A and (B or C)'");
4418 let _ = writeln!(out, "{}", "=".repeat(w));
4419 return out;
4420 }
4421 };
4422
4423 let mut vars: Vec<String> = Vec::new();
4424 collect_vars(&expr, &mut vars);
4425
4426 if vars.is_empty() {
4427 let result = eval_bexpr(&expr, &[]);
4428 let _ = writeln!(out, " Logic | Constant expression: {}", result);
4429 let _ = writeln!(out, "{}", "=".repeat(w));
4430 return out;
4431 }
4432
4433 if vars.len() > 20 {
4434 let _ = writeln!(out, " Logic — too many variables ({}), max 20", vars.len());
4435 let _ = writeln!(out, "{}", "=".repeat(w));
4436 return out;
4437 }
4438
4439 let n = vars.len();
4440 let rows = 1usize << n;
4441
4442 let results: Vec<bool> = (0..rows)
4444 .map(|mask| {
4445 let assignment: Vec<(&str, bool)> = vars
4446 .iter()
4447 .enumerate()
4448 .map(|(i, v)| (v.as_str(), (mask >> (n - 1 - i)) & 1 == 1))
4449 .collect();
4450 eval_bexpr(&expr, &assignment)
4451 })
4452 .collect();
4453
4454 let sat_count = results.iter().filter(|&&b| b).count();
4455 let is_taut = sat_count == rows;
4456 let is_sat = sat_count > 0;
4457 let is_contra = sat_count == 0;
4458
4459 let _ = writeln!(out, " Boolean Logic Analysis");
4460 let _ = writeln!(out, " Expression: {}", bexpr_to_str(&expr));
4461 let _ = writeln!(out, " Variables : {}", vars.join(", "));
4462 let _ = writeln!(
4463 out,
4464 " {} satisfying assignments of {} ({}%)",
4465 sat_count,
4466 rows,
4467 sat_count * 100 / rows
4468 );
4469 let _ = writeln!(
4470 out,
4471 " Status: {}",
4472 if is_taut {
4473 "TAUTOLOGY (always true)"
4474 } else if is_contra {
4475 "CONTRADICTION (always false)"
4476 } else {
4477 "CONTINGENT (sometimes true)"
4478 }
4479 );
4480
4481 match mode {
4482 "sat" => {
4483 if is_sat {
4484 let first_sat = (0..rows).find(|&mask| results[mask]).unwrap();
4485 let assignment: Vec<String> = vars
4486 .iter()
4487 .enumerate()
4488 .map(|(i, v)| format!("{}={}", v, (first_sat >> (n - 1 - i)) & 1 == 1))
4489 .collect();
4490 let _ = writeln!(
4491 out,
4492 " SAT: YES — satisfying assignment: {}",
4493 assignment.join(", ")
4494 );
4495 } else {
4496 let _ = writeln!(out, " SAT: NO — contradiction");
4497 }
4498 }
4499 "taut" => {
4500 let _ = writeln!(out, " TAUTOLOGY: {}", if is_taut { "YES" } else { "NO" });
4501 if !is_taut {
4502 let first_false = (0..rows).find(|&mask| !results[mask]).unwrap();
4503 let assignment: Vec<String> = vars
4504 .iter()
4505 .enumerate()
4506 .map(|(i, v)| format!("{}={}", v, (first_false >> (n - 1 - i)) & 1 == 1))
4507 .collect();
4508 let _ = writeln!(out, " Counterexample: {}", assignment.join(", "));
4509 }
4510 }
4511 "cnf" => {
4512 let false_rows: Vec<usize> = (0..rows).filter(|&m| !results[m]).collect();
4514 if false_rows.is_empty() {
4515 let _ = writeln!(out, " CNF: true (tautology)");
4516 } else {
4517 let _ = writeln!(out, " CNF (maxterms):");
4518 for mask in &false_rows[..false_rows.len().min(8)] {
4519 let clause: Vec<String> = vars
4520 .iter()
4521 .enumerate()
4522 .map(|(i, v)| {
4523 if (mask >> (n - 1 - i)) & 1 == 0 {
4524 v.clone()
4525 } else {
4526 format!("¬{}", v)
4527 }
4528 })
4529 .collect();
4530 let _ = writeln!(out, " ({})", clause.join(" ∨ "));
4531 }
4532 if false_rows.len() > 8 {
4533 let _ = writeln!(out, " ... ({} more clauses)", false_rows.len() - 8);
4534 }
4535 }
4536 }
4537 "dnf" => {
4538 let true_rows: Vec<usize> = (0..rows).filter(|&m| results[m]).collect();
4540 if true_rows.is_empty() {
4541 let _ = writeln!(out, " DNF: false (contradiction)");
4542 } else {
4543 let _ = writeln!(out, " DNF (minterms):");
4544 for mask in &true_rows[..true_rows.len().min(8)] {
4545 let term: Vec<String> = vars
4546 .iter()
4547 .enumerate()
4548 .map(|(i, v)| {
4549 if (mask >> (n - 1 - i)) & 1 == 1 {
4550 v.clone()
4551 } else {
4552 format!("¬{}", v)
4553 }
4554 })
4555 .collect();
4556 let _ = writeln!(out, " ({})", term.join(" ∧ "));
4557 }
4558 if true_rows.len() > 8 {
4559 let _ = writeln!(out, " ... ({} more terms)", true_rows.len() - 8);
4560 }
4561 }
4562 }
4563 "simplify" => {
4564 let simp = simplify_bexpr(expr.clone());
4565 let _ = writeln!(out, " Simplified: {}", bexpr_to_str(&simp));
4566 }
4567 "equiv" => {
4568 let expr2 = match parse_bexpr(expr2_str) {
4569 Ok(e) => e,
4570 Err(e) => {
4571 let _ = writeln!(out, " Parse error (expr2): {}", e);
4572 let _ = writeln!(out, "{}", "=".repeat(w));
4573 return out;
4574 }
4575 };
4576 let mut vars2 = vars.clone();
4577 collect_vars(&expr2, &mut vars2);
4578 vars2.sort();
4579 vars2.dedup();
4580 let n2 = vars2.len();
4581 let rows2 = 1usize << n2;
4582 let equiv = (0..rows2).all(|mask| {
4583 let assignment: Vec<(&str, bool)> = vars2
4584 .iter()
4585 .enumerate()
4586 .map(|(i, v)| (v.as_str(), (mask >> (n2 - 1 - i)) & 1 == 1))
4587 .collect();
4588 eval_bexpr(&expr, &assignment) == eval_bexpr(&expr2, &assignment)
4589 });
4590 let _ = writeln!(out, " Expr1: {}", bexpr_to_str(&expr));
4591 let _ = writeln!(out, " Expr2: {}", bexpr_to_str(&expr2));
4592 let _ = writeln!(
4593 out,
4594 " Logically equivalent: {}",
4595 if equiv { "YES" } else { "NO" }
4596 );
4597 }
4598 _ => {
4599 let max_table_rows = if n <= 4 { rows } else { rows.min(32) };
4601 let _ = writeln!(out, "\n Truth Table:");
4602 let var_header: String = vars
4604 .iter()
4605 .map(|v| format!(" {:>3}", v))
4606 .collect::<Vec<_>>()
4607 .join("");
4608 let _ = writeln!(out, "{} │ Result", var_header);
4609 let _ = writeln!(out, " {}", "-".repeat(vars.len() * 5 + 10));
4610 for mask in 0..max_table_rows {
4611 let row_vals: String = (0..n)
4612 .map(|i| {
4613 format!(
4614 " {:>3}",
4615 if (mask >> (n - 1 - i)) & 1 == 1 {
4616 "T"
4617 } else {
4618 "F"
4619 }
4620 )
4621 })
4622 .collect::<Vec<_>>()
4623 .join("");
4624 let _ = writeln!(
4625 out,
4626 "{} │ {}",
4627 row_vals,
4628 if results[mask] { "T" } else { "F" }
4629 );
4630 }
4631 if max_table_rows < rows {
4632 let _ = writeln!(out, " ... ({} rows omitted — use --logic 'table EXPR' for full table with ≤4 vars)", rows - max_table_rows);
4633 }
4634
4635 if is_sat {
4637 let first_sat = (0..rows).find(|&m| results[m]).unwrap();
4638 let sat_ex: Vec<String> = vars
4639 .iter()
4640 .enumerate()
4641 .map(|(i, v)| {
4642 format!(
4643 "{}={}",
4644 v,
4645 if (first_sat >> (n - 1 - i)) & 1 == 1 {
4646 "T"
4647 } else {
4648 "F"
4649 }
4650 )
4651 })
4652 .collect();
4653 let _ = writeln!(out, "\n SAT witness: {}", sat_ex.join(", "));
4654 }
4655 }
4656 }
4657
4658 let _ = writeln!(out, "{}", "=".repeat(w));
4659 out
4660}
4661
4662type Matrix = Vec<Vec<f64>>;
4682
4683fn mat_rows(m: &Matrix) -> usize {
4684 m.len()
4685}
4686fn mat_cols(m: &Matrix) -> usize {
4687 m.first().map(|r| r.len()).unwrap_or(0)
4688}
4689
4690fn parse_matrix(s: &str) -> Result<Matrix, String> {
4691 let s = s.trim();
4692 if s.starts_with('[') {
4694 return parse_matrix_json(s);
4695 }
4696 let row_strs: Vec<&str> = s
4698 .split([';', '\n'])
4699 .map(str::trim)
4700 .filter(|r| !r.is_empty())
4701 .collect();
4702 if row_strs.is_empty() {
4703 return Err("empty matrix".into());
4704 }
4705 let mut mat: Matrix = Vec::new();
4706 for row_str in &row_strs {
4707 let row: Vec<f64> = row_str
4708 .split([',', ' ', '\t'])
4709 .map(str::trim)
4710 .filter(|s| !s.is_empty())
4711 .map(|tok| {
4712 tok.parse::<f64>()
4713 .map_err(|_| format!("bad number: {}", tok))
4714 })
4715 .collect::<Result<Vec<_>, _>>()?;
4716 mat.push(row);
4717 }
4718 let ncols = mat[0].len();
4719 for (i, row) in mat.iter().enumerate() {
4720 if row.len() != ncols {
4721 return Err(format!(
4722 "row {} has {} columns, expected {}",
4723 i,
4724 row.len(),
4725 ncols
4726 ));
4727 }
4728 }
4729 Ok(mat)
4730}
4731
4732fn parse_matrix_json(s: &str) -> Result<Matrix, String> {
4733 let chars: Vec<char> = s.chars().collect();
4735 let mut pos = 0;
4736 fn skip(chars: &[char], pos: &mut usize) {
4737 while *pos < chars.len() && chars[*pos].is_whitespace() {
4738 *pos += 1;
4739 }
4740 }
4741 fn parse_num(chars: &[char], pos: &mut usize) -> Result<f64, String> {
4742 skip(chars, pos);
4743 let start = *pos;
4744 while *pos < chars.len()
4745 && (chars[*pos].is_ascii_digit() || matches!(chars[*pos], '.' | '-' | '+' | 'e' | 'E'))
4746 {
4747 *pos += 1;
4748 }
4749 let s: String = chars[start..*pos].iter().collect();
4750 s.trim()
4751 .parse::<f64>()
4752 .map_err(|_| format!("bad number: '{}'", s))
4753 }
4754 fn parse_row(chars: &[char], pos: &mut usize) -> Result<Vec<f64>, String> {
4755 skip(chars, pos);
4756 if chars.get(*pos) != Some(&'[') {
4757 return Err("expected '[' for row".into());
4758 }
4759 *pos += 1;
4760 let mut row = Vec::new();
4761 loop {
4762 skip(chars, pos);
4763 if chars.get(*pos) == Some(&']') {
4764 *pos += 1;
4765 break;
4766 }
4767 if !row.is_empty() {
4768 if chars.get(*pos) == Some(&',') {
4769 *pos += 1;
4770 } else {
4771 return Err("expected ','".into());
4772 }
4773 }
4774 row.push(parse_num(chars, pos)?);
4775 }
4776 Ok(row)
4777 }
4778 skip(&chars, &mut pos);
4779 if chars.get(pos) != Some(&'[') {
4780 return Err("expected outer '['".into());
4781 }
4782 pos += 1;
4783 let mut mat: Matrix = Vec::new();
4784 loop {
4785 skip(&chars, &mut pos);
4786 if chars.get(pos) == Some(&']') {
4787 break;
4788 }
4789 if !mat.is_empty() {
4790 if chars.get(pos) == Some(&',') {
4791 pos += 1;
4792 } else {
4793 return Err("expected ','".into());
4794 }
4795 }
4796 skip(&chars, &mut pos);
4797 if chars.get(pos) == Some(&'[') {
4799 mat.push(parse_row(&chars, &mut pos)?);
4800 } else {
4801 let n = parse_num(&chars, &mut pos)?;
4803 mat.push(vec![n]);
4804 }
4805 }
4806 if mat.is_empty() {
4807 return Err("empty matrix".into());
4808 }
4809 let ncols = mat[0].len();
4810 for (i, row) in mat.iter().enumerate() {
4811 if row.len() != ncols {
4812 return Err(format!(
4813 "row {} has {} cols, expected {}",
4814 i,
4815 row.len(),
4816 ncols
4817 ));
4818 }
4819 }
4820 Ok(mat)
4821}
4822
4823fn mat_clone(m: &Matrix) -> Matrix {
4824 m.clone()
4825}
4826
4827fn mat_identity(n: usize) -> Matrix {
4828 (0..n)
4829 .map(|i| (0..n).map(|j| if i == j { 1.0 } else { 0.0 }).collect())
4830 .collect()
4831}
4832
4833fn mat_fmt(m: &Matrix) -> String {
4834 let rows = mat_rows(m);
4835 let cols = mat_cols(m);
4836 let cells: Vec<String> = m
4837 .iter()
4838 .flat_map(|row| {
4839 row.iter().map(|v| {
4840 if v.abs() < 1e-12 {
4841 "0".to_string()
4842 } else if v.fract() == 0.0 && v.abs() < 1e9 {
4843 format!("{}", *v as i64)
4844 } else {
4845 format!("{:.6}", v)
4846 .trim_end_matches('0')
4847 .trim_end_matches('.')
4848 .to_string()
4849 }
4850 })
4851 })
4852 .collect();
4853 let mut col_widths: Vec<usize> = vec![0; cols];
4855 for r in 0..rows {
4856 for c in 0..cols {
4857 col_widths[c] = col_widths[c].max(cells[r * cols + c].len());
4858 }
4859 }
4860 let mut out = String::new();
4861 for r in 0..rows {
4862 out.push_str(" [ ");
4863 for c in 0..cols {
4864 let s = &cells[r * cols + c];
4865 out.push_str(&format!("{:>w$}", s, w = col_widths[c]));
4866 if c < cols - 1 {
4867 out.push_str(" ");
4868 }
4869 }
4870 out.push_str(" ]\n");
4871 }
4872 out
4873}
4874
4875fn lu_decompose(a: &Matrix) -> Result<(Matrix, Matrix, Vec<usize>, i32), String> {
4877 let n = mat_rows(a);
4878 if mat_cols(a) != n {
4879 return Err("LU requires square matrix".into());
4880 }
4881 let mut u = mat_clone(a);
4882 let mut l = mat_identity(n);
4883 let mut perm: Vec<usize> = (0..n).collect();
4884 let mut sign = 1i32;
4885
4886 for col in 0..n {
4887 let mut max_row = col;
4889 let mut max_val = u[col][col].abs();
4890 for row in (col + 1)..n {
4891 if u[row][col].abs() > max_val {
4892 max_val = u[row][col].abs();
4893 max_row = row;
4894 }
4895 }
4896 if max_val < 1e-14 {
4897 return Err("matrix is singular (or near-singular)".into());
4898 }
4899 if max_row != col {
4900 u.swap(col, max_row);
4901 perm.swap(col, max_row);
4902 sign = -sign;
4903 for j in 0..col {
4905 let tmp = l[col][j];
4906 l[col][j] = l[max_row][j];
4907 l[max_row][j] = tmp;
4908 }
4909 }
4910 for row in (col + 1)..n {
4911 let factor = u[row][col] / u[col][col];
4912 l[row][col] = factor;
4913 for k in col..n {
4914 u[row][k] -= factor * u[col][k];
4915 }
4916 }
4917 }
4918 Ok((l, u, perm, sign))
4919}
4920
4921fn mat_det(a: &Matrix) -> Result<f64, String> {
4922 match lu_decompose(a) {
4923 Ok((_, u, _, sign)) => {
4924 let d: f64 = (0..mat_rows(a)).map(|i| u[i][i]).product();
4925 Ok(d * sign as f64)
4926 }
4927 Err(_) => Ok(0.0), }
4929}
4930
4931fn mat_solve_lu(l: &Matrix, u: &Matrix, perm: &[usize], b: &[f64]) -> Vec<f64> {
4932 let n = l.len();
4933 let pb: Vec<f64> = (0..n).map(|i| b[perm[i]]).collect();
4935 let mut y = vec![0.0f64; n];
4937 for i in 0..n {
4938 y[i] = pb[i] - (0..i).map(|j| l[i][j] * y[j]).sum::<f64>();
4939 }
4940 let mut x = vec![0.0f64; n];
4942 for i in (0..n).rev() {
4943 x[i] = (y[i] - (i + 1..n).map(|j| u[i][j] * x[j]).sum::<f64>()) / u[i][i];
4944 }
4945 x
4946}
4947
4948fn mat_inv(a: &Matrix) -> Result<Matrix, String> {
4949 let n = mat_rows(a);
4950 let (l, u, perm, _) = lu_decompose(a)?;
4951 let mut inv = mat_identity(n);
4952 for col in 0..n {
4953 let b: Vec<f64> = (0..n).map(|i| if i == col { 1.0 } else { 0.0 }).collect();
4954 let x = mat_solve_lu(&l, &u, &perm, &b);
4955 for row in 0..n {
4956 inv[row][col] = x[row];
4957 }
4958 }
4959 Ok(inv)
4960}
4961
4962fn mat_mul(a: &Matrix, b: &Matrix) -> Result<Matrix, String> {
4963 let (ar, ac) = (mat_rows(a), mat_cols(a));
4964 let (br, bc) = (mat_rows(b), mat_cols(b));
4965 if ac != br {
4966 return Err(format!(
4967 "incompatible dimensions {}×{} × {}×{}",
4968 ar, ac, br, bc
4969 ));
4970 }
4971 let mut c = vec![vec![0.0f64; bc]; ar];
4972 for i in 0..ar {
4973 for j in 0..bc {
4974 for k in 0..ac {
4975 c[i][j] += a[i][k] * b[k][j];
4976 }
4977 }
4978 }
4979 Ok(c)
4980}
4981
4982fn mat_transpose(a: &Matrix) -> Matrix {
4983 let (r, c) = (mat_rows(a), mat_cols(a));
4984 (0..c).map(|j| (0..r).map(|i| a[i][j]).collect()).collect()
4985}
4986
4987fn mat_rank(a: &Matrix) -> usize {
4988 let mut m = mat_clone(a);
4989 let rows = mat_rows(&m);
4990 let cols = mat_cols(&m);
4991 let mut rank = 0usize;
4992 let mut row_cursor = 0usize;
4993 for col in 0..cols {
4994 let pivot = (row_cursor..rows).find(|&r| m[r][col].abs() > 1e-10);
4995 if let Some(pr) = pivot {
4996 m.swap(row_cursor, pr);
4997 let pivot_val = m[row_cursor][col];
4998 for j in col..cols {
4999 m[row_cursor][j] /= pivot_val;
5000 }
5001 for r in 0..rows {
5002 if r != row_cursor && m[r][col].abs() > 1e-10 {
5003 let factor = m[r][col];
5004 for j in col..cols {
5005 m[r][j] -= factor * m[row_cursor][j];
5006 }
5007 }
5008 }
5009 rank += 1;
5010 row_cursor += 1;
5011 }
5012 }
5013 rank
5014}
5015
5016fn mat_eigen_power(a: &Matrix, max_iter: usize) -> Option<(f64, Vec<f64>)> {
5018 let n = mat_rows(a);
5019 if n == 0 {
5020 return None;
5021 }
5022 let mut v: Vec<f64> = (0..n).map(|i| if i == 0 { 1.0 } else { 0.1 }).collect();
5023 let mut lam = 0.0f64;
5024 for _ in 0..max_iter {
5025 let av: Vec<f64> = (0..n)
5027 .map(|i| (0..n).map(|j| a[i][j] * v[j]).sum::<f64>())
5028 .collect();
5029 let norm = av.iter().map(|x| x * x).sum::<f64>().sqrt();
5030 if norm < 1e-14 {
5031 break;
5032 }
5033 lam = av.iter().zip(&v).map(|(a, b)| a * b).sum::<f64>();
5034 v = av.iter().map(|x| x / norm).collect();
5035 }
5036 Some((lam, v))
5037}
5038
5039fn mat_qr(a: &Matrix) -> (Matrix, Matrix) {
5041 let m = mat_rows(a);
5042 let n = mat_cols(a);
5043 let cols_a: Vec<Vec<f64>> = (0..n).map(|j| (0..m).map(|i| a[i][j]).collect()).collect();
5044 let mut q_cols: Vec<Vec<f64>> = Vec::new();
5045 let mut r: Matrix = vec![vec![0.0; n]; n.min(m)];
5046 for j in 0..n {
5047 let mut v: Vec<f64> = cols_a[j].clone();
5048 for (k, qk) in q_cols.iter().enumerate() {
5049 let proj: f64 = v.iter().zip(qk).map(|(a, b)| a * b).sum();
5050 r[k][j] = proj;
5051 for i in 0..m {
5052 v[i] -= proj * qk[i];
5053 }
5054 }
5055 let norm = v.iter().map(|x| x * x).sum::<f64>().sqrt();
5056 if norm > 1e-12 {
5057 let qj: Vec<f64> = v.iter().map(|x| x / norm).collect();
5058 r[q_cols.len()][j] = norm;
5059 q_cols.push(qj);
5060 }
5061 }
5062 let q: Matrix = (0..m)
5063 .map(|i| {
5064 q_cols
5065 .iter()
5066 .map(|col| *col.get(i).unwrap_or(&0.0))
5067 .collect()
5068 })
5069 .collect();
5070 (q, r)
5071}
5072
5073fn mat_svd_values(a: &Matrix) -> (Vec<f64>, Matrix) {
5075 let n = mat_cols(a);
5076 let at = mat_transpose(a);
5078 let ata = mat_mul(&at, a).unwrap_or_else(|_| vec![vec![0.0; n]; n]);
5079 let mut a_copy = ata.clone();
5081 let mut sigma_vals: Vec<f64> = Vec::new();
5082 let mut v_vecs: Vec<Vec<f64>> = Vec::new();
5083 for _ in 0..n {
5084 match mat_eigen_power(&a_copy, 1000) {
5085 Some((lam, v)) => {
5086 let sv = lam.abs().sqrt();
5087 sigma_vals.push(sv);
5088 v_vecs.push(v.clone());
5089 for i in 0..n {
5091 for j in 0..n {
5092 a_copy[i][j] -= lam * v[i] * v[j];
5093 }
5094 }
5095 }
5096 None => break,
5097 }
5098 }
5099 let mut pairs: Vec<(f64, Vec<f64>)> = sigma_vals.into_iter().zip(v_vecs).collect();
5101 pairs.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
5102 let svs: Vec<f64> = pairs.iter().map(|p| p.0).collect();
5103 let v_mat: Matrix = (0..n)
5104 .map(|i| pairs.iter().map(|p| *p.1.get(i).unwrap_or(&0.0)).collect())
5105 .collect();
5106 (svs, v_mat)
5107}
5108
5109fn mat_cholesky(a: &Matrix) -> Result<Matrix, String> {
5111 let n = mat_rows(a);
5112 let mut l: Matrix = vec![vec![0.0; n]; n];
5113 for i in 0..n {
5114 for j in 0..=i {
5115 let sum: f64 = (0..j).map(|k| l[i][k] * l[j][k]).sum();
5116 if i == j {
5117 let d = a[i][i] - sum;
5118 if d < -1e-10 {
5119 return Err(format!("not positive definite (d[{}] = {:.4e})", i, d));
5120 }
5121 l[i][j] = d.max(0.0).sqrt();
5122 } else {
5123 if l[j][j].abs() < 1e-14 {
5124 return Err("zero pivot".into());
5125 }
5126 l[i][j] = (a[i][j] - sum) / l[j][j];
5127 }
5128 }
5129 }
5130 Ok(l)
5131}
5132
5133fn mat_pinv(a: &Matrix) -> Result<Matrix, String> {
5135 let at = mat_transpose(a);
5136 let ata = mat_mul(&at, a)?;
5137 let ata_inv = mat_inv(&ata)?;
5138 mat_mul(&ata_inv, &at)
5139}
5140
5141pub fn matrix_calc(query: &str) -> String {
5142 let q = query.trim();
5143
5144 let (mode, rest) = {
5146 let words: Vec<&str> = q.splitn(2, char::is_whitespace).collect();
5147 let m = words[0].to_lowercase();
5148 let rest = words.get(1).copied().unwrap_or("").trim();
5149 match m.as_str() {
5150 "det" | "determinant" => ("det", rest.to_string()),
5151 "inv" | "inverse" => ("inv", rest.to_string()),
5152 "solve" => ("solve", rest.to_string()),
5153 "mul" | "multiply" => ("mul", rest.to_string()),
5154 "transpose" | "trans" => ("transpose", rest.to_string()),
5155 "eigen" | "eigenvalues" | "eig" => ("eigen", rest.to_string()),
5156 "rank" => ("rank", rest.to_string()),
5157 "lu" => ("lu", rest.to_string()),
5158 "qr" => ("qr", rest.to_string()),
5159 "svd" => ("svd", rest.to_string()),
5160 "chol" | "cholesky" => ("chol", rest.to_string()),
5161 "pinv" | "pseudoinverse" | "pseudo" => ("pinv", rest.to_string()),
5162 _ => ("info", q.to_string()),
5163 }
5164 };
5165
5166 let mut out = String::new();
5167 let w = 64usize;
5168 let _ = writeln!(out, "{}", "=".repeat(w));
5169
5170 if mode == "solve" {
5172 let parts = split_two_matrices(&rest);
5175 if parts.len() < 2 {
5176 let _ = writeln!(out, " Matrix — Solve Ax = b");
5177 let _ = writeln!(out, " Error: provide matrix A and vector b, e.g.:");
5178 let _ = writeln!(out, " --matrix 'solve [[1,2],[3,4]] [[5],[6]]'");
5179 let _ = writeln!(out, "{}", "=".repeat(w));
5180 return out;
5181 }
5182 let a_str = &parts[0];
5183 let b_str = &parts[1];
5184 let a = match parse_matrix(a_str) {
5185 Ok(m) => m,
5186 Err(e) => {
5187 let _ = writeln!(out, " Parse error (A): {}", e);
5188 let _ = writeln!(out, "{}", "=".repeat(w));
5189 return out;
5190 }
5191 };
5192 let b_mat = match parse_matrix(b_str) {
5193 Ok(m) => m,
5194 Err(e) => {
5195 let _ = writeln!(out, " Parse error (b): {}", e);
5196 let _ = writeln!(out, "{}", "=".repeat(w));
5197 return out;
5198 }
5199 };
5200 let n = mat_rows(&a);
5201 let b_vec: Vec<f64> = if mat_cols(&b_mat) == 1 {
5202 b_mat.iter().map(|r| r[0]).collect()
5203 } else if mat_rows(&b_mat) == 1 {
5204 b_mat[0].clone()
5205 } else {
5206 let _ = writeln!(out, " Error: b must be a column or row vector");
5207 let _ = writeln!(out, "{}", "=".repeat(w));
5208 return out;
5209 };
5210 if b_vec.len() != n {
5211 let _ = writeln!(
5212 out,
5213 " Error: A is {}×{} but b has {} elements",
5214 n,
5215 mat_cols(&a),
5216 b_vec.len()
5217 );
5218 let _ = writeln!(out, "{}", "=".repeat(w));
5219 return out;
5220 }
5221 let _ = writeln!(out, " Matrix — Solve Ax = b");
5222 let _ = writeln!(out, " A ({}×{}):", n, mat_cols(&a));
5223 out.push_str(&mat_fmt(&a));
5224 let _ = writeln!(out, " b:");
5225 let b_col: Matrix = b_vec.iter().map(|&v| vec![v]).collect();
5226 out.push_str(&mat_fmt(&b_col));
5227 match lu_decompose(&a) {
5228 Ok((l, u, perm, _)) => {
5229 let x = mat_solve_lu(&l, &u, &perm, &b_vec);
5230 let _ = writeln!(out, " Solution x:");
5231 for (i, &xi) in x.iter().enumerate() {
5232 let _ = writeln!(out, " x[{}] = {:.8}", i, xi);
5233 }
5234 let residual: f64 = (0..n)
5236 .map(|i| {
5237 let ax_i: f64 = (0..n).map(|j| a[i][j] * x[j]).sum();
5238 (ax_i - b_vec[i]).powi(2)
5239 })
5240 .sum::<f64>()
5241 .sqrt();
5242 let _ = writeln!(out, " Residual |Ax - b| = {:.2e}", residual);
5243 }
5244 Err(e) => {
5245 let _ = writeln!(out, " Error: {}", e);
5246 }
5247 }
5248 let _ = writeln!(out, "{}", "=".repeat(w));
5249 return out;
5250 }
5251
5252 if mode == "mul" {
5254 let parts = split_two_matrices(&rest);
5255 if parts.len() < 2 {
5256 let _ = writeln!(out, " Matrix multiply: provide two matrices, e.g.:");
5257 let _ = writeln!(out, " --matrix 'mul [[1,2],[3,4]] [[5,6],[7,8]]'");
5258 let _ = writeln!(out, "{}", "=".repeat(w));
5259 return out;
5260 }
5261 let a = match parse_matrix(&parts[0]) {
5262 Ok(m) => m,
5263 Err(e) => {
5264 let _ = writeln!(out, " Parse error (A): {}", e);
5265 let _ = writeln!(out, "{}", "=".repeat(w));
5266 return out;
5267 }
5268 };
5269 let b = match parse_matrix(&parts[1]) {
5270 Ok(m) => m,
5271 Err(e) => {
5272 let _ = writeln!(out, " Parse error (B): {}", e);
5273 let _ = writeln!(out, "{}", "=".repeat(w));
5274 return out;
5275 }
5276 };
5277 let _ = writeln!(out, " Matrix Multiply A × B");
5278 let _ = writeln!(out, " A ({}×{}):", mat_rows(&a), mat_cols(&a));
5279 out.push_str(&mat_fmt(&a));
5280 let _ = writeln!(out, " B ({}×{}):", mat_rows(&b), mat_cols(&b));
5281 out.push_str(&mat_fmt(&b));
5282 match mat_mul(&a, &b) {
5283 Ok(c) => {
5284 let _ = writeln!(out, " A × B ({}×{}):", mat_rows(&c), mat_cols(&c));
5285 out.push_str(&mat_fmt(&c));
5286 }
5287 Err(e) => {
5288 let _ = writeln!(out, " Error: {}", e);
5289 }
5290 }
5291 let _ = writeln!(out, "{}", "=".repeat(w));
5292 return out;
5293 }
5294
5295 let mat_str = &rest;
5297 let a = match parse_matrix(mat_str) {
5298 Ok(m) => m,
5299 Err(e) => {
5300 let _ = writeln!(out, " Parse error: {}", e);
5301 let _ = writeln!(
5302 out,
5303 " Input: {}",
5304 mat_str.chars().take(80).collect::<String>()
5305 );
5306 let _ = writeln!(out, " Formats: [[1,2],[3,4]] or 1 2; 3 4");
5307 let _ = writeln!(out, "{}", "=".repeat(w));
5308 return out;
5309 }
5310 };
5311
5312 let (rows, cols) = (mat_rows(&a), mat_cols(&a));
5313 let _ = writeln!(out, " Matrix Operations ({}×{})", rows, cols);
5314 out.push_str(&mat_fmt(&a));
5315
5316 match mode {
5317 "det" => {
5318 if rows != cols {
5319 let _ = writeln!(out, " Error: det requires square matrix");
5320 } else {
5321 match mat_det(&a) {
5322 Ok(d) => {
5323 let _ = writeln!(out, " det(A) = {:.8}", d);
5324 }
5325 Err(e) => {
5326 let _ = writeln!(out, " Error: {}", e);
5327 }
5328 }
5329 }
5330 }
5331 "inv" => {
5332 if rows != cols {
5333 let _ = writeln!(out, " Error: inv requires square matrix");
5334 } else {
5335 match mat_inv(&a) {
5336 Ok(inv) => {
5337 let _ = writeln!(out, " A⁻¹:");
5338 out.push_str(&mat_fmt(&inv));
5339 }
5340 Err(e) => {
5341 let _ = writeln!(out, " Error: {}", e);
5342 }
5343 }
5344 }
5345 }
5346 "transpose" => {
5347 let t = mat_transpose(&a);
5348 let _ = writeln!(out, " Aᵀ ({}×{}):", mat_cols(&a), rows);
5349 out.push_str(&mat_fmt(&t));
5350 }
5351 "rank" => {
5352 let r = mat_rank(&a);
5353 let _ = writeln!(out, " rank(A) = {}", r);
5354 if rows == cols {
5355 let _ = writeln!(
5356 out,
5357 " {} ({}×{} square, rank {})",
5358 if r == rows {
5359 "Full rank"
5360 } else {
5361 "Rank-deficient"
5362 },
5363 rows,
5364 cols,
5365 r
5366 );
5367 }
5368 }
5369 "lu" => {
5370 if rows != cols {
5371 let _ = writeln!(out, " Error: LU requires square matrix");
5372 } else {
5373 match lu_decompose(&a) {
5374 Ok((l, u, perm, _)) => {
5375 let _ = writeln!(out, " L (lower triangular):");
5376 out.push_str(&mat_fmt(&l));
5377 let _ = writeln!(out, " U (upper triangular):");
5378 out.push_str(&mat_fmt(&u));
5379 let perm_str = perm
5380 .iter()
5381 .map(|&p| p.to_string())
5382 .collect::<Vec<_>>()
5383 .join(", ");
5384 let _ = writeln!(out, " Pivot permutation: [{}]", perm_str);
5385 }
5386 Err(e) => {
5387 let _ = writeln!(out, " Error: {}", e);
5388 }
5389 }
5390 }
5391 }
5392 "eigen" => {
5393 if rows != cols {
5394 let _ = writeln!(out, " Error: eigenvalues require square matrix");
5395 } else if rows > 8 {
5396 let _ = writeln!(
5397 out,
5398 " Error: power iteration limited to 8×8 (matrix is {}×{})",
5399 rows, cols
5400 );
5401 } else {
5402 let _ = writeln!(out, " Eigenvalues (power iteration + deflation):");
5403 let mut a_copy = mat_clone(&a);
5404 for k in 0..rows {
5405 match mat_eigen_power(&a_copy, 500) {
5406 Some((lam, v)) => {
5407 let v_str = v
5408 .iter()
5409 .map(|x| format!("{:.4}", x))
5410 .collect::<Vec<_>>()
5411 .join(", ");
5412 let _ = writeln!(
5413 out,
5414 " λ{} = {:.6} eigenvector ≈ [{}]",
5415 k + 1,
5416 lam,
5417 v_str
5418 );
5419 for i in 0..rows {
5421 for j in 0..rows {
5422 a_copy[i][j] -= lam * v[i] * v[j];
5423 }
5424 }
5425 }
5426 None => break,
5427 }
5428 }
5429 let trace: f64 = (0..rows).map(|i| a[i][i]).sum();
5430 let _ = writeln!(out, " Trace = {:.6}", trace);
5431 if let Ok(d) = mat_det(&a) {
5432 let _ = writeln!(out, " Det = {:.6}", d);
5433 }
5434 }
5435 }
5436 "qr" => {
5437 let (q_mat, r_mat) = mat_qr(&a);
5439 let _ = writeln!(out, " QR Decomposition (A = Q · R)");
5440 let _ = writeln!(out, " Q (orthonormal columns):");
5441 out.push_str(&mat_fmt(&q_mat));
5442 let _ = writeln!(out, " R (upper-triangular):");
5443 out.push_str(&mat_fmt(&r_mat));
5444 }
5445 "svd" => {
5446 if rows > 8 || cols > 8 {
5449 let _ = writeln!(
5450 out,
5451 " Error: SVD limited to 8×8 matrices ({}×{})",
5452 rows, cols
5453 );
5454 } else {
5455 let (s_vals, v_mat) = mat_svd_values(&a);
5456 let _ = writeln!(out, " SVD Singular Values:");
5457 for (i, sv) in s_vals.iter().enumerate() {
5458 let bar_len = if s_vals[0].abs() > 1e-12 {
5459 (sv / s_vals[0] * 20.0) as usize
5460 } else {
5461 0
5462 };
5463 let _ = writeln!(out, " σ{} = {:.6} {}", i + 1, sv, "#".repeat(bar_len));
5464 }
5465 let rank: usize = s_vals.iter().filter(|&&v| v.abs() > 1e-9).count();
5466 let cond = if s_vals.last().map(|v| v.abs()).unwrap_or(0.0) > 1e-12 {
5467 format!(
5468 "{:.4}",
5469 s_vals[0] / s_vals.iter().cloned().fold(f64::INFINITY, f64::min)
5470 )
5471 } else {
5472 "∞ (singular)".to_string()
5473 };
5474 let _ = writeln!(out, " Rank: {} | Condition number: {}", rank, cond);
5475 let _ = writeln!(out, " V (right singular vectors):");
5476 out.push_str(&mat_fmt(&v_mat));
5477 }
5478 }
5479 "chol" => {
5480 if rows != cols {
5481 let _ = writeln!(out, " Error: Cholesky requires square matrix");
5482 } else {
5483 match mat_cholesky(&a) {
5484 Ok(l) => {
5485 let _ = writeln!(out, " Cholesky Decomposition (A = L · Lᵀ)");
5486 let _ = writeln!(out, " L (lower-triangular):");
5487 out.push_str(&mat_fmt(&l));
5488 }
5489 Err(e) => {
5490 let _ = writeln!(
5491 out,
5492 " Error: {} (matrix must be symmetric positive-definite)",
5493 e
5494 );
5495 }
5496 }
5497 }
5498 }
5499 "pinv" => {
5500 match mat_pinv(&a) {
5502 Ok(p) => {
5503 let _ = writeln!(out, " Moore-Penrose Pseudoinverse (A⁺):");
5504 out.push_str(&mat_fmt(&p));
5505 if let Ok(aa_p) = mat_mul(&a, &p) {
5507 if let Ok(aapa) = mat_mul(&aa_p, &a) {
5508 let mut err = 0.0f64;
5509 for i in 0..aapa.len() {
5510 for j in 0..aapa[i].len() {
5511 let d = (aapa[i][j] - a[i][j]).abs();
5512 if d > err {
5513 err = d;
5514 }
5515 }
5516 }
5517 let _ = writeln!(out, " Verify ||A*A+*A - A||_inf = {:.2e}", err);
5518 }
5519 }
5520 }
5521 Err(e) => {
5522 let _ = writeln!(out, " Error: {}", e);
5523 }
5524 }
5525 }
5526 _ => {
5527 let _ = writeln!(out, " Rank: {}", mat_rank(&a));
5529 if rows == cols {
5530 if let Ok(d) = mat_det(&a) {
5531 let _ = writeln!(out, " Det: {:.6}", d);
5532 }
5533 match mat_inv(&a) {
5534 Ok(inv) => {
5535 let _ = writeln!(out, " Inverse:");
5536 out.push_str(&mat_fmt(&inv));
5537 }
5538 Err(_) => {
5539 let _ = writeln!(out, " Inverse: N/A (singular)");
5540 }
5541 }
5542 let trace: f64 = (0..rows).map(|i| a[i][i]).sum();
5543 let _ = writeln!(out, " Trace: {:.6}", trace);
5544 let frobenius: f64 = a
5545 .iter()
5546 .flat_map(|r| r.iter())
5547 .map(|v| v * v)
5548 .sum::<f64>()
5549 .sqrt();
5550 let _ = writeln!(out, " Frobenius norm: {:.6}", frobenius);
5551 }
5552 let t = mat_transpose(&a);
5553 let _ = writeln!(out, " Transpose:");
5554 out.push_str(&mat_fmt(&t));
5555 }
5556 }
5557
5558 let _ = writeln!(out, "{}", "=".repeat(w));
5559 out
5560}
5561
5562fn split_two_matrices(s: &str) -> Vec<String> {
5563 let s = s.trim();
5565 if s.starts_with('[') {
5566 let mut depth = 0;
5568 let mut end = 0;
5569 for (i, c) in s.chars().enumerate() {
5570 if c == '[' {
5571 depth += 1;
5572 } else if c == ']' {
5573 depth -= 1;
5574 if depth == 0 {
5575 end = i + 1;
5576 break;
5577 }
5578 }
5579 }
5580 if end == 0 || end >= s.len() {
5581 return vec![s.to_string()];
5582 }
5583 let a_str = s[..end].trim().to_string();
5584 let b_str = s[end..].trim().trim_start_matches([',', ' ']).to_string();
5585 if b_str.is_empty() {
5586 return vec![a_str];
5587 }
5588 return vec![a_str, b_str];
5589 }
5590 if let Some(pos) = s.find(" / ") {
5592 return vec![s[..pos].trim().to_string(), s[pos + 3..].trim().to_string()];
5593 }
5594 vec![s.to_string()]
5595}
5596
5597pub fn finance_calc(query: &str) -> String {
5610 let q = query.trim();
5611 let tokens: Vec<&str> = q.split_whitespace().collect();
5612 if tokens.is_empty() {
5613 return finance_usage();
5614 }
5615
5616 let mut out = String::new();
5617 let w = 64usize;
5618 let _ = writeln!(out, "{}", "=".repeat(w));
5619
5620 match tokens[0].to_lowercase().as_str() {
5621 "npv" => {
5622 if tokens.len() < 3 {
5623 let _ = writeln!(out, " Usage: npv RATE CF0 CF1 CF2 ...");
5624 let _ = writeln!(out, "{}", "=".repeat(w));
5625 return out;
5626 }
5627 let rate: f64 = match tokens[1].trim_end_matches('%').parse() {
5628 Ok(v) => {
5629 if tokens[1].contains('%') {
5630 v / 100.0
5631 } else {
5632 v
5633 }
5634 }
5635 Err(_) => {
5636 let _ = writeln!(out, " Error: bad rate '{}'", tokens[1]);
5637 let _ = writeln!(out, "{}", "=".repeat(w));
5638 return out;
5639 }
5640 };
5641 let cfs: Vec<f64> = tokens[2..]
5642 .iter()
5643 .filter_map(|s| s.replace(',', "").parse::<f64>().ok())
5644 .collect();
5645 if cfs.is_empty() {
5646 let _ = writeln!(out, " Error: no cash flows found");
5647 let _ = writeln!(out, "{}", "=".repeat(w));
5648 return out;
5649 }
5650 let npv: f64 = cfs
5651 .iter()
5652 .enumerate()
5653 .map(|(t, &cf)| cf / (1.0 + rate).powi(t as i32))
5654 .sum();
5655 let _ = writeln!(out, " NPV Analysis");
5656 let _ = writeln!(out, " Discount rate : {:.4}%", rate * 100.0);
5657 let _ = writeln!(
5658 out,
5659 " Cash flows : {}",
5660 cfs.iter()
5661 .map(|cf| format!("{:.2}", cf))
5662 .collect::<Vec<_>>()
5663 .join(" ")
5664 );
5665 let _ = writeln!(out, " NPV : {:.4}", npv);
5666 let _ = writeln!(
5667 out,
5668 " Decision : {}",
5669 if npv > 0.0 {
5670 "Accept (NPV > 0)"
5671 } else if npv < 0.0 {
5672 "Reject (NPV < 0)"
5673 } else {
5674 "Indifferent"
5675 }
5676 );
5677 }
5678 "irr" => {
5679 if tokens.len() < 3 {
5680 let _ = writeln!(out, " Usage: irr CF0 CF1 CF2 ...");
5681 let _ = writeln!(out, "{}", "=".repeat(w));
5682 return out;
5683 }
5684 let cfs: Vec<f64> = tokens[1..]
5685 .iter()
5686 .filter_map(|s| s.replace(',', "").parse::<f64>().ok())
5687 .collect();
5688 if cfs.is_empty() {
5689 let _ = writeln!(out, " Error: no cash flows");
5690 let _ = writeln!(out, "{}", "=".repeat(w));
5691 return out;
5692 }
5693 fn npv_at(rate: f64, cfs: &[f64]) -> f64 {
5694 cfs.iter()
5695 .enumerate()
5696 .map(|(t, &cf)| cf / (1.0 + rate).powi(t as i32))
5697 .sum()
5698 }
5699 let mut lo = -0.9999f64;
5701 let mut hi = 10.0f64;
5702 let npv_lo = npv_at(lo, &cfs);
5703 let npv_hi = npv_at(hi, &cfs);
5704 let _ = writeln!(out, " IRR Analysis");
5705 if npv_lo * npv_hi > 0.0 {
5706 let _ = writeln!(out, " IRR: no unique root found in (-99.99%, 1000%) — check sign changes in cash flows");
5707 } else {
5708 for _ in 0..200 {
5709 let mid = (lo + hi) / 2.0;
5710 if npv_at(mid, &cfs) * npv_at(lo, &cfs) < 0.0 {
5711 hi = mid;
5712 } else {
5713 lo = mid;
5714 }
5715 if (hi - lo).abs() < 1e-10 {
5716 break;
5717 }
5718 }
5719 let irr = (lo + hi) / 2.0;
5720 let _ = writeln!(
5721 out,
5722 " Cash flows : {}",
5723 cfs.iter()
5724 .map(|cf| format!("{:.2}", cf))
5725 .collect::<Vec<_>>()
5726 .join(" ")
5727 );
5728 let _ = writeln!(out, " IRR : {:.6}%", irr * 100.0);
5729 let npv_check = npv_at(irr, &cfs);
5730 let _ = writeln!(out, " NPV @ IRR : {:.8} (should be ~0)", npv_check);
5731 }
5732 }
5733 "loan" => {
5734 if tokens.len() < 4 {
5735 let _ = writeln!(out, " Usage: loan PRINCIPAL RATE_PCT YEARS");
5736 let _ = writeln!(out, "{}", "=".repeat(w));
5737 return out;
5738 }
5739 let principal: f64 = tokens[1].replace(',', "").parse().unwrap_or(0.0);
5740 let annual_rate: f64 = tokens[2]
5741 .trim_end_matches('%')
5742 .parse::<f64>()
5743 .unwrap_or(0.0)
5744 / 100.0;
5745 let years: f64 = tokens[3].parse().unwrap_or(0.0);
5746 let n = (years * 12.0).round() as u32;
5747 let r = annual_rate / 12.0;
5748 let payment = if r.abs() < 1e-12 {
5749 principal / n as f64
5750 } else {
5751 principal * r * (1.0 + r).powi(n as i32) / ((1.0 + r).powi(n as i32) - 1.0)
5752 };
5753 let total_paid = payment * n as f64;
5754 let total_interest = total_paid - principal;
5755 let _ = writeln!(out, " Loan Amortization");
5756 let _ = writeln!(out, " Principal : {:>12.2}", principal);
5757 let _ = writeln!(out, " Annual rate : {:>12.4}%", annual_rate * 100.0);
5758 let _ = writeln!(out, " Term : {:>12} months ({} years)", n, years);
5759 let _ = writeln!(out, " Monthly payment: {:>12.2}", payment);
5760 let _ = writeln!(out, " Total paid : {:>12.2}", total_paid);
5761 let _ = writeln!(out, " Total interest : {:>12.2}", total_interest);
5762 let _ = writeln!(
5763 out,
5764 " Interest ratio : {:>12.2}%",
5765 total_interest / total_paid * 100.0
5766 );
5767 if n <= 60 || n <= 360 {
5769 let show_rows = 6usize.min(n as usize);
5770 let _ = writeln!(
5771 out,
5772 "\n {:<6} {:>12} {:>12} {:>12} {:>12}",
5773 "Month", "Payment", "Principal", "Interest", "Balance"
5774 );
5775 let _ = writeln!(out, " {}", "-".repeat(58));
5776 let mut balance = principal;
5777 for mo in 1..=n {
5778 let interest_part = balance * r;
5779 let principal_part = payment - interest_part;
5780 balance -= principal_part;
5781 if balance < 0.0 {
5782 balance = 0.0;
5783 }
5784 if mo as usize <= show_rows || mo as usize > n as usize - show_rows {
5785 let _ = writeln!(
5786 out,
5787 " {:<6} {:>12.2} {:>12.2} {:>12.2} {:>12.2}",
5788 mo, payment, principal_part, interest_part, balance
5789 );
5790 } else if mo as usize == show_rows + 1 {
5791 let _ = writeln!(out, " {:^58}", "...");
5792 }
5793 }
5794 }
5795 }
5796 "compound" => {
5797 if tokens.len() < 4 {
5798 let _ = writeln!(out, " Usage: compound PRINCIPAL RATE_PCT YEARS [PERIODS]");
5799 let _ = writeln!(out, "{}", "=".repeat(w));
5800 return out;
5801 }
5802 let p: f64 = tokens[1].replace(',', "").parse().unwrap_or(0.0);
5803 let r: f64 = tokens[2]
5804 .trim_end_matches('%')
5805 .parse::<f64>()
5806 .unwrap_or(0.0)
5807 / 100.0;
5808 let t: f64 = tokens[3].parse().unwrap_or(1.0);
5809 let n: f64 = tokens.get(4).and_then(|s| s.parse().ok()).unwrap_or(1.0);
5810 let fv = p * (1.0 + r / n).powf(n * t);
5811 let fv_cont = p * (r * t).exp();
5812 let _ = writeln!(out, " Compound Interest");
5813 let _ = writeln!(out, " Principal : {:>12.2}", p);
5814 let _ = writeln!(out, " Annual rate : {:>12.4}%", r * 100.0);
5815 let _ = writeln!(out, " Years : {:>12}", t);
5816 let _ = writeln!(out, " Periods/year : {:>12}", n);
5817 let _ = writeln!(out, " Future value : {:>12.4}", fv);
5818 let _ = writeln!(out, " Interest earned: {:>12.4}", fv - p);
5819 let _ = writeln!(out, " Continuous FV : {:>12.4}", fv_cont);
5820 let eff_rate = (1.0 + r / n).powf(n) - 1.0;
5821 let _ = writeln!(out, " Effective rate: {:>12.4}%", eff_rate * 100.0);
5822 }
5823 "bond" => {
5824 if tokens.len() < 6 {
5825 let _ = writeln!(
5826 out,
5827 " Usage: bond FACE COUPON_PCT YIELD_PCT YEARS [PERIODS]"
5828 );
5829 let _ = writeln!(out, "{}", "=".repeat(w));
5830 return out;
5831 }
5832 let face: f64 = tokens[1].replace(',', "").parse().unwrap_or(1000.0);
5833 let coupon_rate: f64 = tokens[2]
5834 .trim_end_matches('%')
5835 .parse::<f64>()
5836 .unwrap_or(0.0)
5837 / 100.0;
5838 let yield_rate: f64 = tokens[3]
5839 .trim_end_matches('%')
5840 .parse::<f64>()
5841 .unwrap_or(0.0)
5842 / 100.0;
5843 let years: f64 = tokens[4].parse().unwrap_or(1.0);
5844 let m: f64 = tokens.get(5).and_then(|s| s.parse().ok()).unwrap_or(2.0); let n = (years * m).round() as i32;
5846 let r = yield_rate / m;
5847 let c = face * coupon_rate / m;
5848 let pv_coupons = if r.abs() < 1e-12 {
5850 c * n as f64
5851 } else {
5852 c * (1.0 - (1.0 + r).powi(-n)) / r
5853 };
5854 let pv_face = face / (1.0 + r).powi(n);
5855 let price = pv_coupons + pv_face;
5856 let duration_num: f64 = (1..=n)
5857 .map(|t| t as f64 / m * c / (1.0 + r).powi(t))
5858 .sum::<f64>()
5859 + years * pv_face;
5860 let duration = duration_num / price;
5861 let _ = writeln!(out, " Bond Pricing");
5862 let _ = writeln!(out, " Face value : {:>12.2}", face);
5863 let _ = writeln!(
5864 out,
5865 " Coupon rate : {:>12.4}% ({:.2} per period)",
5866 coupon_rate * 100.0,
5867 c
5868 );
5869 let _ = writeln!(out, " Yield to mat. : {:>12.4}%", yield_rate * 100.0);
5870 let _ = writeln!(out, " Years to mat. : {:>12}", years);
5871 let _ = writeln!(out, " Periods/year : {:>12}", m);
5872 let _ = writeln!(out, " Total periods : {:>12}", n);
5873 let _ = writeln!(out, " Bond price : {:>12.4}", price);
5874 let _ = writeln!(out, " PV of coupons : {:>12.4}", pv_coupons);
5875 let _ = writeln!(out, " PV of face : {:>12.4}", pv_face);
5876 let status = if price > face {
5877 "Premium"
5878 } else if price < face {
5879 "Discount"
5880 } else {
5881 "Par"
5882 };
5883 let _ = writeln!(
5884 out,
5885 " Bond trades at : {} ({:.2}% of face)",
5886 status,
5887 price / face * 100.0
5888 );
5889 let _ = writeln!(out, " Macaulay dur. : {:>12.4} years", duration);
5890 }
5891 "bs" | "black-scholes" | "blackscholes" | "option" => {
5892 if tokens.len() < 6 {
5893 let _ = writeln!(
5894 out,
5895 " Usage: bs SPOT STRIKE RATE_PCT SIGMA_PCT YEARS [call|put]"
5896 );
5897 let _ = writeln!(out, "{}", "=".repeat(w));
5898 return out;
5899 }
5900 let s: f64 = tokens[1].replace(',', "").parse().unwrap_or(0.0);
5901 let k: f64 = tokens[2].replace(',', "").parse().unwrap_or(0.0);
5902 let r: f64 = tokens[3]
5903 .trim_end_matches('%')
5904 .parse::<f64>()
5905 .unwrap_or(0.0)
5906 / 100.0;
5907 let sigma: f64 = tokens[4]
5908 .trim_end_matches('%')
5909 .parse::<f64>()
5910 .unwrap_or(0.0)
5911 / 100.0;
5912 let t: f64 = tokens[5].parse().unwrap_or(1.0);
5913 let opt_type = tokens.get(6).copied().unwrap_or("call");
5914 let d1 = ((s / k).ln() + (r + 0.5 * sigma * sigma) * t) / (sigma * t.sqrt());
5915 let d2 = d1 - sigma * t.sqrt();
5916 let nd1 = bs_ncdf(d1);
5917 let nd2 = bs_ncdf(d2);
5918 let (price, delta) = if opt_type.to_lowercase().starts_with('p') {
5919 let p = k * (-r * t).exp() * bs_ncdf(-d2) - s * bs_ncdf(-d1);
5920 (p, nd1 - 1.0)
5921 } else {
5922 let c = s * nd1 - k * (-r * t).exp() * nd2;
5923 (c, nd1)
5924 };
5925 let gamma = bs_npdf(d1) / (s * sigma * t.sqrt());
5926 let vega = s * bs_npdf(d1) * t.sqrt() / 100.0;
5927 let theta_call = (-s * bs_npdf(d1) * sigma / (2.0 * t.sqrt())
5928 - r * k * (-r * t).exp() * nd2)
5929 / 365.0;
5930 let _ = writeln!(out, " Black-Scholes Option Pricing");
5931 let _ = writeln!(out, " Spot price : {:>12.4}", s);
5932 let _ = writeln!(out, " Strike price : {:>12.4}", k);
5933 let _ = writeln!(out, " Risk-free rate : {:>12.4}%", r * 100.0);
5934 let _ = writeln!(out, " Volatility (σ) : {:>12.4}%", sigma * 100.0);
5935 let _ = writeln!(out, " Time (years) : {:>12.4}", t);
5936 let _ = writeln!(out, " Option type : {:>12}", opt_type.to_uppercase());
5937 let _ = writeln!(out, " ─────────────────────────────────────────────");
5938 let _ = writeln!(out, " d1 : {:>12.6}", d1);
5939 let _ = writeln!(out, " d2 : {:>12.6}", d2);
5940 let _ = writeln!(out, " N(d1) / N(d2) : {:>12.6} / {:>12.6}", nd1, nd2);
5941 let _ = writeln!(out, " ─────────────────────────────────────────────");
5942 let _ = writeln!(out, " Option price : {:>12.6}", price);
5943 let _ = writeln!(out, " Delta : {:>12.6}", delta);
5944 let _ = writeln!(out, " Gamma : {:>12.6}", gamma);
5945 let _ = writeln!(out, " Vega (per 1%σ) : {:>12.6}", vega);
5946 let _ = writeln!(out, " Theta (per day): {:>12.6}", theta_call);
5947 }
5948 _ => {
5949 let _ = writeln!(out, "{}", finance_usage());
5950 let _ = writeln!(out, "{}", "=".repeat(w));
5951 return out;
5952 }
5953 }
5954
5955 let _ = writeln!(out, "{}", "=".repeat(w));
5956 out
5957}
5958
5959fn bs_ncdf(x: f64) -> f64 {
5960 if x < -8.0 {
5962 return 0.0;
5963 }
5964 if x > 8.0 {
5965 return 1.0;
5966 }
5967 if x >= 0.0 {
5968 0.5 * (1.0 + erf_approx(x / std::f64::consts::SQRT_2))
5969 } else {
5970 0.5 * (1.0 - erf_approx(-x / std::f64::consts::SQRT_2))
5971 }
5972}
5973
5974fn bs_npdf(x: f64) -> f64 {
5975 (-0.5 * x * x).exp() / (2.0 * std::f64::consts::PI).sqrt()
5976}
5977
5978fn finance_usage() -> String {
5979 "Financial math:\n\
5980 hematite --finance 'npv 10% -1000 300 400 500 200' NPV\n\
5981 hematite --finance 'irr -1000 300 400 500 200' IRR\n\
5982 hematite --finance 'loan 200000 6.5% 30' 30yr mortgage\n\
5983 hematite --finance 'compound 10000 7% 10 12' compound interest\n\
5984 hematite --finance 'bond 1000 5% 4% 10 2' bond pricing\n\
5985 hematite --finance 'bs 100 100 5% 20% 1 call' Black-Scholes call\n\
5986 hematite --finance 'bs 100 105 5% 20% 0.5 put' Black-Scholes put"
5987 .into()
5988}
5989
5990pub fn graph_theory(query: &str) -> String {
6008 let q = query.trim();
6009
6010 let (mode, rest) = {
6016 let tokens: Vec<&str> = q.splitn(2, ['\n', ';']).collect();
6017 let first_line = tokens[0].trim();
6018 let _fl_lower = first_line.to_lowercase();
6019 let looks_like_mode = !first_line.contains("->")
6021 && !first_line.contains(" - ")
6022 && first_line.split_whitespace().count() <= 3;
6023 if looks_like_mode {
6024 let words: Vec<&str> = first_line.splitn(2, char::is_whitespace).collect();
6025 let m = words[0].to_lowercase();
6026 let after_mode = words.get(1).copied().unwrap_or("").trim();
6027 let rest_str = if tokens.len() > 1 {
6028 format!("{}\n{}", after_mode, tokens[1])
6029 } else {
6030 after_mode.to_string()
6031 };
6032 match m.as_str() {
6033 "bfs" | "dfs" | "shortest" | "path" | "components" | "topo" | "topological"
6034 | "info" | "degree" => (m, rest_str),
6035 _ => {
6036 ("info".to_string(), q.to_string())
6038 }
6039 }
6040 } else {
6041 ("info".to_string(), q.to_string())
6042 }
6043 };
6044
6045 let edge_strs: Vec<&str> = rest
6048 .split(['\n', ';'])
6049 .map(str::trim)
6050 .filter(|s| !s.is_empty())
6051 .collect();
6052
6053 let mut directed = false;
6054 let mut nodes: Vec<String> = Vec::new();
6055 let mut edges: Vec<(String, String, f64)> = Vec::new();
6056
6057 let node_id = |name: &str, nodes: &mut Vec<String>| -> usize {
6058 if let Some(p) = nodes.iter().position(|n| n == name) {
6059 p
6060 } else {
6061 nodes.push(name.to_string());
6062 nodes.len() - 1
6063 }
6064 };
6065
6066 for line in &edge_strs {
6067 let line = line.trim();
6068 if line.is_empty() {
6069 continue;
6070 }
6071 let (a, b, w, dir) = if let Some(pos) = line.find("->") {
6073 directed = true;
6074 let a = line[..pos].trim().trim_matches(':');
6075 let rest2 = line[pos + 2..].trim();
6076 let (b, w) = parse_node_weight(rest2);
6077 (a, b, w, true)
6078 } else if let Some(pos) = line.find(" - ").or_else(|| {
6079 let parts: Vec<&str> = line.splitn(3, char::is_whitespace).collect();
6081 if parts.len() >= 2 {
6082 None
6084 } else {
6085 let hp = line.find('-');
6087 hp.filter(|&h| h > 0 && line[..h].trim().parse::<f64>().is_err())
6088 }
6089 }) {
6090 let sep_len = if line[pos..].starts_with(" - ") { 3 } else { 1 };
6091 let a = line[..pos].trim();
6092 let rest2 = line[pos + sep_len..].trim();
6093 let (b, w) = parse_node_weight(rest2);
6094 (a, b, w, false)
6095 } else {
6096 let parts: Vec<&str> = line.splitn(3, char::is_whitespace).collect();
6098 if parts.len() < 2 {
6099 node_id(line, &mut nodes);
6100 continue;
6101 }
6102 let a = parts[0].trim();
6103 let b_raw = parts[1].trim();
6104 let (b, w) = if let Some(cp) = b_raw.find(':') {
6106 let wt = b_raw[cp + 1..].parse::<f64>().unwrap_or(1.0);
6107 (&b_raw[..cp], wt)
6108 } else {
6109 let wt = parts
6110 .get(2)
6111 .and_then(|s| s.trim().parse::<f64>().ok())
6112 .unwrap_or(1.0);
6113 (b_raw, wt)
6114 };
6115 (a, b, w, false)
6116 };
6117
6118 if a.is_empty() || b.is_empty() {
6119 continue;
6120 }
6121 let ai = node_id(a, &mut nodes);
6122 let bi = node_id(b, &mut nodes);
6123 edges.push((nodes[ai].clone(), nodes[bi].clone(), w));
6124 if !dir { }
6125 }
6126
6127 if nodes.is_empty() {
6128 return graph_usage();
6129 }
6130
6131 let n = nodes.len();
6132
6133 let mut adj: Vec<Vec<(usize, f64)>> = vec![Vec::new(); n];
6135 for (a_name, b_name, w) in &edges {
6136 let ai = nodes.iter().position(|x| x == a_name).unwrap();
6137 let bi = nodes.iter().position(|x| x == b_name).unwrap();
6138 adj[ai].push((bi, *w));
6139 if !directed {
6140 adj[bi].push((ai, *w));
6141 }
6142 }
6143
6144 let mut out = String::new();
6145 let w = 64usize;
6146 let _ = writeln!(out, "{}", "=".repeat(w));
6147 let _ = writeln!(
6148 out,
6149 " Graph Analysis | {} nodes | {} edges | {}",
6150 n,
6151 edges.len(),
6152 if directed { "directed" } else { "undirected" }
6153 );
6154 let _ = writeln!(out, "{}", "=".repeat(w));
6155
6156 match mode.as_str() {
6157 "bfs" => {
6158 let start_name = rest.split_whitespace().next().unwrap_or(&nodes[0]);
6159 let start = nodes.iter().position(|x| x == start_name).unwrap_or(0);
6160 let order = bfs_order(&adj, start, n);
6161 let _ = writeln!(out, " BFS from \"{}\":", nodes[start]);
6162 let _ = writeln!(
6163 out,
6164 " Visit order: {}",
6165 order
6166 .iter()
6167 .map(|&i| nodes[i].as_str())
6168 .collect::<Vec<_>>()
6169 .join(" → ")
6170 );
6171 }
6172 "dfs" => {
6173 let start_name = rest.split_whitespace().next().unwrap_or(&nodes[0]);
6174 let start = nodes.iter().position(|x| x == start_name).unwrap_or(0);
6175 let order = dfs_order(&adj, start, n);
6176 let _ = writeln!(out, " DFS from \"{}\":", nodes[start]);
6177 let _ = writeln!(
6178 out,
6179 " Visit order: {}",
6180 order
6181 .iter()
6182 .map(|&i| nodes[i].as_str())
6183 .collect::<Vec<_>>()
6184 .join(" → ")
6185 );
6186 }
6187 "shortest" | "path" => {
6188 let parts: Vec<&str> = rest.split_whitespace().collect();
6189 let from_name = parts.first().copied().unwrap_or(&nodes[0]);
6190 let to_name = parts.get(1).copied().unwrap_or(&nodes[n - 1]);
6191 let from = nodes.iter().position(|x| x == from_name).unwrap_or(0);
6192 let to = nodes
6193 .iter()
6194 .position(|x| x == to_name)
6195 .unwrap_or(n.saturating_sub(1));
6196 match dijkstra(&adj, from, to, n) {
6197 Some((dist, path)) => {
6198 let path_str = path
6199 .iter()
6200 .map(|&i| nodes[i].as_str())
6201 .collect::<Vec<_>>()
6202 .join(" → ");
6203 let _ = writeln!(out, " Shortest path: {} → {}", nodes[from], nodes[to]);
6204 let _ = writeln!(out, " Distance: {:.4}", dist);
6205 let _ = writeln!(out, " Path: {}", path_str);
6206 }
6207 None => {
6208 let _ = writeln!(
6209 out,
6210 " No path from \"{}\" to \"{}\"",
6211 nodes[from], nodes[to]
6212 );
6213 }
6214 }
6215 let dists = dijkstra_all(&adj, from, n);
6217 let _ = writeln!(out, "\n All distances from \"{}\":", nodes[from]);
6218 for (i, d) in dists.iter().enumerate() {
6219 if i == from {
6220 continue;
6221 }
6222 if *d == f64::INFINITY {
6223 let _ = writeln!(out, " → {:<20} unreachable", &nodes[i]);
6224 } else {
6225 let _ = writeln!(out, " → {:<20} {:.4}", &nodes[i], d);
6226 }
6227 }
6228 }
6229 "components" => {
6230 let comps = connected_components(&adj, n, directed);
6231 let _ = writeln!(out, " Connected components: {}", comps.len());
6232 for (ci, comp) in comps.iter().enumerate() {
6233 let names: Vec<&str> = comp.iter().map(|&i| nodes[i].as_str()).collect();
6234 let _ = writeln!(out, " [{}] {}", ci + 1, names.join(", "));
6235 }
6236 }
6237 "topo" | "topological" => match topo_sort(&adj, n) {
6238 Ok(order) => {
6239 let _ = writeln!(out, " Topological sort:");
6240 let _ = writeln!(
6241 out,
6242 " {}",
6243 order
6244 .iter()
6245 .map(|&i| nodes[i].as_str())
6246 .collect::<Vec<_>>()
6247 .join(" → ")
6248 );
6249 }
6250 Err(_) => {
6251 let _ = writeln!(out, " Cycle detected — topological sort not possible.");
6252 }
6253 },
6254 "centrality" | "betweenness" => {
6255 let bc = betweenness_centrality(&adj, n);
6257 let mut ranked: Vec<(usize, f64)> = bc.iter().copied().enumerate().collect();
6258 ranked.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
6259 let _ = writeln!(
6260 out,
6261 " Betweenness Centrality (fraction of shortest paths through node):"
6262 );
6263 let _ = writeln!(out, " {:<22} {:>12} bar", "Node", "Centrality");
6264 let _ = writeln!(out, " {}", "-".repeat(w - 2));
6265 let max_bc = ranked.first().map(|x| x.1).unwrap_or(1.0).max(1e-9);
6266 for &(i, val) in &ranked {
6267 let bar_len = (val / max_bc * 30.0) as usize;
6268 let _ = writeln!(
6269 out,
6270 " {:<22} {:>12.6} {}",
6271 &nodes[i],
6272 val,
6273 "#".repeat(bar_len)
6274 );
6275 }
6276 }
6277 "pagerank" | "pr" => {
6278 let d: f64 = rest
6279 .split_whitespace()
6280 .next()
6281 .and_then(|s| s.parse().ok())
6282 .unwrap_or(0.85);
6283 let pr = pagerank(&adj, n, d, 100);
6284 let mut ranked: Vec<(usize, f64)> = pr.iter().copied().enumerate().collect();
6285 ranked.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
6286 let _ = writeln!(out, " PageRank (damping={:.2}, 100 iterations):", d);
6287 let _ = writeln!(out, " {:<22} {:>10} bar", "Node", "Score");
6288 let _ = writeln!(out, " {}", "-".repeat(w - 2));
6289 let max_pr = ranked.first().map(|x| x.1).unwrap_or(1e-9).max(1e-9);
6290 for &(i, val) in &ranked {
6291 let bar_len = (val / max_pr * 30.0) as usize;
6292 let _ = writeln!(
6293 out,
6294 " {:<22} {:>10.6} {}",
6295 &nodes[i],
6296 val,
6297 "#".repeat(bar_len)
6298 );
6299 }
6300 }
6301 "clustering" | "cluster" => {
6302 let cc = clustering_coefficients(&adj, n, directed);
6303 let global_cc = if n > 0 {
6304 cc.iter().sum::<f64>() / n as f64
6305 } else {
6306 0.0
6307 };
6308 let mut ranked: Vec<(usize, f64)> = cc.iter().copied().enumerate().collect();
6309 ranked.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
6310 let _ = writeln!(
6311 out,
6312 " Clustering Coefficients (global avg: {:.4}):",
6313 global_cc
6314 );
6315 let _ = writeln!(out, " {:<22} {:>12} bar", "Node", "Coefficient");
6316 let _ = writeln!(out, " {}", "-".repeat(w - 2));
6317 for &(i, val) in &ranked {
6318 let bar_len = (val * 30.0) as usize;
6319 let _ = writeln!(
6320 out,
6321 " {:<22} {:>12.6} {}",
6322 &nodes[i],
6323 val,
6324 "#".repeat(bar_len)
6325 );
6326 }
6327 }
6328 "diameter" | "stats" | "metrics" => {
6329 let (diameter, avg_path, eccentricities) = graph_diameter(&adj, n);
6331 let _ = writeln!(out, " Network Metrics:");
6332 if diameter == f64::INFINITY {
6333 let _ = writeln!(out, " Diameter : ∞ (disconnected graph)");
6334 let _ = writeln!(out, " Avg path length : N/A");
6335 } else {
6336 let _ = writeln!(out, " Diameter : {:.4}", diameter);
6337 let _ = writeln!(out, " Avg path length : {:.4}", avg_path);
6338 }
6339 let max_edges = if directed {
6341 n * (n - 1)
6342 } else {
6343 n * (n - 1) / 2
6344 };
6345 let density = if max_edges > 0 {
6346 edges.len() as f64 / max_edges as f64
6347 } else {
6348 0.0
6349 };
6350 let _ = writeln!(
6351 out,
6352 " Density : {:.4} ({} / {} possible edges)",
6353 density,
6354 edges.len(),
6355 max_edges
6356 );
6357 let _ = writeln!(
6359 out,
6360 "\n Eccentricities (max distance from node to any other):"
6361 );
6362 let _ = writeln!(out, " {:<22} {:>12}", "Node", "Eccentricity");
6363 let _ = writeln!(out, " {}", "-".repeat(36));
6364 let mut ecc_sorted: Vec<(usize, f64)> =
6365 eccentricities.into_iter().enumerate().collect();
6366 ecc_sorted.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
6367 for (i, ecc) in &ecc_sorted {
6368 if *ecc == f64::INFINITY {
6369 let _ = writeln!(out, " {:<22} {:>12}", &nodes[*i], "∞");
6370 } else {
6371 let _ = writeln!(out, " {:<22} {:>12.4}", &nodes[*i], ecc);
6372 }
6373 }
6374 let min_ecc = ecc_sorted
6376 .iter()
6377 .map(|x| x.1)
6378 .filter(|x| x.is_finite())
6379 .fold(f64::INFINITY, f64::min);
6380 if min_ecc.is_finite() {
6381 let centers: Vec<&str> = ecc_sorted
6382 .iter()
6383 .filter(|&&(_, e)| (e - min_ecc).abs() < 1e-9)
6384 .map(|&(i, _)| nodes[i].as_str())
6385 .collect();
6386 let _ = writeln!(out, "\n Center node(s): {}", centers.join(", "));
6387 }
6388 }
6389 _ => {
6390 let mut in_deg = vec![0usize; n];
6392 let mut out_deg = vec![0usize; n];
6393 for (ai, nbrs) in adj.iter().enumerate() {
6394 out_deg[ai] = nbrs.len();
6395 for &(bi, _) in nbrs {
6396 in_deg[bi] += 1;
6397 }
6398 }
6399 let _ = writeln!(
6400 out,
6401 " {:<20} {:>8} {:>8}",
6402 "Node",
6403 if directed { "Out-deg" } else { "Degree" },
6404 if directed { "In-deg" } else { "" }
6405 );
6406 let _ = writeln!(out, " {}", "-".repeat(40));
6407 let mut sorted_nodes: Vec<usize> = (0..n).collect();
6408 sorted_nodes.sort_by(|&a, &b| out_deg[b].cmp(&out_deg[a]));
6409 for &i in &sorted_nodes {
6410 if directed {
6411 let _ = writeln!(
6412 out,
6413 " {:<20} {:>8} {:>8}",
6414 &nodes[i], out_deg[i], in_deg[i]
6415 );
6416 } else {
6417 let _ = writeln!(out, " {:<20} {:>8}", &nodes[i], out_deg[i]);
6418 }
6419 }
6420 let comps = connected_components(&adj, n, directed);
6422 let _ = writeln!(
6423 out,
6424 "\n Components: {} | {}",
6425 comps.len(),
6426 if comps.len() == 1 {
6427 "connected".to_string()
6428 } else {
6429 "disconnected".to_string()
6430 }
6431 );
6432 let has_cycle = detect_cycle(&adj, n, directed);
6434 let _ = writeln!(
6435 out,
6436 " Cycles: {}",
6437 if has_cycle { "yes" } else { "none detected" }
6438 );
6439 if directed {
6440 if let Ok(order) = topo_sort(&adj, n) {
6441 let _ = writeln!(
6442 out,
6443 " Topo order: {}",
6444 order
6445 .iter()
6446 .map(|&i| nodes[i].as_str())
6447 .collect::<Vec<_>>()
6448 .join(" → ")
6449 );
6450 }
6451 }
6452 }
6453 }
6454
6455 let _ = writeln!(out, "{}", "=".repeat(w));
6456 out
6457}
6458
6459fn parse_node_weight(s: &str) -> (&str, f64) {
6460 if let Some(pos) = s.find(':') {
6462 let name = &s[..pos];
6463 let w = s[pos + 1..].trim().parse::<f64>().unwrap_or(1.0);
6464 (name.trim(), w)
6465 } else {
6466 let parts: Vec<&str> = s.splitn(2, char::is_whitespace).collect();
6467 let name = parts[0].trim();
6468 let w = parts
6469 .get(1)
6470 .and_then(|x| x.trim().parse::<f64>().ok())
6471 .unwrap_or(1.0);
6472 (name, w)
6473 }
6474}
6475
6476fn bfs_order(adj: &[Vec<(usize, f64)>], start: usize, n: usize) -> Vec<usize> {
6477 let mut visited = vec![false; n];
6478 let mut queue = std::collections::VecDeque::new();
6479 let mut order = Vec::new();
6480 visited[start] = true;
6481 queue.push_back(start);
6482 while let Some(u) = queue.pop_front() {
6483 order.push(u);
6484 let mut nbrs: Vec<usize> = adj[u].iter().map(|&(v, _)| v).collect();
6485 nbrs.sort();
6486 for v in nbrs {
6487 if !visited[v] {
6488 visited[v] = true;
6489 queue.push_back(v);
6490 }
6491 }
6492 }
6493 order
6494}
6495
6496fn dfs_order(adj: &[Vec<(usize, f64)>], start: usize, n: usize) -> Vec<usize> {
6497 let mut visited = vec![false; n];
6498 let mut stack = vec![start];
6499 let mut order = Vec::new();
6500 while let Some(u) = stack.pop() {
6501 if visited[u] {
6502 continue;
6503 }
6504 visited[u] = true;
6505 order.push(u);
6506 let mut nbrs: Vec<usize> = adj[u].iter().map(|&(v, _)| v).collect();
6507 nbrs.sort_by(|a, b| b.cmp(a));
6508 for v in nbrs {
6509 if !visited[v] {
6510 stack.push(v);
6511 }
6512 }
6513 }
6514 order
6515}
6516
6517fn dijkstra(
6518 adj: &[Vec<(usize, f64)>],
6519 from: usize,
6520 to: usize,
6521 n: usize,
6522) -> Option<(f64, Vec<usize>)> {
6523 use std::cmp::Ordering;
6524 use std::collections::BinaryHeap;
6525 #[derive(PartialEq)]
6526 struct State {
6527 cost: f64,
6528 node: usize,
6529 }
6530 impl Eq for State {}
6531 impl PartialOrd for State {
6532 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
6533 Some(self.cmp(other))
6534 }
6535 }
6536 impl Ord for State {
6537 fn cmp(&self, other: &Self) -> Ordering {
6538 other
6539 .cost
6540 .partial_cmp(&self.cost)
6541 .unwrap_or(Ordering::Equal)
6542 }
6543 }
6544
6545 let mut dist = vec![f64::INFINITY; n];
6546 let mut prev = vec![usize::MAX; n];
6547 dist[from] = 0.0;
6548 let mut heap = BinaryHeap::new();
6549 heap.push(State {
6550 cost: 0.0,
6551 node: from,
6552 });
6553
6554 while let Some(State { cost, node }) = heap.pop() {
6555 if node == to {
6556 break;
6557 }
6558 if cost > dist[node] {
6559 continue;
6560 }
6561 for &(v, w) in &adj[node] {
6562 let next_cost = dist[node] + w;
6563 if next_cost < dist[v] {
6564 dist[v] = next_cost;
6565 prev[v] = node;
6566 heap.push(State {
6567 cost: next_cost,
6568 node: v,
6569 });
6570 }
6571 }
6572 }
6573
6574 if dist[to] == f64::INFINITY {
6575 return None;
6576 }
6577 let mut path = Vec::new();
6578 let mut cur = to;
6579 while cur != usize::MAX {
6580 path.push(cur);
6581 cur = prev[cur];
6582 }
6583 path.reverse();
6584 Some((dist[to], path))
6585}
6586
6587fn dijkstra_all(adj: &[Vec<(usize, f64)>], from: usize, n: usize) -> Vec<f64> {
6588 use std::cmp::Ordering;
6589 use std::collections::BinaryHeap;
6590 #[derive(PartialEq)]
6591 struct State {
6592 cost: f64,
6593 node: usize,
6594 }
6595 impl Eq for State {}
6596 impl PartialOrd for State {
6597 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
6598 Some(self.cmp(other))
6599 }
6600 }
6601 impl Ord for State {
6602 fn cmp(&self, other: &Self) -> Ordering {
6603 other
6604 .cost
6605 .partial_cmp(&self.cost)
6606 .unwrap_or(Ordering::Equal)
6607 }
6608 }
6609 let mut dist = vec![f64::INFINITY; n];
6610 dist[from] = 0.0;
6611 let mut heap = BinaryHeap::new();
6612 heap.push(State {
6613 cost: 0.0,
6614 node: from,
6615 });
6616 while let Some(State { cost, node }) = heap.pop() {
6617 if cost > dist[node] {
6618 continue;
6619 }
6620 for &(v, w) in &adj[node] {
6621 let nc = dist[node] + w;
6622 if nc < dist[v] {
6623 dist[v] = nc;
6624 heap.push(State { cost: nc, node: v });
6625 }
6626 }
6627 }
6628 dist
6629}
6630
6631fn connected_components(adj: &[Vec<(usize, f64)>], n: usize, directed: bool) -> Vec<Vec<usize>> {
6632 let mut visited = vec![false; n];
6634 let mut comps = Vec::new();
6635 for start in 0..n {
6636 if visited[start] {
6637 continue;
6638 }
6639 let mut comp = Vec::new();
6640 let mut stack = vec![start];
6641 while let Some(u) = stack.pop() {
6642 if visited[u] {
6643 continue;
6644 }
6645 visited[u] = true;
6646 comp.push(u);
6647 for &(v, _) in &adj[u] {
6648 if !visited[v] {
6649 stack.push(v);
6650 }
6651 }
6652 if directed {
6653 for other in 0..n {
6655 if !visited[other] && adj[other].iter().any(|&(t, _)| t == u) {
6656 stack.push(other);
6657 }
6658 }
6659 }
6660 }
6661 comp.sort();
6662 comps.push(comp);
6663 }
6664 comps
6665}
6666
6667fn topo_sort(adj: &[Vec<(usize, f64)>], n: usize) -> Result<Vec<usize>, ()> {
6668 let mut in_deg = vec![0usize; n];
6669 for u in 0..n {
6670 for &(v, _) in &adj[u] {
6671 in_deg[v] += 1;
6672 }
6673 }
6674 let mut queue: std::collections::VecDeque<usize> = (0..n).filter(|&i| in_deg[i] == 0).collect();
6675 let mut order = Vec::new();
6676 while let Some(u) = queue.pop_front() {
6677 order.push(u);
6678 for &(v, _) in &adj[u] {
6679 in_deg[v] -= 1;
6680 if in_deg[v] == 0 {
6681 queue.push_back(v);
6682 }
6683 }
6684 }
6685 if order.len() == n {
6686 Ok(order)
6687 } else {
6688 Err(())
6689 }
6690}
6691
6692fn detect_cycle(adj: &[Vec<(usize, f64)>], n: usize, directed: bool) -> bool {
6693 let mut color = vec![0u8; n]; fn dfs_cycle(
6696 u: usize,
6697 adj: &[Vec<(usize, f64)>],
6698 color: &mut Vec<u8>,
6699 directed: bool,
6700 parent: usize,
6701 ) -> bool {
6702 color[u] = 1;
6703 for &(v, _) in &adj[u] {
6704 if color[v] == 0 {
6705 if dfs_cycle(v, adj, color, directed, u) {
6706 return true;
6707 }
6708 } else if (directed && color[v] == 1) || (!directed && v != parent) {
6709 return true;
6710 }
6711 }
6712 color[u] = 2;
6713 false
6714 }
6715 for start in 0..n {
6716 if color[start] == 0 && dfs_cycle(start, adj, &mut color, directed, usize::MAX) {
6717 return true;
6718 }
6719 }
6720 false
6721}
6722
6723fn betweenness_centrality(adj: &[Vec<(usize, f64)>], n: usize) -> Vec<f64> {
6725 let mut bc = vec![0.0f64; n];
6726 for s in 0..n {
6727 let mut stack = Vec::new();
6728 let mut pred: Vec<Vec<usize>> = vec![Vec::new(); n];
6729 let mut sigma = vec![0.0f64; n];
6730 sigma[s] = 1.0;
6731 let mut dist = vec![-1i64; n];
6732 dist[s] = 0;
6733 let mut queue = std::collections::VecDeque::new();
6734 queue.push_back(s);
6735 while let Some(v) = queue.pop_front() {
6736 stack.push(v);
6737 for &(w, _) in &adj[v] {
6738 if dist[w] < 0 {
6739 queue.push_back(w);
6740 dist[w] = dist[v] + 1;
6741 }
6742 if dist[w] == dist[v] + 1 {
6743 sigma[w] += sigma[v];
6744 pred[w].push(v);
6745 }
6746 }
6747 }
6748 let mut delta = vec![0.0f64; n];
6749 while let Some(w) = stack.pop() {
6750 for &v in &pred[w] {
6751 delta[v] += (sigma[v] / sigma[w]) * (1.0 + delta[w]);
6752 }
6753 if w != s {
6754 bc[w] += delta[w];
6755 }
6756 }
6757 }
6758 let norm = if n > 2 {
6760 ((n - 1) * (n - 2)) as f64
6761 } else {
6762 1.0
6763 };
6764 bc.iter_mut().for_each(|x| *x /= norm);
6765 bc
6766}
6767
6768fn pagerank(adj: &[Vec<(usize, f64)>], n: usize, damping: f64, iters: usize) -> Vec<f64> {
6770 let mut pr = vec![1.0 / n as f64; n];
6771 let out_deg: Vec<usize> = adj.iter().map(|nbrs| nbrs.len()).collect();
6772 for _ in 0..iters {
6773 let mut new_pr = vec![(1.0 - damping) / n as f64; n];
6774 for v in 0..n {
6775 if out_deg[v] == 0 {
6776 let share = damping * pr[v] / n as f64;
6778 new_pr.iter_mut().for_each(|x| *x += share);
6779 } else {
6780 let share = damping * pr[v] / out_deg[v] as f64;
6781 for &(u, _) in &adj[v] {
6782 new_pr[u] += share;
6783 }
6784 }
6785 }
6786 pr = new_pr;
6787 }
6788 pr
6789}
6790
6791fn clustering_coefficients(adj: &[Vec<(usize, f64)>], n: usize, directed: bool) -> Vec<f64> {
6793 let mut cc = vec![0.0f64; n];
6794 for u in 0..n {
6795 let nbrs: Vec<usize> = adj[u].iter().map(|&(v, _)| v).collect();
6796 let k = nbrs.len();
6797 if k < 2 {
6798 continue;
6799 }
6800 let mut triangles = 0usize;
6801 for i in 0..k {
6802 for j in (i + 1)..k {
6803 let vi = nbrs[i];
6804 let vj = nbrs[j];
6805 if adj[vi].iter().any(|&(x, _)| x == vj) || adj[vj].iter().any(|&(x, _)| x == vi) {
6806 triangles += 1;
6807 }
6808 }
6809 }
6810 let denom = if directed {
6811 k * (k - 1)
6812 } else {
6813 k * (k - 1) / 2
6814 };
6815 if denom > 0 {
6816 cc[u] = triangles as f64 / denom as f64;
6817 }
6818 }
6819 cc
6820}
6821
6822fn graph_diameter(adj: &[Vec<(usize, f64)>], n: usize) -> (f64, f64, Vec<f64>) {
6824 let mut diameter = 0.0f64;
6825 let mut path_sum = 0.0f64;
6826 let mut path_cnt = 0u64;
6827 let mut ecc = vec![0.0f64; n];
6828 for s in 0..n {
6829 let dists = dijkstra_all(adj, s, n);
6830 let finite: Vec<f64> = dists
6831 .iter()
6832 .copied()
6833 .filter(|d| d.is_finite() && *d > 0.0)
6834 .collect();
6835 let max_d = finite.iter().cloned().fold(0.0f64, f64::max);
6836 if finite.len() < n - 1 {
6837 ecc[s] = f64::INFINITY; } else {
6839 ecc[s] = max_d;
6840 }
6841 if max_d.is_finite() && max_d > diameter {
6842 diameter = max_d;
6843 }
6844 for d in &finite {
6845 path_sum += d;
6846 path_cnt += 1;
6847 }
6848 }
6849 let avg = if path_cnt > 0 {
6850 path_sum / path_cnt as f64
6851 } else {
6852 f64::INFINITY
6853 };
6854 let diam = if ecc.iter().any(|x| x.is_infinite()) {
6855 f64::INFINITY
6856 } else {
6857 diameter
6858 };
6859 (diam, avg, ecc)
6860}
6861
6862fn graph_usage() -> String {
6863 "Graph theory — edge list input:\n\
6864 hematite --graph 'A B\\nB C\\nC D' info (degree table, components)\n\
6865 hematite --graph 'bfs A\\nA B\\nB C\\nA C' BFS from node A\n\
6866 hematite --graph 'dfs A\\nA B\\nB C\\nA C' DFS from node A\n\
6867 hematite --graph 'shortest A D\\nA B 2\\nB D 3\\nA D 10' Dijkstra shortest path\n\
6868 hematite --graph 'components\\nA B\\nC D' connected components\n\
6869 hematite --graph 'topo\\nA->B\\nA->C\\nB->D' topological sort\n\
6870 hematite --graph 'centrality\\nA B\\nB C\\nA C' betweenness centrality\n\
6871 hematite --graph 'pagerank\\nA->B\\nB->C\\nC->A' PageRank scores\n\
6872 hematite --graph 'clustering\\nA B\\nB C\\nA C' local clustering coefficients\n\
6873 hematite --graph 'diameter\\nA B 1\\nB C 2\\nA C 4' diameter, avg path, eccentricity\n\
6874 \n\
6875 Edge formats: 'A B' 'A B 5' 'A->B' 'A->B:5' 'A-B:3'\n\
6876 Weighted edges: add weight as third token or after colon"
6877 .into()
6878}
6879
6880#[derive(Clone, Debug)]
6892enum Expr {
6893 Num(f64),
6894 Var(String),
6895 Add(Box<Expr>, Box<Expr>),
6896 Sub(Box<Expr>, Box<Expr>),
6897 Mul(Box<Expr>, Box<Expr>),
6898 Div(Box<Expr>, Box<Expr>),
6899 Pow(Box<Expr>, Box<Expr>),
6900 Neg(Box<Expr>),
6901 Sin(Box<Expr>),
6902 Cos(Box<Expr>),
6903 Tan(Box<Expr>),
6904 Ln(Box<Expr>),
6905 Exp(Box<Expr>),
6906 Sqrt(Box<Expr>),
6907 Abs(Box<Expr>),
6908}
6909
6910struct Parser<'a> {
6913 chars: &'a [char],
6914 pos: usize,
6915}
6916
6917impl<'a> Parser<'a> {
6918 fn new(chars: &'a [char]) -> Self {
6919 Self { chars, pos: 0 }
6920 }
6921
6922 fn peek(&self) -> Option<char> {
6923 self.chars.get(self.pos).copied()
6924 }
6925
6926 fn consume(&mut self) -> Option<char> {
6927 let c = self.chars.get(self.pos).copied();
6928 self.pos += 1;
6929 c
6930 }
6931
6932 fn skip_ws(&mut self) {
6933 while matches!(self.peek(), Some(' ') | Some('\t')) {
6934 self.pos += 1;
6935 }
6936 }
6937
6938 fn parse_expr(&mut self) -> Result<Expr, String> {
6939 self.parse_add()
6940 }
6941
6942 fn parse_add(&mut self) -> Result<Expr, String> {
6943 let mut left = self.parse_mul()?;
6944 loop {
6945 self.skip_ws();
6946 match self.peek() {
6947 Some('+') => {
6948 self.consume();
6949 let r = self.parse_mul()?;
6950 left = Expr::Add(Box::new(left), Box::new(r));
6951 }
6952 Some('-') => {
6953 self.consume();
6954 let r = self.parse_mul()?;
6955 left = Expr::Sub(Box::new(left), Box::new(r));
6956 }
6957 _ => break,
6958 }
6959 }
6960 Ok(left)
6961 }
6962
6963 fn parse_mul(&mut self) -> Result<Expr, String> {
6964 let mut left = self.parse_pow()?;
6965 loop {
6966 self.skip_ws();
6967 match self.peek() {
6968 Some('*') => {
6969 self.consume();
6970 let r = self.parse_pow()?;
6971 left = Expr::Mul(Box::new(left), Box::new(r));
6972 }
6973 Some('/') => {
6974 self.consume();
6975 let r = self.parse_pow()?;
6976 left = Expr::Div(Box::new(left), Box::new(r));
6977 }
6978 Some(c) if c.is_alphabetic() || c == '(' => {
6980 let r = self.parse_pow()?;
6981 left = Expr::Mul(Box::new(left), Box::new(r));
6982 }
6983 _ => break,
6984 }
6985 }
6986 Ok(left)
6987 }
6988
6989 fn parse_pow(&mut self) -> Result<Expr, String> {
6990 let base = self.parse_unary()?;
6991 self.skip_ws();
6992 if self.peek() == Some('^') {
6993 self.consume();
6994 let exp = self.parse_unary()?;
6995 return Ok(Expr::Pow(Box::new(base), Box::new(exp)));
6996 }
6997 Ok(base)
6998 }
6999
7000 fn parse_unary(&mut self) -> Result<Expr, String> {
7001 self.skip_ws();
7002 if self.peek() == Some('-') {
7003 self.consume();
7004 let inner = self.parse_atom()?;
7005 return Ok(Expr::Neg(Box::new(inner)));
7006 }
7007 if self.peek() == Some('+') {
7008 self.consume();
7009 }
7010 self.parse_atom()
7011 }
7012
7013 fn parse_atom(&mut self) -> Result<Expr, String> {
7014 self.skip_ws();
7015 match self.peek() {
7016 Some('(') => {
7017 self.consume();
7018 let inner = self.parse_expr()?;
7019 self.skip_ws();
7020 if self.peek() == Some(')') {
7021 self.consume();
7022 }
7023 Ok(inner)
7024 }
7025 Some(c) if c.is_ascii_digit() || c == '.' => self.parse_number(),
7026 Some(c) if c.is_alphabetic() || c == '_' => self.parse_name(),
7027 Some(c) => Err(format!("unexpected char '{}'", c)),
7028 None => Err("unexpected end".into()),
7029 }
7030 }
7031
7032 fn parse_number(&mut self) -> Result<Expr, String> {
7033 let start = self.pos;
7034 while matches!(self.peek(), Some(c) if c.is_ascii_digit() || c == '.' || c == 'e' || c == 'E')
7035 {
7036 self.pos += 1;
7037 if matches!(self.chars.get(self.pos - 1), Some('e') | Some('E'))
7039 && matches!(self.peek(), Some('+') | Some('-'))
7040 {
7041 self.pos += 1;
7042 }
7043 }
7044 let s: String = self.chars[start..self.pos].iter().collect();
7045 s.parse::<f64>()
7046 .map(Expr::Num)
7047 .map_err(|_| format!("bad number: {}", s))
7048 }
7049
7050 fn parse_name(&mut self) -> Result<Expr, String> {
7051 let start = self.pos;
7052 while matches!(self.peek(), Some(c) if c.is_alphanumeric() || c == '_') {
7053 self.pos += 1;
7054 }
7055 let name: String = self.chars[start..self.pos].iter().collect();
7056 self.skip_ws();
7057 if self.peek() == Some('(') {
7059 self.consume();
7060 let arg = self.parse_expr()?;
7061 self.skip_ws();
7062 if self.peek() == Some(')') {
7063 self.consume();
7064 }
7065 let e = Box::new(arg);
7066 return match name.to_lowercase().as_str() {
7067 "sin" => Ok(Expr::Sin(e)),
7068 "cos" => Ok(Expr::Cos(e)),
7069 "tan" => Ok(Expr::Tan(e)),
7070 "ln" => Ok(Expr::Ln(e)),
7071 "log" => Ok(Expr::Ln(e)), "exp" => Ok(Expr::Exp(e)),
7073 "sqrt" => Ok(Expr::Sqrt(e)),
7074 "abs" => Ok(Expr::Abs(e)),
7075 _ => Err(format!("unknown function: {}", name)),
7076 };
7077 }
7078 match name.as_str() {
7080 "pi" | "PI" => return Ok(Expr::Num(std::f64::consts::PI)),
7081 "e" | "E" => return Ok(Expr::Num(std::f64::consts::E)),
7082 _ => {}
7083 }
7084 Ok(Expr::Var(name))
7085 }
7086}
7087
7088fn parse_sym(s: &str) -> Result<Expr, String> {
7089 let chars: Vec<char> = s.chars().collect();
7090 let mut p = Parser::new(&chars);
7091 let e = p.parse_expr()?;
7092 p.skip_ws();
7093 if p.pos < p.chars.len() {
7094 let rest: String = p.chars[p.pos..].iter().collect();
7096 if !rest.trim().is_empty() {
7097 return Err(format!("unexpected trailing: '{}'", rest.trim()));
7098 }
7099 }
7100 Ok(e)
7101}
7102
7103fn fmt_expr(e: &Expr) -> String {
7106 match e {
7107 Expr::Num(n) => {
7108 if n.fract() == 0.0 && n.abs() < 1e12 {
7109 format!("{}", *n as i64)
7110 } else {
7111 format!("{}", n)
7112 }
7113 }
7114 Expr::Var(v) => v.clone(),
7115 Expr::Add(a, b) => format!("({} + {})", fmt_expr(a), fmt_expr(b)),
7116 Expr::Sub(a, b) => format!("({} - {})", fmt_expr(a), fmt_expr(b)),
7117 Expr::Mul(a, b) => format!("({} * {})", fmt_expr(a), fmt_expr(b)),
7118 Expr::Div(a, b) => format!("({} / {})", fmt_expr(a), fmt_expr(b)),
7119 Expr::Pow(a, b) => format!("({}^{})", fmt_expr(a), fmt_expr(b)),
7120 Expr::Neg(a) => format!("(-{})", fmt_expr(a)),
7121 Expr::Sin(a) => format!("sin({})", fmt_expr(a)),
7122 Expr::Cos(a) => format!("cos({})", fmt_expr(a)),
7123 Expr::Tan(a) => format!("tan({})", fmt_expr(a)),
7124 Expr::Ln(a) => format!("ln({})", fmt_expr(a)),
7125 Expr::Exp(a) => format!("exp({})", fmt_expr(a)),
7126 Expr::Sqrt(a) => format!("sqrt({})", fmt_expr(a)),
7127 Expr::Abs(a) => format!("abs({})", fmt_expr(a)),
7128 }
7129}
7130
7131fn simplify(e: Expr) -> Expr {
7135 match e {
7136 Expr::Add(a, b) => {
7137 let a = simplify(*a);
7138 let b = simplify(*b);
7139 match (&a, &b) {
7140 (Expr::Num(x), Expr::Num(y)) => Expr::Num(x + y),
7141 (Expr::Num(0.0), _) => b,
7142 (_, Expr::Num(0.0)) => a,
7143 _ => Expr::Add(Box::new(a), Box::new(b)),
7144 }
7145 }
7146 Expr::Sub(a, b) => {
7147 let a = simplify(*a);
7148 let b = simplify(*b);
7149 match (&a, &b) {
7150 (Expr::Num(x), Expr::Num(y)) => Expr::Num(x - y),
7151 (_, Expr::Num(0.0)) => a,
7152 _ if fmt_expr(&a) == fmt_expr(&b) => Expr::Num(0.0),
7153 _ => Expr::Sub(Box::new(a), Box::new(b)),
7154 }
7155 }
7156 Expr::Mul(a, b) => {
7157 let a = simplify(*a);
7158 let b = simplify(*b);
7159 match (&a, &b) {
7160 (Expr::Num(x), Expr::Num(y)) => Expr::Num(x * y),
7161 (Expr::Num(0.0), _) | (_, Expr::Num(0.0)) => Expr::Num(0.0),
7162 (Expr::Num(1.0), _) => b,
7163 (_, Expr::Num(1.0)) => a,
7164 (Expr::Num(-1.0), _) => Expr::Neg(Box::new(b)),
7165 _ => Expr::Mul(Box::new(a), Box::new(b)),
7166 }
7167 }
7168 Expr::Div(a, b) => {
7169 let a = simplify(*a);
7170 let b = simplify(*b);
7171 match (&a, &b) {
7172 (_, Expr::Num(1.0)) => a,
7173 (Expr::Num(x), Expr::Num(y)) if *y != 0.0 => Expr::Num(x / y),
7174 _ => Expr::Div(Box::new(a), Box::new(b)),
7175 }
7176 }
7177 Expr::Pow(a, b) => {
7178 let a = simplify(*a);
7179 let b = simplify(*b);
7180 match (&a, &b) {
7181 (_, Expr::Num(0.0)) => Expr::Num(1.0),
7182 (_, Expr::Num(1.0)) => a,
7183 (Expr::Num(1.0), _) => Expr::Num(1.0),
7184 (Expr::Num(x), Expr::Num(y)) => Expr::Num(x.powf(*y)),
7185 _ => Expr::Pow(Box::new(a), Box::new(b)),
7186 }
7187 }
7188 Expr::Neg(a) => {
7189 let a = simplify(*a);
7190 match a {
7191 Expr::Num(n) => Expr::Num(-n),
7192 Expr::Neg(inner) => *inner,
7193 _ => Expr::Neg(Box::new(a)),
7194 }
7195 }
7196 Expr::Sin(a) => {
7197 let a = simplify(*a);
7198 Expr::Sin(Box::new(a))
7199 }
7200 Expr::Cos(a) => {
7201 let a = simplify(*a);
7202 Expr::Cos(Box::new(a))
7203 }
7204 Expr::Tan(a) => {
7205 let a = simplify(*a);
7206 Expr::Tan(Box::new(a))
7207 }
7208 Expr::Ln(a) => {
7209 let a = simplify(*a);
7210 if let Expr::Num(n) = &a {
7211 if (*n - std::f64::consts::E).abs() < 1e-12 {
7212 return Expr::Num(1.0);
7213 }
7214 }
7215 Expr::Ln(Box::new(a))
7216 }
7217 Expr::Exp(a) => {
7218 let a = simplify(*a);
7219 if let Expr::Num(n) = &a {
7220 return Expr::Num(n.exp());
7221 }
7222 if let Expr::Num(0.0) = &a {
7223 return Expr::Num(1.0);
7224 }
7225 Expr::Exp(Box::new(a))
7226 }
7227 Expr::Sqrt(a) => {
7228 let a = simplify(*a);
7229 if let Expr::Num(n) = &a {
7230 if *n >= 0.0 {
7231 return Expr::Num(n.sqrt());
7232 }
7233 }
7234 Expr::Sqrt(Box::new(a))
7235 }
7236 other => other,
7237 }
7238}
7239
7240fn diff(e: &Expr, var: &str) -> Expr {
7243 match e {
7244 Expr::Num(_) => Expr::Num(0.0),
7245 Expr::Var(v) => {
7246 if v == var {
7247 Expr::Num(1.0)
7248 } else {
7249 Expr::Num(0.0)
7250 }
7251 }
7252 Expr::Add(a, b) => simplify(Expr::Add(Box::new(diff(a, var)), Box::new(diff(b, var)))),
7253 Expr::Sub(a, b) => simplify(Expr::Sub(Box::new(diff(a, var)), Box::new(diff(b, var)))),
7254 Expr::Mul(a, b) => {
7255 let fp_g = Expr::Mul(Box::new(diff(a, var)), Box::new(*b.clone()));
7257 let f_gp = Expr::Mul(Box::new(*a.clone()), Box::new(diff(b, var)));
7258 simplify(Expr::Add(Box::new(fp_g), Box::new(f_gp)))
7259 }
7260 Expr::Div(a, b) => {
7261 let fp_g = Expr::Mul(Box::new(diff(a, var)), Box::new(*b.clone()));
7263 let f_gp = Expr::Mul(Box::new(*a.clone()), Box::new(diff(b, var)));
7264 let num = Expr::Sub(Box::new(fp_g), Box::new(f_gp));
7265 let den = Expr::Pow(Box::new(*b.clone()), Box::new(Expr::Num(2.0)));
7266 simplify(Expr::Div(Box::new(num), Box::new(den)))
7267 }
7268 Expr::Pow(base, exp) => {
7269 if let Expr::Num(n) = exp.as_ref() {
7271 let new_exp = Expr::Num(n - 1.0);
7272 let power = Expr::Pow(Box::new(*base.clone()), Box::new(new_exp));
7273 let coeff = Expr::Mul(Box::new(Expr::Num(*n)), Box::new(power));
7274 let chain = diff(base, var);
7275 return simplify(Expr::Mul(Box::new(coeff), Box::new(chain)));
7276 }
7277 let ln_base = Expr::Ln(Box::new(*base.clone()));
7279 let g_ln_f = Expr::Mul(Box::new(*exp.clone()), Box::new(ln_base));
7280 let g_ln_f_d = diff(&g_ln_f, var);
7281 let result = Expr::Mul(Box::new(e.clone()), Box::new(g_ln_f_d));
7282 simplify(result)
7283 }
7284 Expr::Neg(a) => simplify(Expr::Neg(Box::new(diff(a, var)))),
7285 Expr::Sin(a) => {
7286 let cos_a = Expr::Cos(Box::new(*a.clone()));
7287 simplify(Expr::Mul(Box::new(cos_a), Box::new(diff(a, var))))
7288 }
7289 Expr::Cos(a) => {
7290 let neg_sin = Expr::Neg(Box::new(Expr::Sin(Box::new(*a.clone()))));
7291 simplify(Expr::Mul(Box::new(neg_sin), Box::new(diff(a, var))))
7292 }
7293 Expr::Tan(a) => {
7294 let cos_a = Expr::Cos(Box::new(*a.clone()));
7296 let cos2 = Expr::Pow(Box::new(cos_a), Box::new(Expr::Num(2.0)));
7297 let sec2 = Expr::Div(Box::new(Expr::Num(1.0)), Box::new(cos2));
7298 simplify(Expr::Mul(Box::new(sec2), Box::new(diff(a, var))))
7299 }
7300 Expr::Ln(a) => {
7301 let inv_a = Expr::Div(Box::new(Expr::Num(1.0)), Box::new(*a.clone()));
7303 simplify(Expr::Mul(Box::new(inv_a), Box::new(diff(a, var))))
7304 }
7305 Expr::Exp(a) => {
7306 simplify(Expr::Mul(Box::new(e.clone()), Box::new(diff(a, var))))
7308 }
7309 Expr::Sqrt(a) => {
7310 let two_sqrt = Expr::Mul(
7312 Box::new(Expr::Num(2.0)),
7313 Box::new(Expr::Sqrt(Box::new(*a.clone()))),
7314 );
7315 let inv = Expr::Div(Box::new(Expr::Num(1.0)), Box::new(two_sqrt));
7316 simplify(Expr::Mul(Box::new(inv), Box::new(diff(a, var))))
7317 }
7318 Expr::Abs(a) => {
7319 let sign = Expr::Div(
7321 Box::new(*a.clone()),
7322 Box::new(Expr::Abs(Box::new(*a.clone()))),
7323 );
7324 simplify(Expr::Mul(Box::new(sign), Box::new(diff(a, var))))
7325 }
7326 }
7327}
7328
7329fn integrate(e: &Expr, var: &str) -> Option<Expr> {
7334 match e {
7335 Expr::Num(n) => {
7336 Some(Expr::Mul(
7338 Box::new(Expr::Num(*n)),
7339 Box::new(Expr::Var(var.to_string())),
7340 ))
7341 }
7342 Expr::Var(v) => {
7343 if v == var {
7344 Some(Expr::Div(
7346 Box::new(Expr::Pow(
7347 Box::new(Expr::Var(v.clone())),
7348 Box::new(Expr::Num(2.0)),
7349 )),
7350 Box::new(Expr::Num(2.0)),
7351 ))
7352 } else {
7353 Some(Expr::Mul(
7355 Box::new(Expr::Var(v.clone())),
7356 Box::new(Expr::Var(var.to_string())),
7357 ))
7358 }
7359 }
7360 Expr::Add(a, b) => {
7361 let ia = integrate(a, var)?;
7362 let ib = integrate(b, var)?;
7363 Some(simplify(Expr::Add(Box::new(ia), Box::new(ib))))
7364 }
7365 Expr::Sub(a, b) => {
7366 let ia = integrate(a, var)?;
7367 let ib = integrate(b, var)?;
7368 Some(simplify(Expr::Sub(Box::new(ia), Box::new(ib))))
7369 }
7370 Expr::Neg(a) => {
7371 let ia = integrate(a, var)?;
7372 Some(simplify(Expr::Neg(Box::new(ia))))
7373 }
7374 Expr::Mul(a, b) => {
7375 if !contains_var(a, var) {
7377 let ib = integrate(b, var)?;
7378 return Some(simplify(Expr::Mul(Box::new(*a.clone()), Box::new(ib))));
7379 }
7380 if !contains_var(b, var) {
7381 let ia = integrate(a, var)?;
7382 return Some(simplify(Expr::Mul(Box::new(*b.clone()), Box::new(ia))));
7383 }
7384 None }
7386 Expr::Pow(base, exp) => {
7387 if let Expr::Var(v) = base.as_ref() {
7388 if v == var {
7389 if let Expr::Num(n) = exp.as_ref() {
7390 if (*n + 1.0).abs() < 1e-12 {
7391 return Some(Expr::Ln(Box::new(Expr::Abs(Box::new(Expr::Var(
7393 v.clone(),
7394 ))))));
7395 }
7396 let new_exp = Expr::Num(n + 1.0);
7398 let pow =
7399 Expr::Pow(Box::new(Expr::Var(v.clone())), Box::new(new_exp.clone()));
7400 return Some(simplify(Expr::Div(Box::new(pow), Box::new(new_exp))));
7401 }
7402 }
7403 }
7404 None
7405 }
7406 Expr::Sin(a) => {
7407 if let Expr::Var(v) = a.as_ref() {
7408 if v == var {
7409 return Some(Expr::Neg(Box::new(Expr::Cos(Box::new(*a.clone())))));
7410 }
7411 }
7412 if let Some((coeff, _inner_var)) = linear_coeff(a, var) {
7414 let cos_part = Expr::Cos(Box::new(*a.clone()));
7415 let neg_cos = Expr::Neg(Box::new(cos_part));
7416 return Some(simplify(Expr::Div(
7417 Box::new(neg_cos),
7418 Box::new(Expr::Num(coeff)),
7419 )));
7420 }
7421 None
7422 }
7423 Expr::Cos(a) => {
7424 if let Expr::Var(v) = a.as_ref() {
7425 if v == var {
7426 return Some(Expr::Sin(Box::new(*a.clone())));
7427 }
7428 }
7429 if let Some((coeff, _)) = linear_coeff(a, var) {
7430 let sin_part = Expr::Sin(Box::new(*a.clone()));
7431 return Some(simplify(Expr::Div(
7432 Box::new(sin_part),
7433 Box::new(Expr::Num(coeff)),
7434 )));
7435 }
7436 None
7437 }
7438 Expr::Exp(a) => {
7439 if let Expr::Var(v) = a.as_ref() {
7440 if v == var {
7441 return Some(e.clone()); }
7443 }
7444 if let Some((coeff, _)) = linear_coeff(a, var) {
7445 return Some(simplify(Expr::Div(
7446 Box::new(e.clone()),
7447 Box::new(Expr::Num(coeff)),
7448 )));
7449 }
7450 None
7451 }
7452 Expr::Ln(a) => {
7453 if let Expr::Var(v) = a.as_ref() {
7454 if v == var {
7455 let x_ln_x = Expr::Mul(Box::new(Expr::Var(v.clone())), Box::new(e.clone()));
7457 return Some(simplify(Expr::Sub(
7458 Box::new(x_ln_x),
7459 Box::new(Expr::Var(v.clone())),
7460 )));
7461 }
7462 }
7463 None
7464 }
7465 Expr::Div(a, b) => {
7466 if let (Expr::Num(1.0), Expr::Var(v)) = (a.as_ref(), b.as_ref()) {
7468 if v == var {
7469 return Some(Expr::Ln(Box::new(Expr::Abs(Box::new(Expr::Var(
7470 v.clone(),
7471 ))))));
7472 }
7473 }
7474 if let Expr::Var(v) = b.as_ref() {
7476 if v == var && !contains_var(a, var) {
7477 let ln_abs = Expr::Ln(Box::new(Expr::Abs(Box::new(Expr::Var(v.clone())))));
7478 return Some(simplify(Expr::Mul(Box::new(*a.clone()), Box::new(ln_abs))));
7479 }
7480 }
7481 None
7482 }
7483 _ => None,
7484 }
7485}
7486
7487fn linear_coeff<'a>(e: &'a Expr, var: &'a str) -> Option<(f64, &'a str)> {
7489 match e {
7490 Expr::Mul(a, b) => {
7491 if let (Expr::Num(c), Expr::Var(v)) = (a.as_ref(), b.as_ref()) {
7492 if v == var {
7493 return Some((*c, var));
7494 }
7495 }
7496 if let (Expr::Var(v), Expr::Num(c)) = (a.as_ref(), b.as_ref()) {
7497 if v == var {
7498 return Some((*c, var));
7499 }
7500 }
7501 None
7502 }
7503 _ => None,
7504 }
7505}
7506
7507fn contains_var(e: &Expr, var: &str) -> bool {
7508 match e {
7509 Expr::Var(v) => v == var,
7510 Expr::Num(_) => false,
7511 Expr::Add(a, b) | Expr::Sub(a, b) | Expr::Mul(a, b) | Expr::Div(a, b) | Expr::Pow(a, b) => {
7512 contains_var(a, var) || contains_var(b, var)
7513 }
7514 Expr::Neg(a)
7515 | Expr::Sin(a)
7516 | Expr::Cos(a)
7517 | Expr::Tan(a)
7518 | Expr::Ln(a)
7519 | Expr::Exp(a)
7520 | Expr::Sqrt(a)
7521 | Expr::Abs(a) => contains_var(a, var),
7522 }
7523}
7524
7525fn eval_expr(e: &Expr, var: &str, val: f64) -> Result<f64, String> {
7528 match e {
7529 Expr::Num(n) => Ok(*n),
7530 Expr::Var(v) => {
7531 if v == var {
7532 Ok(val)
7533 } else {
7534 Err(format!("unbound variable: {}", v))
7535 }
7536 }
7537 Expr::Add(a, b) => Ok(eval_expr(a, var, val)? + eval_expr(b, var, val)?),
7538 Expr::Sub(a, b) => Ok(eval_expr(a, var, val)? - eval_expr(b, var, val)?),
7539 Expr::Mul(a, b) => Ok(eval_expr(a, var, val)? * eval_expr(b, var, val)?),
7540 Expr::Div(a, b) => {
7541 let d = eval_expr(b, var, val)?;
7542 if d.abs() < 1e-300 {
7543 return Err("division by zero".into());
7544 }
7545 Ok(eval_expr(a, var, val)? / d)
7546 }
7547 Expr::Pow(a, b) => Ok(eval_expr(a, var, val)?.powf(eval_expr(b, var, val)?)),
7548 Expr::Neg(a) => Ok(-eval_expr(a, var, val)?),
7549 Expr::Sin(a) => Ok(eval_expr(a, var, val)?.sin()),
7550 Expr::Cos(a) => Ok(eval_expr(a, var, val)?.cos()),
7551 Expr::Tan(a) => Ok(eval_expr(a, var, val)?.tan()),
7552 Expr::Ln(a) => Ok(eval_expr(a, var, val)?.ln()),
7553 Expr::Exp(a) => Ok(eval_expr(a, var, val)?.exp()),
7554 Expr::Sqrt(a) => Ok(eval_expr(a, var, val)?.sqrt()),
7555 Expr::Abs(a) => Ok(eval_expr(a, var, val)?.abs()),
7556 }
7557}
7558
7559pub fn symbolic_calc(query: &str) -> String {
7562 let q = query.trim();
7563
7564 let (q_body, var) = if let Some(pos) = q.to_lowercase().rfind(" wrt ") {
7566 let v = q[pos + 5..].trim().to_string();
7567 (q[..pos].trim(), v)
7568 } else {
7569 (q, "x".to_string())
7570 };
7571
7572 let (mode, expr_str) = {
7574 let low = q_body.to_lowercase();
7575 if low.starts_with("diff ")
7576 || low.starts_with("differentiate ")
7577 || low.starts_with("d/dx ")
7578 || low.starts_with("d/d")
7579 {
7580 let (m, rest, var_from_mode) = if low.starts_with("d/d") {
7582 let after = &q_body[3..];
7583 let sp = after.find(char::is_whitespace).unwrap_or(after.len());
7584 let v = after[..sp].to_string();
7585 let rest = after[sp..].trim();
7586 ("diff", rest, Some(v))
7587 } else {
7588 let rest = q_body
7589 .split_once(char::is_whitespace)
7590 .map(|x| x.1)
7591 .unwrap_or("")
7592 .trim();
7593 ("diff", rest, None)
7594 };
7595 let var2 = var_from_mode.unwrap_or_else(|| var.clone());
7596 (m, (rest.to_string(), var2))
7597 } else if low.starts_with("int ") || low.starts_with("integrate ") || low.starts_with("∫")
7598 {
7599 let rest = q_body
7600 .split_once(char::is_whitespace)
7601 .map(|x| x.1)
7602 .unwrap_or("")
7603 .trim();
7604 ("integrate", (rest.to_string(), var.clone()))
7605 } else if low.starts_with("simplify ") || low.starts_with("simplify") {
7606 let rest = q_body
7607 .split_once(char::is_whitespace)
7608 .map(|x| x.1)
7609 .unwrap_or("")
7610 .trim();
7611 ("simplify", (rest.to_string(), var.clone()))
7612 } else if low.contains(" at ") {
7613 ("eval", (q_body.to_string(), var.clone()))
7614 } else {
7615 ("diff", (q_body.to_string(), var.clone()))
7617 }
7618 };
7619
7620 let (expr_text, var_name) = expr_str;
7621 let var_name = var_name.trim().to_string();
7622 let var_name = if var_name.is_empty() {
7623 "x".to_string()
7624 } else {
7625 var_name
7626 };
7627
7628 let mut out = String::new();
7629 let w = 64usize;
7630 let _ = writeln!(out, "{}", "=".repeat(w));
7631 let _ = writeln!(out, " Symbolic Calculus");
7632
7633 if mode == "eval" {
7634 let parts: Vec<&str> = expr_text.splitn(2, " at ").collect();
7636 if parts.len() != 2 {
7637 let _ = writeln!(out, " Error: use 'EXPR at VAR=VALUE'");
7638 return out;
7639 }
7640 let e_str = parts[0].trim();
7641 let at_str = parts[1].trim();
7642 let (av, val_str) = if let Some(eq) = at_str.find('=') {
7643 (&at_str[..eq], &at_str[eq + 1..])
7644 } else {
7645 (&var_name[..], at_str)
7646 };
7647 let val: f64 = match val_str.trim().parse() {
7648 Ok(v) => v,
7649 Err(_) => {
7650 let _ = writeln!(out, " Error: bad value '{}'", val_str);
7651 return out;
7652 }
7653 };
7654 match parse_sym(e_str) {
7655 Ok(expr) => {
7656 let _ = writeln!(out, " f({}) = {}", av, fmt_expr(&expr));
7657 match eval_expr(&expr, av.trim(), val) {
7658 Ok(result) => {
7659 let _ = writeln!(out, " f({} = {}) = {}", av.trim(), val, result);
7660 }
7661 Err(e) => {
7662 let _ = writeln!(out, " Eval error: {}", e);
7663 }
7664 }
7665 }
7666 Err(e) => {
7667 let _ = writeln!(out, " Parse error: {}", e);
7668 }
7669 }
7670 let _ = writeln!(out, "{}", "=".repeat(w));
7671 return out;
7672 }
7673
7674 let expr_text = expr_text.trim();
7675 match parse_sym(expr_text) {
7676 Err(e) => {
7677 let _ = writeln!(out, " Parse error: {}", e);
7678 let _ = writeln!(out, " Input: {}", expr_text);
7679 let _ = writeln!(out, "{}", "=".repeat(w));
7680 return out;
7681 }
7682 Ok(expr) => {
7683 let simplified = simplify(expr.clone());
7684 let _ = writeln!(out, " f({}) = {}", var_name, fmt_expr(&simplified));
7685 match mode {
7686 "diff" => {
7687 let d = diff(&simplified, &var_name);
7688 let d_simp = simplify(d);
7689 let _ = writeln!(out, " d/d{} = {}", var_name, fmt_expr(&d_simp));
7690 let h = 1e-6f64;
7692 let x0 = 1.5f64;
7693 if let (Ok(fp), Ok(fm)) = (
7694 eval_expr(&simplified, &var_name, x0 + h),
7695 eval_expr(&simplified, &var_name, x0 - h),
7696 ) {
7697 let numeric = (fp - fm) / (2.0 * h);
7698 if let Ok(symbolic_val) = eval_expr(&d_simp, &var_name, x0) {
7699 let err = (symbolic_val - numeric).abs();
7700 if err < 1e-4 {
7701 let _ = writeln!(out, " ✓ Verified: numeric check at {}={} → diff={:.6}, numeric={:.6}", var_name, x0, symbolic_val, numeric);
7702 } else {
7703 let _ = writeln!(out, " ⚠ Numeric check mismatch at {}={}: symbolic={:.6}, numeric={:.6}", var_name, x0, symbolic_val, numeric);
7704 }
7705 }
7706 }
7707 }
7708 "integrate" => {
7709 match integrate(&simplified, &var_name) {
7710 Some(integral) => {
7711 let i_simp = simplify(integral);
7712 let _ = writeln!(out, " ∫f d{} = {} + C", var_name, fmt_expr(&i_simp));
7713 let check = simplify(diff(&i_simp, &var_name));
7715 let orig = fmt_expr(&simplified);
7716 let back = fmt_expr(&check);
7717 if orig == back {
7718 let _ =
7719 writeln!(out, " ✓ Verified: d/d{} of integral = f", var_name);
7720 } else {
7721 let x0 = 1.5f64;
7723 if let (Ok(v1), Ok(v2)) = (
7724 eval_expr(&simplified, &var_name, x0),
7725 eval_expr(&check, &var_name, x0),
7726 ) {
7727 if (v1 - v2).abs() < 1e-6 {
7728 let _ = writeln!(out, " ✓ Numerically verified: d/d{}(integral) = f at {}={}", var_name, var_name, x0);
7729 } else {
7730 let _ = writeln!(
7731 out,
7732 " ⚠ Verification: d/d{}(integral) = {} (expected {})",
7733 var_name, back, orig
7734 );
7735 }
7736 }
7737 }
7738 }
7739 None => {
7740 let _ = writeln!(
7741 out,
7742 " ∫f d{} = (not in table — try --simulate or a CAS)",
7743 var_name
7744 );
7745 }
7746 }
7747 }
7748 "simplify" => {
7749 let _ = writeln!(out, " simplified: {}", fmt_expr(&simplified));
7750 }
7751 _ => {}
7752 }
7753 }
7754 }
7755
7756 let _ = writeln!(out, "{}", "=".repeat(w));
7757 out
7758}
7759
7760#[allow(dead_code)]
7761fn symbolic_usage() -> String {
7762 "Symbolic calculus:\n\
7763 hematite --symbolic 'diff x^3 + 2*x' differentiate (wrt x)\n\
7764 hematite --symbolic 'diff sin(x)*cos(x)' product rule\n\
7765 hematite --symbolic 'diff x^2 + y wrt y' differentiate wrt y\n\
7766 hematite --symbolic 'integrate x^3' antiderivative\n\
7767 hematite --symbolic 'integrate sin(x) + 3*x^2' linearity\n\
7768 hematite --symbolic 'simplify (x+1)*(x+1)' simplify\n\
7769 hematite --symbolic 'x^2 + 2*x at x=3' numeric eval\n\
7770 Supported: + - * / ^ sin cos tan ln exp sqrt abs"
7771 .into()
7772}
7773
7774use std::f64::consts::PI;
7780
7781fn dft(signal: &[f64]) -> Vec<(f64, f64)> {
7782 let n = signal.len();
7783 (0..n)
7784 .map(|k| {
7785 let (mut re, mut im) = (0.0_f64, 0.0_f64);
7786 for (t, &x) in signal.iter().enumerate() {
7787 let angle = -2.0 * PI * (k * t) as f64 / n as f64;
7788 re += x * angle.cos();
7789 im += x * angle.sin();
7790 }
7791 (re, im)
7792 })
7793 .collect()
7794}
7795
7796fn idft(spectrum: &[(f64, f64)]) -> Vec<f64> {
7797 let n = spectrum.len();
7798 (0..n)
7799 .map(|t| {
7800 let mut val = 0.0_f64;
7801 for (k, &(re, im)) in spectrum.iter().enumerate() {
7802 let angle = 2.0 * PI * (k * t) as f64 / n as f64;
7803 val += re * angle.cos() - im * angle.sin();
7804 }
7805 val / n as f64
7806 })
7807 .collect()
7808}
7809
7810fn convolve(x: &[f64], h: &[f64]) -> Vec<f64> {
7811 let n = x.len() + h.len() - 1;
7812 (0..n)
7813 .map(|i| {
7814 let mut s = 0.0_f64;
7815 for (j, &hv) in h.iter().enumerate() {
7816 if i >= j && i - j < x.len() {
7817 s += x[i - j] * hv;
7818 }
7819 }
7820 s
7821 })
7822 .collect()
7823}
7824
7825fn xcorr(x: &[f64], y: &[f64]) -> Vec<f64> {
7826 let n = x.len();
7827 let m = y.len();
7828 let out_len = n + m - 1;
7829 (0..out_len)
7830 .map(|lag| {
7831 let lag_i = lag as isize - (m as isize - 1);
7832 let mut s = 0.0_f64;
7833 for (i, &xv) in x.iter().enumerate() {
7834 let j = i as isize - lag_i;
7835 if j >= 0 && j < m as isize {
7836 s += xv * y[j as usize];
7837 }
7838 }
7839 s
7840 })
7841 .collect()
7842}
7843
7844fn moving_avg(signal: &[f64], window: usize) -> Vec<f64> {
7845 let w = window.max(1);
7846 signal
7847 .windows(w)
7848 .map(|s| s.iter().sum::<f64>() / w as f64)
7849 .collect()
7850}
7851
7852fn hann_window(n: usize) -> Vec<f64> {
7853 (0..n)
7854 .map(|i| 0.5 * (1.0 - (2.0 * PI * i as f64 / (n - 1) as f64).cos()))
7855 .collect()
7856}
7857
7858fn hamming_window(n: usize) -> Vec<f64> {
7859 (0..n)
7860 .map(|i| 0.54 - 0.46 * (2.0 * PI * i as f64 / (n - 1) as f64).cos())
7861 .collect()
7862}
7863
7864fn blackman_window(n: usize) -> Vec<f64> {
7865 (0..n)
7866 .map(|i| {
7867 let a = 2.0 * PI * i as f64 / (n - 1) as f64;
7868 0.42 - 0.5 * a.cos() + 0.08 * (2.0 * a).cos()
7869 })
7870 .collect()
7871}
7872
7873fn sinc(x: f64) -> f64 {
7874 if x == 0.0 {
7875 1.0
7876 } else {
7877 (PI * x).sin() / (PI * x)
7878 }
7879}
7880
7881fn fir_lowpass(n_taps: usize, cutoff_norm: f64, window: &str) -> Vec<f64> {
7882 let m = n_taps - 1;
7883 let wins: Vec<f64> = match window {
7884 "hann" | "hanning" => hann_window(n_taps),
7885 "hamming" => hamming_window(n_taps),
7886 "blackman" => blackman_window(n_taps),
7887 _ => vec![1.0; n_taps],
7888 };
7889 let mut h: Vec<f64> = (0..n_taps)
7890 .map(|i| {
7891 let n = i as f64 - m as f64 / 2.0;
7892 2.0 * cutoff_norm * sinc(2.0 * cutoff_norm * n) * wins[i]
7893 })
7894 .collect();
7895 let sum: f64 = h.iter().sum();
7896 if sum.abs() > 1e-12 {
7897 for v in &mut h {
7898 *v /= sum;
7899 }
7900 }
7901 h
7902}
7903
7904fn fir_highpass(n_taps: usize, cutoff_norm: f64, window: &str) -> Vec<f64> {
7905 let mut h = fir_lowpass(n_taps, cutoff_norm, window);
7906 for (i, v) in h.iter_mut().enumerate() {
7907 *v = if i == n_taps / 2 { 1.0 - *v } else { -*v };
7908 }
7909 h
7910}
7911
7912fn parse_signal(s: &str) -> Option<Vec<f64>> {
7913 let v: Vec<f64> = s
7914 .split([',', ' ', '\t', ';'].as_ref())
7915 .filter_map(|t| t.trim().parse::<f64>().ok())
7916 .collect();
7917 if v.is_empty() {
7918 None
7919 } else {
7920 Some(v)
7921 }
7922}
7923
7924fn signal_stats(sig: &[f64]) -> (f64, f64, f64, f64) {
7925 let n = sig.len() as f64;
7926 let mean = sig.iter().sum::<f64>() / n;
7927 let rms = (sig.iter().map(|x| x * x).sum::<f64>() / n).sqrt();
7928 let min = sig.iter().cloned().fold(f64::INFINITY, f64::min);
7929 let max = sig.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
7930 (mean, rms, min, max)
7931}
7932
7933fn ascii_waveform(sig: &[f64], width: usize, height: usize) -> String {
7934 if sig.is_empty() {
7935 return String::new();
7936 }
7937 let mn = sig.iter().cloned().fold(f64::INFINITY, f64::min);
7938 let mx = sig.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
7939 let range = (mx - mn).max(1e-12);
7940 let step = sig.len().max(1) as f64 / width as f64;
7941 let samples: Vec<f64> = (0..width)
7942 .map(|col| {
7943 let idx = ((col as f64 * step) as usize).min(sig.len() - 1);
7944 sig[idx]
7945 })
7946 .collect();
7947 let mut rows = vec![vec![' '; width]; height];
7948 for (col, &val) in samples.iter().enumerate() {
7949 let row = height - 1 - ((val - mn) / range * (height - 1) as f64).round() as usize;
7950 let row = row.min(height - 1);
7951 rows[row][col] = '█';
7952 }
7953 rows.iter()
7954 .map(|r| r.iter().collect::<String>())
7955 .collect::<Vec<_>>()
7956 .join("\n")
7957}
7958
7959pub fn signal_calc(query: &str) -> String {
7960 let mut out = String::new();
7961 let w = 60;
7962 let sep = "═".repeat(w);
7963 let _ = writeln!(out, "{}", sep);
7964 let _ = writeln!(out, " SIGNAL PROCESSING");
7965 let _ = writeln!(out, "{}", sep);
7966
7967 let q = query.trim();
7968 let lower = q.to_lowercase();
7969
7970 if lower.starts_with("dft ") || lower.starts_with("fft ") {
7972 let rest = q[4..].trim();
7973 match parse_signal(rest) {
7974 None => {
7975 let _ = writeln!(out, " ERROR: no numeric values found.");
7976 }
7977 Some(sig) => {
7978 let n = sig.len();
7979 let spectrum = dft(&sig);
7980 let (mean, rms, mn, mx) = signal_stats(&sig);
7981 let _ = writeln!(out, " DFT of {}-point signal", n);
7982 let _ = writeln!(
7983 out,
7984 " mean={:.4} RMS={:.4} min={:.4} max={:.4}",
7985 mean, rms, mn, mx
7986 );
7987 let _ = writeln!(out);
7988 let _ = writeln!(
7989 out,
7990 " {:>5} {:>10} {:>10} {:>10} {:>10}",
7991 "Bin", "Re", "Im", "Magnitude", "Phase°"
7992 );
7993 let _ = writeln!(out, " {}", "-".repeat(52));
7994 let show = (n / 2 + 1).min(20);
7995 for k in 0..show {
7996 let (re, im) = spectrum[k];
7997 let mag = (re * re + im * im).sqrt();
7998 let phase = im.atan2(re).to_degrees();
7999 let _ = writeln!(
8000 out,
8001 " {:>5} {:>10.4} {:>10.4} {:>10.4} {:>10.2}",
8002 k, re, im, mag, phase
8003 );
8004 }
8005 if show < n / 2 + 1 {
8006 let _ = writeln!(out, " … ({} bins total)", n / 2 + 1);
8007 }
8008 let dc = spectrum[0].0 / n as f64;
8009 let _ = writeln!(out);
8010 let _ = writeln!(out, " DC component: {:.6}", dc);
8011 let dominant = spectrum[1..n / 2 + 1]
8012 .iter()
8013 .enumerate()
8014 .map(|(i, &(r, im))| (i + 1, (r * r + im * im).sqrt()))
8015 .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
8016 if let Some((k, mag)) = dominant {
8017 let _ = writeln!(out, " Dominant frequency bin: {} (mag={:.4})", k, mag);
8018 }
8019 }
8020 }
8021 }
8022 else if lower.starts_with("idft ") {
8024 let rest = q[5..].trim();
8025 match parse_signal(rest) {
8026 None => {
8027 let _ = writeln!(out, " ERROR: no numeric values found.");
8028 }
8029 Some(vals) => {
8030 if vals.len() % 2 != 0 {
8031 let _ = writeln!(
8032 out,
8033 " ERROR: IDFT needs even number of values (re,im pairs)."
8034 );
8035 } else {
8036 let spectrum: Vec<(f64, f64)> = vals.chunks(2).map(|c| (c[0], c[1])).collect();
8037 let sig = idft(&spectrum);
8038 let _ = writeln!(out, " IDFT result ({} samples):", sig.len());
8039 let _ = writeln!(
8040 out,
8041 " {:?}",
8042 sig.iter()
8043 .map(|v| format!("{:.6}", v))
8044 .collect::<Vec<_>>()
8045 .join(", ")
8046 );
8047 }
8048 }
8049 }
8050 }
8051 else if lower.starts_with("conv ") || lower.starts_with("convolve ") {
8053 let rest = q[q.find(' ').unwrap_or(0)..].trim();
8054 if let Some(mid) = rest
8055 .find(" ; ")
8056 .or_else(|| rest.find(" with "))
8057 .or_else(|| rest.find(" | "))
8058 {
8059 let (a_str, b_str) = rest.split_at(mid);
8060 let b_str = b_str
8061 .trim_start_matches([' ', ';', '|'].as_ref())
8062 .trim_start_matches("with")
8063 .trim();
8064 match (parse_signal(a_str.trim()), parse_signal(b_str)) {
8065 (Some(x), Some(h)) => {
8066 let y = convolve(&x, &h);
8067 let _ = writeln!(
8068 out,
8069 " Convolution x[{}] * h[{}] = y[{}]",
8070 x.len(),
8071 h.len(),
8072 y.len()
8073 );
8074 let _ = writeln!(
8075 out,
8076 " x: {}",
8077 x.iter()
8078 .map(|v| format!("{:.4}", v))
8079 .collect::<Vec<_>>()
8080 .join(", ")
8081 );
8082 let _ = writeln!(
8083 out,
8084 " h: {}",
8085 h.iter()
8086 .map(|v| format!("{:.4}", v))
8087 .collect::<Vec<_>>()
8088 .join(", ")
8089 );
8090 let _ = writeln!(
8091 out,
8092 " y: {}",
8093 y.iter()
8094 .map(|v| format!("{:.4}", v))
8095 .collect::<Vec<_>>()
8096 .join(", ")
8097 );
8098 let (mean, rms, mn, mx) = signal_stats(&y);
8099 let _ = writeln!(
8100 out,
8101 " mean={:.4} RMS={:.4} min={:.4} max={:.4}",
8102 mean, rms, mn, mx
8103 );
8104 }
8105 _ => {
8106 let _ = writeln!(
8107 out,
8108 " ERROR: use conv A,B,C ; D,E,F (separate signals with ;)"
8109 );
8110 }
8111 }
8112 } else {
8113 let _ = writeln!(
8114 out,
8115 " ERROR: use conv A,B,C ; D,E,F (separate signals with ;)"
8116 );
8117 }
8118 }
8119 else if lower.starts_with("xcorr ") || lower.starts_with("correlate ") {
8121 let rest = q[q.find(' ').unwrap_or(0)..].trim();
8122 if let Some(mid) = rest.find(" ; ").or_else(|| rest.find(" | ")) {
8123 let (a_str, b_str) = rest.split_at(mid);
8124 let b_str = b_str.trim_start_matches([' ', ';', '|'].as_ref()).trim();
8125 match (parse_signal(a_str.trim()), parse_signal(b_str)) {
8126 (Some(x), Some(y)) => {
8127 let r = xcorr(&x, &y);
8128 let peak_lag = r
8129 .iter()
8130 .enumerate()
8131 .max_by(|a, b| a.1.abs().partial_cmp(&b.1.abs()).unwrap())
8132 .map(|(i, _)| i as isize - (y.len() as isize - 1));
8133 let _ = writeln!(
8134 out,
8135 " Cross-correlation x[{}] ⋆ y[{}] = r[{}]",
8136 x.len(),
8137 y.len(),
8138 r.len()
8139 );
8140 let _ = writeln!(
8141 out,
8142 " r: {}",
8143 r.iter()
8144 .map(|v| format!("{:.4}", v))
8145 .collect::<Vec<_>>()
8146 .join(", ")
8147 );
8148 if let Some(lag) = peak_lag {
8149 let _ = writeln!(out, " Peak lag: {} samples", lag);
8150 }
8151 }
8152 _ => {
8153 let _ = writeln!(out, " ERROR: use xcorr A,B,C ; D,E,F");
8154 }
8155 }
8156 } else {
8157 let _ = writeln!(out, " ERROR: use xcorr A,B,C ; D,E,F");
8158 }
8159 }
8160 else if lower.starts_with("movavg ")
8162 || lower.starts_with("moving-avg ")
8163 || lower.starts_with("sma ")
8164 {
8165 let rest = q[q.find(' ').unwrap_or(0)..].trim();
8166 let parts: Vec<&str> = rest.splitn(2, ' ').collect();
8167 let window = parts[0].parse::<usize>().unwrap_or(3);
8168 let data_str = if parts.len() > 1 { parts[1] } else { "" };
8169 match parse_signal(data_str) {
8170 None => {
8171 let _ = writeln!(out, " ERROR: use movavg WINDOW v1,v2,...");
8172 }
8173 Some(sig) => {
8174 let smoothed = moving_avg(&sig, window);
8175 let _ = writeln!(out, " Simple Moving Average window={}", window);
8176 let _ = writeln!(
8177 out,
8178 " Input ({} pts): {}",
8179 sig.len(),
8180 sig.iter()
8181 .take(8)
8182 .map(|v| format!("{:.3}", v))
8183 .collect::<Vec<_>>()
8184 .join(", ")
8185 );
8186 let _ = writeln!(
8187 out,
8188 " Output ({} pts): {}",
8189 smoothed.len(),
8190 smoothed
8191 .iter()
8192 .take(8)
8193 .map(|v| format!("{:.4}", v))
8194 .collect::<Vec<_>>()
8195 .join(", ")
8196 );
8197 if sig.len() > 8 {
8198 let _ = writeln!(out, " (showing first 8 of {} values)", sig.len());
8199 }
8200 }
8201 }
8202 }
8203 else if lower.starts_with("fir-lp ")
8205 || lower.starts_with("lowpass ")
8206 || lower.starts_with("lp ")
8207 {
8208 let rest = q[q.find(' ').unwrap_or(0)..].trim();
8209 let parts: Vec<&str> = rest.splitn(3, ' ').collect();
8210 let cutoff = parts
8211 .first()
8212 .and_then(|s| s.trim_end_matches('%').parse::<f64>().ok())
8213 .unwrap_or(0.25)
8214 / if rest.contains('%') { 100.0 } else { 1.0 };
8215 let n_taps = parts
8216 .get(1)
8217 .and_then(|s| s.parse::<usize>().ok())
8218 .unwrap_or(21);
8219 let window = parts.get(2).map(|s| s.trim()).unwrap_or("hamming");
8220 let h = fir_lowpass(n_taps, cutoff.min(0.5), window);
8221 let _ = writeln!(out, " FIR Low-Pass Filter");
8222 let _ = writeln!(
8223 out,
8224 " Cutoff: {:.4} (normalized, 0.5 = Nyquist) Taps: {} Window: {}",
8225 cutoff, n_taps, window
8226 );
8227 let _ = writeln!(out, " Coefficients:");
8228 for (i, c) in h.iter().enumerate() {
8229 let _ = write!(out, " h[{:2}]={:>10.6}", i, c);
8230 if (i + 1) % 4 == 0 {
8231 let _ = writeln!(out);
8232 }
8233 }
8234 let _ = writeln!(out);
8235 let sum: f64 = h.iter().sum();
8236 let _ = writeln!(
8237 out,
8238 " Sum of taps: {:.6} (DC gain = {:.4} dB)",
8239 sum,
8240 20.0 * sum.abs().log10()
8241 );
8242 }
8243 else if lower.starts_with("fir-hp ")
8245 || lower.starts_with("highpass ")
8246 || lower.starts_with("hp ")
8247 {
8248 let rest = q[q.find(' ').unwrap_or(0)..].trim();
8249 let parts: Vec<&str> = rest.splitn(3, ' ').collect();
8250 let cutoff = parts
8251 .first()
8252 .and_then(|s| s.trim_end_matches('%').parse::<f64>().ok())
8253 .unwrap_or(0.25)
8254 / if rest.contains('%') { 100.0 } else { 1.0 };
8255 let n_taps = parts
8256 .get(1)
8257 .and_then(|s| s.parse::<usize>().ok())
8258 .unwrap_or(21);
8259 let window = parts.get(2).map(|s| s.trim()).unwrap_or("hamming");
8260 let h = fir_highpass(n_taps, cutoff.min(0.49), window);
8261 let _ = writeln!(out, " FIR High-Pass Filter");
8262 let _ = writeln!(
8263 out,
8264 " Cutoff: {:.4} Taps: {} Window: {}",
8265 cutoff, n_taps, window
8266 );
8267 let _ = writeln!(out, " Coefficients:");
8268 for (i, c) in h.iter().enumerate() {
8269 let _ = write!(out, " h[{:2}]={:>10.6}", i, c);
8270 if (i + 1) % 4 == 0 {
8271 let _ = writeln!(out);
8272 }
8273 }
8274 let _ = writeln!(out);
8275 }
8276 else if lower.starts_with("filter ") {
8278 let rest = q[7..].trim();
8279 if let Some(mid) = rest.find(" ; ") {
8280 let (a_str, b_str) = rest.split_at(mid);
8281 let b_str = b_str[3..].trim();
8282 match (parse_signal(a_str.trim()), parse_signal(b_str)) {
8283 (Some(h), Some(x)) => {
8284 let y = convolve(&x, &h);
8285 let _ = writeln!(
8286 out,
8287 " Filter applied h[{}] * x[{}] = y[{}]",
8288 h.len(),
8289 x.len(),
8290 y.len()
8291 );
8292 let (_, rms_x, _, _) = signal_stats(&x);
8293 let (_, rms_y, _, _) = signal_stats(&y);
8294 let _ = writeln!(out, " Input RMS: {:.4}", rms_x);
8295 let _ = writeln!(out, " Output RMS: {:.4}", rms_y);
8296 let _ = writeln!(
8297 out,
8298 " y: {}",
8299 y.iter()
8300 .map(|v| format!("{:.4}", v))
8301 .collect::<Vec<_>>()
8302 .join(", ")
8303 );
8304 }
8305 _ => {
8306 let _ = writeln!(out, " ERROR: use filter h1,h2,... ; x1,x2,...");
8307 }
8308 }
8309 } else {
8310 let _ = writeln!(out, " ERROR: use filter h1,h2,... ; x1,x2,...");
8311 }
8312 }
8313 else if lower.starts_with("stats ") || lower.starts_with("info ") || lower.starts_with("rms ")
8315 {
8316 let rest = q[q.find(' ').unwrap_or(0)..].trim();
8317 match parse_signal(rest) {
8318 None => {
8319 let _ = writeln!(out, " ERROR: no numeric values.");
8320 }
8321 Some(sig) => {
8322 let n = sig.len();
8323 let (mean, rms, mn, mx) = signal_stats(&sig);
8324 let variance = sig.iter().map(|x| (x - mean) * (x - mean)).sum::<f64>() / n as f64;
8325 let std_dev = variance.sqrt();
8326 let energy: f64 = sig.iter().map(|x| x * x).sum();
8327 let _ = writeln!(out, " Signal Statistics ({} samples)", n);
8328 let _ = writeln!(out, " Mean: {:>12.6}", mean);
8329 let _ = writeln!(out, " RMS: {:>12.6}", rms);
8330 let _ = writeln!(out, " Std dev: {:>12.6}", std_dev);
8331 let _ = writeln!(out, " Min: {:>12.6}", mn);
8332 let _ = writeln!(out, " Max: {:>12.6}", mx);
8333 let _ = writeln!(out, " Range: {:>12.6}", mx - mn);
8334 let _ = writeln!(out, " Energy: {:>12.6}", energy);
8335 let _ = writeln!(out, " Power: {:>12.6}", energy / n as f64);
8336 if rms > 1e-12 {
8337 let crest = mx.abs().max(mn.abs()) / rms;
8338 let _ = writeln!(
8339 out,
8340 " Crest: {:>12.6} ({:.2} dB)",
8341 crest,
8342 20.0 * crest.log10()
8343 );
8344 }
8345 let _ = writeln!(out);
8346 let _ = writeln!(out, " Waveform ({}×8):", sig.len().min(60));
8347 let wave = ascii_waveform(&sig, sig.len().min(60), 8);
8348 for line in wave.lines() {
8349 let _ = writeln!(out, " |{}|", line);
8350 }
8351 }
8352 }
8353 }
8354 else if lower.starts_with("gen ")
8356 || lower.starts_with("wave ")
8357 || lower.starts_with("generate ")
8358 {
8359 let rest = q[q.find(' ').unwrap_or(0)..].trim();
8360 let parts: Vec<&str> = rest.splitn(4, ' ').collect();
8361 let shape = parts
8362 .first()
8363 .map(|s| s.to_lowercase())
8364 .unwrap_or_else(|| "sine".into());
8365 let freq: f64 = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(1.0);
8366 let n: usize = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(64);
8367 let amp: f64 = parts.get(3).and_then(|s| s.parse().ok()).unwrap_or(1.0);
8368 let sig: Vec<f64> = (0..n)
8369 .map(|i| {
8370 let t = i as f64 / n as f64;
8371 let phase = 2.0 * PI * freq * t;
8372 match shape.as_str() {
8373 "cos" | "cosine" => amp * phase.cos(),
8374 "square" => amp * if phase.sin() >= 0.0 { 1.0 } else { -1.0 },
8375 "sawtooth" | "saw" => amp * (2.0 * (freq * t - (freq * t + 0.5).floor())),
8376 "triangle" | "tri" => {
8377 amp * 2.0 * (2.0 * (freq * t - (freq * t + 0.5).floor())).abs() - 1.0
8378 }
8379 "noise" | "rand" => {
8380 amp * (((i * 6364136223846793005 + 1442695040888963407) >> 33) as f64
8381 / u32::MAX as f64
8382 * 2.0
8383 - 1.0)
8384 }
8385 _ => amp * phase.sin(),
8386 }
8387 })
8388 .collect();
8389 let (mean, rms, mn, mx) = signal_stats(&sig);
8390 let _ = writeln!(
8391 out,
8392 " Waveform: {} freq={} cycles n={} amp={}",
8393 shape, freq, n, amp
8394 );
8395 let _ = writeln!(
8396 out,
8397 " mean={:.4} RMS={:.4} min={:.4} max={:.4}",
8398 mean, rms, mn, mx
8399 );
8400 let _ = writeln!(out);
8401 let wave = ascii_waveform(&sig, sig.len().min(60), 8);
8402 for line in wave.lines() {
8403 let _ = writeln!(out, " |{}|", line);
8404 }
8405 let _ = writeln!(out);
8406 let first = sig
8407 .iter()
8408 .take(16)
8409 .map(|v| format!("{:.4}", v))
8410 .collect::<Vec<_>>()
8411 .join(", ");
8412 let _ = writeln!(
8413 out,
8414 " First 16 samples: {}{}",
8415 first,
8416 if n > 16 { " …" } else { "" }
8417 );
8418 }
8419 else if lower.starts_with("window ") {
8421 let parts: Vec<&str> = q.splitn(3, ' ').collect();
8422 let win_type = parts
8423 .get(1)
8424 .map(|s| s.to_lowercase())
8425 .unwrap_or_else(|| "hann".into());
8426 let n: usize = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(32);
8427 let w = match win_type.as_str() {
8428 "hamming" => hamming_window(n),
8429 "blackman" => blackman_window(n),
8430 _ => hann_window(n),
8431 };
8432 let (mean, rms, mn, mx) = signal_stats(&w);
8433 let _ = writeln!(out, " {} window n={}", win_type, n);
8434 let _ = writeln!(
8435 out,
8436 " mean={:.4} RMS={:.4} min={:.4} max={:.4}",
8437 mean, rms, mn, mx
8438 );
8439 let _ = writeln!(out);
8440 let wave = ascii_waveform(&w, n.min(60), 6);
8441 for line in wave.lines() {
8442 let _ = writeln!(out, " |{}|", line);
8443 }
8444 let _ = writeln!(out);
8445 let first8 = w
8446 .iter()
8447 .take(8)
8448 .map(|v| format!("{:.4}", v))
8449 .collect::<Vec<_>>()
8450 .join(", ");
8451 let _ = writeln!(out, " First 8 coeffs: {}", first8);
8452 }
8453 else {
8455 let _ = writeln!(out, "{}", signal_usage());
8456 }
8457
8458 let _ = writeln!(out, "{}", sep);
8459 out
8460}
8461
8462fn signal_usage() -> String {
8463 "Signal processing (DSP) — no model, no cloud:\n\
8464 \n\
8465 hematite --signal 'dft 1,0,-1,0,1,0,-1,0' Discrete Fourier Transform\n\
8466 hematite --signal 'idft 4,0,0,0 ; 0,0,0,0' Inverse DFT (re,im pairs)\n\
8467 hematite --signal 'conv 1,2,3 ; 1,-1' Convolution (separate with ;)\n\
8468 hematite --signal 'xcorr 1,0,1 ; 0,1,0' Cross-correlation\n\
8469 hematite --signal 'movavg 3 1,3,5,7,5,3,1' 3-point moving average\n\
8470 hematite --signal 'lowpass 0.1 31 hamming' FIR low-pass (cutoff taps window)\n\
8471 hematite --signal 'highpass 0.3 21 hann' FIR high-pass\n\
8472 hematite --signal 'filter 0.25,0.5,0.25 ; 1,2,3,4' Apply FIR filter to signal\n\
8473 hematite --signal 'stats 1,2,3,4,5' RMS, energy, crest, waveform plot\n\
8474 hematite --signal 'gen sine 2 64' Generate 64-pt sine, 2 cycles\n\
8475 hematite --signal 'gen square 1 32 2.5' Square wave, amp=2.5\n\
8476 hematite --signal 'gen sawtooth 3 128' Sawtooth wave\n\
8477 hematite --signal 'window hann 64' Preview Hann window coefficients\n\
8478 \n\
8479 Window types: rectangular hann hamming blackman\n\
8480 Wave shapes: sine cosine square sawtooth triangle noise"
8481 .into()
8482}
8483
8484fn interp_parse_points(s: &str) -> Option<Vec<(f64, f64)>> {
8489 let clean = s.replace(['(', ')'], "").replace(';', " ");
8491 let tokens: Vec<f64> = clean
8492 .split([',', ' ', '\t'].as_ref())
8493 .filter_map(|t| t.trim().parse::<f64>().ok())
8494 .collect();
8495 if tokens.len() < 4 || tokens.len() % 2 != 0 {
8496 return None;
8497 }
8498 Some(tokens.chunks(2).map(|c| (c[0], c[1])).collect())
8499}
8500
8501fn interp_linear(points: &[(f64, f64)], x: f64) -> f64 {
8502 let n = points.len();
8503 if n == 0 {
8504 return f64::NAN;
8505 }
8506 if n == 1 {
8507 return points[0].1;
8508 }
8509 if x <= points[0].0 {
8511 let (x0, y0) = points[0];
8512 let (x1, y1) = points[1];
8513 return y0 + (x - x0) * (y1 - y0) / (x1 - x0);
8514 }
8515 if x >= points[n - 1].0 {
8516 let (x0, y0) = points[n - 2];
8517 let (x1, y1) = points[n - 1];
8518 return y0 + (x - x0) * (y1 - y0) / (x1 - x0);
8519 }
8520 for i in 0..n - 1 {
8521 let (x0, y0) = points[i];
8522 let (x1, y1) = points[i + 1];
8523 if x >= x0 && x <= x1 {
8524 return y0 + (x - x0) * (y1 - y0) / (x1 - x0);
8525 }
8526 }
8527 f64::NAN
8528}
8529
8530fn interp_nearest(points: &[(f64, f64)], x: f64) -> f64 {
8531 points
8532 .iter()
8533 .min_by(|a, b| (a.0 - x).abs().partial_cmp(&(b.0 - x).abs()).unwrap())
8534 .map(|p| p.1)
8535 .unwrap_or(f64::NAN)
8536}
8537
8538fn interp_lagrange(points: &[(f64, f64)], x: f64) -> f64 {
8539 let n = points.len();
8540 (0..n)
8541 .map(|i| {
8542 let (xi, yi) = points[i];
8543 let li = (0..n).filter(|&j| j != i).fold(1.0_f64, |acc, j| {
8544 acc * (x - points[j].0) / (xi - points[j].0)
8545 });
8546 yi * li
8547 })
8548 .sum()
8549}
8550
8551fn interp_spline_build(points: &[(f64, f64)]) -> Vec<(f64, f64, f64, f64)> {
8553 let n = points.len();
8554 if n < 2 {
8555 return vec![];
8556 }
8557 let h: Vec<f64> = (0..n - 1).map(|i| points[i + 1].0 - points[i].0).collect();
8558 let mut alpha = vec![0.0_f64; n];
8559 for i in 1..n - 1 {
8560 alpha[i] = (3.0 / h[i]) * (points[i + 1].1 - points[i].1)
8561 - (3.0 / h[i - 1]) * (points[i].1 - points[i - 1].1);
8562 }
8563 let mut l = vec![1.0_f64; n];
8564 let mut mu = vec![0.0_f64; n];
8565 let mut z = vec![0.0_f64; n];
8566 for i in 1..n - 1 {
8567 l[i] = 2.0 * (points[i + 1].0 - points[i - 1].0) - h[i - 1] * mu[i - 1];
8568 mu[i] = h[i] / l[i];
8569 z[i] = (alpha[i] - h[i - 1] * z[i - 1]) / l[i];
8570 }
8571 let mut c = vec![0.0_f64; n];
8572 let mut b = vec![0.0_f64; n];
8573 let mut d = vec![0.0_f64; n];
8574 for j in (0..n - 1).rev() {
8575 c[j] = z[j] - mu[j] * c[j + 1];
8576 b[j] = (points[j + 1].1 - points[j].1) / h[j] - h[j] * (c[j + 1] + 2.0 * c[j]) / 3.0;
8577 d[j] = (c[j + 1] - c[j]) / (3.0 * h[j]);
8578 }
8579 (0..n - 1)
8580 .map(|i| (b[i], c[i], d[i], points[i].1))
8581 .collect()
8582}
8583
8584fn interp_spline_eval(points: &[(f64, f64)], coeffs: &[(f64, f64, f64, f64)], x: f64) -> f64 {
8585 let n = points.len();
8586 if n == 0 {
8587 return f64::NAN;
8588 }
8589 let i = if x <= points[0].0 {
8590 0
8591 } else if x >= points[n - 1].0 {
8592 n - 2
8593 } else {
8594 points[..n - 1]
8595 .iter()
8596 .enumerate()
8597 .find(|(i, p)| x >= p.0 && x <= points[i + 1].0)
8598 .map(|(i, _)| i)
8599 .unwrap_or(n - 2)
8600 };
8601 let i = i.min(coeffs.len().saturating_sub(1));
8602 let dx = x - points[i].0;
8603 let (b, c, d, a) = coeffs[i];
8604 a + b * dx + c * dx * dx + d * dx * dx * dx
8605}
8606
8607fn interp_ascii_curve(
8608 points: &[(f64, f64)],
8609 eval_fn: &dyn Fn(f64) -> f64,
8610 width: usize,
8611 height: usize,
8612) -> String {
8613 if points.is_empty() {
8614 return String::new();
8615 }
8616 let xs: Vec<f64> = points.iter().map(|p| p.0).collect();
8617 let ys: Vec<f64> = points.iter().map(|p| p.1).collect();
8618 let xmin = xs.iter().cloned().fold(f64::INFINITY, f64::min);
8619 let xmax = xs.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
8620 let step = (xmax - xmin) / (width - 1) as f64;
8621 let curve_y: Vec<f64> = (0..width)
8622 .map(|i| eval_fn(xmin + i as f64 * step))
8623 .collect();
8624 let ymin = curve_y
8625 .iter()
8626 .cloned()
8627 .chain(ys.iter().cloned())
8628 .fold(f64::INFINITY, f64::min);
8629 let ymax = curve_y
8630 .iter()
8631 .cloned()
8632 .chain(ys.iter().cloned())
8633 .fold(f64::NEG_INFINITY, f64::max);
8634 let yrange = (ymax - ymin).max(1e-12);
8635 let mut grid = vec![vec![' '; width]; height];
8636 for (col, &y) in curve_y.iter().enumerate() {
8637 let row = height - 1 - ((y - ymin) / yrange * (height - 1) as f64).round() as usize;
8638 let row = row.min(height - 1);
8639 grid[row][col] = '·';
8640 }
8641 for &(xp, yp) in points {
8642 let col = ((xp - xmin) / (xmax - xmin) * (width - 1) as f64).round() as usize;
8643 let row = height - 1 - ((yp - ymin) / yrange * (height - 1) as f64).round() as usize;
8644 let col = col.min(width - 1);
8645 let row = row.min(height - 1);
8646 grid[row][col] = '●';
8647 }
8648 let result = grid
8649 .iter()
8650 .map(|r| r.iter().collect::<String>())
8651 .collect::<Vec<_>>()
8652 .join("\n");
8653 format!(
8654 "{}\n y: [{:.4} .. {:.4}]\n x: [{:.4} .. {:.4}]\n ● = data point · = curve",
8655 result, ymin, ymax, xmin, xmax
8656 )
8657}
8658
8659pub fn interpolate_calc(query: &str) -> String {
8660 let mut out = String::new();
8661 let sep = "═".repeat(60);
8662 let _ = writeln!(out, "{}", sep);
8663 let _ = writeln!(out, " INTERPOLATION & CURVE FITTING");
8664 let _ = writeln!(out, "{}", sep);
8665
8666 let q = query.trim();
8667 let lower = q.to_lowercase();
8668
8669 let (method, rest) = if lower.starts_with("linear ") {
8671 ("linear", &q[7..])
8672 } else if lower.starts_with("spline ") || lower.starts_with("cubic ") {
8673 ("spline", &q[7..])
8674 } else if lower.starts_with("lagrange ") || lower.starts_with("poly ") {
8675 ("lagrange", &q[lower.find(' ').unwrap_or(0) + 1..])
8676 } else if lower.starts_with("nearest ") {
8677 ("nearest", &q[8..])
8678 } else {
8679 ("linear", q)
8680 };
8681
8682 let (pts_str, query_str) = if let Some(pos) = rest.to_lowercase().rfind(" at ") {
8684 (&rest[..pos], rest[pos + 4..].trim())
8685 } else {
8686 (rest, "")
8687 };
8688
8689 let mut points = match interp_parse_points(pts_str.trim()) {
8690 Some(p) => p,
8691 None => {
8692 let _ = writeln!(out, " ERROR: could not parse data points.");
8693 let _ = writeln!(out, " Format: x1,y1 x2,y2 x3,y3 ...");
8694 let _ = writeln!(out, "{}", sep);
8695 return out;
8696 }
8697 };
8698 points.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
8699
8700 let _ = writeln!(out, " Method: {} | {} data points", method, points.len());
8701 let _ = writeln!(
8702 out,
8703 " Points: {}",
8704 points
8705 .iter()
8706 .map(|(x, y)| format!("({:.4},{:.4})", x, y))
8707 .collect::<Vec<_>>()
8708 .join(" ")
8709 );
8710 let _ = writeln!(out);
8711
8712 let spline_coeffs = if method == "spline" {
8714 interp_spline_build(&points)
8715 } else {
8716 vec![]
8717 };
8718
8719 let eval_fn: Box<dyn Fn(f64) -> f64> = match method {
8720 "spline" => {
8721 let pts = points.clone();
8722 let sc = spline_coeffs.clone();
8723 Box::new(move |x| interp_spline_eval(&pts, &sc, x))
8724 }
8725 "lagrange" => {
8726 let pts = points.clone();
8727 Box::new(move |x| interp_lagrange(&pts, x))
8728 }
8729 "nearest" => {
8730 let pts = points.clone();
8731 Box::new(move |x| interp_nearest(&pts, x))
8732 }
8733 _ => {
8734 let pts = points.clone();
8735 Box::new(move |x| interp_linear(&pts, x))
8736 }
8737 };
8738
8739 if !query_str.is_empty() {
8741 let xs: Vec<f64> = query_str
8742 .split([',', ' '].as_ref())
8743 .filter_map(|s| s.trim().parse::<f64>().ok())
8744 .collect();
8745 if !xs.is_empty() {
8746 let _ = writeln!(out, " {:>12} {:>14}", "x", "y (interpolated)");
8747 let _ = writeln!(out, " {}", "-".repeat(28));
8748 for &x in &xs {
8749 let y = eval_fn(x);
8750 let xmin = points[0].0;
8751 let xmax = points[points.len() - 1].0;
8752 let tag = if x < xmin || x > xmax {
8753 " [extrapolated]"
8754 } else {
8755 ""
8756 };
8757 let _ = writeln!(out, " {:>12.6} {:>14.8}{}", x, y, tag);
8758 }
8759 let _ = writeln!(out);
8760 }
8761 }
8762
8763 let xmin = points[0].0;
8765 let xmax = points[points.len() - 1].0;
8766 let steps = 9_usize;
8767 let _ = writeln!(out, " Sampled curve ({} pts across range):", steps + 1);
8768 let _ = writeln!(out, " {:>10} {:>14}", "x", "y");
8769 let _ = writeln!(out, " {}", "-".repeat(26));
8770 for i in 0..=steps {
8771 let x = xmin + i as f64 * (xmax - xmin) / steps as f64;
8772 let y = eval_fn(x);
8773 let _ = writeln!(out, " {:>10.4} {:>14.8}", x, y);
8774 }
8775 let _ = writeln!(out);
8776
8777 let curve_str = interp_ascii_curve(&points, &eval_fn, 56, 10);
8779 for line in curve_str.lines() {
8780 let _ = writeln!(out, " {}", line);
8781 }
8782
8783 let _ = writeln!(out, "{}", sep);
8784 out
8785}
8786
8787struct UnitDef {
8792 names: &'static [&'static str],
8793 to_base: f64, }
8795
8796struct UnitCat {
8797 name: &'static str,
8798 base: &'static str,
8799 units: &'static [UnitDef],
8800}
8801
8802static UNIT_TABLE: &[UnitCat] = &[
8803 UnitCat {
8804 name: "Length",
8805 base: "m",
8806 units: &[
8807 UnitDef {
8808 names: &["m", "meter", "metre", "meters", "metres"],
8809 to_base: 1.0,
8810 },
8811 UnitDef {
8812 names: &["km", "kilometer", "kilometre", "kilometers", "kilometres"],
8813 to_base: 1e3,
8814 },
8815 UnitDef {
8816 names: &[
8817 "cm",
8818 "centimeter",
8819 "centimetre",
8820 "centimeters",
8821 "centimetres",
8822 ],
8823 to_base: 0.01,
8824 },
8825 UnitDef {
8826 names: &[
8827 "mm",
8828 "millimeter",
8829 "millimetre",
8830 "millimeters",
8831 "millimetres",
8832 ],
8833 to_base: 0.001,
8834 },
8835 UnitDef {
8836 names: &["um", "micrometer", "micrometre", "micron"],
8837 to_base: 1e-6,
8838 },
8839 UnitDef {
8840 names: &["nm", "nanometer", "nanometre"],
8841 to_base: 1e-9,
8842 },
8843 UnitDef {
8844 names: &["mi", "mile", "miles"],
8845 to_base: 1609.344,
8846 },
8847 UnitDef {
8848 names: &["yd", "yard", "yards"],
8849 to_base: 0.9144,
8850 },
8851 UnitDef {
8852 names: &["ft", "foot", "feet"],
8853 to_base: 0.3048,
8854 },
8855 UnitDef {
8856 names: &["in", "inch", "inches"],
8857 to_base: 0.0254,
8858 },
8859 UnitDef {
8860 names: &["nmi", "nautical_mile", "nm_sea"],
8861 to_base: 1852.0,
8862 },
8863 UnitDef {
8864 names: &["ly", "lightyear", "light_year"],
8865 to_base: 9.461e15,
8866 },
8867 UnitDef {
8868 names: &["au", "astronomical_unit"],
8869 to_base: 1.496e11,
8870 },
8871 UnitDef {
8872 names: &["pc", "parsec"],
8873 to_base: 3.086e16,
8874 },
8875 UnitDef {
8876 names: &["angstrom", "ang"],
8877 to_base: 1e-10,
8878 },
8879 ],
8880 },
8881 UnitCat {
8882 name: "Area",
8883 base: "m2",
8884 units: &[
8885 UnitDef {
8886 names: &["m2", "sqm", "square_meter", "square_metre"],
8887 to_base: 1.0,
8888 },
8889 UnitDef {
8890 names: &["km2", "sqkm", "square_kilometer"],
8891 to_base: 1e6,
8892 },
8893 UnitDef {
8894 names: &["cm2", "sqcm", "square_centimeter"],
8895 to_base: 1e-4,
8896 },
8897 UnitDef {
8898 names: &["mm2", "sqmm", "square_millimeter"],
8899 to_base: 1e-6,
8900 },
8901 UnitDef {
8902 names: &["ha", "hectare", "hectares"],
8903 to_base: 1e4,
8904 },
8905 UnitDef {
8906 names: &["acre", "acres"],
8907 to_base: 4046.856,
8908 },
8909 UnitDef {
8910 names: &["ft2", "sqft", "square_foot", "square_feet"],
8911 to_base: 0.09290304,
8912 },
8913 UnitDef {
8914 names: &["in2", "sqin", "square_inch"],
8915 to_base: 6.4516e-4,
8916 },
8917 UnitDef {
8918 names: &["mi2", "sqmi", "square_mile"],
8919 to_base: 2.58999e6,
8920 },
8921 UnitDef {
8922 names: &["yd2", "sqyd", "square_yard"],
8923 to_base: 0.83612736,
8924 },
8925 ],
8926 },
8927 UnitCat {
8928 name: "Volume",
8929 base: "m3",
8930 units: &[
8931 UnitDef {
8932 names: &["m3", "cbm", "cubic_meter", "cubic_metre"],
8933 to_base: 1.0,
8934 },
8935 UnitDef {
8936 names: &["l", "liter", "litre", "liters", "litres"],
8937 to_base: 0.001,
8938 },
8939 UnitDef {
8940 names: &[
8941 "ml",
8942 "milliliter",
8943 "millilitre",
8944 "milliliters",
8945 "millilitres",
8946 ],
8947 to_base: 1e-6,
8948 },
8949 UnitDef {
8950 names: &["cl", "centiliter", "centilitre"],
8951 to_base: 1e-5,
8952 },
8953 UnitDef {
8954 names: &["dl", "deciliter", "decilitre"],
8955 to_base: 1e-4,
8956 },
8957 UnitDef {
8958 names: &["gal", "gallon", "gallons"],
8959 to_base: 0.00378541,
8960 },
8961 UnitDef {
8962 names: &["qt", "quart", "quarts"],
8963 to_base: 9.46353e-4,
8964 },
8965 UnitDef {
8966 names: &["pt", "pint", "pints"],
8967 to_base: 4.73176e-4,
8968 },
8969 UnitDef {
8970 names: &["cup", "cups"],
8971 to_base: 2.36588e-4,
8972 },
8973 UnitDef {
8974 names: &["floz", "fl_oz", "fluid_ounce", "fluid_ounces"],
8975 to_base: 2.95735e-5,
8976 },
8977 UnitDef {
8978 names: &["tbsp", "tablespoon", "tablespoons"],
8979 to_base: 1.47868e-5,
8980 },
8981 UnitDef {
8982 names: &["tsp", "teaspoon", "teaspoons"],
8983 to_base: 4.92892e-6,
8984 },
8985 UnitDef {
8986 names: &["ft3", "cbft", "cubic_foot", "cubic_feet"],
8987 to_base: 0.0283168,
8988 },
8989 UnitDef {
8990 names: &["in3", "cbin", "cubic_inch"],
8991 to_base: 1.63871e-5,
8992 },
8993 UnitDef {
8994 names: &["bbl", "barrel", "barrels"],
8995 to_base: 0.158987,
8996 },
8997 ],
8998 },
8999 UnitCat {
9000 name: "Mass",
9001 base: "kg",
9002 units: &[
9003 UnitDef {
9004 names: &["kg", "kilogram", "kilograms", "kilo"],
9005 to_base: 1.0,
9006 },
9007 UnitDef {
9008 names: &["g", "gram", "grams"],
9009 to_base: 0.001,
9010 },
9011 UnitDef {
9012 names: &["mg", "milligram", "milligrams"],
9013 to_base: 1e-6,
9014 },
9015 UnitDef {
9016 names: &["ug", "microgram", "micrograms"],
9017 to_base: 1e-9,
9018 },
9019 UnitDef {
9020 names: &["t", "tonne", "metric_ton", "tonnes"],
9021 to_base: 1000.0,
9022 },
9023 UnitDef {
9024 names: &["lb", "pound", "pounds", "lbs"],
9025 to_base: 0.453592,
9026 },
9027 UnitDef {
9028 names: &["oz", "ounce", "ounces"],
9029 to_base: 0.0283495,
9030 },
9031 UnitDef {
9032 names: &["st", "stone", "stones"],
9033 to_base: 6.35029,
9034 },
9035 UnitDef {
9036 names: &["ton", "short_ton", "tons"],
9037 to_base: 907.185,
9038 },
9039 UnitDef {
9040 names: &["long_ton", "long_tons"],
9041 to_base: 1016.05,
9042 },
9043 UnitDef {
9044 names: &["ct", "carat", "carats"],
9045 to_base: 2e-4,
9046 },
9047 UnitDef {
9048 names: &["gr", "grain", "grains"],
9049 to_base: 6.47989e-5,
9050 },
9051 UnitDef {
9052 names: &["u", "amu", "dalton"],
9053 to_base: 1.66054e-27,
9054 },
9055 ],
9056 },
9057 UnitCat {
9058 name: "Time",
9059 base: "s",
9060 units: &[
9061 UnitDef {
9062 names: &["s", "sec", "second", "seconds"],
9063 to_base: 1.0,
9064 },
9065 UnitDef {
9066 names: &["ms", "millisecond", "milliseconds"],
9067 to_base: 0.001,
9068 },
9069 UnitDef {
9070 names: &["us", "microsecond", "microseconds"],
9071 to_base: 1e-6,
9072 },
9073 UnitDef {
9074 names: &["ns", "nanosecond", "nanoseconds"],
9075 to_base: 1e-9,
9076 },
9077 UnitDef {
9078 names: &["min", "minute", "minutes"],
9079 to_base: 60.0,
9080 },
9081 UnitDef {
9082 names: &["h", "hr", "hour", "hours"],
9083 to_base: 3600.0,
9084 },
9085 UnitDef {
9086 names: &["d", "day", "days"],
9087 to_base: 86400.0,
9088 },
9089 UnitDef {
9090 names: &["wk", "week", "weeks"],
9091 to_base: 604800.0,
9092 },
9093 UnitDef {
9094 names: &["mo", "month", "months"],
9095 to_base: 2.628e6,
9096 },
9097 UnitDef {
9098 names: &["yr", "year", "years"],
9099 to_base: 3.156e7,
9100 },
9101 UnitDef {
9102 names: &["decade", "decades"],
9103 to_base: 3.156e8,
9104 },
9105 UnitDef {
9106 names: &["century", "centuries"],
9107 to_base: 3.156e9,
9108 },
9109 ],
9110 },
9111 UnitCat {
9112 name: "Speed",
9113 base: "m/s",
9114 units: &[
9115 UnitDef {
9116 names: &["mps", "m/s", "meter_per_second"],
9117 to_base: 1.0,
9118 },
9119 UnitDef {
9120 names: &["kph", "km/h", "kmh", "kilometer_per_hour"],
9121 to_base: 1.0 / 3.6,
9122 },
9123 UnitDef {
9124 names: &["mph", "mi/h", "mile_per_hour"],
9125 to_base: 0.44704,
9126 },
9127 UnitDef {
9128 names: &["kn", "kt", "knot", "knots"],
9129 to_base: 0.514444,
9130 },
9131 UnitDef {
9132 names: &["fps", "ft/s", "foot_per_second"],
9133 to_base: 0.3048,
9134 },
9135 UnitDef {
9136 names: &["mach"],
9137 to_base: 343.0,
9138 },
9139 UnitDef {
9140 names: &["c", "speed_of_light"],
9141 to_base: 2.998e8,
9142 },
9143 ],
9144 },
9145 UnitCat {
9146 name: "Pressure",
9147 base: "Pa",
9148 units: &[
9149 UnitDef {
9150 names: &["pa", "pascal", "pascals"],
9151 to_base: 1.0,
9152 },
9153 UnitDef {
9154 names: &["kpa", "kilopascal", "kilopascals"],
9155 to_base: 1000.0,
9156 },
9157 UnitDef {
9158 names: &["mpa", "megapascal", "megapascals"],
9159 to_base: 1e6,
9160 },
9161 UnitDef {
9162 names: &["bar", "bars"],
9163 to_base: 1e5,
9164 },
9165 UnitDef {
9166 names: &["mbar", "millibar", "millibars"],
9167 to_base: 100.0,
9168 },
9169 UnitDef {
9170 names: &["atm", "atmosphere", "atmospheres"],
9171 to_base: 101325.0,
9172 },
9173 UnitDef {
9174 names: &["torr", "mmhg"],
9175 to_base: 133.322,
9176 },
9177 UnitDef {
9178 names: &["psi", "pound_per_square_inch"],
9179 to_base: 6894.76,
9180 },
9181 UnitDef {
9182 names: &["inhg", "in_hg", "inches_of_mercury"],
9183 to_base: 3386.39,
9184 },
9185 ],
9186 },
9187 UnitCat {
9188 name: "Energy",
9189 base: "J",
9190 units: &[
9191 UnitDef {
9192 names: &["j", "joule", "joules"],
9193 to_base: 1.0,
9194 },
9195 UnitDef {
9196 names: &["kj", "kilojoule", "kilojoules"],
9197 to_base: 1000.0,
9198 },
9199 UnitDef {
9200 names: &["mj", "megajoule", "megajoules"],
9201 to_base: 1e6,
9202 },
9203 UnitDef {
9204 names: &["gj", "gigajoule", "gigajoules"],
9205 to_base: 1e9,
9206 },
9207 UnitDef {
9208 names: &["cal", "calorie", "calories"],
9209 to_base: 4.184,
9210 },
9211 UnitDef {
9212 names: &["kcal", "kilocalorie", "kilocalories", "food_calorie"],
9213 to_base: 4184.0,
9214 },
9215 UnitDef {
9216 names: &["wh", "watt_hour", "watt_hours"],
9217 to_base: 3600.0,
9218 },
9219 UnitDef {
9220 names: &["kwh", "kilowatt_hour", "kilowatt_hours"],
9221 to_base: 3.6e6,
9222 },
9223 UnitDef {
9224 names: &["mwh", "megawatt_hour"],
9225 to_base: 3.6e9,
9226 },
9227 UnitDef {
9228 names: &["gwh", "gigawatt_hour"],
9229 to_base: 3.6e12,
9230 },
9231 UnitDef {
9232 names: &["btu", "british_thermal_unit"],
9233 to_base: 1055.06,
9234 },
9235 UnitDef {
9236 names: &["ev", "electronvolt", "electron_volt"],
9237 to_base: 1.602e-19,
9238 },
9239 UnitDef {
9240 names: &["ftlb", "ft_lb", "foot_pound"],
9241 to_base: 1.35582,
9242 },
9243 UnitDef {
9244 names: &["erg", "ergs"],
9245 to_base: 1e-7,
9246 },
9247 ],
9248 },
9249 UnitCat {
9250 name: "Power",
9251 base: "W",
9252 units: &[
9253 UnitDef {
9254 names: &["w", "watt", "watts"],
9255 to_base: 1.0,
9256 },
9257 UnitDef {
9258 names: &["kw", "kilowatt", "kilowatts"],
9259 to_base: 1000.0,
9260 },
9261 UnitDef {
9262 names: &["mw", "megawatt", "megawatts"],
9263 to_base: 1e6,
9264 },
9265 UnitDef {
9266 names: &["gw", "gigawatt", "gigawatts"],
9267 to_base: 1e9,
9268 },
9269 UnitDef {
9270 names: &["hp", "horsepower"],
9271 to_base: 745.7,
9272 },
9273 UnitDef {
9274 names: &["btu_h", "btu_per_hour"],
9275 to_base: 0.29307,
9276 },
9277 UnitDef {
9278 names: &["mw_th", "milliwatt"],
9279 to_base: 1e-3,
9280 },
9281 ],
9282 },
9283 UnitCat {
9284 name: "Digital Storage",
9285 base: "bytes",
9286 units: &[
9287 UnitDef {
9288 names: &["b", "bit", "bits"],
9289 to_base: 0.125,
9290 },
9291 UnitDef {
9292 names: &["byte", "bytes"],
9293 to_base: 1.0,
9294 },
9295 UnitDef {
9296 names: &["kb", "kilobyte", "kilobytes"],
9297 to_base: 1000.0,
9298 },
9299 UnitDef {
9300 names: &["mb", "megabyte", "megabytes"],
9301 to_base: 1e6,
9302 },
9303 UnitDef {
9304 names: &["gb", "gigabyte", "gigabytes"],
9305 to_base: 1e9,
9306 },
9307 UnitDef {
9308 names: &["tb", "terabyte", "terabytes"],
9309 to_base: 1e12,
9310 },
9311 UnitDef {
9312 names: &["pb", "petabyte", "petabytes"],
9313 to_base: 1e15,
9314 },
9315 UnitDef {
9316 names: &["kib", "kibibyte", "kibibytes"],
9317 to_base: 1024.0,
9318 },
9319 UnitDef {
9320 names: &["mib", "mebibyte", "mebibytes"],
9321 to_base: 1048576.0,
9322 },
9323 UnitDef {
9324 names: &["gib", "gibibyte", "gibibytes"],
9325 to_base: 1073741824.0,
9326 },
9327 UnitDef {
9328 names: &["tib", "tebibyte", "tebibytes"],
9329 to_base: 1.0995e12,
9330 },
9331 UnitDef {
9332 names: &["kbps", "kilobit_per_second"],
9333 to_base: 125.0,
9334 },
9335 UnitDef {
9336 names: &["mbps", "megabit_per_second"],
9337 to_base: 125000.0,
9338 },
9339 UnitDef {
9340 names: &["gbps", "gigabit_per_second"],
9341 to_base: 125000000.0,
9342 },
9343 ],
9344 },
9345 UnitCat {
9346 name: "Angle",
9347 base: "rad",
9348 units: &[
9349 UnitDef {
9350 names: &["rad", "radian", "radians"],
9351 to_base: 1.0,
9352 },
9353 UnitDef {
9354 names: &["deg", "degree", "degrees"],
9355 to_base: std::f64::consts::PI / 180.0,
9356 },
9357 UnitDef {
9358 names: &["grad", "gradian", "gradians", "gon"],
9359 to_base: std::f64::consts::PI / 200.0,
9360 },
9361 UnitDef {
9362 names: &["rev", "revolution", "turn", "turns"],
9363 to_base: 2.0 * std::f64::consts::PI,
9364 },
9365 UnitDef {
9366 names: &["arcmin", "arcminute", "arcminutes"],
9367 to_base: std::f64::consts::PI / 10800.0,
9368 },
9369 UnitDef {
9370 names: &["arcsec", "arcsecond", "arcseconds"],
9371 to_base: std::f64::consts::PI / 648000.0,
9372 },
9373 ],
9374 },
9375 UnitCat {
9376 name: "Force",
9377 base: "N",
9378 units: &[
9379 UnitDef {
9380 names: &["n", "newton", "newtons"],
9381 to_base: 1.0,
9382 },
9383 UnitDef {
9384 names: &["kn_f", "kilonewton", "kilonewtons"],
9385 to_base: 1000.0,
9386 },
9387 UnitDef {
9388 names: &["lbf", "pound_force", "pounds_force"],
9389 to_base: 4.44822,
9390 },
9391 UnitDef {
9392 names: &["kgf", "kilogram_force"],
9393 to_base: 9.80665,
9394 },
9395 UnitDef {
9396 names: &["dyn", "dyne", "dynes"],
9397 to_base: 1e-5,
9398 },
9399 UnitDef {
9400 names: &["gf", "gram_force"],
9401 to_base: 0.00980665,
9402 },
9403 ],
9404 },
9405 UnitCat {
9406 name: "Temperature",
9407 base: "K",
9408 units: &[
9409 UnitDef {
9410 names: &["k", "kelvin", "kelvins"],
9411 to_base: f64::NAN,
9412 },
9413 UnitDef {
9414 names: &["c", "celsius", "degc"],
9415 to_base: f64::NAN,
9416 },
9417 UnitDef {
9418 names: &["f", "fahrenheit", "degf"],
9419 to_base: f64::NAN,
9420 },
9421 UnitDef {
9422 names: &["r", "rankine", "degr"],
9423 to_base: f64::NAN,
9424 },
9425 ],
9426 },
9427 UnitCat {
9428 name: "Fuel Economy",
9429 base: "L/100km",
9430 units: &[
9431 UnitDef {
9432 names: &["l/100km", "lpkm", "liter_per_100km"],
9433 to_base: 1.0,
9434 },
9435 UnitDef {
9436 names: &["mpg", "mile_per_gallon", "miles_per_gallon"],
9437 to_base: f64::NAN,
9438 },
9439 UnitDef {
9440 names: &["km/l", "kpl", "kilometer_per_liter"],
9441 to_base: f64::NAN,
9442 },
9443 ],
9444 },
9445];
9446
9447fn units_to_base(val: f64, from_lower: &str) -> Option<(f64, usize)> {
9448 for (cat_idx, cat) in UNIT_TABLE.iter().enumerate() {
9449 for entry in cat.units {
9450 if entry.names.contains(&from_lower) {
9451 if cat.name == "Temperature" {
9452 let k = temp_to_kelvin(val, from_lower)?;
9453 return Some((k, cat_idx));
9454 }
9455 if cat.name == "Fuel Economy" {
9456 let l100 = fuel_to_l100(val, from_lower)?;
9457 return Some((l100, cat_idx));
9458 }
9459 return Some((val * entry.to_base, cat_idx));
9460 }
9461 }
9462 }
9463 None
9464}
9465
9466fn units_from_base(base_val: f64, to_lower: &str, cat_idx: usize) -> Option<f64> {
9467 let cat = &UNIT_TABLE[cat_idx];
9468 for entry in cat.units {
9469 if entry.names.contains(&to_lower) {
9470 if cat.name == "Temperature" {
9471 return kelvin_to_temp(base_val, to_lower);
9472 }
9473 if cat.name == "Fuel Economy" {
9474 return l100_to_fuel(base_val, to_lower);
9475 }
9476 return Some(base_val / entry.to_base);
9477 }
9478 }
9479 None
9480}
9481
9482fn temp_to_kelvin(val: f64, unit: &str) -> Option<f64> {
9483 match unit {
9484 "k" | "kelvin" | "kelvins" => Some(val),
9485 "c" | "celsius" | "degc" => Some(val + 273.15),
9486 "f" | "fahrenheit" | "degf" => Some((val + 459.67) * 5.0 / 9.0),
9487 "r" | "rankine" | "degr" => Some(val * 5.0 / 9.0),
9488 _ => None,
9489 }
9490}
9491
9492fn kelvin_to_temp(k: f64, unit: &str) -> Option<f64> {
9493 match unit {
9494 "k" | "kelvin" | "kelvins" => Some(k),
9495 "c" | "celsius" | "degc" => Some(k - 273.15),
9496 "f" | "fahrenheit" | "degf" => Some(k * 9.0 / 5.0 - 459.67),
9497 "r" | "rankine" | "degr" => Some(k * 9.0 / 5.0),
9498 _ => None,
9499 }
9500}
9501
9502fn fuel_to_l100(val: f64, unit: &str) -> Option<f64> {
9503 match unit {
9504 "l/100km" | "lpkm" | "liter_per_100km" => Some(val),
9505 "mpg" | "mile_per_gallon" | "miles_per_gallon" => Some(235.215 / val),
9506 "km/l" | "kpl" | "kilometer_per_liter" => Some(100.0 / val),
9507 _ => None,
9508 }
9509}
9510
9511fn l100_to_fuel(l100: f64, unit: &str) -> Option<f64> {
9512 match unit {
9513 "l/100km" | "lpkm" | "liter_per_100km" => Some(l100),
9514 "mpg" | "mile_per_gallon" | "miles_per_gallon" => Some(235.215 / l100),
9515 "km/l" | "kpl" | "kilometer_per_liter" => Some(100.0 / l100),
9516 _ => None,
9517 }
9518}
9519
9520fn find_unit_category(unit_lower: &str) -> Option<usize> {
9521 for (i, cat) in UNIT_TABLE.iter().enumerate() {
9522 for entry in cat.units {
9523 if entry.names.contains(&unit_lower) {
9524 return Some(i);
9525 }
9526 }
9527 }
9528 None
9529}
9530
9531fn fmt_unit_val(v: f64) -> String {
9532 if v == 0.0 {
9533 return "0".into();
9534 }
9535 let abs = v.abs();
9536 if !(1e-4..1e9).contains(&abs) {
9537 format!("{:.6e}", v)
9538 } else if abs >= 1000.0 {
9539 format!("{:.4}", v)
9540 } else {
9541 format!("{:.8}", v)
9542 }
9543}
9544
9545pub fn units_calc(query: &str) -> String {
9546 let mut out = String::new();
9547 let sep = "═".repeat(60);
9548 let _ = writeln!(out, "{}", sep);
9549 let _ = writeln!(out, " UNIT CONVERSION");
9550 let _ = writeln!(out, "{}", sep);
9551
9552 let q = query.trim();
9554 let tokens: Vec<&str> = q.split_whitespace().collect();
9555
9556 if tokens.is_empty() {
9557 let _ = writeln!(out, "{}", units_usage());
9558 let _ = writeln!(out, "{}", sep);
9559 return out;
9560 }
9561
9562 let val = match tokens[0].replace(',', "").parse::<f64>() {
9564 Ok(v) => v,
9565 Err(_) => {
9566 if tokens[0].to_lowercase() == "list" {
9568 let cat_query = tokens[1..].join(" ").to_lowercase();
9569 for cat in UNIT_TABLE {
9570 if cat_query.is_empty() || cat.name.to_lowercase().contains(&cat_query) {
9571 let _ = writeln!(out, " {} (base: {})", cat.name, cat.base);
9572 for entry in cat.units {
9573 let _ = writeln!(out, " {}", entry.names[0]);
9574 }
9575 let _ = writeln!(out);
9576 }
9577 }
9578 let _ = writeln!(out, "{}", sep);
9579 return out;
9580 }
9581 let _ = writeln!(
9582 out,
9583 " ERROR: first token must be a number. Got '{}'",
9584 tokens[0]
9585 );
9586 let _ = writeln!(out, "{}", units_usage());
9587 let _ = writeln!(out, "{}", sep);
9588 return out;
9589 }
9590 };
9591
9592 if tokens.len() < 2 {
9593 let _ = writeln!(out, " ERROR: need VALUE UNIT e.g. 5 km");
9594 let _ = writeln!(out, "{}", sep);
9595 return out;
9596 }
9597
9598 let from_raw = tokens[1].to_lowercase();
9599
9600 let connector_set = ["to", "in", "->", "as", "into"];
9602 let to_raw: Option<String> = tokens[2..]
9603 .iter()
9604 .find(|t| !connector_set.contains(&t.to_lowercase().as_str()))
9605 .map(|t| t.to_lowercase());
9606
9607 let (base_val, cat_idx) = match units_to_base(val, &from_raw) {
9609 Some(v) => v,
9610 None => {
9611 let _ = writeln!(out, " ERROR: unknown unit '{}'.", tokens[1]);
9612 let _ = writeln!(out, " Tip: run hematite --units list to see all units.");
9613 let _ = writeln!(out, "{}", sep);
9614 return out;
9615 }
9616 };
9617
9618 let cat = &UNIT_TABLE[cat_idx];
9619
9620 if let Some(to_unit) = to_raw {
9621 match units_from_base(base_val, &to_unit, cat_idx) {
9623 Some(result) => {
9624 let _ = writeln!(
9625 out,
9626 " {} {} = {} {}",
9627 val,
9628 tokens[1],
9629 fmt_unit_val(result),
9630 &to_unit
9631 );
9632 }
9633 None => {
9634 if let Some(other_idx) = find_unit_category(&to_unit) {
9636 let _ = writeln!(
9637 out,
9638 " ERROR: '{}' is a {} unit, but '{}' is {}.",
9639 &to_unit, UNIT_TABLE[other_idx].name, tokens[1], cat.name
9640 );
9641 } else {
9642 let _ = writeln!(out, " ERROR: unknown target unit '{}'.", &to_unit);
9643 }
9644 }
9645 }
9646 } else {
9647 let _ = writeln!(out, " {} {} =", val, tokens[1]);
9649 let _ = writeln!(out, " {:>30} unit", "value");
9650 let _ = writeln!(out, " {}", "-".repeat(42));
9651 for entry in cat.units {
9652 let to_name = entry.names[0];
9653 if to_name == from_raw {
9654 continue;
9655 }
9656 if let Some(result) = units_from_base(base_val, to_name, cat_idx) {
9657 let abs = result.abs();
9658 if abs > 0.0 && !(1e-30..=1e30).contains(&abs) {
9660 continue;
9661 }
9662 let _ = writeln!(out, " {:>30} {}", fmt_unit_val(result), to_name);
9663 }
9664 }
9665 }
9666
9667 let _ = writeln!(out, "{}", sep);
9668 out
9669}
9670
9671fn units_usage() -> &'static str {
9672 "Usage:\n\
9673 hematite --units '100 km to miles' convert single value\n\
9674 hematite --units '32 f to c' temperature: 32°F → °C\n\
9675 hematite --units '5 kg' broadcast: kg to all mass units\n\
9676 hematite --units '1 kwh to j' energy: kWh → Joules\n\
9677 hematite --units '1 gbps to mbps' digital storage speed\n\
9678 hematite --units '2.5 atm to psi' pressure\n\
9679 hematite --units 'list length' list all length units\n\
9680 hematite --units 'list' list all categories\n\
9681 \n\
9682 Categories: Length Area Volume Mass Time Speed Pressure Energy\n\
9683 Power Digital-Storage Angle Force Temperature Fuel-Economy"
9684}
9685
9686fn ode_eval_expr(expr_str: &str, t: f64, y: f64) -> f64 {
9694 let substituted = expr_str
9695 .replace("exp(", "\x00EXP\x00(")
9696 .replace('y', &format!("({:.17e})", y))
9697 .replace('t', &format!("({:.17e})", t))
9698 .replace("\x00EXP\x00(", "exp(");
9699 let expr = match parse_sym(&substituted) {
9700 Ok(e) => e,
9701 Err(_) => return f64::NAN,
9702 };
9703 eval_expr(&expr, "x", 0.0).unwrap_or(f64::NAN)
9704}
9705
9706fn rk4_step(f: &dyn Fn(f64, f64) -> f64, t: f64, y: f64, h: f64) -> f64 {
9708 let k1 = f(t, y);
9709 let k2 = f(t + h / 2.0, y + h * k1 / 2.0);
9710 let k3 = f(t + h / 2.0, y + h * k2 / 2.0);
9711 let k4 = f(t + h, y + h * k3);
9712 y + h * (k1 + 2.0 * k2 + 2.0 * k3 + k4) / 6.0
9713}
9714
9715fn euler_step(f: &dyn Fn(f64, f64) -> f64, t: f64, y: f64, h: f64) -> f64 {
9717 y + h * f(t, y)
9718}
9719
9720fn rk45_step(f: &dyn Fn(f64, f64) -> f64, t: f64, y: f64, h: f64) -> (f64, f64) {
9722 let k1 = f(t, y);
9724 let k2 = f(t + h / 5.0, y + h * (k1 / 5.0));
9725 let k3 = f(
9726 t + 3.0 * h / 10.0,
9727 y + h * (3.0 * k1 / 40.0 + 9.0 * k2 / 40.0),
9728 );
9729 let k4 = f(
9730 t + 4.0 * h / 5.0,
9731 y + h * (44.0 * k1 / 45.0 - 56.0 * k2 / 15.0 + 32.0 * k3 / 9.0),
9732 );
9733 let k5 = f(
9734 t + 8.0 * h / 9.0,
9735 y + h
9736 * (19372.0 * k1 / 6561.0 - 25360.0 * k2 / 2187.0 + 64448.0 * k3 / 6561.0
9737 - 212.0 * k4 / 729.0),
9738 );
9739 let k6 = f(
9740 t + h,
9741 y + h
9742 * (9017.0 * k1 / 3168.0 - 355.0 * k2 / 33.0
9743 + 46732.0 * k3 / 5247.0
9744 + 49.0 * k4 / 176.0
9745 - 5103.0 * k5 / 18656.0),
9746 );
9747 let y5 = y + h
9749 * (35.0 * k1 / 384.0 + 500.0 * k3 / 1113.0 + 125.0 * k4 / 192.0 - 2187.0 * k5 / 6784.0
9750 + 11.0 * k6 / 84.0);
9751 let y4 = y + h
9753 * (5179.0 * k1 / 57600.0 + 7571.0 * k3 / 16695.0 + 393.0 * k4 / 640.0
9754 - 92097.0 * k5 / 339200.0
9755 + 187.0 * k6 / 2100.0
9756 + k6 / 40.0);
9757 let err = (y5 - y4).abs();
9758 (y5, err)
9759}
9760
9761fn rk4_system_step(
9763 f1: &dyn Fn(f64, f64, f64) -> f64,
9764 f2: &dyn Fn(f64, f64, f64) -> f64,
9765 t: f64,
9766 y1: f64,
9767 y2: f64,
9768 h: f64,
9769) -> (f64, f64) {
9770 let k1a = f1(t, y1, y2);
9771 let k1b = f2(t, y1, y2);
9772 let k2a = f1(t + h / 2.0, y1 + h * k1a / 2.0, y2 + h * k1b / 2.0);
9773 let k2b = f2(t + h / 2.0, y1 + h * k1a / 2.0, y2 + h * k1b / 2.0);
9774 let k3a = f1(t + h / 2.0, y1 + h * k2a / 2.0, y2 + h * k2b / 2.0);
9775 let k3b = f2(t + h / 2.0, y1 + h * k2a / 2.0, y2 + h * k2b / 2.0);
9776 let k4a = f1(t + h, y1 + h * k3a, y2 + h * k3b);
9777 let k4b = f2(t + h, y1 + h * k3a, y2 + h * k3b);
9778 (
9779 y1 + h * (k1a + 2.0 * k2a + 2.0 * k3a + k4a) / 6.0,
9780 y2 + h * (k1b + 2.0 * k2b + 2.0 * k3b + k4b) / 6.0,
9781 )
9782}
9783
9784fn ode_ascii_plot(ts: &[f64], ys: &[f64], width: usize, height: usize) -> String {
9785 if ts.is_empty() || ys.is_empty() {
9786 return String::new();
9787 }
9788 let mn = ys.iter().cloned().fold(f64::INFINITY, f64::min);
9789 let mx = ys.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
9790 let yrange = (mx - mn).max(1e-12);
9791 let tmin = ts[0];
9792 let tmax = ts[ts.len() - 1];
9793 let trange = (tmax - tmin).max(1e-12);
9794 let mut grid = vec![vec![' '; width]; height];
9795 for (i, (&t, &y)) in ts.iter().zip(ys.iter()).enumerate() {
9796 let _ = i;
9797 let col = ((t - tmin) / trange * (width - 1) as f64).round() as usize;
9798 let row = height - 1 - ((y - mn) / yrange * (height - 1) as f64).round() as usize;
9799 let col = col.min(width - 1);
9800 let row = row.min(height - 1);
9801 grid[row][col] = '·';
9802 }
9803 let result = grid
9804 .iter()
9805 .map(|r| r.iter().collect::<String>())
9806 .collect::<Vec<_>>()
9807 .join("\n");
9808 format!(
9809 "{}\n y: [{:.4} .. {:.4}]\n t: [{:.4} .. {:.4}]",
9810 result, mn, mx, tmin, tmax
9811 )
9812}
9813
9814pub fn ode_solve(query: &str) -> String {
9815 let mut out = String::new();
9816 let sep = "═".repeat(60);
9817 let _ = writeln!(out, "{}", sep);
9818 let _ = writeln!(out, " ODE SOLVER");
9819 let _ = writeln!(out, "{}", sep);
9820
9821 let q = query.trim().to_lowercase();
9822
9823 if q.starts_with("logistic") || q.contains("logistic growth") {
9825 let tokens: Vec<&str> = query.split_whitespace().collect();
9827 let r: f64 = tokens
9828 .iter()
9829 .position(|&t| t.to_lowercase() == "r")
9830 .and_then(|i| tokens.get(i + 1)?.parse().ok())
9831 .unwrap_or(1.0);
9832 let k: f64 = tokens
9833 .iter()
9834 .position(|&t| t.to_lowercase() == "k")
9835 .and_then(|i| tokens.get(i + 1)?.parse().ok())
9836 .unwrap_or(100.0);
9837 let y0: f64 = tokens
9838 .iter()
9839 .position(|&t| t.to_lowercase() == "y0")
9840 .and_then(|i| tokens.get(i + 1)?.parse().ok())
9841 .unwrap_or(10.0);
9842 let t_end: f64 = tokens
9843 .iter()
9844 .position(|&t| t.to_lowercase() == "t")
9845 .and_then(|i| tokens.get(i + 1)?.parse().ok())
9846 .unwrap_or(10.0);
9847 let n = 200_usize;
9848 let h = t_end / n as f64;
9849 let _ = writeln!(out, " Logistic Growth: dy/dt = r·y·(1 - y/K)");
9850 let _ = writeln!(
9851 out,
9852 " r={:.4} K={:.4} y0={:.4} t=[0,{:.4}]",
9853 r, k, y0, t_end
9854 );
9855 let _ = writeln!(out);
9856 let f = |_t: f64, y: f64| r * y * (1.0 - y / k);
9857 let mut t = 0.0_f64;
9858 let mut y = y0;
9859 let mut ts = vec![t];
9860 let mut ys = vec![y];
9861 for _ in 0..n {
9862 y = rk4_step(&f, t, y, h);
9863 t += h;
9864 ts.push(t);
9865 ys.push(y);
9866 }
9867 let analytical_k = k / (1.0 + (k / y0 - 1.0) * (-r * t_end).exp());
9868 let _ = writeln!(
9869 out,
9870 " y(t_end) = {:.6} [analytical: {:.6}]",
9871 ys[ys.len() - 1],
9872 analytical_k
9873 );
9874 let plot = ode_ascii_plot(&ts, &ys, 56, 10);
9875 let _ = writeln!(out);
9876 for line in plot.lines() {
9877 let _ = writeln!(out, " {}", line);
9878 }
9879 let _ = writeln!(out, "{}", sep);
9880 return out;
9881 }
9882
9883 if q.starts_with("exponential") || q.starts_with("decay") || q.starts_with("growth") {
9884 let tokens: Vec<&str> = query.split_whitespace().collect();
9885 let r: f64 = tokens
9886 .iter()
9887 .position(|&t| t.to_lowercase() == "r")
9888 .and_then(|i| tokens.get(i + 1)?.parse().ok())
9889 .unwrap_or(1.0);
9890 let y0: f64 = tokens
9891 .iter()
9892 .position(|&t| t.to_lowercase() == "y0")
9893 .and_then(|i| tokens.get(i + 1)?.parse().ok())
9894 .unwrap_or(1.0);
9895 let t_end: f64 = tokens
9896 .iter()
9897 .position(|&t| t.to_lowercase() == "t")
9898 .and_then(|i| tokens.get(i + 1)?.parse().ok())
9899 .unwrap_or(5.0);
9900 let n = 200_usize;
9901 let h = t_end / n as f64;
9902 let _ = writeln!(out, " Exponential: dy/dt = r·y");
9903 let _ = writeln!(out, " r={:.4} y0={:.4} t=[0,{:.4}]", r, y0, t_end);
9904 let f = |_t: f64, y: f64| r * y;
9905 let mut t = 0.0_f64;
9906 let mut y = y0;
9907 let mut ts = vec![t];
9908 let mut ys = vec![y];
9909 for _ in 0..n {
9910 y = rk4_step(&f, t, y, h);
9911 t += h;
9912 ts.push(t);
9913 ys.push(y);
9914 }
9915 let analytical = y0 * (r * t_end).exp();
9916 let _ = writeln!(
9917 out,
9918 " y(t_end) = {:.6} [analytical: {:.6}]",
9919 ys[ys.len() - 1],
9920 analytical
9921 );
9922 let plot = ode_ascii_plot(&ts, &ys, 56, 10);
9923 let _ = writeln!(out);
9924 for line in plot.lines() {
9925 let _ = writeln!(out, " {}", line);
9926 }
9927 let _ = writeln!(out, "{}", sep);
9928 return out;
9929 }
9930
9931 if q.starts_with("lotka") || q.contains("predator") || q.contains("prey") {
9932 let tokens: Vec<&str> = query.split_whitespace().collect();
9934 let alpha: f64 = tokens
9935 .iter()
9936 .position(|&t| t.to_lowercase() == "alpha")
9937 .and_then(|i| tokens.get(i + 1)?.parse().ok())
9938 .unwrap_or(1.0);
9939 let beta: f64 = tokens
9940 .iter()
9941 .position(|&t| t.to_lowercase() == "beta")
9942 .and_then(|i| tokens.get(i + 1)?.parse().ok())
9943 .unwrap_or(0.1);
9944 let delta: f64 = tokens
9945 .iter()
9946 .position(|&t| t.to_lowercase() == "delta")
9947 .and_then(|i| tokens.get(i + 1)?.parse().ok())
9948 .unwrap_or(0.075);
9949 let gamma: f64 = tokens
9950 .iter()
9951 .position(|&t| t.to_lowercase() == "gamma")
9952 .and_then(|i| tokens.get(i + 1)?.parse().ok())
9953 .unwrap_or(1.5);
9954 let x0: f64 = tokens
9955 .iter()
9956 .position(|&t| t.to_lowercase() == "x0")
9957 .and_then(|i| tokens.get(i + 1)?.parse().ok())
9958 .unwrap_or(10.0);
9959 let y0: f64 = tokens
9960 .iter()
9961 .position(|&t| t.to_lowercase() == "y0")
9962 .and_then(|i| tokens.get(i + 1)?.parse().ok())
9963 .unwrap_or(5.0);
9964 let t_end: f64 = tokens
9965 .iter()
9966 .position(|&t| t.to_lowercase() == "t")
9967 .and_then(|i| tokens.get(i + 1)?.parse().ok())
9968 .unwrap_or(30.0);
9969 let n = 2000_usize;
9970 let h = t_end / n as f64;
9971 let _ = writeln!(out, " Lotka-Volterra (predator-prey)");
9972 let _ = writeln!(out, " dx/dt = {:.3}x - {:.3}xy (prey)", alpha, beta);
9973 let _ = writeln!(out, " dy/dt = {:.3}xy - {:.3}y (predator)", delta, gamma);
9974 let _ = writeln!(out, " x0={:.2} y0={:.2} t=[0,{:.1}]", x0, y0, t_end);
9975 let f1 = |_t: f64, x: f64, y: f64| alpha * x - beta * x * y;
9976 let f2 = |_t: f64, x: f64, y: f64| delta * x * y - gamma * y;
9977 let mut t = 0.0_f64;
9978 let mut x = x0;
9979 let mut y = y0;
9980 let mut ts = vec![t];
9981 let mut xs = vec![x];
9982 let mut ys = vec![y];
9983 for _ in 0..n {
9984 let (nx, ny) = rk4_system_step(&f1, &f2, t, x, y, h);
9985 x = nx;
9986 y = ny;
9987 t += h;
9988 ts.push(t);
9989 xs.push(x);
9990 ys.push(y);
9991 }
9992 let _ = writeln!(
9993 out,
9994 " x(t_end)={:.4} y(t_end)={:.4}",
9995 xs[xs.len() - 1],
9996 ys[ys.len() - 1]
9997 );
9998 let _ = writeln!(out, " Prey trajectory:");
9999 let plot = ode_ascii_plot(&ts, &xs, 56, 8);
10000 for line in plot.lines() {
10001 let _ = writeln!(out, " {}", line);
10002 }
10003 let _ = writeln!(out, " Predator trajectory:");
10004 let plot2 = ode_ascii_plot(&ts, &ys, 56, 8);
10005 for line in plot2.lines() {
10006 let _ = writeln!(out, " {}", line);
10007 }
10008 let _ = writeln!(out, "{}", sep);
10009 return out;
10010 }
10011
10012 if q.starts_with("sir") || q.contains("susceptible") || q.contains("epidemic") {
10013 let tokens: Vec<&str> = query.split_whitespace().collect();
10015 let n_pop: f64 = tokens
10016 .iter()
10017 .position(|&t| t.to_lowercase() == "n")
10018 .and_then(|i| tokens.get(i + 1)?.parse().ok())
10019 .unwrap_or(1000.0);
10020 let beta: f64 = tokens
10021 .iter()
10022 .position(|&t| t.to_lowercase() == "beta")
10023 .and_then(|i| tokens.get(i + 1)?.parse().ok())
10024 .unwrap_or(0.3);
10025 let gamma: f64 = tokens
10026 .iter()
10027 .position(|&t| t.to_lowercase() == "gamma")
10028 .and_then(|i| tokens.get(i + 1)?.parse().ok())
10029 .unwrap_or(0.05);
10030 let i0: f64 = tokens
10031 .iter()
10032 .position(|&t| t.to_lowercase() == "i0")
10033 .and_then(|i| tokens.get(i + 1)?.parse().ok())
10034 .unwrap_or(1.0);
10035 let t_end: f64 = tokens
10036 .iter()
10037 .position(|&t| t.to_lowercase() == "t")
10038 .and_then(|i| tokens.get(i + 1)?.parse().ok())
10039 .unwrap_or(200.0);
10040 let n_steps = 2000_usize;
10041 let h = t_end / n_steps as f64;
10042 let r0 = beta / gamma;
10043 let _ = writeln!(
10044 out,
10045 " SIR Epidemic Model (N={}, β={:.4}, γ={:.4}, R₀={:.2})",
10046 n_pop, beta, gamma, r0
10047 );
10048 let _ = writeln!(
10049 out,
10050 " dS/dt = -β·S·I/N dI/dt = β·S·I/N - γ·I dR/dt = γ·I"
10051 );
10052 let mut s = n_pop - i0;
10053 let mut inf = i0;
10054 let mut r = 0.0_f64;
10055 let mut t = 0.0_f64;
10056 let mut ts = vec![t];
10057 let mut is = vec![inf];
10058 let mut peak_i = i0;
10059 let mut peak_t = 0.0_f64;
10060 for _ in 0..n_steps {
10061 let ds = -beta * s * inf / n_pop;
10062 let di = beta * s * inf / n_pop - gamma * inf;
10063 let dr = gamma * inf;
10064 s += h * ds;
10065 inf += h * di;
10066 r += h * dr;
10067 t += h;
10068 if inf > peak_i {
10069 peak_i = inf;
10070 peak_t = t;
10071 }
10072 ts.push(t);
10073 is.push(inf);
10074 }
10075 let _ = writeln!(out, " Peak infected: {:.1} at t={:.1}", peak_i, peak_t);
10076 let _ = writeln!(out, " Final: S={:.1} I={:.1} R={:.1}", s, inf, r);
10077 let plot = ode_ascii_plot(&ts, &is, 56, 10);
10078 for line in plot.lines() {
10079 let _ = writeln!(out, " {}", line);
10080 }
10081 let _ = writeln!(out, "{}", sep);
10082 return out;
10083 }
10084
10085 let tokens: Vec<&str> = query.split_whitespace().collect();
10087
10088 let eq_str: String = if let Some(eq_pos) = query.find('=') {
10090 let before = &query[..eq_pos].trim().to_lowercase();
10093 if before.ends_with("dy/dt") || before.ends_with("f") || before.ends_with("dydt") {
10094 query[eq_pos + 1..]
10095 .split_whitespace()
10096 .take_while(|t| {
10097 !t.to_lowercase().starts_with("y0=")
10098 && !t.to_lowercase().starts_with("t=")
10099 && !t.to_lowercase().starts_with("n=")
10100 && !t.to_lowercase().starts_with("method=")
10101 })
10102 .collect::<Vec<_>>()
10103 .join("")
10104 } else {
10105 tokens[0].to_string()
10106 }
10107 } else {
10108 tokens[0].to_string()
10109 };
10110
10111 let get_param = |key: &str| -> Option<f64> {
10113 query
10114 .split_whitespace()
10115 .find(|t| t.to_lowercase().starts_with(&format!("{}=", key)))
10116 .and_then(|t| t.split_once('=')?.1.parse().ok())
10117 };
10118 let get_str_param = |key: &str| -> Option<String> {
10119 query
10120 .split_whitespace()
10121 .find(|t| t.to_lowercase().starts_with(&format!("{}=", key)))
10122 .and_then(|t| t.split_once('=').map(|x| x.1).map(|s| s.to_lowercase()))
10123 };
10124
10125 let y0 = get_param("y0").unwrap_or(1.0);
10126 let t_end = get_param("t")
10127 .or_else(|| get_param("tend"))
10128 .or_else(|| get_param("t_end"))
10129 .unwrap_or(10.0);
10130 let n = get_param("n")
10131 .map(|v| v as usize)
10132 .unwrap_or(100)
10133 .clamp(4, 10000);
10134 let method = get_str_param("method").unwrap_or_else(|| "rk4".into());
10135
10136 if eq_str.is_empty() || eq_str == "0" {
10137 let _ = writeln!(out, "{}", ode_usage());
10138 let _ = writeln!(out, "{}", sep);
10139 return out;
10140 }
10141
10142 let eq = eq_str.clone();
10143 let f = move |t: f64, y: f64| ode_eval_expr(&eq, t, y);
10144 let h = t_end / n as f64;
10145
10146 let _ = writeln!(
10147 out,
10148 " dy/dt = {} y0={} t=[0,{}] n={} method={}",
10149 eq_str, y0, t_end, n, method
10150 );
10151 let _ = writeln!(out);
10152
10153 let (ts, ys): (Vec<f64>, Vec<f64>) = match method.as_str() {
10154 "euler" => {
10155 let mut t = 0.0_f64;
10156 let mut y = y0;
10157 let mut ts = vec![t];
10158 let mut ys = vec![y];
10159 for _ in 0..n {
10160 y = euler_step(&f, t, y, h);
10161 t += h;
10162 ts.push(t);
10163 ys.push(y);
10164 }
10165 (ts, ys)
10166 }
10167 "rk45" | "adaptive" => {
10168 let mut t = 0.0_f64;
10169 let mut y = y0;
10170 let mut ts = vec![t];
10171 let mut ys = vec![y];
10172 let mut h_cur = h;
10173 let tol = 1e-6_f64;
10174 let mut steps = 0_usize;
10175 while t < t_end && steps < 100_000 {
10176 let (y_new, err) = rk45_step(&f, t, y, h_cur);
10177 if err < tol || h_cur < 1e-10 {
10178 t += h_cur;
10179 y = y_new;
10180 ts.push(t);
10181 ys.push(y);
10182 steps += 1;
10183 }
10184 let scale = if err > 0.0 {
10185 0.9 * (tol / err).powf(0.2)
10186 } else {
10187 2.0
10188 };
10189 h_cur = (h_cur * scale.clamp(0.1, 5.0)).min(t_end - t + 1e-15);
10190 }
10191 (ts, ys)
10192 }
10193 _ => {
10194 let mut t = 0.0_f64;
10195 let mut y = y0;
10196 let mut ts = vec![t];
10197 let mut ys = vec![y];
10198 for _ in 0..n {
10199 y = rk4_step(&f, t, y, h);
10200 t += h;
10201 ts.push(t);
10202 ys.push(y);
10203 }
10204 (ts, ys)
10205 }
10206 };
10207
10208 let step = (ts.len() / 20).max(1);
10210 let _ = writeln!(out, " {:>10} {:>16}", "t", "y");
10211 let _ = writeln!(out, " {}", "-".repeat(28));
10212 for (i, (&t, &y)) in ts.iter().zip(ys.iter()).enumerate() {
10213 if i % step == 0 || i == ts.len() - 1 {
10214 let _ = writeln!(out, " {:>10.6} {:>16.10}", t, y);
10215 }
10216 }
10217 let _ = writeln!(out);
10218
10219 let plot = ode_ascii_plot(&ts, &ys, 56, 10);
10221 for line in plot.lines() {
10222 let _ = writeln!(out, " {}", line);
10223 }
10224
10225 let _ = writeln!(out, "{}", sep);
10226 out
10227}
10228
10229fn ode_usage() -> &'static str {
10230 "ODE solver — no model, no cloud:\n\
10231 \n\
10232 Generic:\n\
10233 hematite --ode 'dy/dt = -y y0=1 t=5' exponential decay\n\
10234 hematite --ode 'dy/dt = t*y y0=1 t=3 n=50' custom ODE\n\
10235 hematite --ode 'dy/dt = sin(t)-y y0=0 t=20 method=rk45' adaptive\n\
10236 hematite --ode 'dy/dt = r*y y0=0.5 t=4 method=euler'\n\
10237 \n\
10238 Preset models:\n\
10239 hematite --ode 'exponential r=0.5 y0=1 t=5'\n\
10240 hematite --ode 'logistic r=1 K=100 y0=5 t=10'\n\
10241 hematite --ode 'lotka alpha=1 beta=0.1 delta=0.075 gamma=1.5 x0=10 y0=5 t=30'\n\
10242 hematite --ode 'sir N=10000 beta=0.3 gamma=0.05 i0=1 t=200'\n\
10243 \n\
10244 Methods: euler rk4 (default) rk45 (adaptive)\n\
10245 Params: y0=INITIAL t=TEND n=STEPS method=NAME"
10246}
10247
10248fn opt_eval(expr_str: &str, x: f64, y: f64) -> f64 {
10253 let s = expr_str
10254 .replace("exp(", "\x00E\x00(")
10255 .replace('y', &format!("({:.17e})", y))
10256 .replace('x', &format!("({:.17e})", x))
10257 .replace("\x00E\x00(", "exp(");
10258 match parse_sym(&s) {
10259 Ok(e) => eval_expr(&e, "z", 0.0).unwrap_or(f64::NAN),
10260 Err(_) => f64::NAN,
10261 }
10262}
10263
10264fn golden_section(
10266 f: &dyn Fn(f64) -> f64,
10267 mut a: f64,
10268 mut b: f64,
10269 tol: f64,
10270 max_iter: usize,
10271) -> (f64, f64, usize) {
10272 let phi = (5.0_f64.sqrt() - 1.0) / 2.0;
10273 let mut c = b - phi * (b - a);
10274 let mut d = a + phi * (b - a);
10275 let mut fc = f(c);
10276 let mut fd = f(d);
10277 let mut iters = 0;
10278 while (b - a).abs() > tol && iters < max_iter {
10279 if fc < fd {
10280 b = d;
10281 d = c;
10282 fd = fc;
10283 c = b - phi * (b - a);
10284 fc = f(c);
10285 } else {
10286 a = c;
10287 c = d;
10288 fc = fd;
10289 d = a + phi * (b - a);
10290 fd = f(d);
10291 }
10292 iters += 1;
10293 }
10294 let x_min = (a + b) / 2.0;
10295 (x_min, f(x_min), iters)
10296}
10297
10298fn grad(f: &dyn Fn(&[f64]) -> f64, x: &[f64], h: f64) -> Vec<f64> {
10300 x.iter()
10301 .enumerate()
10302 .map(|(i, _)| {
10303 let mut xp = x.to_vec();
10304 xp[i] += h;
10305 let mut xm = x.to_vec();
10306 xm[i] -= h;
10307 (f(&xp) - f(&xm)) / (2.0 * h)
10308 })
10309 .collect()
10310}
10311
10312fn gradient_descent(
10314 f: &dyn Fn(&[f64]) -> f64,
10315 x0: &[f64],
10316 max_iter: usize,
10317 tol: f64,
10318) -> (Vec<f64>, f64, usize) {
10319 let mut x = x0.to_vec();
10320 let mut iters = 0;
10321 let mut alpha = 0.1_f64;
10322 for _ in 0..max_iter {
10323 let g = grad(f, &x, 1e-6);
10324 let gnorm = g.iter().map(|v| v * v).sum::<f64>().sqrt();
10325 if gnorm < tol {
10326 break;
10327 }
10328 let fx = f(&x);
10330 let mut step = alpha;
10331 let mut x_new = x
10332 .iter()
10333 .zip(g.iter())
10334 .map(|(&xi, &gi)| xi - step * gi)
10335 .collect::<Vec<_>>();
10336 let mut tries = 0;
10337 while f(&x_new) > fx - 0.5 * step * gnorm * gnorm && tries < 30 {
10338 step *= 0.5;
10339 tries += 1;
10340 x_new = x
10341 .iter()
10342 .zip(g.iter())
10343 .map(|(&xi, &gi)| xi - step * gi)
10344 .collect();
10345 }
10346 alpha = step;
10347 x = x_new;
10348 iters += 1;
10349 }
10350 let fval = f(&x);
10351 (x, fval, iters)
10352}
10353
10354fn nelder_mead(
10356 f: &dyn Fn(f64, f64) -> f64,
10357 x0: f64,
10358 y0: f64,
10359 max_iter: usize,
10360 tol: f64,
10361) -> (f64, f64, f64, usize) {
10362 let g = |p: &[f64; 2]| f(p[0], p[1]);
10363 let mut s = [[x0, y0], [x0 + 1.0, y0], [x0, y0 + 1.0]];
10364 let mut iters = 0;
10365 loop {
10366 let mut fs: [(f64, usize); 3] = [(g(&s[0]), 0), (g(&s[1]), 1), (g(&s[2]), 2)];
10368 fs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
10369 let (_, i_best) = fs[0];
10370 let (_, i_worst) = fs[2];
10371 let (_, i_2nd) = fs[1];
10372 let spread = (fs[2].0 - fs[0].0).abs();
10374 if spread < tol || iters >= max_iter {
10375 break;
10376 }
10377 let cx = (s[i_best][0] + s[i_2nd][0]) / 2.0;
10379 let cy = (s[i_best][1] + s[i_2nd][1]) / 2.0;
10380 let rx = 2.0 * cx - s[i_worst][0];
10382 let ry = 2.0 * cy - s[i_worst][1];
10383 let fr = g(&[rx, ry]);
10384 if fr < fs[0].0 {
10385 let ex = 3.0 * cx - 2.0 * s[i_worst][0];
10387 let ey = 3.0 * cy - 2.0 * s[i_worst][1];
10388 if g(&[ex, ey]) < fr {
10389 s[i_worst] = [ex, ey];
10390 } else {
10391 s[i_worst] = [rx, ry];
10392 }
10393 } else if fr < fs[1].0 {
10394 s[i_worst] = [rx, ry];
10395 } else {
10396 let kx = 0.5 * (cx + s[i_worst][0]);
10398 let ky = 0.5 * (cy + s[i_worst][1]);
10399 if g(&[kx, ky]) < fs[2].0 {
10400 s[i_worst] = [kx, ky];
10401 } else {
10402 for j in 1..3 {
10404 let idx = fs[j].1;
10405 s[idx] = [
10406 (s[i_best][0] + s[idx][0]) / 2.0,
10407 (s[i_best][1] + s[idx][1]) / 2.0,
10408 ];
10409 }
10410 }
10411 }
10412 iters += 1;
10413 }
10414 let mut fs: [(f64, usize); 3] = [(g(&s[0]), 0), (g(&s[1]), 1), (g(&s[2]), 2)];
10415 fs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
10416 let best = s[fs[0].1];
10417 (best[0], best[1], fs[0].0, iters)
10418}
10419
10420pub fn optimize_calc(query: &str) -> String {
10421 let mut out = String::new();
10422 let sep = "═".repeat(60);
10423 let _ = writeln!(out, "{}", sep);
10424 let _ = writeln!(out, " NUMERICAL OPTIMIZATION");
10425 let _ = writeln!(out, "{}", sep);
10426
10427 let tokens: Vec<&str> = query.split_whitespace().collect();
10428 if tokens.is_empty() {
10429 let _ = writeln!(out, "{}", optimize_usage());
10430 let _ = writeln!(out, "{}", sep);
10431 return out;
10432 }
10433
10434 let get_param = |key: &str| -> Option<f64> {
10435 query
10436 .split_whitespace()
10437 .find(|t| t.to_lowercase().starts_with(&format!("{}=", key)))
10438 .and_then(|t| t.split_once('=')?.1.parse().ok())
10439 };
10440 let mode = tokens[0].to_lowercase();
10441
10442 match mode.as_str() {
10443 "min" | "minimize" | "max" | "maximize" | "find-min" | "find-max" => {
10445 let maximize = mode == "max" || mode == "maximize" || mode == "find-max";
10446 let f_str = tokens.get(1).copied().unwrap_or("x^2");
10447 let a = get_param("a")
10448 .or_else(|| get_param("from"))
10449 .unwrap_or(-10.0);
10450 let b = get_param("b").or_else(|| get_param("to")).unwrap_or(10.0);
10451 let tol = get_param("tol").unwrap_or(1e-8);
10452 let max_iter = get_param("iter").map(|v| v as usize).unwrap_or(500);
10453
10454 let sign = if maximize { -1.0_f64 } else { 1.0_f64 };
10455 let f = |x: f64| sign * opt_eval(f_str, x, 0.0);
10456 let (x_opt, f_opt_signed, iters) = golden_section(&f, a, b, tol, max_iter);
10457 let f_opt = if maximize {
10458 -f_opt_signed
10459 } else {
10460 f_opt_signed
10461 };
10462
10463 let _ = writeln!(
10464 out,
10465 " {} f(x) = {} x ∈ [{}, {}]",
10466 if maximize { "Maximize" } else { "Minimize" },
10467 f_str,
10468 a,
10469 b
10470 );
10471 let _ = writeln!(out, " Converged in {} iterations (tol={:.2e})", iters, tol);
10472 let _ = writeln!(out);
10473 let _ = writeln!(out, " x* = {:.10} f(x*) = {:.10}", x_opt, f_opt);
10474 let _ = writeln!(out);
10475
10476 let n_plot = 56_usize;
10478 let ys: Vec<f64> = (0..n_plot)
10479 .map(|i| {
10480 let x = a + i as f64 * (b - a) / (n_plot - 1) as f64;
10481 opt_eval(f_str, x, 0.0)
10482 })
10483 .collect();
10484 let ymin = ys.iter().cloned().fold(f64::INFINITY, f64::min);
10485 let ymax = ys.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
10486 let yrange = (ymax - ymin).max(1e-12);
10487 let h = 10_usize;
10488 let mut grid = vec![vec![' '; n_plot]; h];
10489 for (col, &y) in ys.iter().enumerate() {
10490 let row = h - 1 - ((y - ymin) / yrange * (h - 1) as f64).round() as usize;
10491 let row = row.min(h - 1);
10492 grid[row][col] = '·';
10493 }
10494 let opt_col = ((x_opt - a) / (b - a) * (n_plot - 1) as f64).round() as usize;
10496 let opt_row = h - 1 - ((f_opt - ymin) / yrange * (h - 1) as f64).round() as usize;
10497 if opt_col < n_plot && opt_row < h {
10498 grid[opt_row][opt_col] = '★';
10499 }
10500 for row in &grid {
10501 let _ = writeln!(out, " |{}|", row.iter().collect::<String>());
10502 }
10503 let _ = writeln!(
10504 out,
10505 " f: [{:.4} .. {:.4}] x: [{:.4} .. {:.4}] ★=optimum",
10506 ymin, ymax, a, b
10507 );
10508 }
10509
10510 "min2" | "minimize2" | "max2" | "maximize2" | "simplex" => {
10512 let maximize = mode.starts_with("max");
10513 let f_str = tokens.get(1).copied().unwrap_or("x^2+y^2");
10514 let x0 = get_param("x0").unwrap_or(0.0);
10515 let y0 = get_param("y0").unwrap_or(0.0);
10516 let max_iter = get_param("iter").map(|v| v as usize).unwrap_or(1000);
10517 let tol = get_param("tol").unwrap_or(1e-8);
10518 let sign = if maximize { -1.0_f64 } else { 1.0_f64 };
10519 let f = |x: f64, y: f64| sign * opt_eval(f_str, x, y);
10520 let (xopt, yopt, fval_s, iters) = nelder_mead(&f, x0, y0, max_iter, tol);
10521 let fval = if maximize { -fval_s } else { fval_s };
10522 let _ = writeln!(
10523 out,
10524 " {} f(x,y) = {} start: ({}, {})",
10525 if maximize { "Maximize" } else { "Minimize" },
10526 f_str,
10527 x0,
10528 y0
10529 );
10530 let _ = writeln!(out, " Nelder-Mead simplex iterations={}", iters);
10531 let _ = writeln!(out);
10532 let _ = writeln!(out, " x* = {:.10}", xopt);
10533 let _ = writeln!(out, " y* = {:.10}", yopt);
10534 let _ = writeln!(out, " f(x*,y*) = {:.10}", fval);
10535 }
10536
10537 "gradient" | "gd" | "grad-descent" => {
10539 let f_str = tokens.get(1).copied().unwrap_or("x^2");
10540 let x0_str = tokens.get(2).copied().unwrap_or("0");
10541 let x0: Vec<f64> = x0_str.split(',').filter_map(|s| s.parse().ok()).collect();
10542 let x0 = if x0.is_empty() { vec![0.0] } else { x0 };
10543 let max_iter = get_param("iter").map(|v| v as usize).unwrap_or(2000);
10544 let tol = get_param("tol").unwrap_or(1e-7);
10545 let f = |v: &[f64]| opt_eval(f_str, v[0], v.get(1).copied().unwrap_or(0.0));
10546 let (x_opt, f_opt, iters) = gradient_descent(&f, &x0, max_iter, tol);
10547 let _ = writeln!(out, " Gradient Descent f = {} x0={:?}", f_str, x0);
10548 let _ = writeln!(out, " Iterations: {}", iters);
10549 let _ = writeln!(out);
10550 let _ = writeln!(
10551 out,
10552 " x* = {:?}",
10553 x_opt
10554 .iter()
10555 .map(|v| format!("{:.10}", v))
10556 .collect::<Vec<_>>()
10557 );
10558 let _ = writeln!(out, " f(x*) = {:.10}", f_opt);
10559 }
10560
10561 "root" | "find-root" | "zero" => {
10563 let f_str = tokens.get(1).copied().unwrap_or("x^2-2");
10564 let a = get_param("a")
10565 .or_else(|| get_param("from"))
10566 .unwrap_or(-10.0);
10567 let b = get_param("b").or_else(|| get_param("to")).unwrap_or(10.0);
10568 let tol = get_param("tol").unwrap_or(1e-10);
10569 let max_iter = get_param("iter").map(|v| v as usize).unwrap_or(200);
10570 let f = |x: f64| opt_eval(f_str, x, 0.0);
10571 let fa = f(a);
10572 let fb = f(b);
10573 if fa * fb > 0.0 {
10574 let _ = writeln!(out, " Root finding: f(x) = {} [{}, {}]", f_str, a, b);
10575 let _ = writeln!(
10576 out,
10577 " ERROR: f(a) and f(b) must have opposite signs for bisection."
10578 );
10579 let _ = writeln!(out, " f({}) = {:.6} f({}) = {:.6}", a, fa, b, fb);
10580 } else {
10581 let mut lo = a;
10582 let mut hi = b;
10583 let mut iters = 0;
10584 while (hi - lo).abs() > tol && iters < max_iter {
10585 let mid = (lo + hi) / 2.0;
10586 if f(lo) * f(mid) <= 0.0 {
10587 hi = mid;
10588 } else {
10589 lo = mid;
10590 }
10591 iters += 1;
10592 }
10593 let root = (lo + hi) / 2.0;
10594 let _ = writeln!(out, " Root finding: f(x) = {} [{}, {}]", f_str, a, b);
10595 let _ = writeln!(out, " Bisection: {} iterations (tol={:.2e})", iters, tol);
10596 let _ = writeln!(out);
10597 let _ = writeln!(out, " x* = {:.12} f(x*) = {:.3e}", root, f(root));
10598 }
10599 }
10600
10601 _ => {
10602 let _ = writeln!(out, "{}", optimize_usage());
10603 }
10604 }
10605
10606 let _ = writeln!(out, "{}", sep);
10607 out
10608}
10609
10610fn optimize_usage() -> &'static str {
10611 "Numerical optimization — no model, no cloud:\n\
10612 \n\
10613 hematite --optimize 'min x^2-4*x+3 a=0 b=5' minimize 1D\n\
10614 hematite --optimize 'max sin(x) a=0 b=6.28' maximize 1D\n\
10615 hematite --optimize 'min2 x^2+y^2 x0=3 y0=2' minimize 2D (Nelder-Mead)\n\
10616 hematite --optimize 'gradient x^4-4*x^2 0' gradient descent\n\
10617 hematite --optimize 'root x^3-2 a=0 b=2' find root (bisection)\n\
10618 hematite --optimize 'min (x-2)^2+(x-3)^2 a=-5 b=8' least-squares style\n\
10619 \n\
10620 Parameters: a= b= range bounds x0= y0= start point\n\
10621 tol= tolerance (default 1e-8)\n\
10622 iter= max iterations (default 500)\n\
10623 Expressions: x y t sin cos exp ln sqrt ^ + - * /"
10624}
10625
10626pub fn bitwise_calc(query: &str) -> String {
10629 let mut out = String::new();
10630 let sep = "─".repeat(60);
10631 let _ = writeln!(out, "{}", sep);
10632 let _ = writeln!(out, " BITWISE CALCULATOR");
10633 let _ = writeln!(out, "{}", sep);
10634
10635 let q = query.trim();
10636
10637 let parse_val = |s: &str| -> Option<u64> {
10639 let s = s.trim();
10640 if s.is_empty() {
10641 return None;
10642 }
10643 if let Some(h) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
10644 return u64::from_str_radix(h, 16).ok();
10645 }
10646 if let Some(b) = s.strip_prefix("0b").or_else(|| s.strip_prefix("0B")) {
10647 return u64::from_str_radix(b, 2).ok();
10648 }
10649 if let Some(o) = s.strip_prefix("0o").or_else(|| s.strip_prefix("0O")) {
10650 return u64::from_str_radix(o, 8).ok();
10651 }
10652 if s.starts_with('\'') && s.ends_with('\'') && s.len() >= 3 {
10653 let inner = &s[1..s.len() - 1];
10654 let mut chars = inner.chars();
10655 if let Some(c) = chars.next() {
10656 if chars.next().is_none() {
10657 return Some(c as u64);
10658 }
10659 }
10660 }
10661 s.parse::<u64>()
10662 .ok()
10663 .or_else(|| s.parse::<i64>().ok().map(|v| v as u64))
10664 };
10665
10666 let fmt_row = |out: &mut String, label: &str, v: u64| {
10667 let _ = writeln!(out, " {:<18} {:>20} (0x{:016X})", label, v as i64, v);
10668 };
10669
10670 let fmt_bin = |out: &mut String, label: &str, v: u64| {
10671 let b = format!("{:064b}", v);
10673 let groups: Vec<&str> = [&b[0..16], &b[16..32], &b[32..48], &b[48..64]].to_vec();
10674 let _ = writeln!(
10675 out,
10676 " {:<18} {}_{}_{}_{}",
10677 label, groups[0], groups[1], groups[2], groups[3]
10678 );
10679 };
10680
10681 if q.starts_with("ieee754 ") || q.starts_with("float ") {
10683 let raw = q.split_once(' ').map(|x| x.1).unwrap_or("").trim();
10684 let fval: f64 = match raw.parse::<f64>() {
10685 Ok(v) => v,
10686 Err(_) => {
10687 let _ = writeln!(out, " Cannot parse float: {}", raw);
10688 return out;
10689 }
10690 };
10691 let bits = fval.to_bits();
10692 let sign = (bits >> 63) & 1;
10693 let exp_raw = ((bits >> 52) & 0x7FF) as i64;
10694 let mantissa = bits & 0x000F_FFFF_FFFF_FFFF;
10695 let exp_actual = exp_raw - 1023;
10696
10697 let _ = writeln!(out, " Value: {}", fval);
10698 let _ = writeln!(out, " Bits: 0x{:016X}", bits);
10699 let b = format!("{:064b}", bits);
10700 let _ = writeln!(
10701 out,
10702 " Binary: {} {} {} {}",
10703 &b[0..1],
10704 &b[1..12],
10705 &b[12..32],
10706 &b[32..64]
10707 );
10708 let _ = writeln!(
10709 out,
10710 " Sign: {} ({})",
10711 sign,
10712 if sign == 0 { "positive" } else { "negative" }
10713 );
10714 let _ = writeln!(
10715 out,
10716 " Exponent: {:011b} raw={} actual={}",
10717 exp_raw, exp_raw, exp_actual
10718 );
10719 let _ = writeln!(out, " Mantissa: {:052b}", mantissa);
10720 let category = if exp_raw == 0x7FF {
10721 if mantissa == 0 {
10722 if sign == 0 {
10723 "+Infinity"
10724 } else {
10725 "-Infinity"
10726 }
10727 } else {
10728 "NaN"
10729 }
10730 } else if exp_raw == 0 {
10731 if mantissa == 0 {
10732 "Zero"
10733 } else {
10734 "Subnormal"
10735 }
10736 } else {
10737 "Normal"
10738 };
10739 let _ = writeln!(out, " Category: {}", category);
10740 let _ = writeln!(out, "{}", sep);
10741 return out;
10742 }
10743
10744 let words: Vec<&str> = q.split_whitespace().collect();
10746
10747 if words.is_empty() {
10748 let _ = writeln!(out, "{}", bitwise_usage());
10749 let _ = writeln!(out, "{}", sep);
10750 return out;
10751 }
10752
10753 if words.len() >= 3 {
10755 let a_str = words[0];
10756 let op = words[1];
10757 let b_str = words[2];
10758 let a = match parse_val(a_str) {
10759 Some(v) => v,
10760 None => {
10761 let _ = writeln!(out, " Cannot parse operand: {}", a_str);
10762 return out;
10763 }
10764 };
10765 match op.to_lowercase().as_str() {
10766 "and" | "&" => {
10767 let result = a & parse_val(b_str).unwrap_or(0);
10768 let _ = writeln!(out, " A (dec): {}", a as i64);
10769 let _ = writeln!(out, " B (dec): {}", parse_val(b_str).unwrap_or(0) as i64);
10770 let b_val = parse_val(b_str).unwrap_or(0);
10771 fmt_bin(&mut out, " A binary:", a);
10772 fmt_bin(&mut out, " B binary:", b_val);
10773 fmt_bin(&mut out, " AND: ", result);
10774 fmt_row(&mut out, " Result:", result);
10775 let _ = writeln!(out, "{}", sep);
10776 return out;
10777 }
10778 "or" | "|" => {
10779 let b_val = parse_val(b_str).unwrap_or(0);
10780 let result = a | b_val;
10781 fmt_bin(&mut out, " A binary:", a);
10782 fmt_bin(&mut out, " B binary:", b_val);
10783 fmt_bin(&mut out, " OR: ", result);
10784 fmt_row(&mut out, " Result:", result);
10785 let _ = writeln!(out, "{}", sep);
10786 return out;
10787 }
10788 "xor" | "^" => {
10789 let b_val = parse_val(b_str).unwrap_or(0);
10790 let result = a ^ b_val;
10791 fmt_bin(&mut out, " A binary:", a);
10792 fmt_bin(&mut out, " B binary:", b_val);
10793 fmt_bin(&mut out, " XOR: ", result);
10794 fmt_row(&mut out, " Result:", result);
10795 let _ = writeln!(out, "{}", sep);
10796 return out;
10797 }
10798 "shl" | "<<" | "lsh" => {
10799 let n = parse_val(b_str).unwrap_or(0) as u32;
10800 let result = a.checked_shl(n).unwrap_or(0);
10801 fmt_bin(&mut out, " Before: ", a);
10802 fmt_bin(&mut out, " SHL by:", result);
10803 let _ = writeln!(out, " Shift by: {}", n);
10804 fmt_row(&mut out, " Result:", result);
10805 let _ = writeln!(out, "{}", sep);
10806 return out;
10807 }
10808 "shr" | ">>" | "rsh" => {
10809 let n = parse_val(b_str).unwrap_or(0) as u32;
10810 let result = a.checked_shr(n).unwrap_or(0);
10811 fmt_bin(&mut out, " Before: ", a);
10812 fmt_bin(&mut out, " SHR by:", result);
10813 let _ = writeln!(out, " Shift by: {}", n);
10814 fmt_row(&mut out, " Result:", result);
10815 let _ = writeln!(out, "{}", sep);
10816 return out;
10817 }
10818 "rol" | "rotl" => {
10819 let n = (parse_val(b_str).unwrap_or(0) % 64) as u32;
10820 let result = a.rotate_left(n);
10821 fmt_bin(&mut out, " Before: ", a);
10822 fmt_bin(&mut out, " ROL: ", result);
10823 let _ = writeln!(out, " Rotate by: {}", n);
10824 fmt_row(&mut out, " Result:", result);
10825 let _ = writeln!(out, "{}", sep);
10826 return out;
10827 }
10828 "ror" | "rotr" => {
10829 let n = (parse_val(b_str).unwrap_or(0) % 64) as u32;
10830 let result = a.rotate_right(n);
10831 fmt_bin(&mut out, " Before: ", a);
10832 fmt_bin(&mut out, " ROR: ", result);
10833 let _ = writeln!(out, " Rotate by: {}", n);
10834 fmt_row(&mut out, " Result:", result);
10835 let _ = writeln!(out, "{}", sep);
10836 return out;
10837 }
10838 _ => {}
10839 }
10840 }
10841
10842 let val_str = if words.len() == 2 && words[0].to_lowercase() == "not" {
10844 words[1]
10845 } else {
10846 words[0]
10847 };
10848 let is_not = words.len() == 2 && words[0].to_lowercase() == "not";
10849
10850 let val = match parse_val(val_str) {
10851 Some(v) => v,
10852 None => {
10853 let _ = writeln!(out, "{}", bitwise_usage());
10854 let _ = writeln!(out, "{}", sep);
10855 return out;
10856 }
10857 };
10858 let display_val = if is_not { !val } else { val };
10859 let label = if is_not { "NOT result" } else { "Value" };
10860
10861 let popcount = display_val.count_ones();
10862 let parity = popcount % 2;
10863 let leading = display_val.leading_zeros();
10864 let trailing = display_val.trailing_zeros();
10865 let msb_pos = if display_val == 0 { 0u32 } else { 63 - leading };
10866
10867 let _ = writeln!(out, " {} (input): {}", label, val as i64);
10868 if is_not {
10869 let _ = writeln!(out, " NOT {:016X} = {:016X}", val, !val);
10870 }
10871 let _ = writeln!(out);
10872 let _ = writeln!(out, " ─── Representations ───");
10873 let _ = writeln!(out, " Decimal (signed): {}", display_val as i64);
10874 let _ = writeln!(out, " Decimal (unsigned): {}", display_val);
10875 let _ = writeln!(out, " Hexadecimal: 0x{:016X}", display_val);
10876 let _ = writeln!(out, " Octal: 0o{:o}", display_val);
10877
10878 let b = format!("{:064b}", display_val);
10880 let _ = writeln!(out, " Binary (64-bit):");
10881 let _ = writeln!(out, " Bit 63-48: {}_{}", &b[0..8], &b[8..16]);
10882 let _ = writeln!(out, " Bit 47-32: {}_{}", &b[16..24], &b[24..32]);
10883 let _ = writeln!(out, " Bit 31-16: {}_{}", &b[32..40], &b[40..48]);
10884 let _ = writeln!(out, " Bit 15- 0: {}_{}", &b[48..56], &b[56..64]);
10885
10886 let twos = (!display_val).wrapping_add(1);
10888 let _ = writeln!(out);
10889 let _ = writeln!(out, " ─── Bit Properties ───");
10890 let _ = writeln!(out, " Popcount (set bits): {}", popcount);
10891 let _ = writeln!(
10892 out,
10893 " Parity: {} ({})",
10894 parity,
10895 if parity == 0 { "even" } else { "odd" }
10896 );
10897 let _ = writeln!(out, " Leading zeros: {}", leading);
10898 let _ = writeln!(out, " Trailing zeros: {}", trailing);
10899 let _ = writeln!(out, " MSB position: {}", msb_pos);
10900 let _ = writeln!(
10901 out,
10902 " Two's complement: {} (0x{:016X})",
10903 twos as i64, twos
10904 );
10905 let is_pow2 = display_val != 0 && (display_val & display_val.wrapping_sub(1)) == 0;
10906 let _ = writeln!(
10907 out,
10908 " Power of 2: {}",
10909 if is_pow2 { "yes" } else { "no" }
10910 );
10911
10912 let _ = writeln!(out);
10914 let _ = writeln!(out, " ─── Byte Decomposition (little-endian) ───");
10915 for i in 0..8usize {
10916 let byte = ((display_val >> (i * 8)) & 0xFF) as u8;
10917 let _ = writeln!(
10918 out,
10919 " Byte {}: 0x{:02X} {:08b} dec={}",
10920 i, byte, byte, byte
10921 );
10922 }
10923
10924 let _ = writeln!(out, "{}", sep);
10925 out
10926}
10927
10928fn bitwise_usage() -> &'static str {
10929 "Bitwise calculator — no model, no cloud:\n\
10930 \n\
10931 hematite --bitwise '255' inspect value (dec/hex/bin/octal/bytes)\n\
10932 hematite --bitwise '0xFF' hex input\n\
10933 hematite --bitwise '0b11001010' binary input\n\
10934 hematite --bitwise 'NOT 0xFF' bitwise NOT\n\
10935 hematite --bitwise '0xF0 AND 0x3C' AND\n\
10936 hematite --bitwise '0xF0 OR 0x0F' OR\n\
10937 hematite --bitwise '0xAB XOR 0xFF' XOR\n\
10938 hematite --bitwise '1 SHL 7' left shift\n\
10939 hematite --bitwise '256 SHR 4' right shift\n\
10940 hematite --bitwise '0xDEAD ROL 4' rotate left\n\
10941 hematite --bitwise '0xDEAD ROR 4' rotate right\n\
10942 hematite --bitwise 'ieee754 3.14159' IEEE 754 float breakdown\n\
10943 hematite --bitwise 'ieee754 NaN' special float analysis\n\
10944 \n\
10945 Inputs: decimal, 0x hex, 0b binary, 0o octal, negative (-1)"
10946}
10947
10948pub fn set_calc(query: &str) -> String {
10951 let mut out = String::new();
10952 let sep = "─".repeat(60);
10953 let _ = writeln!(out, "{}", sep);
10954 let _ = writeln!(out, " SET THEORY CALCULATOR");
10955 let _ = writeln!(out, "{}", sep);
10956
10957 let q = query.trim();
10958
10959 let parse_set = |s: &str| -> Vec<String> {
10962 let s = s
10963 .trim()
10964 .trim_start_matches('{')
10965 .trim_end_matches('}')
10966 .trim_start_matches('[')
10967 .trim_end_matches(']');
10968 let mut items: Vec<String> = s
10969 .split(',')
10970 .map(|x| x.trim().to_string())
10971 .filter(|x| !x.is_empty())
10972 .collect();
10973 items.sort();
10974 items.dedup();
10975 items
10976 };
10977
10978 let fmt_set = |v: &[String]| -> String {
10979 if v.is_empty() {
10980 "{}".to_string()
10981 } else {
10982 format!("{{{}}}", v.join(", "))
10983 }
10984 };
10985
10986 let q_lower = q.to_lowercase();
10987
10988 if q_lower.starts_with("powerset ")
10990 || q_lower.starts_with("power_set ")
10991 || q_lower.starts_with("power set ")
10992 {
10993 let raw = q.split_once(' ').map(|x| x.1).unwrap_or("").trim();
10994 let set = parse_set(raw);
10995 if set.len() > 20 {
10996 let _ = writeln!(out, " Set too large for power set (max 20 elements).");
10997 let _ = writeln!(out, "{}", sep);
10998 return out;
10999 }
11000 let n = set.len();
11001 let count = 1usize << n;
11002 let _ = writeln!(out, " Set A = {}", fmt_set(&set));
11003 let _ = writeln!(out, " |A| = {} |P(A)| = {}", n, count);
11004 let _ = writeln!(out);
11005 let _ = writeln!(out, " Power set P(A):");
11006 let mut subsets: Vec<Vec<String>> = Vec::with_capacity(count);
11007 for mask in 0..count {
11008 let subset: Vec<String> = (0..n)
11009 .filter(|&i| mask & (1 << i) != 0)
11010 .map(|i| set[i].clone())
11011 .collect();
11012 subsets.push(subset);
11013 }
11014 subsets.sort_by(|a, b| a.len().cmp(&b.len()).then(a.cmp(b)));
11016 for (i, subset) in subsets.iter().enumerate() {
11017 let _ = writeln!(out, " {:3}. {}", i + 1, fmt_set(subset));
11018 }
11019 let _ = writeln!(out, "{}", sep);
11020 return out;
11021 }
11022
11023 if q_lower.starts_with("cartesian ")
11025 || q_lower.starts_with("product ")
11026 || q_lower.contains(" x ")
11027 {
11028 let parts: Vec<&str> = if q_lower.contains(" x ") {
11030 q.splitn(3, " x ").collect()
11031 } else {
11032 let kw = if q_lower.starts_with("cartesian ") {
11033 "cartesian "
11034 } else {
11035 "product "
11036 };
11037 let rest = &q[kw.len()..];
11038 rest.splitn(2, " x ").collect()
11039 };
11040 if parts.len() < 2 {
11041 let _ = writeln!(out, " Usage: cartesian {{1,2}} x {{a,b,c}}");
11042 let _ = writeln!(out, "{}", sep);
11043 return out;
11044 }
11045 let a = parse_set(parts[0]);
11046 let b = parse_set(parts[1]);
11047 let _ = writeln!(out, " A = {} (|A|={})", fmt_set(&a), a.len());
11048 let _ = writeln!(out, " B = {} (|B|={})", fmt_set(&b), b.len());
11049 let _ = writeln!(out, " |A × B| = {}", a.len() * b.len());
11050 let _ = writeln!(out);
11051 let _ = writeln!(out, " A × B:");
11052 for x in &a {
11053 for y in &b {
11054 let _ = writeln!(out, " ({}, {})", x, y);
11055 }
11056 }
11057 let _ = writeln!(out, "{}", sep);
11058 return out;
11059 }
11060
11061 let ops = [
11064 "symmetric_difference",
11065 "sym_diff",
11066 "symdiff",
11067 "difference",
11068 "intersection",
11069 "intersect",
11070 "union",
11071 "subset",
11072 "superset",
11073 "disjoint",
11074 "equal",
11075 ];
11076 let mut op_found: Option<(&str, Vec<String>, Vec<String>)> = None;
11077 for &op in &ops {
11078 let needle = format!(" {} ", op);
11079 if let Some(pos) = q_lower.find(needle.as_str()) {
11080 let a_raw = q[..pos].trim();
11081 let b_raw = q[pos + needle.len()..].trim();
11082 op_found = Some((op, parse_set(a_raw), parse_set(b_raw)));
11083 break;
11084 }
11085 }
11086
11087 if let Some((op, a, b)) = op_found {
11088 let _ = writeln!(out, " A = {} (|A|={})", fmt_set(&a), a.len());
11089 let _ = writeln!(out, " B = {} (|B|={})", fmt_set(&b), b.len());
11090 let _ = writeln!(out);
11091 match op {
11092 "union" => {
11093 let mut result = a.clone();
11094 for x in &b {
11095 if !result.contains(x) {
11096 result.push(x.clone());
11097 }
11098 }
11099 result.sort();
11100 let _ = writeln!(out, " A ∪ B = {}", fmt_set(&result));
11101 let _ = writeln!(out, " |A ∪ B| = {}", result.len());
11102 }
11103 "intersection" | "intersect" => {
11104 let result: Vec<String> = a.iter().filter(|x| b.contains(x)).cloned().collect();
11105 let _ = writeln!(out, " A ∩ B = {}", fmt_set(&result));
11106 let _ = writeln!(out, " |A ∩ B| = {}", result.len());
11107 }
11108 "difference" => {
11109 let result: Vec<String> = a.iter().filter(|x| !b.contains(x)).cloned().collect();
11110 let _ = writeln!(out, " A \\ B = {}", fmt_set(&result));
11111 let _ = writeln!(out, " |A \\ B| = {}", result.len());
11112 let result2: Vec<String> = b.iter().filter(|x| !a.contains(x)).cloned().collect();
11113 let _ = writeln!(out, " B \\ A = {}", fmt_set(&result2));
11114 }
11115 "symmetric_difference" | "sym_diff" | "symdiff" => {
11116 let in_a_not_b: Vec<String> =
11117 a.iter().filter(|x| !b.contains(x)).cloned().collect();
11118 let in_b_not_a: Vec<String> =
11119 b.iter().filter(|x| !a.contains(x)).cloned().collect();
11120 let mut result = in_a_not_b.clone();
11121 result.extend(in_b_not_a.clone());
11122 result.sort();
11123 let _ = writeln!(out, " A Δ B = {}", fmt_set(&result));
11124 let _ = writeln!(out, " |A Δ B| = {}", result.len());
11125 let _ = writeln!(out, " (in A not B: {})", fmt_set(&in_a_not_b));
11126 let _ = writeln!(out, " (in B not A: {})", fmt_set(&in_b_not_a));
11127 }
11128 "subset" => {
11129 let is_sub = a.iter().all(|x| b.contains(x));
11130 let is_proper = is_sub && a.len() < b.len();
11131 let _ = writeln!(out, " A ⊆ B: {}", if is_sub { "YES" } else { "NO" });
11132 let _ = writeln!(
11133 out,
11134 " A ⊂ B (proper): {}",
11135 if is_proper { "YES" } else { "NO" }
11136 );
11137 }
11138 "superset" => {
11139 let is_super = b.iter().all(|x| a.contains(x));
11140 let is_proper = is_super && a.len() > b.len();
11141 let _ = writeln!(out, " A ⊇ B: {}", if is_super { "YES" } else { "NO" });
11142 let _ = writeln!(
11143 out,
11144 " A ⊃ B (proper): {}",
11145 if is_proper { "YES" } else { "NO" }
11146 );
11147 }
11148 "disjoint" => {
11149 let is_disj = !a.iter().any(|x| b.contains(x));
11150 let _ = writeln!(
11151 out,
11152 " A ∩ B = ∅ (disjoint): {}",
11153 if is_disj { "YES" } else { "NO" }
11154 );
11155 if !is_disj {
11156 let common: Vec<String> = a.iter().filter(|x| b.contains(x)).cloned().collect();
11157 let _ = writeln!(out, " Common elements: {}", fmt_set(&common));
11158 }
11159 }
11160 "equal" => {
11161 let equal = a.iter().all(|x| b.contains(x)) && b.iter().all(|x| a.contains(x));
11162 let _ = writeln!(out, " A = B: {}", if equal { "YES" } else { "NO" });
11163 }
11164 _ => {}
11165 }
11166 let _ = writeln!(out, "{}", sep);
11167 return out;
11168 }
11169
11170 let set = parse_set(q);
11172 if !set.is_empty() {
11173 let _ = writeln!(out, " Set = {}", fmt_set(&set));
11174 let _ = writeln!(out, " Cardinality: {}", set.len());
11175 let _ = writeln!(
11176 out,
11177 " Min: {} Max: {}",
11178 set.first().unwrap_or(&String::new()),
11179 set.last().unwrap_or(&String::new())
11180 );
11181 let nums: Vec<f64> = set.iter().filter_map(|x| x.parse::<f64>().ok()).collect();
11183 if nums.len() == set.len() && !nums.is_empty() {
11184 let sum: f64 = nums.iter().sum();
11185 let mean = sum / nums.len() as f64;
11186 let _ = writeln!(out, " Sum: {} Mean: {:.4}", sum, mean);
11187 }
11188 let _ = writeln!(out, "{}", sep);
11189 return out;
11190 }
11191
11192 let _ = writeln!(out, "{}", set_usage());
11193 let _ = writeln!(out, "{}", sep);
11194 out
11195}
11196
11197fn set_usage() -> &'static str {
11198 "Set theory calculator — no model, no cloud:\n\
11199 \n\
11200 hematite --set '{1,2,3} union {3,4,5}' A ∪ B\n\
11201 hematite --set '{1,2,3} intersection {2,3,4}' A ∩ B\n\
11202 hematite --set '{1,2,3,4} difference {2,4}' A \\ B\n\
11203 hematite --set '{1,2,3} sym_diff {2,3,4}' A Δ B\n\
11204 hematite --set '{1,2} subset {1,2,3}' subset check\n\
11205 hematite --set '{1,2,3} superset {1,2}' superset check\n\
11206 hematite --set '{1,2,3} disjoint {4,5,6}' disjoint check\n\
11207 hematite --set 'powerset {a,b,c}' P(A) — all subsets\n\
11208 hematite --set 'cartesian {1,2} x {a,b}' A × B\n\
11209 hematite --set '{5,3,1,4,2}' inspect a set\n\
11210 \n\
11211 Elements can be numbers or strings. Sets are auto-deduplicated and sorted."
11212}
11213
11214pub fn cipher_calc(query: &str) -> String {
11217 let mut out = String::new();
11218 let sep = "─".repeat(60);
11219 let _ = writeln!(out, "{}", sep);
11220 let _ = writeln!(out, " CLASSICAL CIPHER");
11221 let _ = writeln!(out, "{}", sep);
11222
11223 let q = query.trim();
11224 let words: Vec<&str> = q.splitn(3, ' ').collect();
11225 if words.is_empty() {
11226 let _ = writeln!(out, "{}", cipher_usage());
11227 let _ = writeln!(out, "{}", sep);
11228 return out;
11229 }
11230
11231 let cipher_name = words[0].to_lowercase();
11232
11233 if cipher_name == "rot13" {
11235 let text = words[1..].join(" ");
11236 let result: String = text
11237 .chars()
11238 .map(|c| {
11239 if c.is_ascii_alphabetic() {
11240 let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
11241 (((c as u8 - base + 13) % 26) + base) as char
11242 } else {
11243 c
11244 }
11245 })
11246 .collect();
11247 let _ = writeln!(out, " Cipher: ROT13 (symmetric)");
11248 let _ = writeln!(out, " Input: {}", text);
11249 let _ = writeln!(out, " Output: {}", result);
11250 let _ = writeln!(out, "{}", sep);
11251 return out;
11252 }
11253
11254 if cipher_name == "atbash" {
11256 let text = words[1..].join(" ");
11257 let result: String = text
11258 .chars()
11259 .map(|c| {
11260 if c.is_ascii_alphabetic() {
11261 let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
11262 (base + 25 - (c as u8 - base)) as char
11263 } else {
11264 c
11265 }
11266 })
11267 .collect();
11268 let _ = writeln!(out, " Cipher: Atbash (symmetric)");
11269 let _ = writeln!(out, " Input: {}", text);
11270 let _ = writeln!(out, " Output: {}", result);
11271 let _ = writeln!(out, "{}", sep);
11272 return out;
11273 }
11274
11275 if cipher_name == "caesar" || cipher_name == "rot" {
11277 if words.len() < 3 {
11278 let _ = writeln!(out, " Usage: caesar <shift> <text> or rot <n> <text>");
11279 let _ = writeln!(out, "{}", sep);
11280 return out;
11281 }
11282 let shift: i32 = words[1].parse().unwrap_or(13);
11283 let text = words[2];
11284 let shift_norm = ((shift % 26) + 26) as u8 % 26;
11285 let result: String = text
11286 .chars()
11287 .map(|c| {
11288 if c.is_ascii_alphabetic() {
11289 let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
11290 ((c as u8 - base + shift_norm) % 26 + base) as char
11291 } else {
11292 c
11293 }
11294 })
11295 .collect();
11296 let decode: String = text
11297 .chars()
11298 .map(|c| {
11299 if c.is_ascii_alphabetic() {
11300 let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
11301 let s = (26 - shift_norm) % 26;
11302 ((c as u8 - base + s) % 26 + base) as char
11303 } else {
11304 c
11305 }
11306 })
11307 .collect();
11308 let _ = writeln!(out, " Cipher: Caesar / ROT-{}", shift);
11309 let _ = writeln!(out, " Input: {}", text);
11310 let _ = writeln!(out, " Encoded: {}", result);
11311 let _ = writeln!(out, " Decoded: {}", decode);
11312 let _ = writeln!(out);
11314 let _ = writeln!(out, " ─── Brute-force all rotations ───");
11315 for n in 0u8..26 {
11316 let r: String = text
11317 .chars()
11318 .map(|c| {
11319 if c.is_ascii_alphabetic() {
11320 let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
11321 ((c as u8 - base + n) % 26 + base) as char
11322 } else {
11323 c
11324 }
11325 })
11326 .collect();
11327 let _ = writeln!(out, " ROT-{:2}: {}", n, r);
11328 }
11329 let _ = writeln!(out, "{}", sep);
11330 return out;
11331 }
11332
11333 if cipher_name == "vigenere" || cipher_name == "vigenère" {
11335 let parts: Vec<&str> = q.splitn(4, ' ').collect();
11337 if parts.len() < 4 {
11338 let _ = writeln!(out, " Usage: vigenere encode <key> <text>");
11339 let _ = writeln!(out, " vigenere decode <key> <text>");
11340 let _ = writeln!(out, "{}", sep);
11341 return out;
11342 }
11343 let mode = parts[1].to_lowercase();
11344 let key = parts[2];
11345 let text = parts[3];
11346 let decode = mode == "decode" || mode == "dec" || mode == "d";
11347
11348 let key_clean: Vec<u8> = key
11349 .chars()
11350 .filter(|c| c.is_ascii_alphabetic())
11351 .map(|c| c.to_ascii_uppercase() as u8 - b'A')
11352 .collect();
11353 if key_clean.is_empty() {
11354 let _ = writeln!(out, " Key must contain at least one letter.");
11355 let _ = writeln!(out, "{}", sep);
11356 return out;
11357 }
11358 let mut ki = 0usize;
11359 let result: String = text
11360 .chars()
11361 .map(|c| {
11362 if c.is_ascii_alphabetic() {
11363 let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
11364 let shift = key_clean[ki % key_clean.len()];
11365 ki += 1;
11366 if decode {
11367 ((c as u8 - base + 26 - shift) % 26 + base) as char
11368 } else {
11369 ((c as u8 - base + shift) % 26 + base) as char
11370 }
11371 } else {
11372 c
11373 }
11374 })
11375 .collect();
11376 let _ = writeln!(out, " Cipher: Vigenère");
11377 let _ = writeln!(
11378 out,
11379 " Mode: {}",
11380 if decode { "decode" } else { "encode" }
11381 );
11382 let _ = writeln!(out, " Key: {}", key);
11383 let _ = writeln!(out, " Input: {}", text);
11384 let _ = writeln!(out, " Output: {}", result);
11385 let _ = writeln!(out, "{}", sep);
11386 return out;
11387 }
11388
11389 if cipher_name == "railfence" || cipher_name == "rail_fence" || cipher_name == "rail" {
11391 let parts: Vec<&str> = q.splitn(4, ' ').collect();
11392 if parts.len() < 4 {
11393 let _ = writeln!(out, " Usage: railfence encode <rails> <text>");
11394 let _ = writeln!(out, " railfence decode <rails> <text>");
11395 let _ = writeln!(out, "{}", sep);
11396 return out;
11397 }
11398 let mode = parts[1].to_lowercase();
11399 let rails: usize = parts[2].parse().unwrap_or(2).max(2);
11400 let text = parts[3];
11401 let decode = mode == "decode" || mode == "dec" || mode == "d";
11402
11403 let chars: Vec<char> = text.chars().collect();
11404 let n = chars.len();
11405
11406 if decode {
11407 let mut pattern = vec![0usize; n];
11409 let mut rail = 0i32;
11410 let mut dir = 1i32;
11411 for i in 0..n {
11412 pattern[i] = rail as usize;
11413 if rail == 0 {
11414 dir = 1;
11415 } else if rail == (rails as i32 - 1) {
11416 dir = -1;
11417 }
11418 rail += dir;
11419 }
11420 let mut rail_len = vec![0usize; rails];
11421 for &r in &pattern {
11422 rail_len[r] += 1;
11423 }
11424 let mut rail_start = vec![0usize; rails];
11425 for r in 1..rails {
11426 rail_start[r] = rail_start[r - 1] + rail_len[r - 1];
11427 }
11428 let char_arr: Vec<char> = chars.to_vec();
11429 let mut result = vec![' '; n];
11430 let mut rail_idx: Vec<usize> = rail_start.clone();
11431 for i in 0..n {
11432 result[i] = char_arr[rail_idx[pattern[i]]];
11433 rail_idx[pattern[i]] += 1;
11434 }
11435 let decoded: String = result.into_iter().collect();
11436 let _ = writeln!(out, " Cipher: Rail Fence");
11437 let _ = writeln!(out, " Rails: {}", rails);
11438 let _ = writeln!(out, " Input: {}", text);
11439 let _ = writeln!(out, " Decoded: {}", decoded);
11440 } else {
11441 let mut fence: Vec<Vec<char>> = vec![Vec::new(); rails];
11442 let mut rail = 0i32;
11443 let mut dir = 1i32;
11444 for &ch in &chars {
11445 fence[rail as usize].push(ch);
11446 if rail == 0 {
11447 dir = 1;
11448 } else if rail == (rails as i32 - 1) {
11449 dir = -1;
11450 }
11451 rail += dir;
11452 }
11453 let encoded: String = fence.iter().flat_map(|r| r.iter()).collect();
11454 let _ = writeln!(out, " Cipher: Rail Fence");
11455 let _ = writeln!(out, " Rails: {}", rails);
11456 let _ = writeln!(out, " Input: {}", text);
11457 let _ = writeln!(out, " Encoded: {}", encoded);
11458 let _ = writeln!(out);
11459 let _ = writeln!(out, " ─── Rail diagram ───");
11460 for (i, rail_row) in fence.iter().enumerate() {
11461 let s: String = rail_row.iter().collect();
11462 let _ = writeln!(out, " Rail {}: {}", i, s);
11463 }
11464 }
11465 let _ = writeln!(out, "{}", sep);
11466 return out;
11467 }
11468
11469 if cipher_name == "columnar" || cipher_name == "transposition" {
11471 let parts: Vec<&str> = q.splitn(4, ' ').collect();
11472 if parts.len() < 4 {
11473 let _ = writeln!(out, " Usage: columnar encode <key> <text>");
11474 let _ = writeln!(out, " columnar decode <key> <text>");
11475 let _ = writeln!(out, "{}", sep);
11476 return out;
11477 }
11478 let mode = parts[1].to_lowercase();
11479 let key = parts[2];
11480 let text = parts[3];
11481 let decode = mode == "decode" || mode == "dec" || mode == "d";
11482
11483 let num_cols = key.len();
11484 let mut key_chars: Vec<(usize, char)> = key.chars().enumerate().collect();
11485 key_chars.sort_by_key(|&(_, c)| c);
11486 let col_order: Vec<usize> = key_chars.iter().map(|&(i, _)| i).collect();
11487
11488 let pad: usize = if text.len() % num_cols == 0 {
11489 0
11490 } else {
11491 num_cols - text.len() % num_cols
11492 };
11493 let padded: Vec<char> = text.chars().chain(std::iter::repeat_n('_', pad)).collect();
11494 let num_rows = padded.len() / num_cols;
11495
11496 if decode {
11497 let col_lens: Vec<usize> = (0..num_cols).map(|_| num_rows).collect();
11499 let mut cols: Vec<Vec<char>> = vec![Vec::new(); num_cols];
11500 let mut idx = 0;
11501 for &ci in &col_order {
11502 for _ in 0..col_lens[ci] {
11503 if idx < text.len() {
11504 cols[ci].push(text.chars().nth(idx).unwrap_or('_'));
11505 idx += 1;
11506 }
11507 }
11508 }
11509 let mut decoded = String::new();
11510 for r in 0..num_rows {
11511 for c in 0..num_cols {
11512 if r < cols[c].len() {
11513 decoded.push(cols[c][r]);
11514 }
11515 }
11516 }
11517 let decoded = decoded.trim_end_matches('_');
11518 let _ = writeln!(out, " Cipher: Columnar Transposition");
11519 let _ = writeln!(out, " Key: {}", key);
11520 let _ = writeln!(out, " Input: {}", text);
11521 let _ = writeln!(out, " Decoded: {}", decoded);
11522 } else {
11523 let grid: Vec<Vec<char>> = padded.chunks(num_cols).map(|r| r.to_vec()).collect();
11525 let mut encoded = String::new();
11526 for &ci in &col_order {
11527 for row in &grid {
11528 encoded.push(row[ci]);
11529 }
11530 }
11531 let _ = writeln!(out, " Cipher: Columnar Transposition");
11532 let _ = writeln!(out, " Key: {} (sorted order: {:?})", key, col_order);
11533 let _ = writeln!(out, " Input: {}", text);
11534 let _ = writeln!(out, " Encoded: {}", encoded);
11535 let _ = writeln!(out);
11536 let _ = writeln!(out, " ─── Grid ───");
11537 let _ = writeln!(
11539 out,
11540 " Key: {}",
11541 key.chars().map(|c| format!("{} ", c)).collect::<String>()
11542 );
11543 for row in &grid {
11544 let row_str: String = row.iter().map(|c| format!("{} ", c)).collect();
11545 let _ = writeln!(out, " {}", row_str);
11546 }
11547 }
11548 let _ = writeln!(out, "{}", sep);
11549 return out;
11550 }
11551
11552 if cipher_name == "morse" {
11554 let parts: Vec<&str> = q.splitn(3, ' ').collect();
11555 if parts.len() < 3 {
11556 let _ = writeln!(out, " Usage: morse encode <text>");
11557 let _ = writeln!(out, " morse decode <morse>");
11558 let _ = writeln!(out, "{}", sep);
11559 return out;
11560 }
11561 let mode = parts[1].to_lowercase();
11562 let text = parts[2];
11563 let decode = mode == "decode" || mode == "dec" || mode == "d";
11564
11565 let morse_table: &[(&str, &str)] = &[
11566 ("A", ".-"),
11567 ("B", "-..."),
11568 ("C", "-.-."),
11569 ("D", "-.."),
11570 ("E", "."),
11571 ("F", "..-."),
11572 ("G", "--."),
11573 ("H", "...."),
11574 ("I", ".."),
11575 ("J", ".---"),
11576 ("K", "-.-"),
11577 ("L", ".-.."),
11578 ("M", "--"),
11579 ("N", "-."),
11580 ("O", "---"),
11581 ("P", ".--."),
11582 ("Q", "--.-"),
11583 ("R", ".-."),
11584 ("S", "..."),
11585 ("T", "-"),
11586 ("U", "..-"),
11587 ("V", "...-"),
11588 ("W", ".--"),
11589 ("X", "-..-"),
11590 ("Y", "-.--"),
11591 ("Z", "--.."),
11592 ("0", "-----"),
11593 ("1", ".----"),
11594 ("2", "..---"),
11595 ("3", "...--"),
11596 ("4", "....-"),
11597 ("5", "....."),
11598 ("6", "-...."),
11599 ("7", "--..."),
11600 ("8", "---.."),
11601 ("9", "----."),
11602 (",", "--..--"),
11603 (".", ".-.-.-"),
11604 ("?", "..--.."),
11605 ("!", "-.-.--"),
11606 ("/", "-..-."),
11607 ("@", ".--.-."),
11608 ("&", ".-..."),
11609 ];
11610
11611 if decode {
11612 let code_to_char: std::collections::HashMap<&str, &str> =
11613 morse_table.iter().map(|&(c, m)| (m, c)).collect();
11614 let decoded: String = text
11615 .split(" ") .map(|word| {
11617 word.split(' ')
11618 .map(|sym| code_to_char.get(sym).copied().unwrap_or("?"))
11619 .collect::<String>()
11620 })
11621 .collect::<Vec<_>>()
11622 .join(" ");
11623 let _ = writeln!(out, " Cipher: Morse Code");
11624 let _ = writeln!(out, " Input: {}", text);
11625 let _ = writeln!(out, " Decoded: {}", decoded);
11626 } else {
11627 let char_to_morse: std::collections::HashMap<&str, &str> =
11628 morse_table.iter().map(|&(c, m)| (c, m)).collect();
11629 let encoded: String = text
11630 .to_uppercase()
11631 .chars()
11632 .map(|c| {
11633 if c == ' ' {
11634 " ".to_string()
11635 } else {
11636 let s = c.to_string();
11637 char_to_morse
11638 .get(s.as_str())
11639 .copied()
11640 .unwrap_or("?")
11641 .to_string()
11642 + " "
11643 }
11644 })
11645 .collect();
11646 let _ = writeln!(out, " Cipher: Morse Code");
11647 let _ = writeln!(out, " Input: {}", text);
11648 let _ = writeln!(out, " Encoded: {}", encoded.trim());
11649 }
11650 let _ = writeln!(out, "{}", sep);
11651 return out;
11652 }
11653
11654 let _ = writeln!(out, "{}", cipher_usage());
11656 let _ = writeln!(out, "{}", sep);
11657 out
11658}
11659
11660fn cipher_usage() -> &'static str {
11661 "Classical cipher encoder/decoder — no model, no cloud:\n\
11662 \n\
11663 hematite --cipher 'rot13 Hello World' ROT13 (symmetric)\n\
11664 hematite --cipher 'atbash Hello World' Atbash (symmetric)\n\
11665 hematite --cipher 'caesar 13 Hello World' Caesar shift (with brute-force)\n\
11666 hematite --cipher 'vigenere encode KEY plaintext' Vigenère encode\n\
11667 hematite --cipher 'vigenere decode KEY ciphertext' Vigenère decode\n\
11668 hematite --cipher 'railfence encode 3 WEAREDISCOVERED' Rail Fence encode\n\
11669 hematite --cipher 'railfence decode 3 WECRLTEERDSOEEVEAAID' Rail Fence decode\n\
11670 hematite --cipher 'columnar encode ZEBRA plaintext' Columnar transposition encode\n\
11671 hematite --cipher 'morse encode Hello World' Text to Morse\n\
11672 hematite --cipher 'morse decode .... . .-.. .-.. ---' Morse to text\n\
11673 \n\
11674 Ciphers: rot13, atbash, caesar, vigenere, railfence, columnar, morse"
11675}
11676
11677pub fn validate_calc(input: &str) -> String {
11680 let mut out = String::new();
11681 let sep = "─".repeat(60);
11682 let _ = writeln!(out, "{}", sep);
11683 let _ = writeln!(out, " VALIDATION TOOLKIT");
11684 let _ = writeln!(out, "{}", sep);
11685
11686 let q = input.trim();
11687 let _ = writeln!(out, " Input: \"{}\"", q);
11688 let _ = writeln!(out);
11689
11690 let clean: String = q
11692 .chars()
11693 .filter(|c| !c.is_whitespace() && *c != '-' && *c != ' ')
11694 .collect();
11695
11696 let luhn_result = {
11698 let digits: Vec<u32> = clean.chars().filter_map(|c| c.to_digit(10)).collect();
11699 if digits.len() >= 2 {
11700 let sum: u32 = digits
11701 .iter()
11702 .rev()
11703 .enumerate()
11704 .map(|(i, &d)| {
11705 if i % 2 == 1 {
11706 let doubled = d * 2;
11707 if doubled > 9 {
11708 doubled - 9
11709 } else {
11710 doubled
11711 }
11712 } else {
11713 d
11714 }
11715 })
11716 .sum();
11717 Some(sum % 10 == 0)
11718 } else {
11719 None
11720 }
11721 };
11722
11723 let card_network = {
11725 let digits_only: String = clean.chars().filter(|c| c.is_ascii_digit()).collect();
11726 let n = digits_only.len();
11727 if n >= 1 {
11728 let d1: u32 = digits_only
11729 .chars()
11730 .next()
11731 .unwrap()
11732 .to_digit(10)
11733 .unwrap_or(0);
11734 let d2: u32 = if n >= 2 {
11735 digits_only[..2].parse().unwrap_or(0)
11736 } else {
11737 0
11738 };
11739 let d4: u32 = if n >= 4 {
11740 digits_only[..4].parse().unwrap_or(0)
11741 } else {
11742 0
11743 };
11744 let d6: u32 = if n >= 6 {
11745 digits_only[..6].parse().unwrap_or(0)
11746 } else {
11747 0
11748 };
11749 if d1 == 4 {
11750 Some("Visa (starts with 4)")
11751 } else if d2 == 51 || d2 == 52 || d2 == 53 || d2 == 54 || d2 == 55 {
11752 Some("Mastercard (51-55)")
11753 } else if (622126..=622925).contains(&d6) || d4 == 6011 || d2 == 65 {
11754 Some("Discover")
11755 } else if d4 == 3782 || d4 == 3714 || d4 == 3787 || d4 == 3728 || d2 == 34 || d2 == 37 {
11756 Some("American Express")
11757 } else if d4 == 3528 || d4 == 3589 {
11758 Some("JCB")
11759 } else {
11760 Some("Unknown network")
11761 }
11762 } else {
11763 None
11764 }
11765 };
11766
11767 if let Some(valid) = luhn_result {
11768 let _ = writeln!(out, " ─── Luhn (Credit Card) ───");
11769 let _ = writeln!(
11770 out,
11771 " Valid: {} {}",
11772 if valid { "YES ✓" } else { "NO ✗" },
11773 if valid {
11774 "passes Luhn check"
11775 } else {
11776 "fails Luhn check"
11777 }
11778 );
11779 if let Some(network) = card_network {
11780 let digits_only: String = clean.chars().filter(|c| c.is_ascii_digit()).collect();
11781 if digits_only.len() >= 12 {
11782 let _ = writeln!(out, " Network: {}", network);
11783 let _ = writeln!(out, " Length: {} digits", digits_only.len());
11784 }
11785 }
11786 if !valid {
11788 let digits: Vec<u32> = clean.chars().filter_map(|c| c.to_digit(10)).collect();
11789 if digits.len() >= 2 {
11790 let without_last: Vec<u32> = digits[..digits.len() - 1].to_vec();
11791 for check in 0u32..10 {
11792 let mut test = without_last.clone();
11793 test.push(check);
11794 let sum: u32 = test
11795 .iter()
11796 .rev()
11797 .enumerate()
11798 .map(|(i, &d)| {
11799 if i % 2 == 1 {
11800 let x = d * 2;
11801 if x > 9 {
11802 x - 9
11803 } else {
11804 x
11805 }
11806 } else {
11807 d
11808 }
11809 })
11810 .sum();
11811 if sum % 10 == 0 {
11812 let _ =
11813 writeln!(out, " Correct check digit: {} (replace last digit)", check);
11814 break;
11815 }
11816 }
11817 }
11818 }
11819 let _ = writeln!(out);
11820 }
11821
11822 let isbn10_digits: Vec<u32> = clean
11824 .chars()
11825 .filter_map(|c| {
11826 if c == 'X' || c == 'x' {
11827 Some(10)
11828 } else {
11829 c.to_digit(10)
11830 }
11831 })
11832 .collect();
11833 if isbn10_digits.len() == 10 {
11834 let sum: u32 = isbn10_digits
11835 .iter()
11836 .enumerate()
11837 .map(|(i, &d)| (10 - i as u32) * d)
11838 .sum();
11839 let valid = sum % 11 == 0;
11840 let _ = writeln!(out, " ─── ISBN-10 ───");
11841 let _ = writeln!(
11842 out,
11843 " Valid: {} (sum mod 11 = {})",
11844 if valid { "YES ✓" } else { "NO ✗" },
11845 sum % 11
11846 );
11847 if !valid {
11848 let sum9: u32 = isbn10_digits[..9]
11850 .iter()
11851 .enumerate()
11852 .map(|(i, &d)| (10 - i as u32) * d)
11853 .sum();
11854 let check = (11 - (sum9 % 11)) % 11;
11855 let check_str = if check == 10 {
11856 "X".to_string()
11857 } else {
11858 check.to_string()
11859 };
11860 let _ = writeln!(out, " Correct check digit: {}", check_str);
11861 }
11862 let _ = writeln!(out);
11863 }
11864
11865 let isbn13_digits: Vec<u32> = clean.chars().filter_map(|c| c.to_digit(10)).collect();
11867 if isbn13_digits.len() == 13 {
11868 let sum: u32 = isbn13_digits
11869 .iter()
11870 .enumerate()
11871 .map(|(i, &d)| if i % 2 == 0 { d } else { d * 3 })
11872 .sum();
11873 let valid = sum % 10 == 0;
11874 let prefix = &clean[..3];
11875 let kind = if prefix == "978" || prefix == "979" {
11876 "ISBN-13"
11877 } else {
11878 "EAN-13"
11879 };
11880 let _ = writeln!(out, " ─── {} ───", kind);
11881 let _ = writeln!(
11882 out,
11883 " Valid: {} (weighted sum mod 10 = {})",
11884 if valid { "YES ✓" } else { "NO ✗" },
11885 sum % 10
11886 );
11887 if kind == "ISBN-13" {
11888 let _ = writeln!(out, " Prefix: {} (Bookland)", prefix);
11889 }
11890 if !valid {
11891 let sum12: u32 = isbn13_digits[..12]
11892 .iter()
11893 .enumerate()
11894 .map(|(i, &d)| if i % 2 == 0 { d } else { d * 3 })
11895 .sum();
11896 let check = (10 - (sum12 % 10)) % 10;
11897 let _ = writeln!(out, " Correct check digit: {}", check);
11898 }
11899 let _ = writeln!(out);
11900 }
11901
11902 let iban_upper = clean.to_uppercase();
11905 if iban_upper.len() >= 15
11906 && iban_upper.len() <= 34
11907 && iban_upper.chars().take(2).all(|c| c.is_ascii_uppercase())
11908 && iban_upper
11909 .chars()
11910 .skip(2)
11911 .take(2)
11912 .all(|c| c.is_ascii_digit())
11913 {
11914 let rearranged = format!("{}{}", &iban_upper[4..], &iban_upper[..4]);
11915 let numeric: String = rearranged
11916 .chars()
11917 .map(|c| {
11918 if c.is_ascii_uppercase() {
11919 format!("{}", c as u32 - 'A' as u32 + 10)
11920 } else {
11921 c.to_string()
11922 }
11923 })
11924 .collect();
11925 let remainder = numeric.chars().fold(0u64, |acc, c| {
11927 let d = c.to_digit(10).unwrap_or(0) as u64;
11928 (acc * 10 + d) % 97
11929 });
11930 let valid = remainder == 1;
11931 let country = &iban_upper[..2];
11932 let _ = writeln!(out, " ─── IBAN ───");
11933 let _ = writeln!(out, " Country: {}", country);
11934 let _ = writeln!(out, " Length: {} characters", iban_upper.len());
11935 let _ = writeln!(
11936 out,
11937 " Valid: {} (mod 97 = {})",
11938 if valid { "YES ✓" } else { "NO ✗" },
11939 remainder
11940 );
11941 let _ = writeln!(out);
11942 }
11943
11944 let uuid_clean: String = clean.to_lowercase();
11946 let uuid_nodash: String = uuid_clean.chars().filter(|c| *c != '-').collect();
11947 if uuid_nodash.len() == 32 && uuid_nodash.chars().all(|c| c.is_ascii_hexdigit()) {
11948 let formatted = format!(
11949 "{}-{}-{}-{}-{}",
11950 &uuid_nodash[0..8],
11951 &uuid_nodash[8..12],
11952 &uuid_nodash[12..16],
11953 &uuid_nodash[16..20],
11954 &uuid_nodash[20..32]
11955 );
11956 let version = u8::from_str_radix(&uuid_nodash[12..13], 16).unwrap_or(0);
11957 let variant_bits = u8::from_str_radix(&uuid_nodash[16..17], 16).unwrap_or(0);
11958 let variant = if variant_bits & 0xC == 0xC {
11959 "Microsoft (variant 11)"
11960 } else if variant_bits & 0x8 != 0 {
11961 "RFC 4122 (variant 10)"
11962 } else {
11963 "NCS/backward compat (variant 0)"
11964 };
11965 let _ = writeln!(out, " ─── UUID ───");
11966 let _ = writeln!(out, " Format: {}", formatted);
11967 let _ = writeln!(out, " Version: {}", version);
11968 let _ = writeln!(out, " Variant: {}", variant);
11969 let _ = writeln!(out);
11970 }
11971
11972 let nothing = luhn_result.is_none()
11974 && isbn10_digits.len() != 10
11975 && isbn13_digits.len() != 13
11976 && !clean
11977 .to_uppercase()
11978 .starts_with(|c: char| c.is_ascii_uppercase());
11979 if nothing {
11980 let _ = writeln!(out, " No recognizable format detected. Examples:");
11981 let _ = writeln!(
11982 out,
11983 " hematite --validate '4532015112830366' credit card (Luhn)"
11984 );
11985 let _ = writeln!(out, " hematite --validate '0-306-40615-2' ISBN-10");
11986 let _ = writeln!(
11987 out,
11988 " hematite --validate '978-0-306-40615-7' ISBN-13 / EAN-13"
11989 );
11990 let _ = writeln!(out, " hematite --validate 'GB82WEST12345698765432' IBAN");
11991 let _ = writeln!(
11992 out,
11993 " hematite --validate '550e8400-e29b-41d4-a716-446655440000' UUID"
11994 );
11995 }
11996
11997 let _ = writeln!(out, "{}", sep);
11998 out
11999}
12000
12001pub fn checksum_calc(input: &str) -> String {
12004 let mut out = String::new();
12005 let sep = "─".repeat(60);
12006 let _ = writeln!(out, "{}", sep);
12007 let _ = writeln!(out, " CHECKSUM CALCULATOR");
12008 let _ = writeln!(out, "{}", sep);
12009
12010 let bytes: Vec<u8> = input.as_bytes().to_vec();
12011 let len = bytes.len();
12012
12013 let _ = writeln!(out, " Input: \"{}\"", input);
12014 let _ = writeln!(out, " Length: {} bytes", len);
12015 let _ = writeln!(out);
12016
12017 let crc32 = {
12019 let mut table = [0u32; 256];
12020 for i in 0u32..256 {
12021 let mut c = i;
12022 for _ in 0..8 {
12023 if c & 1 != 0 {
12024 c = 0xEDB8_8320 ^ (c >> 1);
12025 } else {
12026 c >>= 1;
12027 }
12028 }
12029 table[i as usize] = c;
12030 }
12031 let mut crc: u32 = 0xFFFF_FFFF;
12032 for &b in &bytes {
12033 crc = (crc >> 8) ^ table[((crc ^ b as u32) & 0xFF) as usize];
12034 }
12035 crc ^ 0xFFFF_FFFF
12036 };
12037
12038 let crc16 = {
12040 let mut crc: u16 = 0xFFFF;
12041 for &b in &bytes {
12042 crc ^= (b as u16) << 8;
12043 for _ in 0..8 {
12044 if crc & 0x8000 != 0 {
12045 crc = (crc << 1) ^ 0x1021;
12046 } else {
12047 crc <<= 1;
12048 }
12049 }
12050 }
12051 crc
12052 };
12053
12054 let adler32 = {
12056 let (mut a, mut b) = (1u32, 0u32);
12057 for &byte in &bytes {
12058 a = (a + byte as u32) % 65521;
12059 b = (b + a) % 65521;
12060 }
12061 (b << 16) | a
12062 };
12063
12064 let fnv1a_32 = {
12066 let mut h: u32 = 2166136261;
12067 for &b in &bytes {
12068 h ^= b as u32;
12069 h = h.wrapping_mul(16777619);
12070 }
12071 h
12072 };
12073
12074 let fnv1a_64 = {
12076 let mut h: u64 = 14695981039346656037;
12077 for &b in &bytes {
12078 h ^= b as u64;
12079 h = h.wrapping_mul(1099511628211);
12080 }
12081 h
12082 };
12083
12084 let djb2 = {
12086 let mut h: u64 = 5381;
12087 for &b in &bytes {
12088 h = h.wrapping_mul(33).wrapping_add(b as u64);
12089 }
12090 h
12091 };
12092
12093 let sdbm = {
12095 let mut h: u64 = 0;
12096 for &b in &bytes {
12097 h = (b as u64)
12098 .wrapping_add(h.wrapping_shl(6))
12099 .wrapping_add(h.wrapping_shl(16))
12100 .wrapping_sub(h);
12101 }
12102 h
12103 };
12104
12105 let xor8: u8 = bytes.iter().fold(0u8, |acc, &b| acc ^ b);
12107 let xor16: u16 = bytes.chunks(2).fold(0u16, |acc, chunk| {
12108 let w = if chunk.len() == 2 {
12109 (chunk[0] as u16) << 8 | chunk[1] as u16
12110 } else {
12111 (chunk[0] as u16) << 8
12112 };
12113 acc ^ w
12114 });
12115
12116 let sum8: u8 = bytes.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
12118 let sum16: u16 = bytes
12119 .iter()
12120 .fold(0u16, |acc, &b| acc.wrapping_add(b as u16));
12121 let sum32: u32 = bytes
12122 .iter()
12123 .fold(0u32, |acc, &b| acc.wrapping_add(b as u32));
12124
12125 let _ = writeln!(out, " ─── Cyclic Redundancy Checks ───");
12126 let _ = writeln!(out, " CRC-32 (IEEE): 0x{:08X} ({:10})", crc32, crc32);
12127 let _ = writeln!(
12128 out,
12129 " CRC-16 (CCITT): 0x{:04X} ({:6})",
12130 crc16, crc16
12131 );
12132 let _ = writeln!(out);
12133 let _ = writeln!(out, " ─── Hash Functions ───");
12134 let _ = writeln!(
12135 out,
12136 " Adler-32: 0x{:08X} ({:10})",
12137 adler32, adler32
12138 );
12139 let _ = writeln!(
12140 out,
12141 " FNV-1a 32: 0x{:08X} ({:10})",
12142 fnv1a_32, fnv1a_32
12143 );
12144 let _ = writeln!(out, " FNV-1a 64: 0x{:016X}", fnv1a_64);
12145 let _ = writeln!(out, " DJB2: 0x{:016X}", djb2);
12146 let _ = writeln!(out, " SDBM: 0x{:016X}", sdbm);
12147 let _ = writeln!(out);
12148 let _ = writeln!(out, " ─── Simple Checksums ───");
12149 let _ = writeln!(out, " XOR-8: 0x{:02X} ({})", xor8, xor8);
12150 let _ = writeln!(out, " XOR-16: 0x{:04X} ({})", xor16, xor16);
12151 let _ = writeln!(out, " Sum-8 (mod 256): 0x{:02X} ({})", sum8, sum8);
12152 let _ = writeln!(out, " Sum-16: 0x{:04X} ({})", sum16, sum16);
12153 let _ = writeln!(out, " Sum-32: 0x{:08X} ({})", sum32, sum32);
12154 let _ = writeln!(out);
12155 let _ = writeln!(out, " ─── Byte Analysis ───");
12156 let _ = writeln!(
12157 out,
12158 " Min byte: 0x{:02X} ({})",
12159 bytes.iter().min().copied().unwrap_or(0),
12160 bytes.iter().min().copied().unwrap_or(0)
12161 );
12162 let _ = writeln!(
12163 out,
12164 " Max byte: 0x{:02X} ({})",
12165 bytes.iter().max().copied().unwrap_or(0),
12166 bytes.iter().max().copied().unwrap_or(0)
12167 );
12168 let avg_byte = bytes.iter().map(|&b| b as f64).sum::<f64>() / len.max(1) as f64;
12169 let _ = writeln!(out, " Avg byte value: {:.2}", avg_byte);
12170 if len <= 32 {
12172 let _ = writeln!(
12173 out,
12174 " Hex: {}",
12175 bytes
12176 .iter()
12177 .map(|b| format!("{:02X}", b))
12178 .collect::<Vec<_>>()
12179 .join(" ")
12180 );
12181 } else {
12182 let preview: String = bytes[..32]
12183 .iter()
12184 .map(|b| format!("{:02X}", b))
12185 .collect::<Vec<_>>()
12186 .join(" ");
12187 let _ = writeln!(out, " Hex (first 32): {} ...", preview);
12188 }
12189
12190 let _ = writeln!(out, "{}", sep);
12191 out
12192}
12193
12194pub fn sort_viz(query: &str) -> String {
12197 let mut out = String::new();
12198 let sep = "─".repeat(60);
12199 let _ = writeln!(out, "{}", sep);
12200 let _ = writeln!(out, " SORTING ALGORITHM VISUALIZER");
12201 let _ = writeln!(out, "{}", sep);
12202
12203 let q = query.trim();
12204 let words: Vec<&str> = q.splitn(2, ' ').collect();
12206 let algos_all = ["bubble", "insertion", "selection", "merge", "quick", "heap"];
12207
12208 let (algos, data_str) = if words.len() == 2 {
12209 let first_lower = words[0].to_lowercase();
12210 if algos_all.contains(&first_lower.as_str()) {
12211 (vec![first_lower], words[1])
12212 } else {
12213 (algos_all.iter().map(|s| s.to_string()).collect(), q)
12214 }
12215 } else {
12216 (algos_all.iter().map(|s| s.to_string()).collect(), q)
12217 };
12218
12219 let data: Vec<i64> = data_str
12221 .split(|c: char| !c.is_numeric() && c != '-')
12222 .filter_map(|s| s.parse().ok())
12223 .collect();
12224
12225 if data.is_empty() || data.len() < 2 {
12226 let _ = writeln!(
12227 out,
12228 " Provide at least 2 numbers: hematite --sort-viz '5,3,8,1,9,2'"
12229 );
12230 let _ = writeln!(
12231 out,
12232 " Or specify an algorithm: hematite --sort-viz 'bubble 5,3,8,1'"
12233 );
12234 let _ = writeln!(out, "{}", sep);
12235 return out;
12236 }
12237 if data.len() > 20 {
12238 let _ = writeln!(
12239 out,
12240 " Max 20 elements for visualization (got {})",
12241 data.len()
12242 );
12243 let _ = writeln!(out, "{}", sep);
12244 return out;
12245 }
12246
12247 let _ = writeln!(out, " Input: {:?}", data);
12248 let _ = writeln!(out);
12249
12250 let max_val = *data.iter().max().unwrap_or(&1);
12251 let min_val = *data.iter().min().unwrap_or(&0);
12252 let range = (max_val - min_val).max(1);
12253
12254 let bar_height = 8usize;
12256 let bar_render = |arr: &[i64]| -> String {
12257 let mut lines = vec![String::new(); bar_height + 1];
12258 for &v in arr {
12259 let h = ((v - min_val) as f64 / range as f64 * bar_height as f64).round() as usize;
12260 let h = h.min(bar_height);
12261 for row in 0..bar_height {
12262 let bar_row = bar_height - 1 - row;
12263 if bar_row < h {
12264 lines[row].push_str("██ ");
12265 } else {
12266 lines[row].push_str(" ");
12267 }
12268 }
12269 lines[bar_height].push_str(&format!("{:<3}", v));
12270 }
12271 lines
12272 .iter()
12273 .map(|l| format!(" {}", l))
12274 .collect::<Vec<_>>()
12275 .join("\n")
12276 };
12277
12278 for algo in &algos {
12279 let _ = writeln!(out, " ═══ {} Sort ═══", algo.to_uppercase());
12280 let _ = writeln!(out);
12281
12282 let mut arr = data.clone();
12283 let mut steps: Vec<(Vec<i64>, String)> = Vec::new();
12284 let mut comparisons = 0usize;
12285 let mut swaps = 0usize;
12286
12287 match algo.as_str() {
12288 "bubble" => {
12289 let n = arr.len();
12290 for i in 0..n {
12291 let mut swapped = false;
12292 for j in 0..n - 1 - i {
12293 comparisons += 1;
12294 if arr[j] > arr[j + 1] {
12295 arr.swap(j, j + 1);
12296 swaps += 1;
12297 swapped = true;
12298 }
12299 }
12300 steps.push((
12301 arr.clone(),
12302 format!("Pass {} (sorted tail: {})", i + 1, i + 1),
12303 ));
12304 if !swapped {
12305 break;
12306 }
12307 }
12308 }
12309 "insertion" => {
12310 for i in 1..arr.len() {
12311 let key = arr[i];
12312 let mut j = i as i64 - 1;
12313 while j >= 0 {
12314 comparisons += 1;
12315 if arr[j as usize] > key {
12316 arr[(j + 1) as usize] = arr[j as usize];
12317 swaps += 1;
12318 j -= 1;
12319 } else {
12320 break;
12321 }
12322 }
12323 arr[(j + 1) as usize] = key;
12324 steps.push((arr.clone(), format!("Insert {} at position {}", key, j + 1)));
12325 }
12326 }
12327 "selection" => {
12328 let n = arr.len();
12329 for i in 0..n - 1 {
12330 let mut min_idx = i;
12331 for j in i + 1..n {
12332 comparisons += 1;
12333 if arr[j] < arr[min_idx] {
12334 min_idx = j;
12335 }
12336 }
12337 if min_idx != i {
12338 arr.swap(i, min_idx);
12339 swaps += 1;
12340 }
12341 steps.push((
12342 arr.clone(),
12343 format!("Select min={} → position {}", arr[i], i),
12344 ));
12345 }
12346 }
12347 "merge" => {
12348 fn merge_sort_steps(
12349 arr: &mut Vec<i64>,
12350 steps: &mut Vec<(Vec<i64>, String)>,
12351 comparisons: &mut usize,
12352 swaps: &mut usize,
12353 lo: usize,
12354 hi: usize,
12355 ) {
12356 if hi - lo <= 1 {
12357 return;
12358 }
12359 let mid = (lo + hi) / 2;
12360 merge_sort_steps(arr, steps, comparisons, swaps, lo, mid);
12361 merge_sort_steps(arr, steps, comparisons, swaps, mid, hi);
12362 let left: Vec<i64> = arr[lo..mid].to_vec();
12363 let right: Vec<i64> = arr[mid..hi].to_vec();
12364 let (mut li, mut ri) = (0, 0);
12365 let mut idx = lo;
12366 while li < left.len() && ri < right.len() {
12367 *comparisons += 1;
12368 if left[li] <= right[ri] {
12369 arr[idx] = left[li];
12370 li += 1;
12371 } else {
12372 arr[idx] = right[ri];
12373 ri += 1;
12374 *swaps += 1;
12375 }
12376 idx += 1;
12377 }
12378 while li < left.len() {
12379 arr[idx] = left[li];
12380 li += 1;
12381 idx += 1;
12382 }
12383 while ri < right.len() {
12384 arr[idx] = right[ri];
12385 ri += 1;
12386 idx += 1;
12387 }
12388 steps.push((arr.clone(), format!("Merge [{}, {})", lo, hi)));
12389 }
12390 let n = arr.len();
12391 merge_sort_steps(&mut arr, &mut steps, &mut comparisons, &mut swaps, 0, n);
12392 }
12393 "quick" => {
12394 fn quick_sort_steps(
12395 arr: &mut Vec<i64>,
12396 steps: &mut Vec<(Vec<i64>, String)>,
12397 comparisons: &mut usize,
12398 swaps: &mut usize,
12399 lo: usize,
12400 hi: usize,
12401 ) {
12402 if lo + 1 >= hi {
12403 return;
12404 }
12405 let pivot = arr[hi - 1];
12406 let mut i = lo;
12407 for j in lo..hi - 1 {
12408 *comparisons += 1;
12409 if arr[j] <= pivot {
12410 arr.swap(i, j);
12411 i += 1;
12412 *swaps += 1;
12413 }
12414 }
12415 arr.swap(i, hi - 1);
12416 *swaps += 1;
12417 steps.push((arr.clone(), format!("Pivot={} partitioned at {}", pivot, i)));
12418 if i > lo {
12419 quick_sort_steps(arr, steps, comparisons, swaps, lo, i);
12420 }
12421 if i + 1 < hi {
12422 quick_sort_steps(arr, steps, comparisons, swaps, i + 1, hi);
12423 }
12424 }
12425 let n = arr.len();
12426 quick_sort_steps(&mut arr, &mut steps, &mut comparisons, &mut swaps, 0, n);
12427 }
12428 "heap" => {
12429 let n = arr.len();
12430 for i in (0..n / 2).rev() {
12432 let mut root = i;
12433 loop {
12434 let left = 2 * root + 1;
12435 let right = 2 * root + 2;
12436 let mut largest = root;
12437 if left < n {
12438 comparisons += 1;
12439 if arr[left] > arr[largest] {
12440 largest = left;
12441 }
12442 }
12443 if right < n {
12444 comparisons += 1;
12445 if arr[right] > arr[largest] {
12446 largest = right;
12447 }
12448 }
12449 if largest != root {
12450 arr.swap(root, largest);
12451 swaps += 1;
12452 root = largest;
12453 } else {
12454 break;
12455 }
12456 }
12457 }
12458 steps.push((arr.clone(), "Heap built".to_string()));
12459 for end in (1..n).rev() {
12460 arr.swap(0, end);
12461 swaps += 1;
12462 let mut root = 0;
12464 loop {
12465 let left = 2 * root + 1;
12466 let right = 2 * root + 2;
12467 let mut largest = root;
12468 if left < end {
12469 comparisons += 1;
12470 if arr[left] > arr[largest] {
12471 largest = left;
12472 }
12473 }
12474 if right < end {
12475 comparisons += 1;
12476 if arr[right] > arr[largest] {
12477 largest = right;
12478 }
12479 }
12480 if largest != root {
12481 arr.swap(root, largest);
12482 swaps += 1;
12483 root = largest;
12484 } else {
12485 break;
12486 }
12487 }
12488 steps.push((
12489 arr.clone(),
12490 format!("Extracted max, sorted: {} elements", n - end),
12491 ));
12492 }
12493 }
12494 _ => {}
12495 }
12496
12497 let max_steps = 12usize;
12499 let step_count = steps.len();
12500 let show_steps = if step_count > max_steps {
12501 let mut s = steps[..4].to_vec();
12503 s.push((vec![], format!("... {} more steps ...", step_count - 8)));
12504 s.extend_from_slice(&steps[step_count - 4..]);
12505 s
12506 } else {
12507 steps.clone()
12508 };
12509
12510 for (step_arr, label) in &show_steps {
12511 if step_arr.is_empty() {
12512 let _ = writeln!(out, " {}", label);
12513 continue;
12514 }
12515 let _ = writeln!(out, " → {}", label);
12516 let _ = write!(out, "{}", bar_render(step_arr));
12517 let _ = writeln!(out);
12518 }
12519
12520 let _ = writeln!(out);
12521 let _ = writeln!(out, " Final: {:?}", arr);
12522 let _ = writeln!(
12523 out,
12524 " Comparisons: {} | Swaps/moves: {} | Steps: {}",
12525 comparisons, swaps, step_count
12526 );
12527 let _ = writeln!(out);
12528 }
12529
12530 let _ = writeln!(out, "{}", sep);
12531 out
12532}
12533
12534pub fn number_format(query: &str) -> String {
12537 let mut out = String::new();
12538 let sep = "─".repeat(60);
12539 let _ = writeln!(out, "{}", sep);
12540 let _ = writeln!(out, " NUMBER FORMAT CONVERTER");
12541 let _ = writeln!(out, "{}", sep);
12542
12543 let q = query.trim();
12544
12545 let val: f64 = match q.replace(['_', ','], "").parse::<f64>() {
12547 Ok(v) => v,
12548 Err(_) => {
12549 let _ = writeln!(out, " Cannot parse: {}", q);
12550 let _ = writeln!(out, " Usage: hematite --number-format '1234567890'");
12551 let _ = writeln!(out, " hematite --number-format '6.022e23'");
12552 let _ = writeln!(out, "{}", sep);
12553 return out;
12554 }
12555 };
12556
12557 let _ = writeln!(out, " Input: {}", q);
12558 let _ = writeln!(out);
12559
12560 let fmt_thousands = |v: f64| -> String {
12562 if v == v.floor() && v.abs() < 1e15 {
12563 let i = v as i64;
12564 let s = format!("{}", i.abs());
12565 let with_sep: String = s
12566 .chars()
12567 .rev()
12568 .enumerate()
12569 .flat_map(|(i, c)| {
12570 if i > 0 && i % 3 == 0 {
12571 vec![',', c]
12572 } else {
12573 vec![c]
12574 }
12575 })
12576 .collect::<String>()
12577 .chars()
12578 .rev()
12579 .collect();
12580 if i < 0 {
12581 format!("-{}", with_sep)
12582 } else {
12583 with_sep
12584 }
12585 } else {
12586 let int_part = v.abs().floor() as i64;
12588 let frac = (v.abs() - int_part as f64).to_string();
12589 let frac_str = if frac.len() > 2 { &frac[1..] } else { "" };
12590 let s = format!("{}", int_part);
12591 let with_sep: String = s
12592 .chars()
12593 .rev()
12594 .enumerate()
12595 .flat_map(|(i, c)| {
12596 if i > 0 && i % 3 == 0 {
12597 vec![',', c]
12598 } else {
12599 vec![c]
12600 }
12601 })
12602 .collect::<String>()
12603 .chars()
12604 .rev()
12605 .collect();
12606 let signed = if v < 0.0 {
12607 format!("-{}{}", with_sep, frac_str)
12608 } else {
12609 format!("{}{}", with_sep, frac_str)
12610 };
12611 signed
12612 }
12613 };
12614
12615 let sci = if val == 0.0 {
12617 "0.000000e0".to_string()
12618 } else {
12619 let exp = val.abs().log10().floor() as i32;
12620 let mantissa = val / 10f64.powi(exp);
12621 format!("{:.6}e{}", mantissa, exp)
12622 };
12623
12624 let eng = if val == 0.0 {
12626 "0.000e0".to_string()
12627 } else {
12628 let exp = val.abs().log10().floor() as i32;
12629 let eng_exp = (exp as f64 / 3.0).floor() as i32 * 3;
12630 let mantissa = val / 10f64.powi(eng_exp);
12631 format!("{:.3}e{}", mantissa, eng_exp)
12632 };
12633
12634 let si = {
12636 let si_prefixes: &[(f64, &str, &str)] = &[
12637 (1e24, "Y", "yotta"),
12638 (1e21, "Z", "zetta"),
12639 (1e18, "E", "exa"),
12640 (1e15, "P", "peta"),
12641 (1e12, "T", "tera"),
12642 (1e9, "G", "giga"),
12643 (1e6, "M", "mega"),
12644 (1e3, "k", "kilo"),
12645 (1e0, "", ""),
12646 (1e-3, "m", "milli"),
12647 (1e-6, "μ", "micro"),
12648 (1e-9, "n", "nano"),
12649 (1e-12, "p", "pico"),
12650 (1e-15, "f", "femto"),
12651 ];
12652 let abs_val = val.abs();
12653 let mut result = format!("{:.6}", val);
12654 for &(scale, sym, name) in si_prefixes {
12655 if abs_val >= scale * 0.999 || scale <= 1e-12 {
12656 let scaled = val / scale;
12657 if sym.is_empty() {
12658 result = format!("{:.4}", scaled);
12659 } else {
12660 result = format!("{:.4} {} ({})", scaled, sym, name);
12661 }
12662 break;
12663 }
12664 }
12665 result
12666 };
12667
12668 let int_forms = if val == val.floor() && val.abs() < 2e63 {
12670 let i = val as i64;
12671 let u = i as u64;
12672 Some((
12673 format!("0x{:X}", u),
12674 format!("0b{:b}", u),
12675 format!("0o{:o}", u),
12676 ))
12677 } else {
12678 None
12679 };
12680
12681 let word_form = number_to_words(val);
12683
12684 let _ = writeln!(out, " ─── Number Representations ───");
12685 let _ = writeln!(out, " Decimal (formatted): {}", fmt_thousands(val));
12686 let _ = writeln!(out, " Scientific notation: {}", sci);
12687 let _ = writeln!(out, " Engineering notation: {}", eng);
12688 let _ = writeln!(out, " SI prefix: {}", si);
12689 if let Some((hex, bin, oct)) = int_forms {
12690 let _ = writeln!(out, " Hexadecimal: {}", hex);
12691 let _ = writeln!(out, " Binary: {}", bin);
12692 let _ = writeln!(out, " Octal: {}", oct);
12693 }
12694 let _ = writeln!(out, " Word form: {}", word_form);
12695 if val != 0.0 {
12696 let _ = writeln!(out, " Reciprocal: {:.6} (1/{})", 1.0 / val, q);
12697 let _ = writeln!(out, " Percentage: {:.4}%", val * 100.0);
12698 let log10 = val.abs().log10();
12699 let ln_v = val.abs().ln();
12700 let _ = writeln!(out, " log₁₀: {:.6}", log10);
12701 let _ = writeln!(out, " ln: {:.6}", ln_v);
12702 let _ = writeln!(out, " √: {:.6}", val.abs().sqrt());
12703 if val == val.floor() && val.abs() < 1e15 {
12704 let _ = writeln!(out, " ²: {}", fmt_thousands(val * val));
12705 }
12706 }
12707 let _ = writeln!(out, "{}", sep);
12708 out
12709}
12710
12711fn number_to_words(v: f64) -> String {
12712 if v == 0.0 {
12713 return "zero".to_string();
12714 }
12715 let is_neg = v < 0.0;
12716 let abs_v = v.abs();
12717
12718 if abs_v != abs_v.floor() || abs_v >= 1e15 {
12720 return "(too large or fractional for word form)".to_string();
12721 }
12722 let n = abs_v as u64;
12723
12724 let ones = [
12725 "",
12726 "one",
12727 "two",
12728 "three",
12729 "four",
12730 "five",
12731 "six",
12732 "seven",
12733 "eight",
12734 "nine",
12735 "ten",
12736 "eleven",
12737 "twelve",
12738 "thirteen",
12739 "fourteen",
12740 "fifteen",
12741 "sixteen",
12742 "seventeen",
12743 "eighteen",
12744 "nineteen",
12745 ];
12746 let tens = [
12747 "", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety",
12748 ];
12749
12750 let under100 = |n: u64| -> String {
12751 if n < 20 {
12752 ones[n as usize].to_string()
12753 } else {
12754 let t = tens[(n / 10) as usize];
12755 let o = ones[(n % 10) as usize];
12756 if o.is_empty() {
12757 t.to_string()
12758 } else {
12759 format!("{}-{}", t, o)
12760 }
12761 }
12762 };
12763
12764 let under1000 = |n: u64| -> String {
12765 if n < 100 {
12766 under100(n)
12767 } else {
12768 let h = ones[(n / 100) as usize];
12769 let rem = n % 100;
12770 if rem == 0 {
12771 format!("{} hundred", h)
12772 } else {
12773 format!("{} hundred {}", h, under100(rem))
12774 }
12775 }
12776 };
12777
12778 let scales: &[(u64, &str)] = &[
12779 (1_000_000_000_000, "trillion"),
12780 (1_000_000_000, "billion"),
12781 (1_000_000, "million"),
12782 (1_000, "thousand"),
12783 ];
12784
12785 let mut parts: Vec<String> = Vec::new();
12786 let mut remaining = n;
12787 for &(scale, name) in scales {
12788 if remaining >= scale {
12789 let chunk = remaining / scale;
12790 remaining %= scale;
12791 parts.push(format!("{} {}", under1000(chunk), name));
12792 }
12793 }
12794 if remaining > 0 {
12795 parts.push(under1000(remaining));
12796 }
12797
12798 let result = parts.join(" ");
12799 if is_neg {
12800 format!("negative {}", result)
12801 } else {
12802 result
12803 }
12804}
12805
12806pub fn string_dist(query: &str) -> String {
12809 let mut out = String::new();
12810 let sep = "─".repeat(60);
12811 let _ = writeln!(out, "{}", sep);
12812 let _ = writeln!(out, " STRING DISTANCE METRICS");
12813 let _ = writeln!(out, "{}", sep);
12814
12815 let (a, b) = if let Some(pos) = query.find(" vs ") {
12817 (query[..pos].trim(), query[pos + 4..].trim())
12818 } else if let Some(pos) = query.find(" | ") {
12819 (query[..pos].trim(), query[pos + 3..].trim())
12820 } else {
12821 let parts: Vec<&str> = query.splitn(2, ',').collect();
12823 if parts.len() == 2 {
12824 (parts[0].trim(), parts[1].trim())
12825 } else {
12826 let _ = writeln!(
12827 out,
12828 " Usage: hematite --levenshtein '<string1> vs <string2>'"
12829 );
12830 let _ = writeln!(out, " hematite --levenshtein 'kitten vs sitting'");
12831 let _ = writeln!(out, " hematite --levenshtein 'hello, helo'");
12832 let _ = writeln!(out, "{}", sep);
12833 return out;
12834 }
12835 };
12836
12837 let _ = writeln!(out, " String A: \"{}\" (len={})", a, a.chars().count());
12838 let _ = writeln!(out, " String B: \"{}\" (len={})", b, b.chars().count());
12839 let _ = writeln!(out);
12840
12841 let ac: Vec<char> = a.chars().collect();
12842 let bc: Vec<char> = b.chars().collect();
12843 let m = ac.len();
12844 let n = bc.len();
12845
12846 let lev_dist = {
12848 let mut dp = vec![vec![0usize; n + 1]; m + 1];
12849 for i in 0..=m {
12850 dp[i][0] = i;
12851 }
12852 for j in 0..=n {
12853 dp[0][j] = j;
12854 }
12855 for i in 1..=m {
12856 for j in 1..=n {
12857 if ac[i - 1] == bc[j - 1] {
12858 dp[i][j] = dp[i - 1][j - 1];
12859 } else {
12860 dp[i][j] = 1 + dp[i - 1][j - 1].min(dp[i - 1][j]).min(dp[i][j - 1]);
12861 }
12862 }
12863 }
12864 dp[m][n]
12865 };
12866 let max_len = m.max(n).max(1);
12867 let lev_sim = 1.0 - lev_dist as f64 / max_len as f64;
12868
12869 let dl_dist = {
12871 let mut dp = vec![vec![0usize; n + 1]; m + 1];
12872 for i in 0..=m {
12873 dp[i][0] = i;
12874 }
12875 for j in 0..=n {
12876 dp[0][j] = j;
12877 }
12878 for i in 1..=m {
12879 for j in 1..=n {
12880 let cost = if ac[i - 1] == bc[j - 1] { 0 } else { 1 };
12881 dp[i][j] = (dp[i - 1][j] + 1)
12882 .min(dp[i][j - 1] + 1)
12883 .min(dp[i - 1][j - 1] + cost);
12884 if i > 1 && j > 1 && ac[i - 1] == bc[j - 2] && ac[i - 2] == bc[j - 1] {
12885 dp[i][j] = dp[i][j].min(dp[i - 2][j - 2] + cost);
12886 }
12887 }
12888 }
12889 dp[m][n]
12890 };
12891
12892 let hamming = if m == n {
12894 let h = ac.iter().zip(bc.iter()).filter(|(a, b)| a != b).count();
12895 Some(h)
12896 } else {
12897 None
12898 };
12899
12900 let jaro = {
12902 if m == 0 && n == 0 {
12903 1.0f64
12904 } else if m == 0 || n == 0 {
12905 0.0f64
12906 } else {
12907 let match_dist = (m.max(n) / 2).saturating_sub(1);
12908 let mut s1_matches = vec![false; m];
12909 let mut s2_matches = vec![false; n];
12910 let mut matches = 0usize;
12911 for i in 0..m {
12912 let start = i.saturating_sub(match_dist);
12913 let end = (i + match_dist + 1).min(n);
12914 for j in start..end {
12915 if !s2_matches[j] && ac[i] == bc[j] {
12916 s1_matches[i] = true;
12917 s2_matches[j] = true;
12918 matches += 1;
12919 break;
12920 }
12921 }
12922 }
12923 if matches == 0 {
12924 0.0
12925 } else {
12926 let s1m: Vec<char> = ac
12927 .iter()
12928 .enumerate()
12929 .filter(|(i, _)| s1_matches[*i])
12930 .map(|(_, c)| *c)
12931 .collect();
12932 let s2m: Vec<char> = bc
12933 .iter()
12934 .enumerate()
12935 .filter(|(i, _)| s2_matches[*i])
12936 .map(|(_, c)| *c)
12937 .collect();
12938 let transpositions = s1m.iter().zip(s2m.iter()).filter(|(a, b)| a != b).count() / 2;
12939 let mf = matches as f64;
12940 (mf / m as f64 + mf / n as f64 + (mf - transpositions as f64) / mf) / 3.0
12941 }
12942 }
12943 };
12944
12945 let jaro_winkler = {
12947 let prefix_len = ac
12948 .iter()
12949 .zip(bc.iter())
12950 .take(4)
12951 .take_while(|(a, b)| a == b)
12952 .count();
12953 let p = 0.1f64;
12954 jaro + prefix_len as f64 * p * (1.0 - jaro)
12955 };
12956 let jaro_winkler = jaro_winkler.min(1.0);
12957
12958 let lcs_len = {
12960 let mut dp = vec![vec![0usize; n + 1]; m + 1];
12961 for i in 1..=m {
12962 for j in 1..=n {
12963 if ac[i - 1] == bc[j - 1] {
12964 dp[i][j] = dp[i - 1][j - 1] + 1;
12965 } else {
12966 dp[i][j] = dp[i - 1][j].max(dp[i][j - 1]);
12967 }
12968 }
12969 }
12970 dp[m][n]
12971 };
12972 let lcs_sim = if max_len > 0 {
12973 lcs_len as f64 / max_len as f64
12974 } else {
12975 0.0
12976 };
12977
12978 let (lcsub_len, lcsub) = {
12980 let mut best_len = 0usize;
12981 let mut best_end = 0usize;
12982 let mut dp = vec![vec![0usize; n + 1]; m + 1];
12983 for i in 1..=m {
12984 for j in 1..=n {
12985 if ac[i - 1] == bc[j - 1] {
12986 dp[i][j] = dp[i - 1][j - 1] + 1;
12987 if dp[i][j] > best_len {
12988 best_len = dp[i][j];
12989 best_end = i;
12990 }
12991 }
12992 }
12993 }
12994 let substr: String = ac[best_end.saturating_sub(best_len)..best_end]
12995 .iter()
12996 .collect();
12997 (best_len, substr)
12998 };
12999
13000 let _ = writeln!(out, " ─── Edit Distance ───");
13002 let _ = writeln!(
13003 out,
13004 " Levenshtein: {:>6} (similarity: {:.1}%)",
13005 lev_dist,
13006 lev_sim * 100.0
13007 );
13008 let _ = writeln!(
13009 out,
13010 " Damerau-Levenshtein: {:>6} (allows transpositions)",
13011 dl_dist
13012 );
13013 if let Some(h) = hamming {
13014 let _ = writeln!(
13015 out,
13016 " Hamming: {:>6} (substitutions only)",
13017 h
13018 );
13019 } else {
13020 let _ = writeln!(out, " Hamming: n/a (strings must be same length)");
13021 }
13022 let _ = writeln!(out);
13023 let _ = writeln!(out, " ─── Similarity Scores (0=no match, 1=identical) ───");
13024 let _ = writeln!(
13025 out,
13026 " Jaro: {:.6} ({:.1}%)",
13027 jaro,
13028 jaro * 100.0
13029 );
13030 let _ = writeln!(
13031 out,
13032 " Jaro-Winkler: {:.6} ({:.1}%)",
13033 jaro_winkler,
13034 jaro_winkler * 100.0
13035 );
13036 let _ = writeln!(
13037 out,
13038 " LCS similarity: {:.6} ({:.1}%)",
13039 lcs_sim,
13040 lcs_sim * 100.0
13041 );
13042 let _ = writeln!(out);
13043 let _ = writeln!(out, " ─── Common Subsequence / Substring ───");
13044 let _ = writeln!(
13045 out,
13046 " LCS length: {} ({:.1}% of max length)",
13047 lcs_len,
13048 lcs_sim * 100.0
13049 );
13050 let _ = writeln!(
13051 out,
13052 " Longest common sub: \"{}\" (length {})",
13053 lcsub, lcsub_len
13054 );
13055
13056 let _ = writeln!(out, "{}", sep);
13057 out
13058}
13059
13060pub fn text_stats(input: &str) -> String {
13063 let mut out = String::new();
13064 let sep = "─".repeat(60);
13065 let _ = writeln!(out, "{}", sep);
13066 let _ = writeln!(out, " TEXT STATISTICS & READABILITY");
13067 let _ = writeln!(out, "{}", sep);
13068
13069 let text = input.trim();
13070 if text.is_empty() {
13071 let _ = writeln!(out, " No text provided. Pass text as the argument.");
13072 let _ = writeln!(out, "{}", sep);
13073 return out;
13074 }
13075
13076 let char_count = text.chars().count();
13078 let char_no_space = text.chars().filter(|c| !c.is_whitespace()).count();
13079 let byte_count = text.len();
13080
13081 let words: Vec<&str> = text.split_whitespace().collect();
13083 let word_count = words.len();
13084
13085 let sentence_count = text
13087 .chars()
13088 .filter(|&c| c == '.' || c == '!' || c == '?')
13089 .count()
13090 .max(1);
13091
13092 let paragraph_count = text
13094 .split("\n\n")
13095 .filter(|p| !p.trim().is_empty())
13096 .count()
13097 .max(1);
13098
13099 let count_syllables = |word: &str| -> usize {
13101 let w = word.to_lowercase();
13102 let w: String = w.chars().filter(|c| c.is_alphabetic()).collect();
13103 if w.is_empty() {
13104 return 0;
13105 }
13106 let vowels = "aeiouy";
13107 let mut count = 0usize;
13108 let mut prev_vowel = false;
13109 let chars: Vec<char> = w.chars().collect();
13110 for &ch in &chars {
13111 let is_v = vowels.contains(ch);
13112 if is_v && !prev_vowel {
13113 count += 1;
13114 }
13115 prev_vowel = is_v;
13116 }
13117 if w.ends_with('e') && count > 1 {
13119 count = count.saturating_sub(1);
13120 }
13121 count.max(1)
13122 };
13123
13124 let total_syllables: usize = words.iter().map(|w| count_syllables(w)).sum();
13125 let avg_syllables = if word_count > 0 {
13126 total_syllables as f64 / word_count as f64
13127 } else {
13128 0.0
13129 };
13130
13131 let words_per_sentence = if sentence_count > 0 {
13133 word_count as f64 / sentence_count as f64
13134 } else {
13135 0.0
13136 };
13137 let syllables_per_word = avg_syllables;
13138
13139 let flesch_ease = 206.835 - 1.015 * words_per_sentence - 84.6 * syllables_per_word;
13141 let flesch_ease = flesch_ease.clamp(0.0, 100.0);
13142
13143 let fk_grade = 0.39 * words_per_sentence + 11.8 * syllables_per_word - 15.59;
13145 let fk_grade = fk_grade.max(0.0);
13146
13147 let complex_words = words.iter().filter(|w| count_syllables(w) >= 3).count();
13150 let fog_index =
13151 0.4 * (words_per_sentence + 100.0 * complex_words as f64 / word_count.max(1) as f64);
13152
13153 let smog = (complex_words as f64 * 30.0 / sentence_count as f64).sqrt() + 3.0;
13155
13156 let letters_per_100: f64 = char_no_space as f64 / word_count.max(1) as f64 * 100.0;
13158 let sent_per_100: f64 = sentence_count as f64 / word_count.max(1) as f64 * 100.0;
13159 let coleman_liau = 0.0588 * letters_per_100 - 0.296 * sent_per_100 - 15.8;
13160
13161 let ease_label = if flesch_ease >= 90.0 {
13163 "Very Easy (5th grade)"
13164 } else if flesch_ease >= 80.0 {
13165 "Easy (6th grade)"
13166 } else if flesch_ease >= 70.0 {
13167 "Fairly Easy (7th grade)"
13168 } else if flesch_ease >= 60.0 {
13169 "Standard (8th–9th grade)"
13170 } else if flesch_ease >= 50.0 {
13171 "Fairly Difficult (10th–12th grade)"
13172 } else if flesch_ease >= 30.0 {
13173 "Difficult (College)"
13174 } else {
13175 "Very Confusing (Professional)"
13176 };
13177
13178 let stop_words: &[&str] = &[
13180 "the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "is",
13181 "it", "its", "was", "are", "were", "be", "been", "being", "have", "has", "had", "do",
13182 "does", "did", "will", "would", "could", "should", "may", "might", "shall", "that", "this",
13183 "these", "those", "i", "you", "he", "she", "we", "they", "them", "his", "her", "our",
13184 "their", "my", "your", "as", "by", "from", "up", "about", "into", "through", "than",
13185 "more", "also", "if", "not", "so", "all", "can",
13186 ];
13187 let mut freq: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
13188 for word in &words {
13189 let clean: String = word
13190 .chars()
13191 .filter(|c| c.is_alphanumeric())
13192 .collect::<String>()
13193 .to_lowercase();
13194 if !clean.is_empty() && clean.len() > 1 && !stop_words.contains(&clean.as_str()) {
13195 *freq.entry(clean).or_insert(0) += 1;
13196 }
13197 }
13198 let mut freq_sorted: Vec<(String, usize)> = freq.into_iter().collect();
13199 freq_sorted.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
13200
13201 let mut char_freq: std::collections::HashMap<char, usize> = std::collections::HashMap::new();
13203 for ch in text.chars() {
13204 if ch.is_alphabetic() {
13205 *char_freq.entry(ch.to_ascii_lowercase()).or_insert(0) += 1;
13206 }
13207 }
13208 let mut char_sorted: Vec<(char, usize)> = char_freq.into_iter().collect();
13209 char_sorted.sort_by(|a, b| b.1.cmp(&a.1));
13210
13211 let total_word_chars: usize = words
13213 .iter()
13214 .map(|w| w.chars().filter(|c| c.is_alphabetic()).count())
13215 .sum();
13216 let avg_word_len = if word_count > 0 {
13217 total_word_chars as f64 / word_count as f64
13218 } else {
13219 0.0
13220 };
13221
13222 let mut word_lengths: Vec<(&&str, usize)> = words
13224 .iter()
13225 .map(|w| (w, w.chars().filter(|c| c.is_alphabetic()).count()))
13226 .collect();
13227 word_lengths.sort_by(|a, b| b.1.cmp(&a.1));
13228 word_lengths.dedup_by_key(|w| w.1);
13229
13230 let _ = writeln!(out, " ─── Basic Counts ───");
13232 let _ = writeln!(out, " Characters (total): {:>8}", char_count);
13233 let _ = writeln!(out, " Characters (no spaces): {:>8}", char_no_space);
13234 let _ = writeln!(out, " Bytes: {:>8}", byte_count);
13235 let _ = writeln!(out, " Words: {:>8}", word_count);
13236 let _ = writeln!(out, " Sentences: {:>8}", sentence_count);
13237 let _ = writeln!(out, " Paragraphs: {:>8}", paragraph_count);
13238 let _ = writeln!(out, " Syllables (est.): {:>8}", total_syllables);
13239 let _ = writeln!(out, " Complex words (3+ syl): {:>8}", complex_words);
13240 let _ = writeln!(out);
13241 let _ = writeln!(out, " ─── Averages ───");
13242 let _ = writeln!(out, " Avg word length: {:>8.2} chars", avg_word_len);
13243 let _ = writeln!(out, " Avg syllables/word: {:>8.2}", avg_syllables);
13244 let _ = writeln!(out, " Avg words/sentence: {:>8.2}", words_per_sentence);
13245 let _ = writeln!(out);
13246 let _ = writeln!(out, " ─── Readability Scores ───");
13247 let _ = writeln!(
13248 out,
13249 " Flesch Reading Ease: {:>8.1} ({})",
13250 flesch_ease, ease_label
13251 );
13252 let _ = writeln!(
13253 out,
13254 " Flesch-Kincaid Grade: {:>8.1} (Grade {:.0})",
13255 fk_grade, fk_grade
13256 );
13257 let _ = writeln!(
13258 out,
13259 " Gunning Fog Index: {:>8.1} (Grade {:.0})",
13260 fog_index, fog_index
13261 );
13262 let _ = writeln!(
13263 out,
13264 " SMOG Index: {:>8.1} (Grade {:.0})",
13265 smog, smog
13266 );
13267 let _ = writeln!(
13268 out,
13269 " Coleman-Liau Index: {:>8.1} (Grade {:.0})",
13270 coleman_liau, coleman_liau
13271 );
13272 let _ = writeln!(out);
13273 let _ = writeln!(out, " ─── Top Words (excluding stop words) ───");
13274 for (word, count) in freq_sorted.iter().take(20) {
13275 let bar: String = "█".repeat((count * 30 / freq_sorted[0].1.max(1)).min(30));
13276 let _ = writeln!(out, " {:>4}x {:<20} {}", count, word, bar);
13277 }
13278 let _ = writeln!(out);
13279 let _ = writeln!(out, " ─── Letter Frequency ───");
13280 let total_letters: usize = char_sorted.iter().map(|&(_, c)| c).sum();
13281 for (ch, count) in char_sorted.iter().take(10) {
13282 let pct = *count as f64 / total_letters.max(1) as f64 * 100.0;
13283 let bar: String = "█".repeat((pct as usize * 2).min(40));
13284 let _ = writeln!(out, " '{}': {:>5} ({:5.2}%) {}", ch, count, pct, bar);
13285 }
13286 if !word_lengths.is_empty() {
13287 let _ = writeln!(out);
13288 let _ = writeln!(out, " ─── Longest Words ───");
13289 for (w, len) in word_lengths.iter().take(5) {
13290 let clean: String = w.chars().filter(|c| c.is_alphanumeric()).collect();
13291 let _ = writeln!(out, " {} ({} chars)", clean, len);
13292 }
13293 }
13294
13295 let _ = writeln!(out, "{}", sep);
13296 out
13297}