1use crate::NQuin;
13use std::collections::HashMap;
14
15#[derive(Debug, Clone, PartialEq)]
17pub enum Expr {
18 Const(f64),
19 Var(String),
20 Add(Box<Expr>, Box<Expr>),
21 Sub(Box<Expr>, Box<Expr>),
22 Mul(Box<Expr>, Box<Expr>),
23 Div(Box<Expr>, Box<Expr>),
24 Pow(Box<Expr>, i32),
26 Neg(Box<Expr>),
27 Sqrt(Box<Expr>),
29 Exp(Box<Expr>),
31 Ln(Box<Expr>),
33 Sin(Box<Expr>),
34 Cos(Box<Expr>),
35 Tan(Box<Expr>),
37}
38
39pub fn c(v: f64) -> Expr {
41 Expr::Const(v)
42}
43pub fn var(name: &str) -> Expr {
44 Expr::Var(name.to_string())
45}
46pub fn add(a: Expr, b: Expr) -> Expr {
47 Expr::Add(Box::new(a), Box::new(b))
48}
49pub fn sub(a: Expr, b: Expr) -> Expr {
50 Expr::Sub(Box::new(a), Box::new(b))
51}
52pub fn mul(a: Expr, b: Expr) -> Expr {
53 Expr::Mul(Box::new(a), Box::new(b))
54}
55pub fn div(a: Expr, b: Expr) -> Expr {
56 Expr::Div(Box::new(a), Box::new(b))
57}
58pub fn pow(a: Expr, e: i32) -> Expr {
59 Expr::Pow(Box::new(a), e)
60}
61pub fn neg(a: Expr) -> Expr {
62 Expr::Neg(Box::new(a))
63}
64pub fn sqrt(a: Expr) -> Expr {
65 Expr::Sqrt(Box::new(a))
66}
67pub fn exp(a: Expr) -> Expr {
68 Expr::Exp(Box::new(a))
69}
70pub fn ln(a: Expr) -> Expr {
71 Expr::Ln(Box::new(a))
72}
73pub fn sin(a: Expr) -> Expr {
74 Expr::Sin(Box::new(a))
75}
76pub fn cos(a: Expr) -> Expr {
77 Expr::Cos(Box::new(a))
78}
79pub fn tan(a: Expr) -> Expr {
80 Expr::Tan(Box::new(a))
81}
82
83impl Expr {
84 pub fn eval(&self, env: &HashMap<String, f64>) -> Option<f64> {
87 let v = match self {
88 Expr::Const(k) => *k,
89 Expr::Var(name) => *env.get(name)?,
90 Expr::Add(a, b) => a.eval(env)? + b.eval(env)?,
91 Expr::Sub(a, b) => a.eval(env)? - b.eval(env)?,
92 Expr::Mul(a, b) => a.eval(env)? * b.eval(env)?,
93 Expr::Div(a, b) => {
94 let d = b.eval(env)?;
95 if d == 0.0 {
96 return None;
97 }
98 a.eval(env)? / d
99 }
100 Expr::Pow(a, e) => a.eval(env)?.powi(*e),
101 Expr::Neg(a) => -a.eval(env)?,
102 Expr::Sqrt(a) => {
103 let x = a.eval(env)?;
104 if x < 0.0 {
105 return None;
106 }
107 x.sqrt()
108 }
109 Expr::Exp(a) => a.eval(env)?.exp(),
110 Expr::Ln(a) => {
111 let x = a.eval(env)?;
112 if x <= 0.0 {
113 return None;
114 }
115 x.ln()
116 }
117 Expr::Sin(a) => a.eval(env)?.sin(),
118 Expr::Cos(a) => a.eval(env)?.cos(),
119 Expr::Tan(a) => a.eval(env)?.tan(),
120 };
121 if v.is_finite() {
122 Some(v)
123 } else {
124 None
125 }
126 }
127}
128
129pub fn differentiate(expr: &Expr, wrt: &str) -> Expr {
132 match expr {
133 Expr::Const(_) => c(0.0),
134 Expr::Var(name) => {
135 if name == wrt {
136 c(1.0)
137 } else {
138 c(0.0)
139 }
140 }
141 Expr::Add(a, b) => add(differentiate(a, wrt), differentiate(b, wrt)),
142 Expr::Sub(a, b) => sub(differentiate(a, wrt), differentiate(b, wrt)),
143 Expr::Mul(a, b) => add(
145 mul(differentiate(a, wrt), (**b).clone()),
146 mul((**a).clone(), differentiate(b, wrt)),
147 ),
148 Expr::Div(a, b) => div(
150 sub(
151 mul(differentiate(a, wrt), (**b).clone()),
152 mul((**a).clone(), differentiate(b, wrt)),
153 ),
154 pow((**b).clone(), 2),
155 ),
156 Expr::Pow(a, e) => mul(
158 mul(c(*e as f64), pow((**a).clone(), e - 1)),
159 differentiate(a, wrt),
160 ),
161 Expr::Neg(a) => neg(differentiate(a, wrt)),
162 Expr::Sqrt(a) => div(differentiate(a, wrt), mul(c(2.0), sqrt((**a).clone()))),
164 Expr::Exp(a) => mul(exp((**a).clone()), differentiate(a, wrt)),
166 Expr::Ln(a) => div(differentiate(a, wrt), (**a).clone()),
168 Expr::Sin(a) => mul(cos((**a).clone()), differentiate(a, wrt)),
170 Expr::Cos(a) => mul(neg(sin((**a).clone())), differentiate(a, wrt)),
172 Expr::Tan(a) => div(differentiate(a, wrt), pow(cos((**a).clone()), 2)),
174 }
175}
176
177pub fn simplify(expr: &Expr) -> Expr {
181 let mut cur = expr.clone();
182 for _ in 0..16 {
183 let next = simplify_once(&cur);
184 if next == cur {
185 break;
186 }
187 cur = next;
188 }
189 cur
190}
191
192fn simplify_once(expr: &Expr) -> Expr {
193 match expr {
194 Expr::Const(_) | Expr::Var(_) => expr.clone(),
195 Expr::Add(a, b) => {
196 let (a, b) = (simplify_once(a), simplify_once(b));
197 match (&a, &b) {
198 (Expr::Const(x), Expr::Const(y)) => c(x + y),
199 (Expr::Const(z), _) if *z == 0.0 => b,
200 (_, Expr::Const(z)) if *z == 0.0 => a,
201 _ if a == b => mul(c(2.0), a), _ => add(a, b),
203 }
204 }
205 Expr::Sub(a, b) => {
206 let (a, b) = (simplify_once(a), simplify_once(b));
207 match (&a, &b) {
208 (Expr::Const(x), Expr::Const(y)) => c(x - y),
209 (_, Expr::Const(z)) if *z == 0.0 => a,
210 _ if a == b => c(0.0), _ => sub(a, b),
212 }
213 }
214 Expr::Mul(a, b) => {
215 let (a, b) = (simplify_once(a), simplify_once(b));
216 match (&a, &b) {
217 (Expr::Const(x), Expr::Const(y)) => c(x * y),
218 (Expr::Const(z), _) | (_, Expr::Const(z)) if *z == 0.0 => c(0.0),
219 (Expr::Const(o), _) if *o == 1.0 => b,
220 (_, Expr::Const(o)) if *o == 1.0 => a,
221 _ => mul(a, b),
222 }
223 }
224 Expr::Div(a, b) => {
225 let (a, b) = (simplify_once(a), simplify_once(b));
226 match (&a, &b) {
227 (Expr::Const(x), Expr::Const(y)) if *y != 0.0 => c(x / y),
228 (Expr::Const(z), _) if *z == 0.0 => c(0.0),
229 (_, Expr::Const(o)) if *o == 1.0 => a,
230 _ if a == b => c(1.0), _ => div(a, b),
232 }
233 }
234 Expr::Pow(a, e) => {
235 let a = simplify_once(a);
236 match (&a, e) {
237 (_, 0) => c(1.0),
238 (_, 1) => a,
239 (Expr::Const(x), _) => c(x.powi(*e)),
240 _ => pow(a, *e),
241 }
242 }
243 Expr::Neg(a) => {
244 let a = simplify_once(a);
245 match &a {
246 Expr::Const(x) => c(-x),
247 Expr::Neg(inner) => (**inner).clone(), _ => neg(a),
249 }
250 }
251 Expr::Sqrt(a) => {
252 let a = simplify_once(a);
253 match &a {
254 Expr::Const(x) if *x >= 0.0 => {
255 let r = x.sqrt();
256 if r.fract() == 0.0 {
258 c(r)
259 } else {
260 sqrt(a)
261 }
262 }
263 _ => sqrt(a),
264 }
265 }
266 Expr::Exp(a) => {
267 let a = simplify_once(a);
268 match &a {
269 Expr::Const(z) if *z == 0.0 => c(1.0), Expr::Ln(inner) => (**inner).clone(), _ => exp(a),
272 }
273 }
274 Expr::Ln(a) => {
275 let a = simplify_once(a);
276 match &a {
277 Expr::Const(o) if *o == 1.0 => c(0.0), Expr::Exp(inner) => (**inner).clone(), _ => ln(a),
280 }
281 }
282 Expr::Sin(a) => {
283 let a = simplify_once(a);
284 match &a {
285 Expr::Const(z) if *z == 0.0 => c(0.0), _ => sin(a),
287 }
288 }
289 Expr::Cos(a) => {
290 let a = simplify_once(a);
291 match &a {
292 Expr::Const(z) if *z == 0.0 => c(1.0), _ => cos(a),
294 }
295 }
296 Expr::Tan(a) => {
297 let a = simplify_once(a);
298 match &a {
299 Expr::Const(z) if *z == 0.0 => c(0.0), _ => tan(a),
301 }
302 }
303 }
304}
305
306pub fn solve_quadratic_symbolic(a: f64, b: f64, cc: f64) -> Vec<Expr> {
310 if a == 0.0 {
311 if b == 0.0 {
312 return Vec::new();
313 }
314 return vec![simplify(&div(neg(c(cc)), c(b)))];
315 }
316 let disc = sub(pow(c(b), 2), mul(c(4.0), mul(c(a), c(cc)))); let root_plus = div(add(neg(c(b)), sqrt(disc.clone())), mul(c(2.0), c(a)));
318 let root_minus = div(sub(neg(c(b)), sqrt(disc)), mul(c(2.0), c(a)));
319 vec![simplify(&root_plus), simplify(&root_minus)]
320}
321
322pub fn expand(expr: &Expr) -> Expr {
326 match expr {
327 Expr::Const(_) | Expr::Var(_) => expr.clone(),
328 Expr::Add(a, b) => add(expand(a), expand(b)),
329 Expr::Sub(a, b) => sub(expand(a), expand(b)),
330 Expr::Neg(a) => neg(expand(a)),
331 Expr::Sqrt(a) => sqrt(expand(a)),
332 Expr::Exp(a) => exp(expand(a)),
333 Expr::Ln(a) => ln(expand(a)),
334 Expr::Sin(a) => sin(expand(a)),
335 Expr::Cos(a) => cos(expand(a)),
336 Expr::Tan(a) => tan(expand(a)),
337 Expr::Div(a, b) => div(expand(a), expand(b)),
338 Expr::Mul(a, b) => expand_mul(&expand(a), &expand(b)),
339 Expr::Pow(a, e) => expand_pow(&expand(a), *e),
340 }
341}
342
343fn expand_mul(a: &Expr, b: &Expr) -> Expr {
344 match (a, b) {
345 (Expr::Add(a1, a2), _) => add(expand_mul(a1, b), expand_mul(a2, b)),
346 (Expr::Sub(a1, a2), _) => sub(expand_mul(a1, b), expand_mul(a2, b)),
347 (_, Expr::Add(b1, b2)) => add(expand_mul(a, b1), expand_mul(a, b2)),
348 (_, Expr::Sub(b1, b2)) => sub(expand_mul(a, b1), expand_mul(a, b2)),
349 (Expr::Neg(a1), _) => neg(expand_mul(a1, b)),
350 (_, Expr::Neg(b1)) => neg(expand_mul(a, b1)),
351 _ => mul(a.clone(), b.clone()),
352 }
353}
354
355fn expand_pow(base: &Expr, e: i32) -> Expr {
356 if e <= 1 || e > 8 {
357 return pow(base.clone(), e);
358 }
359 let mut acc = base.clone();
360 for _ in 1..e {
361 acc = expand_mul(&acc, base);
362 }
363 acc
364}
365
366pub fn factor_quadratic(a: f64, b: f64, cc: f64, varname: &str) -> Option<Expr> {
371 if a == 0.0 {
372 return None;
373 }
374 let disc = b * b - 4.0 * a * cc;
375 if disc < 0.0 {
376 return None;
377 }
378 let sq = disc.sqrt();
379 let clean = |r: f64| {
380 let halves = (r * 2.0).round() / 2.0;
381 if (halves - r).abs() < 1e-9 {
382 halves
383 } else {
384 r
385 }
386 };
387 let r1 = clean((-b + sq) / (2.0 * a));
388 let r2 = clean((-b - sq) / (2.0 * a));
389 let prod = mul(sub(var(varname), c(r1)), sub(var(varname), c(r2)));
390 Some(if (a - 1.0).abs() < 1e-12 {
391 prod
392 } else {
393 mul(c(a), prod)
394 })
395}
396
397pub fn expr_citation_hash(expr: &Expr) -> u64 {
401 crate::q_hash(&format!("{expr}"))
402}
403
404fn name_tag(kind: &str) -> u64 {
417 crate::q_hash(kind)
418}
419
420fn pack_name(name: &str) -> (u64, u64) {
421 let bytes = name.as_bytes();
422 let len = bytes.len().min(8);
423 let mut v = 0u64;
424 for (i, &b) in bytes.iter().take(8).enumerate() {
425 v |= (b as u64) << (i * 8);
426 }
427 (v, len as u64)
428}
429
430fn unpack_name(v: u64, len: u64) -> String {
431 let len = (len as usize).min(8);
432 let mut s = String::with_capacity(len);
433 for i in 0..len {
434 s.push(((v >> (i * 8)) & 0xFF) as u8 as char);
435 }
436 s
437}
438
439fn push_node(
440 out: &mut Vec<NQuin>,
441 predicate: u64,
442 object: u64,
443 context: u64,
444 metadata: u64,
445) -> usize {
446 let idx = out.len();
447 let subject = idx as u64;
448 out.push(NQuin {
449 subject,
450 predicate,
451 object,
452 context,
453 metadata,
454 parity: NQuin::calculate_parity(subject, predicate, object, context, metadata),
455 });
456 idx
457}
458
459fn encode(e: &Expr, out: &mut Vec<NQuin>) -> usize {
460 match e {
461 Expr::Const(k) => push_node(out, name_tag("cas:const"), k.to_bits(), 0, 0),
462 Expr::Var(name) => {
463 let (packed, len) = pack_name(name);
464 push_node(out, name_tag("cas:var"), packed, 0, len)
465 }
466 Expr::Add(a, b) => {
467 let (l, r) = (encode(a, out), encode(b, out));
468 push_node(out, name_tag("cas:add"), l as u64, r as u64, 0)
469 }
470 Expr::Sub(a, b) => {
471 let (l, r) = (encode(a, out), encode(b, out));
472 push_node(out, name_tag("cas:sub"), l as u64, r as u64, 0)
473 }
474 Expr::Mul(a, b) => {
475 let (l, r) = (encode(a, out), encode(b, out));
476 push_node(out, name_tag("cas:mul"), l as u64, r as u64, 0)
477 }
478 Expr::Div(a, b) => {
479 let (l, r) = (encode(a, out), encode(b, out));
480 push_node(out, name_tag("cas:div"), l as u64, r as u64, 0)
481 }
482 Expr::Pow(a, exp) => {
483 let l = encode(a, out);
484 push_node(out, name_tag("cas:pow"), l as u64, 0, (*exp as i64) as u64)
485 }
486 Expr::Neg(a) => {
487 let l = encode(a, out);
488 push_node(out, name_tag("cas:neg"), l as u64, 0, 0)
489 }
490 Expr::Sqrt(a) => {
491 let l = encode(a, out);
492 push_node(out, name_tag("cas:sqrt"), l as u64, 0, 0)
493 }
494 Expr::Exp(a) => {
495 let l = encode(a, out);
496 push_node(out, name_tag("cas:exp"), l as u64, 0, 0)
497 }
498 Expr::Ln(a) => {
499 let l = encode(a, out);
500 push_node(out, name_tag("cas:ln"), l as u64, 0, 0)
501 }
502 Expr::Sin(a) => {
503 let l = encode(a, out);
504 push_node(out, name_tag("cas:sin"), l as u64, 0, 0)
505 }
506 Expr::Cos(a) => {
507 let l = encode(a, out);
508 push_node(out, name_tag("cas:cos"), l as u64, 0, 0)
509 }
510 Expr::Tan(a) => {
511 let l = encode(a, out);
512 push_node(out, name_tag("cas:tan"), l as u64, 0, 0)
513 }
514 }
515}
516
517pub fn to_quins(expr: &Expr) -> Vec<NQuin> {
519 let mut out = Vec::new();
520 encode(expr, &mut out);
521 out
522}
523
524fn decode(quins: &[NQuin], idx: usize) -> Result<Expr, String> {
525 let node = quins
526 .get(idx)
527 .ok_or_else(|| format!("child index {idx} out of range"))?;
528 let p = node.predicate;
529 let child = |i: u64| decode(quins, i as usize);
530 if p == name_tag("cas:const") {
531 Ok(c(f64::from_bits(node.object)))
532 } else if p == name_tag("cas:var") {
533 Ok(var(&unpack_name(node.object, node.metadata)))
534 } else if p == name_tag("cas:add") {
535 Ok(add(child(node.object)?, child(node.context)?))
536 } else if p == name_tag("cas:sub") {
537 Ok(sub(child(node.object)?, child(node.context)?))
538 } else if p == name_tag("cas:mul") {
539 Ok(mul(child(node.object)?, child(node.context)?))
540 } else if p == name_tag("cas:div") {
541 Ok(div(child(node.object)?, child(node.context)?))
542 } else if p == name_tag("cas:pow") {
543 Ok(pow(child(node.object)?, node.metadata as i64 as i32))
544 } else if p == name_tag("cas:neg") {
545 Ok(neg(child(node.object)?))
546 } else if p == name_tag("cas:sqrt") {
547 Ok(sqrt(child(node.object)?))
548 } else if p == name_tag("cas:exp") {
549 Ok(exp(child(node.object)?))
550 } else if p == name_tag("cas:ln") {
551 Ok(ln(child(node.object)?))
552 } else if p == name_tag("cas:sin") {
553 Ok(sin(child(node.object)?))
554 } else if p == name_tag("cas:cos") {
555 Ok(cos(child(node.object)?))
556 } else if p == name_tag("cas:tan") {
557 Ok(tan(child(node.object)?))
558 } else {
559 Err(format!("unknown CAS node tag in quin {idx}"))
560 }
561}
562
563pub fn from_quins(quins: &[NQuin]) -> Result<Expr, String> {
565 if quins.is_empty() {
566 return Err("empty quin sequence".to_string());
567 }
568 decode(quins, quins.len() - 1)
569}
570
571pub fn parse(input: &str) -> Result<Expr, String> {
581 let tokens = tokenize(input)?;
582 let mut p = Parser { tokens, pos: 0 };
583 let e = p.parse_expr()?;
584 if p.pos != p.tokens.len() {
585 return Err(format!("unexpected trailing tokens at {}", p.pos));
586 }
587 Ok(e)
588}
589
590#[derive(Debug, Clone, PartialEq)]
591enum Tok {
592 Num(f64),
593 Ident(String),
594 Plus,
595 Minus,
596 Star,
597 Slash,
598 Caret,
599 LParen,
600 RParen,
601}
602
603fn tokenize(s: &str) -> Result<Vec<Tok>, String> {
604 let mut out = Vec::new();
605 let chars: Vec<char> = s.chars().collect();
606 let mut i = 0;
607 while i < chars.len() {
608 let ch = chars[i];
609 match ch {
610 ' ' | '\t' | '\n' | '\r' => i += 1,
611 '+' => {
612 out.push(Tok::Plus);
613 i += 1;
614 }
615 '-' => {
616 out.push(Tok::Minus);
617 i += 1;
618 }
619 '*' => {
620 out.push(Tok::Star);
621 i += 1;
622 }
623 '/' => {
624 out.push(Tok::Slash);
625 i += 1;
626 }
627 '^' => {
628 out.push(Tok::Caret);
629 i += 1;
630 }
631 '(' => {
632 out.push(Tok::LParen);
633 i += 1;
634 }
635 ')' => {
636 out.push(Tok::RParen);
637 i += 1;
638 }
639 c if c.is_ascii_digit() || c == '.' => {
640 let start = i;
641 while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
642 i += 1;
643 }
644 let num: String = chars[start..i].iter().collect();
645 out.push(Tok::Num(
646 num.parse().map_err(|_| format!("bad number '{num}'"))?,
647 ));
648 }
649 c if c.is_ascii_alphabetic() || c == '_' => {
650 let start = i;
651 while i < chars.len() && (chars[i].is_ascii_alphanumeric() || chars[i] == '_') {
652 i += 1;
653 }
654 out.push(Tok::Ident(chars[start..i].iter().collect()));
655 }
656 other => return Err(format!("unexpected character '{other}'")),
657 }
658 }
659 Ok(out)
660}
661
662struct Parser {
663 tokens: Vec<Tok>,
664 pos: usize,
665}
666
667impl Parser {
668 fn peek(&self) -> Option<&Tok> {
669 self.tokens.get(self.pos)
670 }
671 fn next(&mut self) -> Option<Tok> {
672 let t = self.tokens.get(self.pos).cloned();
673 if t.is_some() {
674 self.pos += 1;
675 }
676 t
677 }
678
679 fn parse_expr(&mut self) -> Result<Expr, String> {
680 let mut left = self.parse_term()?;
681 while let Some(op) = self.peek() {
682 match op {
683 Tok::Plus => {
684 self.next();
685 left = add(left, self.parse_term()?);
686 }
687 Tok::Minus => {
688 self.next();
689 left = sub(left, self.parse_term()?);
690 }
691 _ => break,
692 }
693 }
694 Ok(left)
695 }
696
697 fn parse_term(&mut self) -> Result<Expr, String> {
698 let mut left = self.parse_factor()?;
699 while let Some(op) = self.peek() {
700 match op {
701 Tok::Star => {
702 self.next();
703 left = mul(left, self.parse_factor()?);
704 }
705 Tok::Slash => {
706 self.next();
707 left = div(left, self.parse_factor()?);
708 }
709 _ => break,
710 }
711 }
712 Ok(left)
713 }
714
715 fn parse_factor(&mut self) -> Result<Expr, String> {
716 let base = self.parse_unary()?;
717 if let Some(Tok::Caret) = self.peek() {
718 self.next();
719 let neg_exp = matches!(self.peek(), Some(Tok::Minus));
721 if neg_exp {
722 self.next();
723 }
724 match self.next() {
725 Some(Tok::Num(n)) if n.fract() == 0.0 => {
726 let e = n as i32 * if neg_exp { -1 } else { 1 };
727 Ok(pow(base, e))
728 }
729 _ => Err("'^' requires an integer exponent".to_string()),
730 }
731 } else {
732 Ok(base)
733 }
734 }
735
736 fn parse_unary(&mut self) -> Result<Expr, String> {
737 if let Some(Tok::Minus) = self.peek() {
738 self.next();
739 return Ok(neg(self.parse_unary()?));
740 }
741 self.parse_base()
742 }
743
744 fn parse_base(&mut self) -> Result<Expr, String> {
745 match self.next() {
746 Some(Tok::Num(n)) => Ok(c(n)),
747 Some(Tok::Ident(name)) => {
748 let unary: Option<fn(Expr) -> Expr> = match name.as_str() {
750 "sqrt" => Some(sqrt),
751 "exp" => Some(exp),
752 "ln" => Some(ln),
753 "sin" => Some(sin),
754 "cos" => Some(cos),
755 "tan" => Some(tan),
756 _ => None,
757 };
758 if let Some(ctor) = unary {
759 self.expect(Tok::LParen)?;
760 let inner = self.parse_expr()?;
761 self.expect(Tok::RParen)?;
762 Ok(ctor(inner))
763 } else {
764 Ok(var(&name))
765 }
766 }
767 Some(Tok::LParen) => {
768 let inner = self.parse_expr()?;
769 self.expect(Tok::RParen)?;
770 Ok(inner)
771 }
772 other => Err(format!("unexpected token: {other:?}")),
773 }
774 }
775
776 fn expect(&mut self, t: Tok) -> Result<(), String> {
777 if self.next().as_ref() == Some(&t) {
778 Ok(())
779 } else {
780 Err(format!("expected {t:?}"))
781 }
782 }
783}
784
785impl std::fmt::Display for Expr {
786 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
787 match self {
788 Expr::Const(k) => write!(f, "{k}"),
789 Expr::Var(name) => write!(f, "{name}"),
790 Expr::Add(a, b) => write!(f, "({a} + {b})"),
791 Expr::Sub(a, b) => write!(f, "({a} - {b})"),
792 Expr::Mul(a, b) => write!(f, "({a} * {b})"),
793 Expr::Div(a, b) => write!(f, "({a} / {b})"),
794 Expr::Pow(a, e) => write!(f, "({a}^{e})"),
795 Expr::Neg(a) => write!(f, "(-{a})"),
796 Expr::Sqrt(a) => write!(f, "sqrt({a})"),
797 Expr::Exp(a) => write!(f, "exp({a})"),
798 Expr::Ln(a) => write!(f, "ln({a})"),
799 Expr::Sin(a) => write!(f, "sin({a})"),
800 Expr::Cos(a) => write!(f, "cos({a})"),
801 Expr::Tan(a) => write!(f, "tan({a})"),
802 }
803 }
804}
805
806#[cfg(test)]
807mod tests {
808 use super::*;
809
810 fn env1(name: &str, v: f64) -> HashMap<String, f64> {
811 let mut m = HashMap::new();
812 m.insert(name.to_string(), v);
813 m
814 }
815
816 #[test]
817 fn differentiate_matches_finite_difference() {
818 let f = add(sub(pow(var("x"), 3), mul(c(2.0), pow(var("x"), 2))), c(5.0));
821 let df = simplify(&differentiate(&f, "x"));
822 for &x in &[-2.0, -0.5, 1.0, 3.7] {
823 let h = 1e-6;
824 let fd = (f.eval(&env1("x", x + h)).unwrap() - f.eval(&env1("x", x - h)).unwrap())
825 / (2.0 * h);
826 let sym = df.eval(&env1("x", x)).unwrap();
827 assert!((sym - fd).abs() < 1e-4, "x={x}: symbolic {sym} vs fd {fd}");
828 }
829 }
830
831 #[test]
832 fn simplify_identities() {
833 assert_eq!(simplify(&add(var("x"), c(0.0))), var("x"));
834 assert_eq!(simplify(&mul(var("x"), c(1.0))), var("x"));
835 assert_eq!(simplify(&mul(var("x"), c(0.0))), c(0.0));
836 assert_eq!(simplify(&pow(var("x"), 0)), c(1.0));
837 assert_eq!(simplify(&neg(neg(var("x")))), var("x"));
838 assert_eq!(simplify(&sub(var("x"), var("x"))), c(0.0));
839 assert_eq!(simplify(&add(var("x"), var("x"))), mul(c(2.0), var("x")));
840 assert_eq!(simplify(&add(c(2.0), c(3.0))), c(5.0));
841 }
842
843 #[test]
844 fn symbolic_quadratic_agrees_with_numeric() {
845 let roots = solve_quadratic_symbolic(1.0, -5.0, 6.0);
847 assert_eq!(roots.len(), 2);
848 let empty = HashMap::new();
849 let mut vals: Vec<f64> = roots.iter().map(|r| r.eval(&empty).unwrap()).collect();
850 vals.sort_by(|a, b| a.partial_cmp(b).unwrap());
851 assert!((vals[0] - 2.0).abs() < 1e-12 && (vals[1] - 3.0).abs() < 1e-12);
852 }
853
854 #[test]
855 fn parse_and_differentiate_roundtrip() {
856 let f = parse("x^3 - 2*x^2 + 5").unwrap();
859 let df = simplify(&differentiate(&f, "x"));
860 assert!((df.eval(&env1("x", 2.0)).unwrap() - 4.0).abs() < 1e-9);
861 assert_eq!(
863 parse("2 + 3 * 4").unwrap().eval(&HashMap::new()).unwrap(),
864 14.0
865 );
866 let g = parse("sqrt((a + 3))").unwrap();
868 assert!((g.eval(&env1("a", 1.0)).unwrap() - 2.0).abs() < 1e-12);
869 assert!(parse("2 +* 3").is_err());
871 }
872
873 #[test]
874 fn expand_distributes_and_preserves_value() {
875 let e = mul(add(var("x"), c(1.0)), add(var("x"), c(2.0)));
877 let ex = expand(&e);
878 for &x in &[-3.0, 0.0, 2.5, 7.0] {
879 let want = e.eval(&env1("x", x)).unwrap();
880 let got = ex.eval(&env1("x", x)).unwrap();
881 assert!((want - got).abs() < 1e-9, "expand changed value at x={x}");
882 }
883 let cube = pow(add(var("x"), c(1.0)), 3);
885 let cube_x = expand(&cube);
886 assert!((cube_x.eval(&env1("x", 2.0)).unwrap() - 27.0).abs() < 1e-9);
887 }
888
889 #[test]
890 fn factor_quadratic_inverts_expand() {
891 let f = factor_quadratic(1.0, -5.0, 6.0, "x").unwrap();
893 let original = add(sub(pow(var("x"), 2), mul(c(5.0), var("x"))), c(6.0));
894 for &x in &[-1.0, 0.0, 2.0, 3.0, 5.5] {
895 let a = f.eval(&env1("x", x)).unwrap();
896 let b = original.eval(&env1("x", x)).unwrap();
897 assert!((a - b).abs() < 1e-9, "factored != original at x={x}");
898 }
899 assert!(factor_quadratic(1.0, 0.0, 1.0, "x").is_none());
901 }
902
903 #[test]
904 fn expr_quin_roundtrip() {
905 let e = parse("x^2 + 3*x + 2").unwrap();
907 let quins = to_quins(&e);
908 assert!(!quins.is_empty());
909 assert!(
910 quins.iter().all(|q| q.verify_ecc_parity()),
911 "CAS Quins must carry five-field ECC so a v3 volume verify can accept them"
912 );
913 let back = from_quins(&quins).unwrap();
914 assert_eq!(e, back);
915
916 let e2 = sqrt(neg(sub(var("price"), c(4.0))));
918 assert_eq!(from_quins(&to_quins(&e2)).unwrap(), e2);
919 }
920
921 #[test]
922 fn transcendental_diff_eval_parse_and_quins() {
923 for f in [
926 sin(var("x")),
927 cos(var("x")),
928 exp(var("x")),
929 ln(var("x")),
930 tan(var("x")),
931 ] {
932 let df = simplify(&differentiate(&f, "x"));
933 for &x in &[0.4, 1.1, 2.3] {
934 let h = 1e-6;
935 let fd = (f.eval(&env1("x", x + h)).unwrap() - f.eval(&env1("x", x - h)).unwrap())
936 / (2.0 * h);
937 let sym = df.eval(&env1("x", x)).unwrap();
938 assert!(
939 (sym - fd).abs() < 1e-4,
940 "{f}: symbolic {sym} vs fd {fd} at x={x}"
941 );
942 }
943 }
944 assert_eq!(simplify(&ln(exp(var("x")))), var("x"));
946 assert_eq!(simplify(&exp(ln(var("x")))), var("x"));
947 assert_eq!(simplify(&sin(c(0.0))), c(0.0));
948 assert_eq!(simplify(&cos(c(0.0))), c(1.0));
949 let e = parse("sin(x) + exp(2*x) - ln(x)").unwrap();
951 assert!(
952 (e.eval(&env1("x", 1.0)).unwrap() - (1.0_f64.sin() + 2.0_f64.exp() - 1.0_f64.ln()))
953 .abs()
954 < 1e-9
955 );
956 assert_eq!(from_quins(&to_quins(&e)).unwrap(), e);
957 }
958
959 #[test]
960 fn citation_hash_is_structural() {
961 let a = add(var("x"), c(1.0));
962 let b = add(var("x"), c(1.0));
963 assert_eq!(expr_citation_hash(&a), expr_citation_hash(&b));
964 assert_ne!(expr_citation_hash(&a), expr_citation_hash(&var("x")));
965 }
966}