mathhook_core/calculus/integrals/rational/
quadratic.rs

1//! Integration of irreducible quadratic factors in partial fraction decomposition
2//!
3//! Implements both simple quadratic integration (power=1) and Ostrogradsky's
4//! reduction formula for repeated quadratics (power=2).
5
6use crate::core::{Expression, Number, Symbol};
7use crate::simplify::Simplify;
8
9/// Integrate repeated irreducible quadratic using Ostrogradsky's reduction
10///
11/// For `∫(Ax+B)/(x²+px+q)^m dx` where `m > 1`, uses Ostrogradsky's formula:
12/// ```text
13/// ∫f/D^m dx = g/D^(m-1) + ∫h/D dx
14/// ```
15///
16/// # Mathematical Method (Ostrogradsky's Reduction)
17///
18/// 1. Set up reduction formula with unknown linear polynomial `g = ax+b`
19/// 2. Use identity: `f = g'·D - (m-1)·g·D' + h·D^(m-1)`
20/// 3. Expand and collect coefficients to solve for `a`, `b`, `h`
21/// 4. Integrate remainder `h/D` using simple quadratic formula
22///
23/// # Current Implementation
24///
25/// Handles `m=2` case (most common). For higher powers, returns `None`.
26/// Can be generalized using recursive Ostrogradsky reduction.
27///
28/// # Arguments
29///
30/// * `numerator` - Numerator expression (linear in `x`)
31/// * `denominator` - Full denominator (for reference)
32/// * `p` - Linear coefficient in `x²+px+q`
33/// * `q` - Constant term in `x²+px+q`
34/// * `power` - Exponent `m` (currently only `m=2` supported)
35/// * `var` - Integration variable
36///
37/// # Returns
38///
39/// Integrated expression or `None` if unsupported power
40///
41/// # Examples
42///
43/// ```
44/// use mathhook_core::{Expression, symbol};
45/// use mathhook_core::calculus::integrals::rational::quadratic::integrate_repeated_quadratic;
46///
47/// let x = symbol!(x);
48/// let numerator = Expression::integer(1);
49/// let p = Expression::integer(0);
50/// let q = Expression::integer(1);
51/// let denominator = Expression::pow(
52///     Expression::add(vec![
53///         Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
54///         q.clone(),
55///     ]),
56///     Expression::integer(2),
57/// );
58///
59/// let result = integrate_repeated_quadratic(&numerator, &denominator, &p, &q, 2, &x);
60/// assert!(result.is_some());
61/// ```
62///
63/// # References
64///
65/// - Stewart, Calculus (8th ed), Section 7.4
66/// - Bronstein, "Symbolic Integration I" (reduction formulas)
67pub fn integrate_repeated_quadratic(
68    numerator: &Expression,
69    denominator: &Expression,
70    p: &Expression,
71    q: &Expression,
72    power: i64,
73    var: &Symbol,
74) -> Option<Expression> {
75    let d = Expression::add(vec![
76        Expression::pow(Expression::symbol(var.clone()), Expression::integer(2)),
77        Expression::mul(vec![p.clone(), Expression::symbol(var.clone())]),
78        q.clone(),
79    ]);
80
81    if power != 2 {
82        return None;
83    }
84
85    let (coeff_a, coeff_b) = extract_linear_coefficients(numerator, var)?;
86
87    let denom_expr = Expression::add(vec![
88        Expression::mul(vec![Expression::integer(4), q.clone()]),
89        Expression::mul(vec![
90            Expression::integer(-1),
91            Expression::pow(p.clone(), Expression::integer(2)),
92        ]),
93    ])
94    .simplify();
95
96    if denom_expr.is_zero() {
97        return None;
98    }
99
100    let a = Expression::mul(vec![
101        Expression::add(vec![
102            Expression::mul(vec![Expression::integer(2), coeff_b]),
103            Expression::mul(vec![Expression::integer(-1), coeff_a.clone(), p.clone()]),
104        ]),
105        Expression::pow(denom_expr, Expression::integer(-1)),
106    ])
107    .simplify();
108
109    let b = Expression::mul(vec![
110        Expression::rational(1, 2),
111        Expression::add(vec![
112            Expression::mul(vec![a.clone(), p.clone()]),
113            Expression::mul(vec![Expression::integer(-1), coeff_a]),
114        ]),
115    ])
116    .simplify();
117
118    let h_coeff = a.clone();
119
120    let g = Expression::add(vec![
121        Expression::mul(vec![a, Expression::symbol(var.clone())]),
122        b,
123    ]);
124
125    let rational_part = Expression::mul(vec![g, Expression::pow(d, Expression::integer(-1))]);
126
127    let transcendental_part = integrate_simple_quadratic(&h_coeff, denominator, p, q, var)?;
128
129    Some(Expression::add(vec![rational_part, transcendental_part]).simplify())
130}
131
132/// Extract coefficients A and B from linear expression `Ax+B`
133///
134/// # Arguments
135///
136/// * `expr` - Expression to analyze
137/// * `var` - Variable to extract coefficient for
138///
139/// # Returns
140///
141/// `Some((A, B))` if expression is linear, `None` otherwise
142///
143/// # Examples
144///
145/// ```
146/// use mathhook_core::{Expression, symbol};
147/// use mathhook_core::calculus::integrals::rational::quadratic::extract_linear_coefficients;
148///
149/// let x = symbol!(x);
150///
151/// let expr1 = Expression::add(vec![
152///     Expression::mul(vec![Expression::integer(3), Expression::symbol(x.clone())]),
153///     Expression::integer(5),
154/// ]);
155/// let (a, b) = extract_linear_coefficients(&expr1, &x).unwrap();
156///
157/// let expr2 = Expression::integer(7);
158/// let (a2, b2) = extract_linear_coefficients(&expr2, &x).unwrap();
159/// ```
160pub fn extract_linear_coefficients(
161    expr: &Expression,
162    var: &Symbol,
163) -> Option<(Expression, Expression)> {
164    match expr {
165        Expression::Number(_) => Some((Expression::integer(0), expr.clone())),
166
167        Expression::Symbol(s) if *s == *var => {
168            Some((Expression::integer(1), Expression::integer(0)))
169        }
170
171        Expression::Mul(factors) => {
172            let mut coeff = Vec::new();
173            let mut has_var = false;
174
175            for factor in factors.iter() {
176                if let Expression::Symbol(s) = factor {
177                    if *s == *var {
178                        has_var = true;
179                        continue;
180                    }
181                }
182                coeff.push(factor.clone());
183            }
184
185            if has_var {
186                let a = if coeff.is_empty() {
187                    Expression::integer(1)
188                } else {
189                    Expression::mul(coeff)
190                };
191                Some((a, Expression::integer(0)))
192            } else {
193                None
194            }
195        }
196
197        Expression::Add(terms) => {
198            let mut a = Expression::integer(0);
199            let mut b = Expression::integer(0);
200
201            for term in terms.iter() {
202                match term {
203                    Expression::Symbol(s) if *s == *var => {
204                        a = Expression::add(vec![a, Expression::integer(1)]);
205                    }
206                    Expression::Mul(_) => {
207                        if term.contains_variable(var) {
208                            let (term_a, term_b) = extract_linear_coefficients(term, var)?;
209                            a = Expression::add(vec![a, term_a]);
210                            b = Expression::add(vec![b, term_b]);
211                        } else {
212                            b = Expression::add(vec![b, term.clone()]);
213                        }
214                    }
215                    _ => {
216                        if !term.contains_variable(var) {
217                            b = Expression::add(vec![b, term.clone()]);
218                        } else {
219                            return None;
220                        }
221                    }
222                }
223            }
224
225            Some((a.simplify(), b.simplify()))
226        }
227
228        _ => None,
229    }
230}
231
232/// Integrate simple irreducible quadratic `(Bx+C)/(x²+px+q)`
233///
234/// # Mathematical Method
235///
236/// For `x²+px+q` with discriminant `Δ = p²-4q < 0` (irreducible):
237///
238/// 1. Complete the square: `x²+px+q = (x+p/2)² + a²` where `a² = q - p²/4`
239/// 2. Split integral: `∫(Bx+C)/(x²+px+q) dx = B∫x/(x²+px+q) dx + C∫1/(x²+px+q) dx`
240/// 3. Logarithmic part: `∫x/(x²+px+q) dx = (1/2)ln|x²+px+q| - (p/2)∫1/(x²+px+q) dx`
241/// 4. Arctangent part: `∫1/(x²+px+q) dx = (1/a)arctan((x+p/2)/a)`
242///
243/// # Current Implementation
244///
245/// Simplified version assumes constant numerator (B=0, C=1).
246///
247/// # Arguments
248///
249/// * `_numerator` - Numerator (currently unused, assumes 1)
250/// * `_denominator` - Full denominator (for reference)
251/// * `p` - Linear coefficient in `x²+px+q`
252/// * `q` - Constant term in `x²+px+q`
253/// * `var` - Integration variable
254///
255/// # Returns
256///
257/// Integrated expression or `None` if quadratic is not irreducible
258///
259/// # Examples
260///
261/// ```
262/// use mathhook_core::{Expression, symbol};
263/// use mathhook_core::calculus::integrals::rational::quadratic::integrate_simple_quadratic;
264///
265/// let x = symbol!(x);
266/// let numerator = Expression::integer(1);
267/// let p = Expression::integer(0);
268/// let q = Expression::integer(1);
269/// let denominator = Expression::add(vec![
270///     Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
271///     q.clone(),
272/// ]);
273///
274/// let result = integrate_simple_quadratic(&numerator, &denominator, &p, &q, &x);
275/// assert!(result.is_some());
276/// ```
277pub fn integrate_simple_quadratic(
278    _numerator: &Expression,
279    _denominator: &Expression,
280    p: &Expression,
281    q: &Expression,
282    var: &Symbol,
283) -> Option<Expression> {
284    let discriminant = Expression::add(vec![
285        Expression::pow(p.clone(), Expression::integer(2)),
286        Expression::mul(vec![Expression::integer(-4), q.clone()]),
287    ])
288    .simplify();
289
290    if let Expression::Number(Number::Integer(d)) = discriminant {
291        if d >= 0 {
292            return None;
293        }
294    }
295
296    let a_squared = Expression::add(vec![
297        q.clone(),
298        Expression::mul(vec![
299            Expression::rational(-1, 4),
300            Expression::pow(p.clone(), Expression::integer(2)),
301        ]),
302    ])
303    .simplify();
304
305    let a = Expression::function("sqrt", vec![a_squared]);
306
307    let shift = Expression::mul(vec![Expression::rational(1, 2), p.clone()]);
308    let x_shifted = Expression::add(vec![Expression::symbol(var.clone()), shift]).simplify();
309
310    Some(Expression::mul(vec![
311        Expression::pow(a.clone(), Expression::integer(-1)),
312        Expression::function(
313            "atan",
314            vec![Expression::mul(vec![
315                Expression::pow(a, Expression::integer(-1)),
316                x_shifted,
317            ])],
318        ),
319    ]))
320}