1use crate::calculus::integrals::{
26 basic::BasicIntegrals, by_parts::IntegrationByParts, function_integrals::FunctionIntegrals,
27 rational, risch, substitution, table, trigonometric,
28};
29use crate::core::{Expression, Number, Symbol};
30use std::collections::HashSet;
31const MAX_DEPTH: usize = 10;
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum IntegrationStrategy {
42 TableLookup,
43 RationalFunction,
44 FunctionRegistry,
45 IntegrationByParts,
46 Substitution,
47 Trigonometric,
48 Risch,
49 BasicRules,
50}
51#[derive(Debug, Clone)]
56pub struct StrategyContext {
57 active_strategies: HashSet<IntegrationStrategy>,
58 depth: usize,
59}
60impl StrategyContext {
61 pub fn new() -> Self {
63 Self {
64 active_strategies: HashSet::new(),
65 depth: 0,
66 }
67 }
68 pub fn is_active(&self, strategy: IntegrationStrategy) -> bool {
70 self.active_strategies.contains(&strategy)
71 }
72 pub fn with_strategy<F>(&self, strategy: IntegrationStrategy, f: F) -> Option<Expression>
74 where
75 F: FnOnce(&Self) -> Option<Expression>,
76 {
77 if self.is_active(strategy) {
78 return None;
79 }
80 let mut child_context = self.clone();
81 child_context.active_strategies.insert(strategy);
82 child_context.depth += 1;
83 f(&child_context)
84 }
85 pub fn depth(&self) -> usize {
87 self.depth
88 }
89}
90impl Default for StrategyContext {
91 fn default() -> Self {
92 Self::new()
93 }
94}
95pub fn integrate_with_strategy(expr: &Expression, var: Symbol, depth: usize) -> Expression {
103 if depth >= MAX_DEPTH {
104 return Expression::integral(expr.clone(), var);
105 }
106 let context = StrategyContext {
107 active_strategies: HashSet::new(),
108 depth,
109 };
110 integrate_with_context(expr, var, &context)
111}
112fn integrate_with_context(expr: &Expression, var: Symbol, ctx: &StrategyContext) -> Expression {
116 if let Some(result) = try_table_lookup_with_context(expr, &var, ctx) {
117 return result;
118 }
119 if is_rational_function(expr, &var) {
120 if let Some(result) = try_rational_function(expr, &var) {
121 return result;
122 }
123 }
124 if let Some(result) = try_registry_integration_with_context(expr, &var, ctx) {
125 return result;
126 }
127 if let Some(result) = ctx.with_strategy(IntegrationStrategy::IntegrationByParts, |child_ctx| {
128 try_by_parts_with_context(expr, &var, child_ctx, child_ctx.depth())
129 }) {
130 return result;
131 }
132 if let Some(result) = ctx.with_strategy(IntegrationStrategy::Substitution, |child_ctx| {
133 try_substitution_with_context(expr, &var, child_ctx)
134 }) {
135 return result;
136 }
137 if let Some(result) = ctx.with_strategy(IntegrationStrategy::Trigonometric, |child_ctx| {
138 try_trigonometric_with_context(expr, &var, child_ctx)
139 }) {
140 return result;
141 }
142 if let Some(result) = ctx.with_strategy(IntegrationStrategy::Risch, |child_ctx| {
143 try_risch_with_context(expr, &var, child_ctx)
144 }) {
145 return result;
146 }
147 if let Some(result) = try_basic_rules_with_context(expr, &var, ctx) {
148 return result;
149 }
150 Expression::integral(expr.clone(), var)
151}
152fn try_table_lookup_with_context(
154 expr: &Expression,
155 var: &Symbol,
156 _context: &StrategyContext,
157) -> Option<Expression> {
158 table::try_table_lookup(expr, var)
159}
160fn try_rational_function(expr: &Expression, var: &Symbol) -> Option<Expression> {
162 rational::integrate_rational(expr, var)
163}
164pub fn try_registry_integration(expr: &Expression, var: &Symbol) -> Option<Expression> {
168 match expr {
169 Expression::Function { name, args } => {
170 let result = FunctionIntegrals::integrate(name, args, var.clone());
171 if is_symbolic_integral(&result) {
172 None
173 } else {
174 Some(result)
175 }
176 }
177 _ => None,
178 }
179}
180fn try_registry_integration_with_context(
182 expr: &Expression,
183 var: &Symbol,
184 _context: &StrategyContext,
185) -> Option<Expression> {
186 try_registry_integration(expr, var)
187}
188pub fn try_by_parts(expr: &Expression, var: &Symbol, depth: usize) -> Option<Expression> {
192 IntegrationByParts::integrate(expr, var.clone(), depth)
193}
194fn try_by_parts_with_context(
198 expr: &Expression,
199 var: &Symbol,
200 context: &StrategyContext,
201 depth: usize,
202) -> Option<Expression> {
203 IntegrationByParts::integrate_with_context(expr, var.clone(), context, depth)
204}
205fn try_substitution_with_context(
207 expr: &Expression,
208 var: &Symbol,
209 context: &StrategyContext,
210) -> Option<Expression> {
211 substitution::try_substitution(expr, var, context.depth())
212}
213fn try_trigonometric_with_context(
215 expr: &Expression,
216 var: &Symbol,
217 _context: &StrategyContext,
218) -> Option<Expression> {
219 trigonometric::try_trigonometric_integration(expr, var)
220}
221fn try_risch_with_context(
223 expr: &Expression,
224 var: &Symbol,
225 _context: &StrategyContext,
226) -> Option<Expression> {
227 risch::try_risch_integration(expr, var)
228}
229pub fn is_polynomial(expr: &Expression, _var: &Symbol) -> bool {
233 match expr {
234 Expression::Number(_) | Expression::Constant(_) => true,
235 Expression::Symbol(_sym) => true,
236 Expression::Add(terms) => terms.iter().all(|t| is_polynomial(t, _var)),
237 Expression::Mul(factors) => factors.iter().all(|f| is_polynomial(f, _var)),
238 Expression::Pow(base, exp) => {
239 if !is_polynomial(base, _var) {
240 return false;
241 }
242 matches!(exp.as_ref(), Expression::Number(Number::Integer(n)) if * n >= 0)
243 }
244 _ => false,
245 }
246}
247fn is_rational_function(expr: &Expression, var: &Symbol) -> bool {
251 let result = match expr {
252 _ if is_polynomial(expr, var) => true,
253 Expression::Pow(base, exp) => {
254 if let Expression::Number(Number::Integer(_)) = exp.as_ref() {
255 let poly_check = is_polynomial(base.as_ref(), var);
256 poly_check
257 } else {
258 false
259 }
260 }
261 Expression::Mul(factors) => {
262 for factor in factors.iter() {
263 if let Expression::Pow(base, exp) = factor {
264 if let Expression::Number(Number::Integer(n)) = exp.as_ref() {
265 if *n < 0 && !is_polynomial(base.as_ref(), var) {
266 return false;
267 }
268 } else {
269 return false;
270 }
271 }
272 }
273 factors.iter().all(|f| match f {
274 Expression::Pow(base, _) => is_polynomial(base.as_ref(), var),
275 _ => is_polynomial(f, var),
276 })
277 }
278 _ => false,
279 };
280 result
281}
282fn try_basic_rules_with_context(
287 expr: &Expression,
288 var: &Symbol,
289 context: &StrategyContext,
290) -> Option<Expression> {
291 match expr {
292 Expression::Number(_) => Some(BasicIntegrals::handle_constant(expr, var.clone())),
293 Expression::Symbol(sym) => Some(BasicIntegrals::handle_symbol(sym, var)),
294 Expression::Add(terms) => Some(BasicIntegrals::handle_sum(terms, var, context.depth())),
295 Expression::Mul(factors) => Some(BasicIntegrals::handle_product(
296 factors,
297 var.clone(),
298 context.depth(),
299 )),
300 Expression::Pow(base, exp) => Some(BasicIntegrals::handle_power(base, exp, var.clone())),
301 Expression::Calculus(data) => {
302 Some(BasicIntegrals::handle_calculus(expr, data, var.clone()))
303 }
304 _ => None,
305 }
306}
307fn is_symbolic_integral(expr: &Expression) -> bool {
309 matches!(expr, Expression::Calculus(_))
310}
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::symbol;
315 #[test]
316 fn test_is_polynomial_constant() {
317 let x = symbol!(x);
318 assert!(is_polynomial(&Expression::integer(5), &x));
319 }
320 #[test]
321 fn test_is_polynomial_variable() {
322 let x = symbol!(x);
323 assert!(is_polynomial(&Expression::symbol(x.clone()), &x));
324 }
325 #[test]
326 fn test_is_polynomial_sum() {
327 let x = symbol!(x);
328 let poly = Expression::add(vec![
329 Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
330 Expression::symbol(x.clone()),
331 Expression::integer(1),
332 ]);
333 assert!(is_polynomial(&poly, &x));
334 }
335 #[test]
336 fn test_is_polynomial_product() {
337 let x = symbol!(x);
338 let poly = Expression::mul(vec![
339 Expression::integer(3),
340 Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
341 ]);
342 assert!(is_polynomial(&poly, &x));
343 }
344 #[test]
345 fn test_is_not_polynomial_negative_power() {
346 let x = symbol!(x);
347 let expr = Expression::pow(Expression::symbol(x.clone()), Expression::integer(-1));
348 assert!(!is_polynomial(&expr, &x));
349 }
350 #[test]
351 fn test_is_not_polynomial_function() {
352 let x = symbol!(x);
353 let expr = Expression::function("sin", vec![Expression::symbol(x.clone())]);
354 assert!(!is_polynomial(&expr, &x));
355 }
356}