gardesk/garcalc / ca03027

Browse files

Extend CAS symbolic sums/products and simplification

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
ca03027b84896b0fb0d1be860a26cb8d244f25b3
Parents
61a2065
Tree
65de631

5 changed files

StatusFile+-
M garcalc-cas/src/eval.rs 843 54
M garcalc-cas/src/expr.rs 51 3
M garcalc-cas/src/lib.rs 5 5
M garcalc-cas/src/parser.rs 5 5
M garcalc-cas/src/symbolic.rs 177 146
garcalc-cas/src/eval.rsmodified
1085 lines changed — click to load
@@ -2,6 +2,7 @@
22
 //!
33
 //! Supports both numeric and symbolic evaluation modes.
44
 
5
+use std::collections::BTreeSet;
56
 use std::collections::HashMap;
67
 use std::f64::consts::{E, PI};
78
 
@@ -129,10 +130,8 @@ impl Evaluator {
129130
             Expr::Func(name, args) => {
130131
                 // Symbolic functions operate on unevaluated expressions
131132
                 match name.as_str() {
132
-                    "diff" | "derivative" | "integrate" | "integral" |
133
-                    "limit" | "lim" |
134
-                    "solve" | "simplify" | "expand" | "factor" |
135
-                    "substitute" | "subs" => {
133
+                    "diff" | "derivative" | "integrate" | "integral" | "limit" | "lim"
134
+                    | "solve" | "simplify" | "expand" | "factor" | "substitute" | "subs" => {
136135
                         self.call_function(name, args)
137136
                     }
138137
                     _ => {
@@ -163,7 +162,11 @@ impl Evaluator {
163162
             }
164163
 
165164
             // Symbolic operations - perform symbolic computation then try to evaluate
166
-            Expr::Derivative { expr: inner, var, order } => {
165
+            Expr::Derivative {
166
+                expr: inner,
167
+                var,
168
+                order,
169
+            } => {
167170
                 let result = Differentiator::diff_n(inner, var, *order)?;
168171
                 let simplified = Simplifier::simplify(&result);
169172
                 // Try to evaluate the result
@@ -175,7 +178,12 @@ impl Evaluator {
175178
                 }
176179
             }
177180
 
178
-            Expr::Integral { expr: inner, var, lower, upper } => {
181
+            Expr::Integral {
182
+                expr: inner,
183
+                var,
184
+                lower,
185
+                upper,
186
+            } => {
179187
                 if let (Some(l), Some(u)) = (lower, upper) {
180188
                     // Definite integral - evaluate to number
181189
                     let result = Integrator::integrate_definite(inner, var, l, u)?;
@@ -188,22 +196,30 @@ impl Evaluator {
188196
                 }
189197
             }
190198
 
191
-            Expr::Limit { expr: inner, var, point, direction } => {
199
+            Expr::Limit {
200
+                expr: inner,
201
+                var,
202
+                point,
203
+                direction,
204
+            } => {
192205
                 let result = Limits::limit(inner, var, point, *direction)?;
193206
                 let simplified = Simplifier::simplify(&result);
194207
                 self.eval(&simplified)
195208
             }
196209
 
197
-            Expr::Sum { .. }
198
-            | Expr::Product { .. } => {
199
-                if self.exact_mode {
200
-                    Ok(expr.clone())
201
-                } else {
202
-                    Err(CasError::NotImplemented(
203
-                        "sums/products not yet implemented".to_string(),
204
-                    ))
205
-                }
206
-            }
210
+            Expr::Sum {
211
+                expr: inner,
212
+                var,
213
+                lower,
214
+                upper,
215
+            } => self.eval_sum(inner, var, lower, upper),
216
+
217
+            Expr::Product {
218
+                expr: inner,
219
+                var,
220
+                lower,
221
+                upper,
222
+            } => self.eval_product(inner, var, lower, upper),
207223
 
208224
             Expr::Inequality { lhs, op, rhs } => {
209225
                 let lhs_val = self.eval(lhs)?;
@@ -253,9 +269,7 @@ impl Evaluator {
253269
                 Ok(Expr::Complex(re + x, *im))
254270
             }
255271
             (Expr::Complex(re, im), Expr::Integer(n))
256
-            | (Expr::Integer(n), Expr::Complex(re, im)) => {
257
-                Ok(Expr::Complex(re + *n as f64, *im))
258
-            }
272
+            | (Expr::Integer(n), Expr::Complex(re, im)) => Ok(Expr::Complex(re + *n as f64, *im)),
259273
             (Expr::Rational(r1), Expr::Rational(r2)) => {
260274
                 let num = r1.num * r2.den + r2.num * r1.den;
261275
                 let den = r1.den * r2.den;
@@ -279,6 +293,19 @@ impl Evaluator {
279293
     }
280294
 
281295
     fn multiply(&self, a: &Expr, b: &Expr) -> Result<Expr> {
296
+        if a.is_one() {
297
+            return Ok(b.clone());
298
+        }
299
+        if b.is_one() {
300
+            return Ok(a.clone());
301
+        }
302
+        if a.is_negative_one() {
303
+            return self.negate(b);
304
+        }
305
+        if b.is_negative_one() {
306
+            return self.negate(a);
307
+        }
308
+
282309
         match (a, b) {
283310
             (Expr::Integer(x), Expr::Integer(y)) => Ok(Expr::Integer(x * y)),
284311
             (Expr::Float(x), Expr::Float(y)) => Ok(Expr::Float(x * y)),
@@ -297,9 +324,10 @@ impl Evaluator {
297324
                 let n = *n as f64;
298325
                 Ok(Expr::Complex(re * n, im * n))
299326
             }
300
-            (Expr::Rational(r1), Expr::Rational(r2)) => {
301
-                Ok(Expr::Rational(Rational::new(r1.num * r2.num, r1.den * r2.den)))
302
-            }
327
+            (Expr::Rational(r1), Expr::Rational(r2)) => Ok(Expr::Rational(Rational::new(
328
+                r1.num * r2.num,
329
+                r1.den * r2.den,
330
+            ))),
303331
             (Expr::Rational(r), Expr::Integer(n)) | (Expr::Integer(n), Expr::Rational(r)) => {
304332
                 Ok(Expr::Rational(Rational::new(r.num * n, r.den)))
305333
             }
@@ -455,16 +483,12 @@ impl Evaluator {
455483
             }
456484
             ("min", _, _) if !args.is_empty() => {
457485
                 let values: Result<Vec<f64>> = args.iter().map(|a| self.to_f64(a)).collect();
458
-                let min = values?
459
-                    .into_iter()
460
-                    .fold(f64::INFINITY, |a, b| a.min(b));
486
+                let min = values?.into_iter().fold(f64::INFINITY, |a, b| a.min(b));
461487
                 Ok(Expr::Float(min))
462488
             }
463489
             ("max", _, _) if !args.is_empty() => {
464490
                 let values: Result<Vec<f64>> = args.iter().map(|a| self.to_f64(a)).collect();
465
-                let max = values?
466
-                    .into_iter()
467
-                    .fold(f64::NEG_INFINITY, |a, b| a.max(b));
491
+                let max = values?.into_iter().fold(f64::NEG_INFINITY, |a, b| a.max(b));
468492
                 Ok(Expr::Float(max))
469493
             }
470494
             ("gcd", 2, _) => {
@@ -489,7 +513,9 @@ impl Evaluator {
489513
                     let result = Differentiator::diff(&args[0], var)?;
490514
                     Ok(Simplifier::simplify(&result))
491515
                 } else {
492
-                    Err(CasError::Type("diff requires variable as second argument".to_string()))
516
+                    Err(CasError::Type(
517
+                        "diff requires variable as second argument".to_string(),
518
+                    ))
493519
                 }
494520
             }
495521
             ("diff", 3, _) | ("derivative", 3, _) => {
@@ -498,7 +524,9 @@ impl Evaluator {
498524
                     let result = Differentiator::diff_n(&args[0], var, *n as u32)?;
499525
                     Ok(Simplifier::simplify(&result))
500526
                 } else {
501
-                    Err(CasError::Type("diff requires variable and integer order".to_string()))
527
+                    Err(CasError::Type(
528
+                        "diff requires variable and integer order".to_string(),
529
+                    ))
502530
                 }
503531
             }
504532
 
@@ -508,7 +536,9 @@ impl Evaluator {
508536
                     let result = Integrator::integrate(&args[0], var)?;
509537
                     Ok(Simplifier::simplify(&result))
510538
                 } else {
511
-                    Err(CasError::Type("integrate requires variable as second argument".to_string()))
539
+                    Err(CasError::Type(
540
+                        "integrate requires variable as second argument".to_string(),
541
+                    ))
512542
                 }
513543
             }
514544
             ("integrate", 4, _) | ("integral", 4, _) => {
@@ -518,7 +548,9 @@ impl Evaluator {
518548
                     // Try to evaluate the result numerically
519549
                     self.eval(&Simplifier::simplify(&result))
520550
                 } else {
521
-                    Err(CasError::Type("integrate requires variable as second argument".to_string()))
551
+                    Err(CasError::Type(
552
+                        "integrate requires variable as second argument".to_string(),
553
+                    ))
522554
                 }
523555
             }
524556
 
@@ -532,18 +564,36 @@ impl Evaluator {
532564
                         Ok(Expr::Vector(solutions))
533565
                     }
534566
                 } else {
535
-                    Err(CasError::Type("solve requires variable as second argument".to_string()))
567
+                    Err(CasError::Type(
568
+                        "solve requires variable as second argument".to_string(),
569
+                    ))
536570
                 }
537571
             }
538572
 
539
-            ("simplify", 1, _) => {
540
-                Ok(Simplifier::simplify(&args[0]))
573
+            ("sum", 4, _) => {
574
+                if let Expr::Symbol(var) = &args[1] {
575
+                    self.eval_sum(&args[0], var, &args[2], &args[3])
576
+                } else {
577
+                    Err(CasError::Type(
578
+                        "sum requires variable as second argument".to_string(),
579
+                    ))
580
+                }
541581
             }
542582
 
543
-            ("expand", 1, _) => {
544
-                Ok(Simplifier::simplify(&Simplifier::expand(&args[0])))
583
+            ("product", 4, _) | ("prod", 4, _) => {
584
+                if let Expr::Symbol(var) = &args[1] {
585
+                    self.eval_product(&args[0], var, &args[2], &args[3])
586
+                } else {
587
+                    Err(CasError::Type(
588
+                        "product requires variable as second argument".to_string(),
589
+                    ))
590
+                }
545591
             }
546592
 
593
+            ("simplify", 1, _) => Ok(Simplifier::simplify(&args[0])),
594
+
595
+            ("expand", 1, _) => Ok(Simplifier::simplify(&Simplifier::expand(&args[0]))),
596
+
547597
             ("factor", 1, _) => {
548598
                 // Basic factoring - just return simplified for now
549599
                 // Full factoring is complex, can add later
@@ -553,9 +603,13 @@ impl Evaluator {
553603
             ("substitute", 3, _) | ("subs", 3, _) => {
554604
                 // substitute(expr, var, replacement)
555605
                 if let Expr::Symbol(var) = &args[1] {
556
-                    Ok(Simplifier::simplify(&Simplifier::substitute(&args[0], var, &args[2])))
606
+                    Ok(Simplifier::simplify(&Simplifier::substitute(
607
+                        &args[0], var, &args[2],
608
+                    )))
557609
                 } else {
558
-                    Err(CasError::Type("substitute requires variable as second argument".to_string()))
610
+                    Err(CasError::Type(
611
+                        "substitute requires variable as second argument".to_string(),
612
+                    ))
559613
                 }
560614
             }
561615
 
@@ -565,7 +619,9 @@ impl Evaluator {
565619
                     let result = Limits::limit(&args[0], var, &args[2], None)?;
566620
                     self.eval(&Simplifier::simplify(&result))
567621
                 } else {
568
-                    Err(CasError::Type("limit requires variable as second argument".to_string()))
622
+                    Err(CasError::Type(
623
+                        "limit requires variable as second argument".to_string(),
624
+                    ))
569625
                 }
570626
             }
571627
             ("limit", 4, _) | ("lim", 4, _) => {
@@ -583,7 +639,9 @@ impl Evaluator {
583639
                     let result = Limits::limit(&args[0], var, &args[2], direction)?;
584640
                     self.eval(&Simplifier::simplify(&result))
585641
                 } else {
586
-                    Err(CasError::Type("limit requires variable as second argument".to_string()))
642
+                    Err(CasError::Type(
643
+                        "limit requires variable as second argument".to_string(),
644
+                    ))
587645
                 }
588646
             }
589647
 
@@ -608,7 +666,9 @@ impl Evaluator {
608666
                 if let Expr::Matrix(rows) = &args[0] {
609667
                     self.matrix_transpose(rows)
610668
                 } else {
611
-                    Err(CasError::Type("transpose requires a matrix argument".to_string()))
669
+                    Err(CasError::Type(
670
+                        "transpose requires a matrix argument".to_string(),
671
+                    ))
612672
                 }
613673
             }
614674
 
@@ -616,7 +676,9 @@ impl Evaluator {
616676
                 if let Expr::Matrix(rows) = &args[0] {
617677
                     self.matrix_trace(rows)
618678
                 } else {
619
-                    Err(CasError::Type("trace requires a matrix argument".to_string()))
679
+                    Err(CasError::Type(
680
+                        "trace requires a matrix argument".to_string(),
681
+                    ))
620682
                 }
621683
             }
622684
 
@@ -624,14 +686,18 @@ impl Evaluator {
624686
                 if let (Expr::Matrix(a), Expr::Matrix(b)) = (&args[0], &args[1]) {
625687
                     self.matrix_mul(a, b)
626688
                 } else {
627
-                    Err(CasError::Type("matmul requires two matrix arguments".to_string()))
689
+                    Err(CasError::Type(
690
+                        "matmul requires two matrix arguments".to_string(),
691
+                    ))
628692
                 }
629693
             }
630694
 
631695
             ("identity", 1, Some(n)) => {
632696
                 let n = n as usize;
633697
                 if n == 0 || n > 100 {
634
-                    return Err(CasError::EvaluationError("identity matrix size must be 1-100".to_string()));
698
+                    return Err(CasError::EvaluationError(
699
+                        "identity matrix size must be 1-100".to_string(),
700
+                    ));
635701
                 }
636702
                 let mut rows = Vec::with_capacity(n);
637703
                 for i in 0..n {
@@ -659,7 +725,9 @@ impl Evaluator {
659725
             return Err(CasError::EvaluationError("empty matrix".to_string()));
660726
         }
661727
         if rows.iter().any(|r| r.len() != n) {
662
-            return Err(CasError::EvaluationError("det requires square matrix".to_string()));
728
+            return Err(CasError::EvaluationError(
729
+                "det requires square matrix".to_string(),
730
+            ));
663731
         }
664732
 
665733
         // Convert to f64 for numerical computation
@@ -725,7 +793,9 @@ impl Evaluator {
725793
             return Err(CasError::EvaluationError("empty matrix".to_string()));
726794
         }
727795
         if rows.iter().any(|r| r.len() != n) {
728
-            return Err(CasError::EvaluationError("inv requires square matrix".to_string()));
796
+            return Err(CasError::EvaluationError(
797
+                "inv requires square matrix".to_string(),
798
+            ));
729799
         }
730800
 
731801
         // Convert to f64
@@ -823,7 +893,9 @@ impl Evaluator {
823893
             return Err(CasError::EvaluationError("empty matrix".to_string()));
824894
         }
825895
         if rows.iter().any(|r| r.len() != n) {
826
-            return Err(CasError::EvaluationError("trace requires square matrix".to_string()));
896
+            return Err(CasError::EvaluationError(
897
+                "trace requires square matrix".to_string(),
898
+            ));
827899
         }
828900
 
829901
         let mut sum = 0.0;
@@ -851,16 +923,29 @@ impl Evaluator {
851923
         if b.len() != n {
852924
             return Err(CasError::EvaluationError(format!(
853925
                 "matrix dimensions don't match for multiplication: {}x{} * {}x{}",
854
-                m, n, b.len(), p
926
+                m,
927
+                n,
928
+                b.len(),
929
+                p
855930
             )));
856931
         }
857932
 
858933
         // Convert to f64
859
-        let a_num: Vec<Vec<f64>> = a.iter()
860
-            .map(|row| row.iter().map(|e| self.to_f64(e)).collect::<Result<Vec<_>>>())
934
+        let a_num: Vec<Vec<f64>> = a
935
+            .iter()
936
+            .map(|row| {
937
+                row.iter()
938
+                    .map(|e| self.to_f64(e))
939
+                    .collect::<Result<Vec<_>>>()
940
+            })
861941
             .collect::<Result<Vec<_>>>()?;
862
-        let b_num: Vec<Vec<f64>> = b.iter()
863
-            .map(|row| row.iter().map(|e| self.to_f64(e)).collect::<Result<Vec<_>>>())
942
+        let b_num: Vec<Vec<f64>> = b
943
+            .iter()
944
+            .map(|row| {
945
+                row.iter()
946
+                    .map(|e| self.to_f64(e))
947
+                    .collect::<Result<Vec<_>>>()
948
+            })
864949
             .collect::<Result<Vec<_>>>()?;
865950
 
866951
         let mut result = Vec::with_capacity(m);
@@ -882,6 +967,542 @@ impl Evaluator {
882967
 
883968
         Ok(Expr::Matrix(result))
884969
     }
970
+
971
+    fn eval_integer_bound(&self, bound: &Expr) -> Result<i64> {
972
+        let value = self.eval(bound)?;
973
+        match value {
974
+            Expr::Integer(n) => Ok(n),
975
+            Expr::Rational(r) if r.den == 1 => Ok(r.num),
976
+            Expr::Float(x) => {
977
+                if !x.is_finite() {
978
+                    return Err(CasError::Type("bound must be a finite number".to_string()));
979
+                }
980
+                let rounded = x.round();
981
+                if (x - rounded).abs() < 1e-10
982
+                    && rounded >= i64::MIN as f64
983
+                    && rounded <= i64::MAX as f64
984
+                {
985
+                    Ok(rounded as i64)
986
+                } else {
987
+                    Err(CasError::Type(format!(
988
+                        "bound must be an integer, got {}",
989
+                        Expr::Float(x)
990
+                    )))
991
+                }
992
+            }
993
+            _ => Err(CasError::Type(format!(
994
+                "bound must be an integer, got {value}"
995
+            ))),
996
+        }
997
+    }
998
+
999
+    fn eval_sum(
1000
+        &self,
1001
+        body: &Expr,
1002
+        var: &crate::expr::Symbol,
1003
+        lower: &Expr,
1004
+        upper: &Expr,
1005
+    ) -> Result<Expr> {
1006
+        let inferred_var;
1007
+        let active_var = if body.contains_var(var) {
1008
+            var
1009
+        } else {
1010
+            inferred_var = Self::infer_iteration_var(body);
1011
+            inferred_var.as_ref().unwrap_or(var)
1012
+        };
1013
+
1014
+        match self.eval_discrete_series(body, active_var, lower, upper, false) {
1015
+            Ok(value) => Ok(value),
1016
+            Err(_err) => {
1017
+                if let Some(symbolic) = Self::symbolic_sum(body, active_var, lower, upper) {
1018
+                    return Ok(Simplifier::simplify(&symbolic));
1019
+                }
1020
+
1021
+                Ok(Expr::Sum {
1022
+                    expr: Box::new(body.clone()),
1023
+                    var: active_var.clone(),
1024
+                    lower: Box::new(lower.clone()),
1025
+                    upper: Box::new(upper.clone()),
1026
+                })
1027
+            }
1028
+        }
1029
+    }
1030
+
1031
+    fn eval_product(
1032
+        &self,
1033
+        body: &Expr,
1034
+        var: &crate::expr::Symbol,
1035
+        lower: &Expr,
1036
+        upper: &Expr,
1037
+    ) -> Result<Expr> {
1038
+        let inferred_var;
1039
+        let active_var = if body.contains_var(var) {
1040
+            var
1041
+        } else {
1042
+            inferred_var = Self::infer_iteration_var(body);
1043
+            inferred_var.as_ref().unwrap_or(var)
1044
+        };
1045
+
1046
+        match self.eval_discrete_series(body, active_var, lower, upper, true) {
1047
+            Ok(value) => Ok(value),
1048
+            Err(_err) => {
1049
+                if let Some(symbolic) = Self::symbolic_product(body, active_var, lower, upper) {
1050
+                    return Ok(Simplifier::simplify(&symbolic));
1051
+                }
1052
+
1053
+                Ok(Expr::Product {
1054
+                    expr: Box::new(body.clone()),
1055
+                    var: active_var.clone(),
1056
+                    lower: Box::new(lower.clone()),
1057
+                    upper: Box::new(upper.clone()),
1058
+                })
1059
+            }
1060
+        }
1061
+    }
1062
+
1063
+    fn infer_iteration_var(body: &Expr) -> Option<crate::expr::Symbol> {
1064
+        let mut vars = BTreeSet::new();
1065
+        Self::collect_symbols(body, &mut vars);
1066
+        if vars.len() == 1 {
1067
+            vars.into_iter().next().map(crate::expr::Symbol::new)
1068
+        } else {
1069
+            None
1070
+        }
1071
+    }
1072
+
1073
+    fn collect_symbols(expr: &Expr, out: &mut BTreeSet<String>) {
1074
+        match expr {
1075
+            Expr::Symbol(s) => {
1076
+                out.insert(s.as_str().to_string());
1077
+            }
1078
+            Expr::Neg(inner) => Self::collect_symbols(inner, out),
1079
+            Expr::Add(terms) | Expr::Mul(terms) | Expr::Vector(terms) => {
1080
+                for term in terms {
1081
+                    Self::collect_symbols(term, out);
1082
+                }
1083
+            }
1084
+            Expr::Pow(base, exp) => {
1085
+                Self::collect_symbols(base, out);
1086
+                Self::collect_symbols(exp, out);
1087
+            }
1088
+            Expr::Func(_, args) => {
1089
+                for arg in args {
1090
+                    Self::collect_symbols(arg, out);
1091
+                }
1092
+            }
1093
+            Expr::Derivative { expr, .. } => Self::collect_symbols(expr, out),
1094
+            Expr::Integral {
1095
+                expr, lower, upper, ..
1096
+            } => {
1097
+                Self::collect_symbols(expr, out);
1098
+                if let Some(lo) = lower {
1099
+                    Self::collect_symbols(lo, out);
1100
+                }
1101
+                if let Some(hi) = upper {
1102
+                    Self::collect_symbols(hi, out);
1103
+                }
1104
+            }
1105
+            Expr::Limit { expr, point, .. } => {
1106
+                Self::collect_symbols(expr, out);
1107
+                Self::collect_symbols(point, out);
1108
+            }
1109
+            Expr::Sum {
1110
+                expr, lower, upper, ..
1111
+            }
1112
+            | Expr::Product {
1113
+                expr, lower, upper, ..
1114
+            } => {
1115
+                Self::collect_symbols(expr, out);
1116
+                Self::collect_symbols(lower, out);
1117
+                Self::collect_symbols(upper, out);
1118
+            }
1119
+            Expr::Equation(lhs, rhs) => {
1120
+                Self::collect_symbols(lhs, out);
1121
+                Self::collect_symbols(rhs, out);
1122
+            }
1123
+            Expr::Inequality { lhs, rhs, .. } => {
1124
+                Self::collect_symbols(lhs, out);
1125
+                Self::collect_symbols(rhs, out);
1126
+            }
1127
+            Expr::Matrix(rows) => {
1128
+                for row in rows {
1129
+                    for elem in row {
1130
+                        Self::collect_symbols(elem, out);
1131
+                    }
1132
+                }
1133
+            }
1134
+            Expr::Integer(_)
1135
+            | Expr::Rational(_)
1136
+            | Expr::Float(_)
1137
+            | Expr::Complex(_, _)
1138
+            | Expr::Undefined
1139
+            | Expr::Infinity(_) => {}
1140
+        }
1141
+    }
1142
+
1143
+    fn symbolic_sum(
1144
+        body: &Expr,
1145
+        var: &crate::expr::Symbol,
1146
+        lower: &Expr,
1147
+        upper: &Expr,
1148
+    ) -> Option<Expr> {
1149
+        // Finite count for symbolic bounds: upper - lower + 1.
1150
+        let count = Expr::add(vec![
1151
+            Expr::sub(upper.clone(), lower.clone()),
1152
+            Expr::Integer(1),
1153
+        ]);
1154
+
1155
+        if !body.contains_var(var) {
1156
+            return Some(Expr::mul(vec![body.clone(), count]));
1157
+        }
1158
+
1159
+        match body {
1160
+            Expr::Symbol(s) if s == var => Some(Self::sum_linear(lower, upper)),
1161
+
1162
+            Expr::Pow(base, exp) if matches!(base.as_ref(), Expr::Symbol(s) if s == var) => {
1163
+                match exp.as_ref() {
1164
+                    Expr::Integer(0) => Some(count),
1165
+                    Expr::Integer(1) => Some(Self::sum_linear(lower, upper)),
1166
+                    Expr::Integer(2) => Some(Self::sum_square(lower, upper)),
1167
+                    Expr::Integer(3) => Some(Self::sum_cube(lower, upper)),
1168
+                    _ => None,
1169
+                }
1170
+            }
1171
+
1172
+            Expr::Neg(inner) => Self::symbolic_sum(inner, var, lower, upper).map(Expr::neg),
1173
+
1174
+            Expr::Add(terms) => {
1175
+                let mut summed_terms = Vec::with_capacity(terms.len());
1176
+                for term in terms {
1177
+                    summed_terms.push(Self::symbolic_sum(term, var, lower, upper)?);
1178
+                }
1179
+                Some(Expr::add(summed_terms))
1180
+            }
1181
+
1182
+            Expr::Mul(factors) => {
1183
+                let (mut independent, dependent): (Vec<Expr>, Vec<Expr>) =
1184
+                    factors.iter().cloned().partition(|f| !f.contains_var(var));
1185
+
1186
+                if dependent.is_empty() {
1187
+                    independent.push(count);
1188
+                    return Some(Expr::mul(independent));
1189
+                }
1190
+
1191
+                if dependent.len() == 1 {
1192
+                    let dep_sum = Self::symbolic_sum(&dependent[0], var, lower, upper)?;
1193
+                    independent.push(dep_sum);
1194
+                    return Some(Expr::mul(independent));
1195
+                }
1196
+
1197
+                None
1198
+            }
1199
+
1200
+            _ => None,
1201
+        }
1202
+    }
1203
+
1204
+    fn sum_linear(lower: &Expr, upper: &Expr) -> Expr {
1205
+        fn triangular(x: Expr) -> Expr {
1206
+            Expr::mul(vec![
1207
+                x.clone(),
1208
+                Expr::add(vec![x, Expr::Integer(1)]),
1209
+                Expr::Rational(Rational::new(1, 2)),
1210
+            ])
1211
+        }
1212
+
1213
+        let lo_minus_one = Expr::sub(lower.clone(), Expr::Integer(1));
1214
+        Expr::sub(triangular(upper.clone()), triangular(lo_minus_one))
1215
+    }
1216
+
1217
+    fn sum_square(lower: &Expr, upper: &Expr) -> Expr {
1218
+        fn square_sum_prefix(x: Expr) -> Expr {
1219
+            let two_x_plus_one = Expr::add(vec![
1220
+                Expr::mul(vec![Expr::Integer(2), x.clone()]),
1221
+                Expr::Integer(1),
1222
+            ]);
1223
+            Expr::mul(vec![
1224
+                x.clone(),
1225
+                Expr::add(vec![x, Expr::Integer(1)]),
1226
+                two_x_plus_one,
1227
+                Expr::Rational(Rational::new(1, 6)),
1228
+            ])
1229
+        }
1230
+
1231
+        let lo_minus_one = Expr::sub(lower.clone(), Expr::Integer(1));
1232
+        Expr::sub(
1233
+            square_sum_prefix(upper.clone()),
1234
+            square_sum_prefix(lo_minus_one),
1235
+        )
1236
+    }
1237
+
1238
+    fn sum_cube(lower: &Expr, upper: &Expr) -> Expr {
1239
+        fn cube_sum_prefix(x: Expr) -> Expr {
1240
+            let tri = Expr::mul(vec![
1241
+                x.clone(),
1242
+                Expr::add(vec![x, Expr::Integer(1)]),
1243
+                Expr::Rational(Rational::new(1, 2)),
1244
+            ]);
1245
+            Expr::pow(tri, Expr::Integer(2))
1246
+        }
1247
+
1248
+        let lo_minus_one = Expr::sub(lower.clone(), Expr::Integer(1));
1249
+        Expr::sub(
1250
+            cube_sum_prefix(upper.clone()),
1251
+            cube_sum_prefix(lo_minus_one),
1252
+        )
1253
+    }
1254
+
1255
+    fn symbolic_product(
1256
+        body: &Expr,
1257
+        var: &crate::expr::Symbol,
1258
+        lower: &Expr,
1259
+        upper: &Expr,
1260
+    ) -> Option<Expr> {
1261
+        let count = Expr::add(vec![
1262
+            Expr::sub(upper.clone(), lower.clone()),
1263
+            Expr::Integer(1),
1264
+        ]);
1265
+
1266
+        if !body.contains_var(var) {
1267
+            return Some(Expr::pow(body.clone(), count));
1268
+        }
1269
+
1270
+        if let Some(factored) = Self::factor_simple_product_body(body, var) {
1271
+            return Self::symbolic_product(&factored, var, lower, upper);
1272
+        }
1273
+
1274
+        match body {
1275
+            Expr::Symbol(s) if s == var => Some(Self::factorial_range(lower, upper)),
1276
+            Expr::Neg(inner) => {
1277
+                let inner_product = Self::symbolic_product(inner, var, lower, upper)?;
1278
+                Some(Expr::mul(vec![
1279
+                    Expr::pow(Expr::Integer(-1), count),
1280
+                    inner_product,
1281
+                ]))
1282
+            }
1283
+            Expr::Mul(factors) => {
1284
+                let (independent, dependent): (Vec<Expr>, Vec<Expr>) =
1285
+                    factors.iter().cloned().partition(|f| !f.contains_var(var));
1286
+
1287
+                let mut parts = Vec::new();
1288
+                if !independent.is_empty() {
1289
+                    parts.push(Expr::pow(Expr::mul(independent), count.clone()));
1290
+                }
1291
+
1292
+                for dep in dependent {
1293
+                    parts.push(Self::symbolic_product(&dep, var, lower, upper)?);
1294
+                }
1295
+
1296
+                Some(Expr::mul(parts))
1297
+            }
1298
+            Expr::Add(_) => Self::product_linear_term(body, var, lower, upper),
1299
+            Expr::Pow(base, exp) => {
1300
+                let Expr::Integer(power) = exp.as_ref() else {
1301
+                    return None;
1302
+                };
1303
+
1304
+                if matches!(base.as_ref(), Expr::Symbol(s) if s == var) {
1305
+                    return Some(Expr::pow(
1306
+                        Self::factorial_range(lower, upper),
1307
+                        Expr::Integer(*power),
1308
+                    ));
1309
+                }
1310
+
1311
+                if let Some(linear_base_product) =
1312
+                    Self::product_linear_term(base, var, lower, upper)
1313
+                {
1314
+                    return Some(Expr::pow(linear_base_product, Expr::Integer(*power)));
1315
+                }
1316
+
1317
+                None
1318
+            }
1319
+            _ => None,
1320
+        }
1321
+    }
1322
+
1323
+    fn factorial_range(lower: &Expr, upper: &Expr) -> Expr {
1324
+        Self::factorial_range_shifted(lower, upper, 0).unwrap_or_else(|| {
1325
+            let upper_fact = Expr::func("factorial", vec![upper.clone()]);
1326
+            if matches!(lower, Expr::Integer(1)) {
1327
+                upper_fact
1328
+            } else {
1329
+                let lower_minus_one = Expr::sub(lower.clone(), Expr::Integer(1));
1330
+                let lower_fact = Expr::func("factorial", vec![lower_minus_one]);
1331
+                Expr::mul(vec![upper_fact, Expr::pow(lower_fact, Expr::Integer(-1))])
1332
+            }
1333
+        })
1334
+    }
1335
+
1336
+    fn product_linear_term(
1337
+        expr: &Expr,
1338
+        var: &crate::expr::Symbol,
1339
+        lower: &Expr,
1340
+        upper: &Expr,
1341
+    ) -> Option<Expr> {
1342
+        let shift = Self::extract_linear_shift(expr, var)?;
1343
+        Self::factorial_range_shifted(lower, upper, shift)
1344
+    }
1345
+
1346
+    fn extract_linear_shift(expr: &Expr, var: &crate::expr::Symbol) -> Option<i64> {
1347
+        match expr {
1348
+            Expr::Symbol(s) if s == var => Some(0),
1349
+            Expr::Add(terms) => {
1350
+                let mut saw_var = false;
1351
+                let mut shift = 0_i64;
1352
+                for term in terms {
1353
+                    match term {
1354
+                        Expr::Symbol(s) if s == var => {
1355
+                            if saw_var {
1356
+                                return None;
1357
+                            }
1358
+                            saw_var = true;
1359
+                        }
1360
+                        Expr::Integer(n) => {
1361
+                            shift = shift.checked_add(*n)?;
1362
+                        }
1363
+                        Expr::Neg(inner) => {
1364
+                            if let Expr::Integer(n) = inner.as_ref() {
1365
+                                shift = shift.checked_sub(*n)?;
1366
+                            } else {
1367
+                                return None;
1368
+                            }
1369
+                        }
1370
+                        _ => return None,
1371
+                    }
1372
+                }
1373
+                if saw_var {
1374
+                    Some(shift)
1375
+                } else {
1376
+                    None
1377
+                }
1378
+            }
1379
+            _ => None,
1380
+        }
1381
+    }
1382
+
1383
+    fn factorial_range_shifted(lower: &Expr, upper: &Expr, shift: i64) -> Option<Expr> {
1384
+        let shift_minus_one = shift.checked_sub(1)?;
1385
+        if let Expr::Integer(lo) = lower {
1386
+            if lo.checked_add(shift_minus_one)? < 0 {
1387
+                return None;
1388
+            }
1389
+        }
1390
+        if let Expr::Integer(hi) = upper {
1391
+            if hi.checked_add(shift)? < 0 {
1392
+                return None;
1393
+            }
1394
+        }
1395
+
1396
+        let shifted_upper =
1397
+            Simplifier::simplify(&Expr::add(vec![upper.clone(), Expr::Integer(shift)]));
1398
+        let shifted_lower_minus_one = Simplifier::simplify(&Expr::add(vec![
1399
+            lower.clone(),
1400
+            Expr::Integer(shift_minus_one),
1401
+        ]));
1402
+
1403
+        let upper_fact = Expr::func("factorial", vec![shifted_upper]);
1404
+        if matches!(shifted_lower_minus_one, Expr::Integer(0)) {
1405
+            Some(upper_fact)
1406
+        } else {
1407
+            let lower_fact = Expr::func("factorial", vec![shifted_lower_minus_one]);
1408
+            Some(Expr::mul(vec![
1409
+                upper_fact,
1410
+                Expr::pow(lower_fact, Expr::Integer(-1)),
1411
+            ]))
1412
+        }
1413
+    }
1414
+
1415
+    fn factor_simple_product_body(body: &Expr, var: &crate::expr::Symbol) -> Option<Expr> {
1416
+        let Expr::Add(terms) = body else {
1417
+            return None;
1418
+        };
1419
+        if terms.len() != 2 {
1420
+            return None;
1421
+        }
1422
+
1423
+        let mut has_square = false;
1424
+        let mut linear_sign = 0_i64;
1425
+        for term in terms {
1426
+            match term {
1427
+                Expr::Pow(base, exp)
1428
+                    if matches!(base.as_ref(), Expr::Symbol(s) if s == var)
1429
+                        && matches!(exp.as_ref(), Expr::Integer(2)) =>
1430
+                {
1431
+                    has_square = true;
1432
+                }
1433
+                Expr::Symbol(s) if s == var => linear_sign += 1,
1434
+                Expr::Neg(inner) if matches!(inner.as_ref(), Expr::Symbol(s) if s == var) => {
1435
+                    linear_sign -= 1;
1436
+                }
1437
+                _ => return None,
1438
+            }
1439
+        }
1440
+
1441
+        if !has_square {
1442
+            return None;
1443
+        }
1444
+
1445
+        let var_expr = Expr::Symbol(var.clone());
1446
+        match linear_sign {
1447
+            1 => Some(Expr::mul(vec![
1448
+                var_expr.clone(),
1449
+                Expr::add(vec![var_expr, Expr::Integer(1)]),
1450
+            ])),
1451
+            -1 => Some(Expr::mul(vec![
1452
+                var_expr.clone(),
1453
+                Expr::add(vec![var_expr, Expr::Integer(-1)]),
1454
+            ])),
1455
+            _ => None,
1456
+        }
1457
+    }
1458
+
1459
+    fn eval_discrete_series(
1460
+        &self,
1461
+        body: &Expr,
1462
+        var: &crate::expr::Symbol,
1463
+        lower: &Expr,
1464
+        upper: &Expr,
1465
+        is_product: bool,
1466
+    ) -> Result<Expr> {
1467
+        const MAX_TERMS: i64 = 100_000;
1468
+
1469
+        let lo = self.eval_integer_bound(lower)?;
1470
+        let hi = self.eval_integer_bound(upper)?;
1471
+
1472
+        if lo > hi {
1473
+            return Ok(if is_product {
1474
+                Expr::Integer(1)
1475
+            } else {
1476
+                Expr::Integer(0)
1477
+            });
1478
+        }
1479
+
1480
+        let count = hi.saturating_sub(lo).saturating_add(1);
1481
+        if count > MAX_TERMS {
1482
+            let kind = if is_product { "product" } else { "sum" };
1483
+            return Err(CasError::EvaluationError(format!(
1484
+                "{kind} has too many terms ({count}); limit is {MAX_TERMS}"
1485
+            )));
1486
+        }
1487
+
1488
+        let mut acc = if is_product {
1489
+            Expr::Integer(1)
1490
+        } else {
1491
+            Expr::Integer(0)
1492
+        };
1493
+
1494
+        for n in lo..=hi {
1495
+            let substituted = Simplifier::substitute(body, var, &Expr::Integer(n));
1496
+            let term = self.eval(&substituted)?;
1497
+            acc = if is_product {
1498
+                self.multiply(&acc, &term)?
1499
+            } else {
1500
+                self.add(&acc, &term)?
1501
+            };
1502
+        }
1503
+
1504
+        Ok(Simplifier::simplify(&acc))
1505
+    }
8851506
 }
8861507
 
8871508
 // Helper functions
@@ -949,6 +1570,15 @@ mod tests {
9491570
         }
9501571
     }
9511572
 
1573
+    fn expr_to_f64(expr: &Expr) -> f64 {
1574
+        match expr {
1575
+            Expr::Integer(n) => *n as f64,
1576
+            Expr::Rational(r) => r.to_f64(),
1577
+            Expr::Float(x) => *x,
1578
+            other => panic!("expected numeric expression, got {other}"),
1579
+        }
1580
+    }
1581
+
9521582
     #[test]
9531583
     fn test_arithmetic() {
9541584
         assert_eq!(eval("2 + 3").unwrap(), Expr::Integer(5));
@@ -1006,4 +1636,163 @@ mod tests {
10061636
         let result = evaluator.eval(&expr).unwrap();
10071637
         assert_eq!(result, Expr::Integer(6));
10081638
     }
1639
+
1640
+    #[test]
1641
+    fn test_sum_evaluation() {
1642
+        assert_eq!(eval("sum(n, n, 1, 5)").unwrap(), Expr::Integer(15));
1643
+    }
1644
+
1645
+    #[test]
1646
+    fn test_product_evaluation() {
1647
+        assert_eq!(eval("product(n, n, 1, 4)").unwrap(), Expr::Integer(24));
1648
+    }
1649
+
1650
+    #[test]
1651
+    fn test_empty_discrete_range() {
1652
+        assert_eq!(eval("sum(n, n, 5, 1)").unwrap(), Expr::Integer(0));
1653
+        assert_eq!(eval("product(n, n, 5, 1)").unwrap(), Expr::Integer(1));
1654
+    }
1655
+
1656
+    #[test]
1657
+    fn test_symbolic_solve_function() {
1658
+        let result = eval("solve(x^2 - 4, x)").unwrap();
1659
+        if let Expr::Vector(solutions) = result {
1660
+            assert_eq!(solutions.len(), 2);
1661
+            let values: Vec<f64> = solutions
1662
+                .iter()
1663
+                .map(|s| match s {
1664
+                    Expr::Integer(n) => *n as f64,
1665
+                    Expr::Rational(r) => r.to_f64(),
1666
+                    Expr::Float(x) => *x,
1667
+                    other => panic!("expected numeric solution, got {other}"),
1668
+                })
1669
+                .collect();
1670
+            assert!(values.iter().any(|v| (*v - 2.0).abs() < 1e-10));
1671
+            assert!(values.iter().any(|v| (*v + 2.0).abs() < 1e-10));
1672
+        } else {
1673
+            panic!("expected vector of solutions");
1674
+        }
1675
+    }
1676
+
1677
+    #[test]
1678
+    fn test_symbolic_sum_linear_closed_form() {
1679
+        let symbolic = eval("sum(k, k, 1, n)").unwrap();
1680
+        let mut evaluator = Evaluator::new();
1681
+        evaluator.set_var("n", Expr::Integer(10));
1682
+        let value = evaluator.eval(&symbolic).unwrap();
1683
+        assert!((expr_to_f64(&value) - 55.0).abs() < 1e-10);
1684
+    }
1685
+
1686
+    #[test]
1687
+    fn test_symbolic_sum_quadratic_closed_form() {
1688
+        let symbolic = eval("sum(k^2 + k, k, 1, n)").unwrap();
1689
+        let mut evaluator = Evaluator::new();
1690
+        evaluator.set_var("n", Expr::Integer(5));
1691
+        let value = evaluator.eval(&symbolic).unwrap();
1692
+        assert!((expr_to_f64(&value) - 70.0).abs() < 1e-10);
1693
+    }
1694
+
1695
+    #[test]
1696
+    fn test_symbolic_sum_fallback_for_unreduced_form() {
1697
+        let symbolic = eval("sum(1/(k-1), k, 1, n)").unwrap();
1698
+        assert!(matches!(symbolic, Expr::Sum { .. }));
1699
+    }
1700
+
1701
+    #[test]
1702
+    fn test_symbolic_product_constant_closed_form() {
1703
+        let symbolic = eval("product(2, k, 1, n)").unwrap();
1704
+        let mut evaluator = Evaluator::new();
1705
+        evaluator.set_var("n", Expr::Integer(5));
1706
+        let value = evaluator.eval(&symbolic).unwrap();
1707
+        assert!((expr_to_f64(&value) - 32.0).abs() < 1e-10);
1708
+    }
1709
+
1710
+    #[test]
1711
+    fn test_symbolic_product_factorial_closed_form() {
1712
+        let symbolic = eval("product(k, k, 1, n)").unwrap();
1713
+        let mut evaluator = Evaluator::new();
1714
+        evaluator.set_var("n", Expr::Integer(5));
1715
+        let value = evaluator.eval(&symbolic).unwrap();
1716
+        assert!((expr_to_f64(&value) - 120.0).abs() < 1e-10);
1717
+    }
1718
+
1719
+    #[test]
1720
+    fn test_symbolic_product_linear_shift_closed_form() {
1721
+        let symbolic = eval("product(k+1, k, 1, n)").unwrap();
1722
+        let mut evaluator = Evaluator::new();
1723
+        evaluator.set_var("n", Expr::Integer(5));
1724
+        let value = evaluator.eval(&symbolic).unwrap();
1725
+        assert!((expr_to_f64(&value) - 720.0).abs() < 1e-10);
1726
+    }
1727
+
1728
+    #[test]
1729
+    fn test_symbolic_product_mul_decomposition_closed_form() {
1730
+        let symbolic = eval("product(2*k, k, 1, n)").unwrap();
1731
+        let mut evaluator = Evaluator::new();
1732
+        evaluator.set_var("n", Expr::Integer(5));
1733
+        let value = evaluator.eval(&symbolic).unwrap();
1734
+        assert!((expr_to_f64(&value) - 3840.0).abs() < 1e-10);
1735
+    }
1736
+
1737
+    #[test]
1738
+    fn test_symbolic_product_simple_quadratic_factoring() {
1739
+        let symbolic = eval("product(k^2 + k, k, 1, n)").unwrap();
1740
+        let mut evaluator = Evaluator::new();
1741
+        evaluator.set_var("n", Expr::Integer(4));
1742
+        let value = evaluator.eval(&symbolic).unwrap();
1743
+        assert!((expr_to_f64(&value) - 2880.0).abs() < 1e-10);
1744
+    }
1745
+
1746
+    #[test]
1747
+    fn test_symbolic_product_telescoping_ratio() {
1748
+        let symbolic = eval("product(k/(k-1), k, 2, n)").unwrap();
1749
+        let mut evaluator = Evaluator::new();
1750
+        evaluator.set_var("n", Expr::Integer(6));
1751
+        let value = evaluator.eval(&symbolic).unwrap();
1752
+        assert!((expr_to_f64(&value) - 6.0).abs() < 1e-10);
1753
+    }
1754
+
1755
+    #[test]
1756
+    fn test_symbolic_product_fallback_for_unreduced_form() {
1757
+        let symbolic = eval("product(k^2 + 2, k, 1, n)").unwrap();
1758
+        assert!(matches!(symbolic, Expr::Product { .. }));
1759
+    }
1760
+
1761
+    #[test]
1762
+    fn test_sum_infers_iteration_var_when_template_var_unused() {
1763
+        let expr = Expr::Sum {
1764
+            expr: Box::new(Expr::Symbol(crate::expr::Symbol::new("n"))),
1765
+            var: crate::expr::Symbol::new("i"),
1766
+            lower: Box::new(Expr::Integer(1)),
1767
+            upper: Box::new(Expr::Integer(5)),
1768
+        };
1769
+        let result = Evaluator::new().eval(&expr).unwrap();
1770
+        assert_eq!(result, Expr::Integer(15));
1771
+    }
1772
+
1773
+    #[test]
1774
+    fn test_product_infers_iteration_var_when_template_var_unused() {
1775
+        let expr = Expr::Product {
1776
+            expr: Box::new(Expr::Symbol(crate::expr::Symbol::new("n"))),
1777
+            var: crate::expr::Symbol::new("i"),
1778
+            lower: Box::new(Expr::Integer(1)),
1779
+            upper: Box::new(Expr::Integer(5)),
1780
+        };
1781
+        let result = Evaluator::new().eval(&expr).unwrap();
1782
+        assert_eq!(result, Expr::Integer(120));
1783
+    }
1784
+
1785
+    #[test]
1786
+    fn test_exact_multiply_rational_one_identity() {
1787
+        let mut evaluator = Evaluator::new();
1788
+        evaluator.exact_mode = true;
1789
+
1790
+        let expr = Expr::mul(vec![
1791
+            Expr::Rational(crate::expr::Rational::new(1, 1)),
1792
+            Expr::symbol("n"),
1793
+        ]);
1794
+        let result = evaluator.eval(&expr).unwrap();
1795
+
1796
+        assert_eq!(result, Expr::symbol("n"));
1797
+    }
10091798
 }
garcalc-cas/src/expr.rsmodified
@@ -263,6 +263,7 @@ impl Expr {
263263
     pub fn is_zero(&self) -> bool {
264264
         match self {
265265
             Self::Integer(0) => true,
266
+            Self::Rational(r) if r.den != 0 && r.num == 0 => true,
266267
             Self::Float(x) if *x == 0.0 => true,
267268
             _ => false,
268269
         }
@@ -271,6 +272,7 @@ impl Expr {
271272
     pub fn is_one(&self) -> bool {
272273
         match self {
273274
             Self::Integer(1) => true,
275
+            Self::Rational(r) if r.den != 0 && r.num == r.den => true,
274276
             Self::Float(x) if *x == 1.0 => true,
275277
             _ => false,
276278
         }
@@ -279,6 +281,7 @@ impl Expr {
279281
     pub fn is_negative_one(&self) -> bool {
280282
         match self {
281283
             Self::Integer(-1) => true,
284
+            Self::Rational(r) if r.den != 0 && r.num == -r.den => true,
282285
             Self::Float(x) if *x == -1.0 => true,
283286
             _ => false,
284287
         }
@@ -307,12 +310,17 @@ impl Expr {
307310
             Self::Derivative { expr, .. } => expr.contains_var(var),
308311
             Self::Integral { expr, .. } => expr.contains_var(var),
309312
             Self::Limit { expr, point, .. } => expr.contains_var(var) || point.contains_var(var),
310
-            Self::Sum { expr, lower, upper, .. } | Self::Product { expr, lower, upper, .. } => {
311
-                expr.contains_var(var) || lower.contains_var(var) || upper.contains_var(var)
313
+            Self::Sum {
314
+                expr, lower, upper, ..
312315
             }
316
+            | Self::Product {
317
+                expr, lower, upper, ..
318
+            } => expr.contains_var(var) || lower.contains_var(var) || upper.contains_var(var),
313319
             Self::Equation(lhs, rhs) => lhs.contains_var(var) || rhs.contains_var(var),
314320
             Self::Inequality { lhs, rhs, .. } => lhs.contains_var(var) || rhs.contains_var(var),
315
-            Self::Matrix(rows) => rows.iter().any(|row| row.iter().any(|e| e.contains_var(var))),
321
+            Self::Matrix(rows) => rows
322
+                .iter()
323
+                .any(|row| row.iter().any(|e| e.contains_var(var))),
316324
             Self::Vector(elems) => elems.iter().any(|e| e.contains_var(var)),
317325
             _ => false,
318326
         }
@@ -378,6 +386,16 @@ impl fmt::Display for Expr {
378386
             }
379387
             Self::Pow(base, exp) => write!(f, "{base}^{exp}"),
380388
             Self::Func(name, args) => {
389
+                if name == "factorial" && args.len() == 1 {
390
+                    let arg = &args[0];
391
+                    if factorial_arg_needs_parens(arg) {
392
+                        write!(f, "({arg})!")?;
393
+                    } else {
394
+                        write!(f, "{arg}!")?;
395
+                    }
396
+                    return Ok(());
397
+                }
398
+
381399
                 write!(f, "{name}(")?;
382400
                 for (i, arg) in args.iter().enumerate() {
383401
                     if i > 0 {
@@ -474,6 +492,10 @@ impl fmt::Display for Expr {
474492
     }
475493
 }
476494
 
495
+fn factorial_arg_needs_parens(arg: &Expr) -> bool {
496
+    matches!(arg, Expr::Neg(_) | Expr::Pow(_, _))
497
+}
498
+
477499
 #[cfg(test)]
478500
 mod tests {
479501
     use super::*;
@@ -495,10 +517,36 @@ mod tests {
495517
         assert_eq!(expr.to_string(), "(x+(2*y))");
496518
     }
497519
 
520
+    #[test]
521
+    fn test_factorial_display() {
522
+        let simple = Expr::func("factorial", vec![Expr::symbol("n")]);
523
+        assert_eq!(simple.to_string(), "n!");
524
+
525
+        let grouped = Expr::func(
526
+            "factorial",
527
+            vec![Expr::add(vec![Expr::symbol("n"), Expr::integer(1)])],
528
+        );
529
+        assert_eq!(grouped.to_string(), "(n+1)!");
530
+
531
+        let negated = Expr::func("factorial", vec![Expr::neg(Expr::symbol("n"))]);
532
+        assert_eq!(negated.to_string(), "(-n)!");
533
+    }
534
+
498535
     #[test]
499536
     fn test_contains_var() {
500537
         let expr = Expr::add(vec![Expr::symbol("x"), Expr::integer(1)]);
501538
         assert!(expr.contains_var(&Symbol::new("x")));
502539
         assert!(!expr.contains_var(&Symbol::new("y")));
503540
     }
541
+
542
+    #[test]
543
+    fn test_numeric_identity_predicates_include_rationals() {
544
+        let zero = Expr::Rational(Rational::new(0, 5));
545
+        let one = Expr::Rational(Rational::new(2, 2));
546
+        let negative_one = Expr::Rational(Rational::new(-3, 3));
547
+
548
+        assert!(zero.is_zero());
549
+        assert!(one.is_one());
550
+        assert!(negative_one.is_negative_one());
551
+    }
504552
 }
garcalc-cas/src/lib.rsmodified
@@ -7,14 +7,14 @@
77
 //! - Equation solving
88
 //! - Limits and series expansions
99
 
10
+pub mod error;
11
+pub mod eval;
1012
 pub mod expr;
1113
 pub mod parser;
12
-pub mod eval;
13
-pub mod error;
1414
 pub mod symbolic;
1515
 
16
-pub use expr::{Expr, Symbol, Rational, LimitDirection, Sign};
17
-pub use parser::Parser;
18
-pub use eval::Evaluator;
1916
 pub use error::{CasError, Result};
17
+pub use eval::Evaluator;
18
+pub use expr::{Expr, LimitDirection, Rational, Sign, Symbol};
19
+pub use parser::Parser;
2020
 pub use symbolic::{Differentiator, Integrator, Limits, Simplifier, Solver};
garcalc-cas/src/parser.rsmodified
@@ -287,10 +287,7 @@ impl<'a> Parser<'a> {
287287
                     if let Some(last) = factors.last() {
288288
                         if matches!(
289289
                             last,
290
-                            Expr::Symbol(_)
291
-                                | Expr::Integer(_)
292
-                                | Expr::Float(_)
293
-                                | Expr::Func(_, _)
290
+                            Expr::Symbol(_) | Expr::Integer(_) | Expr::Float(_) | Expr::Func(_, _)
294291
                         ) {
295292
                             factors.push(self.parse_power()?);
296293
                             continue;
@@ -472,7 +469,10 @@ impl<'a> Parser<'a> {
472469
                         _ => Symbol::new("x"),
473470
                     };
474471
                     let (lower, upper) = if args.len() >= 2 {
475
-                        (Some(Box::new(args.remove(0))), Some(Box::new(args.remove(0))))
472
+                        (
473
+                            Some(Box::new(args.remove(0))),
474
+                            Some(Box::new(args.remove(0))),
475
+                        )
476476
                     } else {
477477
                         (None, None)
478478
                     };
garcalc-cas/src/symbolic.rsmodified
@@ -2,8 +2,8 @@
22
 //!
33
 //! Provides symbolic differentiation, integration, simplification, and solving.
44
 
5
-use crate::expr::{Expr, Symbol};
65
 use crate::error::{CasError, Result};
6
+use crate::expr::{Expr, Symbol};
77
 
88
 /// Symbolic differentiator
99
 pub struct Differentiator;
@@ -130,7 +130,11 @@ impl Differentiator {
130130
             Expr::Func(name, args) => Self::diff_func(name, args, var),
131131
 
132132
             // Derivative of a derivative: just nest it
133
-            Expr::Derivative { expr, var: v, order } => {
133
+            Expr::Derivative {
134
+                expr,
135
+                var: v,
136
+                order,
137
+            } => {
134138
                 if v == var {
135139
                     Ok(Expr::Derivative {
136140
                         expr: expr.clone(),
@@ -264,7 +268,10 @@ impl Differentiator {
264268
                 // 1 / (2 * sqrt(x))
265269
                 Expr::div(
266270
                     Expr::Integer(1),
267
-                    Expr::mul(vec![Expr::Integer(2), Expr::func("sqrt", vec![arg.clone()])]),
271
+                    Expr::mul(vec![
272
+                        Expr::Integer(2),
273
+                        Expr::func("sqrt", vec![arg.clone()]),
274
+                    ]),
268275
                 )
269276
             }
270277
             "cbrt" => {
@@ -332,10 +339,7 @@ impl Integrator {
332339
     fn integrate_impl(expr: &Expr, var: &Symbol) -> Result<Expr> {
333340
         // If expression doesn't contain variable, it's a constant
334341
         if !expr.contains_var(var) {
335
-            return Ok(Expr::mul(vec![
336
-                expr.clone(),
337
-                Expr::Symbol(var.clone()),
338
-            ]));
342
+            return Ok(Expr::mul(vec![expr.clone(), Expr::Symbol(var.clone())]));
339343
         }
340344
 
341345
         match expr {
@@ -393,10 +397,10 @@ impl Integrator {
393397
                 if **base == Expr::Symbol(var.clone()) && !exp.contains_var(var) {
394398
                     // Check for n = -1 case: ∫ x^(-1) dx = ln|x|
395399
                     if exp.is_negative_one() {
396
-                        return Ok(Expr::func("ln", vec![Expr::func(
397
-                            "abs",
398
-                            vec![Expr::Symbol(var.clone())],
399
-                        )]));
400
+                        return Ok(Expr::func(
401
+                            "ln",
402
+                            vec![Expr::func("abs", vec![Expr::Symbol(var.clone())])],
403
+                        ));
400404
                     }
401405
 
402406
                     let n_plus_1 = Expr::add(vec![(**exp).clone(), Expr::Integer(1)]);
@@ -484,28 +488,40 @@ impl Integrator {
484488
             // Trigonometric
485489
             "sin" => Expr::neg(Expr::func("cos", vec![arg.clone()])),
486490
             "cos" => Expr::func("sin", vec![arg.clone()]),
487
-            "tan" => Expr::neg(Expr::func("ln", vec![Expr::func(
488
-                "abs",
489
-                vec![Expr::func("cos", vec![arg.clone()])],
490
-            )])),
491
-            "cot" => Expr::func("ln", vec![Expr::func(
492
-                "abs",
493
-                vec![Expr::func("sin", vec![arg.clone()])],
494
-            )]),
495
-            "sec" => Expr::func("ln", vec![Expr::func(
496
-                "abs",
497
-                vec![Expr::add(vec![
498
-                    Expr::func("sec", vec![arg.clone()]),
499
-                    Expr::func("tan", vec![arg.clone()]),
500
-                ])],
501
-            )]),
502
-            "csc" => Expr::neg(Expr::func("ln", vec![Expr::func(
503
-                "abs",
504
-                vec![Expr::add(vec![
505
-                    Expr::func("csc", vec![arg.clone()]),
506
-                    Expr::func("cot", vec![arg.clone()]),
507
-                ])],
508
-            )])),
491
+            "tan" => Expr::neg(Expr::func(
492
+                "ln",
493
+                vec![Expr::func(
494
+                    "abs",
495
+                    vec![Expr::func("cos", vec![arg.clone()])],
496
+                )],
497
+            )),
498
+            "cot" => Expr::func(
499
+                "ln",
500
+                vec![Expr::func(
501
+                    "abs",
502
+                    vec![Expr::func("sin", vec![arg.clone()])],
503
+                )],
504
+            ),
505
+            "sec" => Expr::func(
506
+                "ln",
507
+                vec![Expr::func(
508
+                    "abs",
509
+                    vec![Expr::add(vec![
510
+                        Expr::func("sec", vec![arg.clone()]),
511
+                        Expr::func("tan", vec![arg.clone()]),
512
+                    ])],
513
+                )],
514
+            ),
515
+            "csc" => Expr::neg(Expr::func(
516
+                "ln",
517
+                vec![Expr::func(
518
+                    "abs",
519
+                    vec![Expr::add(vec![
520
+                        Expr::func("csc", vec![arg.clone()]),
521
+                        Expr::func("cot", vec![arg.clone()]),
522
+                    ])],
523
+                )],
524
+            )),
509525
 
510526
             // Hyperbolic
511527
             "sinh" => Expr::func("cosh", vec![arg.clone()]),
@@ -535,7 +551,12 @@ pub struct Limits;
535551
 
536552
 impl Limits {
537553
     /// Compute the limit of an expression as var approaches point
538
-    pub fn limit(expr: &Expr, var: &Symbol, point: &Expr, direction: Option<crate::expr::LimitDirection>) -> Result<Expr> {
554
+    pub fn limit(
555
+        expr: &Expr,
556
+        var: &Symbol,
557
+        point: &Expr,
558
+        direction: Option<crate::expr::LimitDirection>,
559
+    ) -> Result<Expr> {
539560
         Self::limit_impl(expr, var, point, direction, 0)
540561
     }
541562
 
@@ -601,7 +622,8 @@ impl Limits {
601622
                             // Handle x^(-n) where n > 1
602623
                             if let Expr::Integer(n) = inner.as_ref() {
603624
                                 if *n > 0 {
604
-                                    denominator_parts.push(Expr::pow((**base).clone(), Expr::Integer(*n)));
625
+                                    denominator_parts
626
+                                        .push(Expr::pow((**base).clone(), Expr::Integer(*n)));
605627
                                     continue;
606628
                                 }
607629
                             }
@@ -624,8 +646,10 @@ impl Limits {
624646
                     };
625647
 
626648
                     // Check if it's an indeterminate form (0/0 or ∞/∞)
627
-                    let num_at_point = Simplifier::simplify(&Simplifier::substitute(&numerator, var, point));
628
-                    let denom_at_point = Simplifier::simplify(&Simplifier::substitute(&denominator, var, point));
649
+                    let num_at_point =
650
+                        Simplifier::simplify(&Simplifier::substitute(&numerator, var, point));
651
+                    let denom_at_point =
652
+                        Simplifier::simplify(&Simplifier::substitute(&denominator, var, point));
629653
 
630654
                     let num_zero = Self::is_zero(&num_at_point);
631655
                     let denom_zero = Self::is_zero(&denom_at_point);
@@ -633,7 +657,14 @@ impl Limits {
633657
                     let denom_inf = matches!(denom_at_point, Expr::Infinity(_));
634658
 
635659
                     if (num_zero && denom_zero) || (num_inf && denom_inf) {
636
-                        return Self::try_lhopital(&numerator, &denominator, var, point, direction, depth);
660
+                        return Self::try_lhopital(
661
+                            &numerator,
662
+                            &denominator,
663
+                            var,
664
+                            point,
665
+                            direction,
666
+                            depth,
667
+                        );
637668
                     }
638669
                 }
639670
 
@@ -652,7 +683,10 @@ impl Limits {
652683
                     // 1/0 -> infinity (sign depends on direction)
653684
                     Ok(Expr::Infinity(crate::expr::Sign::Positive))
654685
                 } else {
655
-                    Ok(Simplifier::simplify(&Expr::pow(base_limit, Expr::Integer(-1))))
686
+                    Ok(Simplifier::simplify(&Expr::pow(
687
+                        base_limit,
688
+                        Expr::Integer(-1),
689
+                    )))
656690
                 }
657691
             }
658692
 
@@ -809,7 +843,9 @@ impl Limits {
809843
             Expr::Func(name, args) if name == "exp" && args.len() == 1 => {
810844
                 if args[0] == Expr::Symbol(var.clone()) {
811845
                     match sign {
812
-                        crate::expr::Sign::Positive => Ok(Expr::Infinity(crate::expr::Sign::Positive)),
846
+                        crate::expr::Sign::Positive => {
847
+                            Ok(Expr::Infinity(crate::expr::Sign::Positive))
848
+                        }
813849
                         crate::expr::Sign::Negative => Ok(Expr::Integer(0)),
814850
                     }
815851
                 } else {
@@ -870,10 +906,7 @@ impl Limits {
870906
             let denom_deriv = Differentiator::diff(denom, var)?;
871907
 
872908
             // Recursive limit of f'/g'
873
-            let quotient = Expr::mul(vec![
874
-                num_deriv,
875
-                Expr::pow(denom_deriv, Expr::Integer(-1)),
876
-            ]);
909
+            let quotient = Expr::mul(vec![num_deriv, Expr::pow(denom_deriv, Expr::Integer(-1))]);
877910
             Self::limit_impl(&quotient, var, point, direction, depth + 1)
878911
         } else if denom_is_zero && !num_is_zero {
879912
             // Limit is ±∞ or doesn't exist
@@ -901,8 +934,7 @@ impl Limits {
901934
             // Check for 0 * ∞ or similar patterns in products
902935
             Expr::Mul(factors) => {
903936
                 let has_infinity = factors.iter().any(|f| {
904
-                    matches!(f, Expr::Infinity(_))
905
-                        || matches!(f, Expr::Rational(r) if r.den == 0)
937
+                    matches!(f, Expr::Infinity(_)) || matches!(f, Expr::Rational(r) if r.den == 0)
906938
                 });
907939
                 let has_zero = factors.iter().any(|f| Self::is_zero(f));
908940
                 // 0 * ∞ is indeterminate
@@ -961,7 +993,11 @@ impl Limits {
961993
                 }
962994
             }
963995
             Expr::Mul(factors) => factors.iter().map(|f| Self::degree_in(f, var)).sum(),
964
-            Expr::Add(terms) => terms.iter().map(|t| Self::degree_in(t, var)).max().unwrap_or(0),
996
+            Expr::Add(terms) => terms
997
+                .iter()
998
+                .map(|t| Self::degree_in(t, var))
999
+                .max()
1000
+                .unwrap_or(0),
9651001
             Expr::Neg(e) => Self::degree_in(e, var),
9661002
             _ => 0,
9671003
         }
@@ -1193,6 +1229,16 @@ impl Simplifier {
11931229
             Expr::Func(name, args) => {
11941230
                 let sargs: Vec<_> = args.iter().map(Self::simplify_impl).collect();
11951231
 
1232
+                if name == "factorial" && sargs.len() == 1 {
1233
+                    if let Expr::Integer(n) = sargs[0] {
1234
+                        if n >= 0 {
1235
+                            if let Some(value) = Self::factorial_i64_checked(n) {
1236
+                                return Expr::Integer(value);
1237
+                            }
1238
+                        }
1239
+                    }
1240
+                }
1241
+
11961242
                 // Try to evaluate sqrt of perfect squares
11971243
                 if name == "sqrt" && sargs.len() == 1 {
11981244
                     if let Expr::Integer(n) = &sargs[0] {
@@ -1411,7 +1457,10 @@ impl Simplifier {
14111457
                     final_factors.insert(0, Expr::Integer(numerator));
14121458
                 }
14131459
             } else {
1414
-                final_factors.insert(0, Expr::Rational(crate::expr::Rational::new(numerator, denominator)));
1460
+                final_factors.insert(
1461
+                    0,
1462
+                    Expr::Rational(crate::expr::Rational::new(numerator, denominator)),
1463
+                );
14151464
             }
14161465
         }
14171466
 
@@ -1423,6 +1472,16 @@ impl Simplifier {
14231472
             Expr::Mul(final_factors)
14241473
         }
14251474
     }
1475
+
1476
+    fn factorial_i64_checked(n: i64) -> Option<i64> {
1477
+        let n = u64::try_from(n).ok()?;
1478
+        let mut acc: i64 = 1;
1479
+        for k in 2..=n {
1480
+            let step = i64::try_from(k).ok()?;
1481
+            acc = acc.checked_mul(step)?;
1482
+        }
1483
+        Some(acc)
1484
+    }
14261485
 }
14271486
 
14281487
 /// Equation solver
@@ -1469,10 +1528,7 @@ impl Solver {
14691528
                         "Variable coefficient is zero".to_string(),
14701529
                     ));
14711530
                 }
1472
-                return Ok(vec![Simplifier::simplify(&Expr::div(
1473
-                    Expr::neg(c),
1474
-                    b,
1475
-                ))]);
1531
+                return Ok(vec![Simplifier::simplify(&Expr::div(Expr::neg(c), b))]);
14761532
             }
14771533
             // Quadratic
14781534
             return Self::solve_quadratic_with_coeffs(&a, &b, &c);
@@ -1504,8 +1560,8 @@ impl Solver {
15041560
 
15051561
         // Try many starting points to find all roots
15061562
         let mut starting_points: Vec<f64> = vec![
1507
-            0.0, 1.0, -1.0, 2.0, -2.0, 0.5, -0.5, 3.0, -3.0, 4.0, -4.0, 5.0, -5.0,
1508
-            0.25, -0.25, 0.75, -0.75, 1.5, -1.5, 2.5, -2.5, 10.0, -10.0, 100.0, -100.0,
1563
+            0.0, 1.0, -1.0, 2.0, -2.0, 0.5, -0.5, 3.0, -3.0, 4.0, -4.0, 5.0, -5.0, 0.25, -0.25,
1564
+            0.75, -0.75, 1.5, -1.5, 2.5, -2.5, 10.0, -10.0, 100.0, -100.0,
15091565
         ];
15101566
         // Add more points based on degree
15111567
         for i in 0..20 {
@@ -1540,21 +1596,29 @@ impl Solver {
15401596
         }
15411597
 
15421598
         // Convert to expressions, cleaning up near-integers
1543
-        let result: Vec<Expr> = roots.into_iter().map(|r| {
1544
-            if r.abs() < 1e-10 {
1545
-                Expr::Integer(0)
1546
-            } else if (r - r.round()).abs() < 1e-10 {
1547
-                Expr::Integer(r.round() as i64)
1548
-            } else {
1549
-                Expr::Float(r)
1550
-            }
1551
-        }).collect();
1599
+        let result: Vec<Expr> = roots
1600
+            .into_iter()
1601
+            .map(|r| {
1602
+                if r.abs() < 1e-10 {
1603
+                    Expr::Integer(0)
1604
+                } else if (r - r.round()).abs() < 1e-10 {
1605
+                    Expr::Integer(r.round() as i64)
1606
+                } else {
1607
+                    Expr::Float(r)
1608
+                }
1609
+            })
1610
+            .collect();
15521611
 
15531612
         Some(result)
15541613
     }
15551614
 
15561615
     /// Newton-Raphson iteration to find a root
1557
-    fn newton_raphson(expr: &Expr, var: &Symbol, start: f64, evaluator: &crate::eval::Evaluator) -> Option<f64> {
1616
+    fn newton_raphson(
1617
+        expr: &Expr,
1618
+        var: &Symbol,
1619
+        start: f64,
1620
+        evaluator: &crate::eval::Evaluator,
1621
+    ) -> Option<f64> {
15581622
         let deriv = Differentiator::diff(expr, var).ok()?;
15591623
         let deriv = Simplifier::simplify(&deriv);
15601624
 
@@ -1565,7 +1629,10 @@ impl Solver {
15651629
         for _ in 0..max_iter {
15661630
             let f_x = {
15671631
                 let subst = Simplifier::substitute(expr, var, &Expr::Float(x));
1568
-                evaluator.eval(&subst).ok().and_then(|e| Self::expr_to_f64(&e))?
1632
+                evaluator
1633
+                    .eval(&subst)
1634
+                    .ok()
1635
+                    .and_then(|e| Self::expr_to_f64(&e))?
15691636
             };
15701637
 
15711638
             if f_x.abs() < tolerance {
@@ -1574,7 +1641,10 @@ impl Solver {
15741641
 
15751642
             let fp_x = {
15761643
                 let subst = Simplifier::substitute(&deriv, var, &Expr::Float(x));
1577
-                evaluator.eval(&subst).ok().and_then(|e| Self::expr_to_f64(&e))?
1644
+                evaluator
1645
+                    .eval(&subst)
1646
+                    .ok()
1647
+                    .and_then(|e| Self::expr_to_f64(&e))?
15781648
             };
15791649
 
15801650
             if fp_x.abs() < 1e-15 {
@@ -1639,77 +1709,6 @@ impl Solver {
16391709
         }
16401710
     }
16411711
 
1642
-    fn classify_polynomial(expr: &Expr, var: &Symbol) -> Option<(u32, Expr, Expr)> {
1643
-        // Very simplified polynomial detection
1644
-        // Returns (degree, leading coefficient, rest)
1645
-        match expr {
1646
-            // x alone: degree 1, coef 1
1647
-            Expr::Symbol(s) if s == var => Some((1, Expr::Integer(1), Expr::Integer(0))),
1648
-
1649
-            // a*x: degree 1, coef a
1650
-            Expr::Mul(factors) => {
1651
-                let mut coef = Expr::Integer(1);
1652
-                let mut has_var = false;
1653
-                let mut var_power = 1u32;
1654
-
1655
-                for f in factors {
1656
-                    match f {
1657
-                        Expr::Symbol(s) if s == var => {
1658
-                            has_var = true;
1659
-                        }
1660
-                        Expr::Pow(base, exp) => {
1661
-                            if let Expr::Symbol(s) = &**base {
1662
-                                if s == var {
1663
-                                    if let Expr::Integer(n) = **exp {
1664
-                                        has_var = true;
1665
-                                        var_power = n as u32;
1666
-                                    }
1667
-                                }
1668
-                            }
1669
-                        }
1670
-                        other if !other.contains_var(var) => {
1671
-                            coef = Expr::mul(vec![coef, other.clone()]);
1672
-                        }
1673
-                        _ => return None,
1674
-                    }
1675
-                }
1676
-
1677
-                if has_var {
1678
-                    Some((var_power, Simplifier::simplify(&coef), Expr::Integer(0)))
1679
-                } else {
1680
-                    None
1681
-                }
1682
-            }
1683
-
1684
-            // ax + b or ax^2 + bx + c
1685
-            Expr::Add(terms) => {
1686
-                let mut max_degree = 0u32;
1687
-                let mut leading_coef = Expr::Integer(0);
1688
-
1689
-                for term in terms {
1690
-                    if let Some((deg, coef, _)) = Self::classify_polynomial(term, var) {
1691
-                        if deg > max_degree {
1692
-                            max_degree = deg;
1693
-                            leading_coef = coef;
1694
-                        }
1695
-                    } else if !term.contains_var(var) {
1696
-                        // Constant term, ignore for degree calculation
1697
-                    } else {
1698
-                        return None;
1699
-                    }
1700
-                }
1701
-
1702
-                if max_degree > 0 {
1703
-                    Some((max_degree, leading_coef, Expr::Integer(0)))
1704
-                } else {
1705
-                    None
1706
-                }
1707
-            }
1708
-
1709
-            _ => None,
1710
-        }
1711
-    }
1712
-
17131712
     fn solve_quadratic_with_coeffs(a: &Expr, b: &Expr, c: &Expr) -> Result<Vec<Expr>> {
17141713
         // Discriminant: b^2 - 4ac
17151714
         let discriminant = Simplifier::simplify(&Expr::sub(
@@ -1726,10 +1725,7 @@ impl Solver {
17261725
             Expr::add(vec![neg_b.clone(), sqrt_d.clone()]),
17271726
             two_a.clone(),
17281727
         ));
1729
-        let x2 = Simplifier::simplify(&Expr::div(
1730
-            Expr::sub(neg_b, sqrt_d),
1731
-            two_a,
1732
-        ));
1728
+        let x2 = Simplifier::simplify(&Expr::div(Expr::sub(neg_b, sqrt_d), two_a));
17331729
 
17341730
         Ok(vec![x1, x2])
17351731
     }
@@ -1741,10 +1737,7 @@ impl Solver {
17411737
         Self::solve_quadratic_with_coeffs(&a, &b, &c)
17421738
     }
17431739
 
1744
-    fn extract_quadratic_coefficients(
1745
-        expr: &Expr,
1746
-        var: &Symbol,
1747
-    ) -> Result<(Expr, Expr, Expr)> {
1740
+    fn extract_quadratic_coefficients(expr: &Expr, var: &Symbol) -> Result<(Expr, Expr, Expr)> {
17481741
         // Initialize coefficients
17491742
         let mut a = Expr::Integer(0);
17501743
         let mut b = Expr::Integer(0);
@@ -1764,7 +1757,7 @@ impl Solver {
17641757
                 _ => {
17651758
                     return Err(CasError::EvaluationError(
17661759
                         "Not a quadratic equation".to_string(),
1767
-                    ))
1760
+                    ));
17681761
                 }
17691762
             }
17701763
         }
@@ -1934,6 +1927,16 @@ mod tests {
19341927
         assert_eq!(result, Expr::symbol("x"));
19351928
     }
19361929
 
1930
+    #[test]
1931
+    fn test_simplify_mul_rational_one_identity() {
1932
+        let expr = Expr::mul(vec![
1933
+            Expr::Rational(crate::expr::Rational::new(1, 1)),
1934
+            Expr::symbol("n"),
1935
+        ]);
1936
+        let result = Simplifier::simplify(&expr);
1937
+        assert_eq!(result, Expr::symbol("n"));
1938
+    }
1939
+
19371940
     #[test]
19381941
     fn test_simplify_combine_like() {
19391942
         // 2x + 3x = 5x
@@ -1968,4 +1971,32 @@ mod tests {
19681971
         let solutions = Solver::solve(&expr, &Symbol::new("x")).unwrap();
19691972
         assert!(!solutions.is_empty());
19701973
     }
1974
+
1975
+    #[test]
1976
+    fn test_simplify_factorial_constant() {
1977
+        let expr = Expr::func("factorial", vec![Expr::Integer(1)]);
1978
+        let simplified = Simplifier::simplify(&expr);
1979
+        assert_eq!(simplified, Expr::Integer(1));
1980
+    }
1981
+
1982
+    #[test]
1983
+    fn test_simplify_product_residual_factorial_inverse() {
1984
+        let expr = Expr::mul(vec![
1985
+            Expr::func("factorial", vec![Expr::symbol("n")]),
1986
+            Expr::func(
1987
+                "factorial",
1988
+                vec![Expr::add(vec![Expr::Integer(1), Expr::symbol("n")])],
1989
+            ),
1990
+            Expr::pow(
1991
+                Expr::func("factorial", vec![Expr::Integer(1)]),
1992
+                Expr::Integer(-1),
1993
+            ),
1994
+        ]);
1995
+
1996
+        let simplified = Simplifier::simplify(&expr);
1997
+        let rendered = simplified.to_string();
1998
+        assert!(!rendered.contains("1!"));
1999
+        assert!(!rendered.contains("factorial"));
2000
+        assert!(rendered.contains("n!"));
2001
+    }
19712002
 }