qualia_core_db/specialized_libs/
symbolic_integration.rs1use super::symbolic_algebra::{
12 add, c, cos, div, exp, ln, mul, neg, pow, simplify, sin, sub, var, Expr,
13};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum IntegrationError {
17 NotIntegrable,
20}
21
22fn is_var(e: &Expr, name: &str) -> bool {
23 matches!(e, Expr::Var(v) if v == name)
24}
25
26pub fn integrate(expr: &Expr, x: &str) -> Result<Expr, IntegrationError> {
28 let r = match expr {
29 Expr::Const(k) => mul(c(*k), var(x)),
31 Expr::Var(name) if name == x => div(pow(var(x), 2), c(2.0)),
33 Expr::Var(_) => mul(expr.clone(), var(x)),
35 Expr::Pow(base, n) if is_var(base, x) => {
37 if *n == -1 {
38 ln(var(x)) } else {
40 div(pow(var(x), n + 1), c((*n + 1) as f64))
41 }
42 }
43 Expr::Exp(inner) if is_var(inner, x) => exp(var(x)),
45 Expr::Sin(inner) if is_var(inner, x) => neg(cos(var(x))),
47 Expr::Cos(inner) if is_var(inner, x) => sin(var(x)),
48 Expr::Add(a, b) => add(integrate(a, x)?, integrate(b, x)?),
49 Expr::Sub(a, b) => sub(integrate(a, x)?, integrate(b, x)?),
50 Expr::Neg(a) => neg(integrate(a, x)?),
51 Expr::Mul(a, b) => match (&**a, &**b) {
53 (Expr::Const(_), _) => mul((**a).clone(), integrate(b, x)?),
54 (_, Expr::Const(_)) => mul((**b).clone(), integrate(a, x)?),
55 _ => return Err(IntegrationError::NotIntegrable),
56 },
57 Expr::Div(a, b) => match (&**a, &**b) {
59 (_, Expr::Const(d)) if *d != 0.0 => mul(c(1.0 / d), integrate(a, x)?),
60 (Expr::Const(k), Expr::Var(name)) if name == x => mul(c(*k), ln(var(x))),
61 (Expr::Const(k), Expr::Pow(base, n)) if is_var(base, x) => {
62 let m = -*n; if m == -1 {
64 mul(c(*k), ln(var(x)))
65 } else {
66 mul(c(*k), div(pow(var(x), m + 1), c((m + 1) as f64)))
67 }
68 }
69 _ => return Err(IntegrationError::NotIntegrable),
70 },
71 _ => return Err(IntegrationError::NotIntegrable),
72 };
73 Ok(simplify(&r))
74}
75
76pub fn integrate_definite(expr: &Expr, x: &str, a: f64, b: f64, steps: usize) -> Option<f64> {
79 if let Ok(anti) = integrate(expr, x) {
80 let fa = eval_at(&anti, x, a)?;
81 let fb = eval_at(&anti, x, b)?;
82 return Some(fb - fa);
83 }
84 let n = if steps.max(2) % 2 == 0 {
86 steps.max(2)
87 } else {
88 steps + 1
89 };
90 let h = (b - a) / n as f64;
91 let mut sum = eval_at(expr, x, a)? + eval_at(expr, x, b)?;
92 for i in 1..n {
93 let xi = a + i as f64 * h;
94 sum += if i % 2 == 1 { 4.0 } else { 2.0 } * eval_at(expr, x, xi)?;
95 }
96 Some(sum * h / 3.0)
97}
98
99fn eval_at(e: &Expr, x: &str, v: f64) -> Option<f64> {
100 let mut env = std::collections::HashMap::new();
101 env.insert(x.to_string(), v);
102 e.eval(&env)
103}
104
105#[cfg(test)]
106mod tests {
107 use super::super::symbolic_algebra::differentiate;
108 use super::*;
109
110 fn roundtrip(expr: &Expr, x: &str) {
112 let anti = integrate(expr, x).unwrap();
113 let back = simplify(&differentiate(&anti, x));
114 for &q in &[0.3, 1.7, -1.1, 2.5] {
115 assert!(
116 (eval_at(&back, x, q).unwrap() - eval_at(expr, x, q).unwrap()).abs() < 1e-7,
117 "round-trip failed at {q}"
118 );
119 }
120 }
121
122 #[test]
123 fn power_rule_roundtrips() {
124 roundtrip(&pow(var("x"), 2), "x"); roundtrip(&pow(var("x"), 5), "x");
126 roundtrip(&var("x"), "x");
127 roundtrip(&c(7.0), "x");
128 }
129
130 #[test]
131 fn linearity_roundtrips() {
132 let f = sub(
134 add(mul(c(3.0), pow(var("x"), 2)), mul(c(2.0), var("x"))),
135 c(5.0),
136 );
137 roundtrip(&f, "x");
138 }
139
140 #[test]
141 fn definite_integral_ftc_and_numeric() {
142 let v = integrate_definite(&pow(var("x"), 2), "x", 0.0, 1.0, 100).unwrap();
144 assert!((v - 1.0 / 3.0).abs() < 1e-9);
145 let v2 = integrate_definite(
148 &super::super::symbolic_algebra::sqrt(var("x")),
149 "x",
150 0.0,
151 1.0,
152 2000,
153 )
154 .unwrap();
155 assert!((v2 - 2.0 / 3.0).abs() < 1e-3);
156 }
157
158 #[test]
159 fn transcendental_antiderivatives_roundtrip() {
160 let pos = |expr: &Expr| {
163 let anti = integrate(expr, "x").unwrap();
164 let back = simplify(&differentiate(&anti, "x"));
165 for &q in &[0.3, 1.7, 2.5] {
166 assert!(
167 (eval_at(&back, "x", q).unwrap() - eval_at(expr, "x", q).unwrap()).abs() < 1e-7
168 );
169 }
170 };
171 pos(&pow(var("x"), -1));
172 pos(&super::super::symbolic_algebra::exp(var("x")));
173 pos(&super::super::symbolic_algebra::sin(var("x")));
174 pos(&super::super::symbolic_algebra::cos(var("x")));
175 }
176
177 #[test]
178 fn fails_closed_on_nonlinear_inner() {
179 let f = super::super::symbolic_algebra::sin(pow(var("x"), 2));
181 assert_eq!(
182 integrate(&f, "x").unwrap_err(),
183 IntegrationError::NotIntegrable
184 );
185 }
186}