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}