mathhook_core/calculus/integrals/risch/
rational.rs1use crate::algebra::gcd::PolynomialGcd;
30use crate::algebra::polynomial_division::polynomial_div;
31use crate::calculus::derivatives::Derivative;
32use crate::core::{Expression, Symbol};
33use crate::simplify::Simplify;
34
35#[derive(Debug, Clone, PartialEq)]
37pub struct RationalIntegral {
38 pub polynomial_part: Expression,
40
41 pub logarithmic_terms: Vec<(Expression, Expression)>,
43
44 pub remaining: Option<Expression>,
46}
47
48pub fn assemble_integral(integral: &RationalIntegral) -> Expression {
50 let mut terms = vec![integral.polynomial_part.clone()];
51
52 for (coeff, arg) in &integral.logarithmic_terms {
53 terms.push(Expression::mul(vec![
54 coeff.clone(),
55 Expression::function("ln", vec![Expression::function("abs", vec![arg.clone()])]),
56 ]));
57 }
58
59 if let Some(remaining) = &integral.remaining {
60 terms.push(Expression::function(
61 "integrate",
62 vec![remaining.clone(), Expression::symbol(Symbol::scalar("x"))],
63 ));
64 }
65
66 Expression::add(terms)
67}
68
69pub fn integrate_rational(
109 numerator: &Expression,
110 denominator: &Expression,
111 var: &Symbol,
112) -> RationalIntegral {
113 if denominator.is_zero() {
114 return RationalIntegral {
115 polynomial_part: Expression::undefined(),
116 logarithmic_terms: vec![],
117 remaining: None,
118 };
119 }
120
121 if numerator.is_zero() {
122 return RationalIntegral {
123 polynomial_part: Expression::integer(0),
124 logarithmic_terms: vec![],
125 remaining: None,
126 };
127 }
128
129 let (quotient, remainder) = match polynomial_div(numerator, denominator, var) {
130 Ok(result) => result,
131 Err(_) => {
132 return RationalIntegral {
133 polynomial_part: Expression::integer(0),
134 logarithmic_terms: vec![],
135 remaining: Some(Expression::mul(vec![
136 numerator.clone(),
137 Expression::pow(denominator.clone(), Expression::integer(-1)),
138 ])),
139 }
140 }
141 };
142
143 let polynomial_part = integrate_polynomial("ient, var);
144
145 if remainder.is_zero() {
146 return RationalIntegral {
147 polynomial_part,
148 logarithmic_terms: vec![],
149 remaining: None,
150 };
151 }
152
153 let (log_terms, remaining_rational) = hermite_reduce(&remainder, denominator, var);
154
155 RationalIntegral {
156 polynomial_part,
157 logarithmic_terms: log_terms,
158 remaining: remaining_rational,
159 }
160}
161
162fn integrate_polynomial(poly: &Expression, var: &Symbol) -> Expression {
166 match poly {
167 Expression::Number(n) => Expression::mul(vec![
168 Expression::Number(n.clone()),
169 Expression::symbol(var.clone()),
170 ]),
171 Expression::Symbol(s) if s == var => Expression::mul(vec![
172 Expression::rational(1, 2),
173 Expression::pow(Expression::symbol(var.clone()), Expression::integer(2)),
174 ]),
175 Expression::Pow(base, exp) => {
176 if let Expression::Symbol(s) = &**base {
177 if s == var {
178 if let Expression::Number(num_exp) = &**exp {
179 let new_exp = Expression::add(vec![
180 Expression::Number(num_exp.clone()),
181 Expression::integer(1),
182 ]);
183 return Expression::mul(vec![
184 Expression::pow(new_exp.clone(), Expression::integer(-1)),
185 Expression::pow(Expression::symbol(var.clone()), new_exp),
186 ]);
187 }
188 }
189 }
190 Expression::integral(poly.clone(), var.clone())
191 }
192 Expression::Add(terms) => {
193 Expression::add(terms.iter().map(|t| integrate_polynomial(t, var)).collect())
194 }
195 Expression::Mul(factors) => {
196 let (constants, variables): (Vec<_>, Vec<_>) =
197 factors.iter().partition(|f| !f.contains_variable(var));
198
199 if variables.is_empty() {
200 Expression::mul(vec![
201 Expression::mul((**factors).clone()),
202 Expression::symbol(var.clone()),
203 ])
204 } else if variables.len() == 1 {
205 let integrated_var = integrate_polynomial(variables[0], var);
206 if constants.is_empty() {
207 integrated_var
208 } else {
209 Expression::mul(vec![
210 Expression::mul(constants.into_iter().cloned().collect()),
211 integrated_var,
212 ])
213 }
214 } else {
215 Expression::integral(poly.clone(), var.clone())
216 }
217 }
218 _ => Expression::integral(poly.clone(), var.clone()),
219 }
220}
221
222fn hermite_reduce(
235 numerator: &Expression,
236 denominator: &Expression,
237 var: &Symbol,
238) -> (Vec<(Expression, Expression)>, Option<Expression>) {
239 let denom_deriv = denominator.derivative(var.clone()).simplify();
240
241 let gcd = PolynomialGcd::gcd(denominator, &denom_deriv);
242
243 if gcd.is_one() {
244 let log_terms = extract_logarithmic_terms(numerator, denominator, var);
245 return (log_terms, None);
246 }
247
248 let square_free_part = divide_polynomials(denominator, &gcd, var);
249
250 let algebraic_part = Expression::mul(vec![
251 Expression::integer(-1),
252 numerator.clone(),
253 Expression::pow(gcd.clone(), Expression::integer(-1)),
254 Expression::pow(square_free_part.clone(), Expression::integer(-1)),
255 ])
256 .simplify();
257
258 let numerator_deriv = numerator.derivative(var.clone());
259 let remaining_num = Expression::add(vec![
260 numerator_deriv,
261 Expression::mul(vec![
262 numerator.clone(),
263 Expression::pow(gcd, Expression::integer(-1)),
264 denom_deriv,
265 ]),
266 ])
267 .simplify();
268
269 let log_terms = extract_logarithmic_terms(&remaining_num, &square_free_part, var);
270
271 let remaining = if algebraic_part.is_zero() {
272 None
273 } else {
274 Some(algebraic_part)
275 };
276
277 (log_terms, remaining)
278}
279
280fn extract_logarithmic_terms(
288 numerator: &Expression,
289 denominator: &Expression,
290 var: &Symbol,
291) -> Vec<(Expression, Expression)> {
292 if denominator.is_one() || numerator.is_zero() {
293 return vec![];
294 }
295
296 let denom_deriv = denominator.derivative(var.clone()).simplify();
297 let gcd = PolynomialGcd::gcd(denominator, &denom_deriv);
298
299 if !gcd.is_one() {
300 return vec![];
301 }
302
303 let coefficient = divide_polynomials(numerator, &denom_deriv, var);
304
305 if is_constant(&coefficient, var) {
306 vec![(coefficient, denominator.clone())]
307 } else {
308 vec![]
309 }
310}
311
312fn divide_polynomials(dividend: &Expression, divisor: &Expression, var: &Symbol) -> Expression {
316 if divisor.is_zero() {
317 return Expression::undefined();
318 }
319
320 if dividend.is_zero() {
321 return Expression::integer(0);
322 }
323
324 if divisor.is_one() {
325 return dividend.clone();
326 }
327
328 match polynomial_div(dividend, divisor, var) {
329 Ok((quotient, remainder)) => {
330 if remainder.is_zero() {
331 quotient
332 } else {
333 Expression::mul(vec![
334 dividend.clone(),
335 Expression::pow(divisor.clone(), Expression::integer(-1)),
336 ])
337 }
338 }
339 Err(_) => Expression::mul(vec![
340 dividend.clone(),
341 Expression::pow(divisor.clone(), Expression::integer(-1)),
342 ]),
343 }
344}
345
346fn is_constant(expr: &Expression, var: &Symbol) -> bool {
348 !expr.contains_variable(var)
349}