mathhook_core/calculus/integrals/trigonometric/
detection.rs1use crate::core::{Expression, Number, Symbol};
6
7#[derive(Debug, Clone, PartialEq)]
9pub enum TrigPattern {
10 SinCosPower {
11 sin_power: i64,
12 cos_power: i64,
13 },
14 TanSecPower {
15 tan_power: i64,
16 sec_power: i64,
17 },
18 CotCscPower {
19 cot_power: i64,
20 csc_power: i64,
21 },
22 ProductDifferentFreq {
23 func1: String,
24 m: i64,
25 func2: String,
26 n: i64,
27 },
28 TanPower {
29 power: i64,
30 },
31 CotPower {
32 power: i64,
33 },
34 SecPower {
35 power: i64,
36 },
37 CscPower {
38 power: i64,
39 },
40}
41
42pub fn detect_trig_pattern(expr: &Expression, var: &Symbol) -> Option<TrigPattern> {
61 match expr {
62 Expression::Pow(base, exp) => detect_power_pattern(base, exp, var),
63 Expression::Mul(factors) => detect_product_pattern(factors, var),
64 Expression::Function { name, args } if args.len() == 1 && is_simple_var(&args[0], var) => {
65 detect_single_function_pattern(name)
66 }
67 _ => None,
68 }
69}
70
71fn detect_power_pattern(base: &Expression, exp: &Expression, var: &Symbol) -> Option<TrigPattern> {
73 if let (Expression::Function { name, args }, Expression::Number(Number::Integer(n))) =
74 (base, exp)
75 {
76 if args.len() == 1 && is_simple_var(&args[0], var) {
77 return match name.as_ref() {
78 "sin" => Some(TrigPattern::SinCosPower {
79 sin_power: *n,
80 cos_power: 0,
81 }),
82 "cos" => Some(TrigPattern::SinCosPower {
83 sin_power: 0,
84 cos_power: *n,
85 }),
86 "tan" => Some(TrigPattern::TanPower { power: *n }),
87 "cot" => Some(TrigPattern::CotPower { power: *n }),
88 "sec" => Some(TrigPattern::SecPower { power: *n }),
89 "csc" => Some(TrigPattern::CscPower { power: *n }),
90 _ => None,
91 };
92 }
93 }
94 None
95}
96
97fn detect_product_pattern(factors: &[Expression], var: &Symbol) -> Option<TrigPattern> {
99 if factors.len() == 2 {
100 if let Some(pattern) = detect_product_different_freq(factors, var) {
101 return Some(pattern);
102 }
103 }
104
105 detect_product_same_var(factors, var)
106}
107
108fn detect_product_different_freq(factors: &[Expression], var: &Symbol) -> Option<TrigPattern> {
110 if let (Some((func1, m)), Some((func2, n))) = (
111 extract_trig_function_with_coeff(&factors[0], var),
112 extract_trig_function_with_coeff(&factors[1], var),
113 ) {
114 if is_elementary_trig(&func1) && is_elementary_trig(&func2) {
115 return Some(TrigPattern::ProductDifferentFreq { func1, m, func2, n });
116 }
117 }
118 None
119}
120
121fn detect_product_same_var(factors: &[Expression], var: &Symbol) -> Option<TrigPattern> {
123 let mut sin_power = 0i64;
124 let mut cos_power = 0i64;
125 let mut tan_power = 0i64;
126 let mut sec_power = 0i64;
127 let mut cot_power = 0i64;
128 let mut csc_power = 0i64;
129 let mut other_factors = Vec::new();
130
131 for factor in factors.iter() {
132 match factor {
133 Expression::Function { name, args }
134 if args.len() == 1 && is_simple_var(&args[0], var) =>
135 {
136 update_trig_powers(
137 name,
138 1,
139 &mut sin_power,
140 &mut cos_power,
141 &mut tan_power,
142 &mut sec_power,
143 &mut cot_power,
144 &mut csc_power,
145 &mut other_factors,
146 factor,
147 );
148 }
149 Expression::Pow(base, exp) => {
150 if let (
151 Expression::Function { name, args },
152 Expression::Number(Number::Integer(n)),
153 ) = (&**base, &**exp)
154 {
155 if args.len() == 1 && is_simple_var(&args[0], var) {
156 update_trig_powers(
157 name,
158 *n,
159 &mut sin_power,
160 &mut cos_power,
161 &mut tan_power,
162 &mut sec_power,
163 &mut cot_power,
164 &mut csc_power,
165 &mut other_factors,
166 factor,
167 );
168 } else {
169 other_factors.push(factor);
170 }
171 } else {
172 other_factors.push(factor);
173 }
174 }
175 _ => other_factors.push(factor),
176 }
177 }
178
179 if !other_factors.is_empty() {
180 return None;
181 }
182
183 classify_trig_powers(
184 sin_power, cos_power, tan_power, sec_power, cot_power, csc_power,
185 )
186}
187
188#[allow(clippy::too_many_arguments)]
190fn update_trig_powers<'a>(
191 name: &str,
192 power: i64,
193 sin_power: &mut i64,
194 cos_power: &mut i64,
195 tan_power: &mut i64,
196 sec_power: &mut i64,
197 cot_power: &mut i64,
198 csc_power: &mut i64,
199 other_factors: &mut Vec<&'a Expression>,
200 factor: &'a Expression,
201) {
202 match name {
203 "sin" => *sin_power += power,
204 "cos" => *cos_power += power,
205 "tan" => *tan_power += power,
206 "sec" => *sec_power += power,
207 "cot" => *cot_power += power,
208 "csc" => *csc_power += power,
209 _ => other_factors.push(factor),
210 }
211}
212
213#[allow(clippy::too_many_arguments)]
215fn classify_trig_powers(
216 sin_power: i64,
217 cos_power: i64,
218 tan_power: i64,
219 sec_power: i64,
220 cot_power: i64,
221 csc_power: i64,
222) -> Option<TrigPattern> {
223 if (sin_power > 0 || cos_power > 0)
224 && tan_power == 0
225 && sec_power == 0
226 && cot_power == 0
227 && csc_power == 0
228 {
229 return Some(TrigPattern::SinCosPower {
230 sin_power,
231 cos_power,
232 });
233 }
234
235 if (tan_power > 0 || sec_power > 0)
236 && sin_power == 0
237 && cos_power == 0
238 && cot_power == 0
239 && csc_power == 0
240 {
241 return Some(TrigPattern::TanSecPower {
242 tan_power,
243 sec_power,
244 });
245 }
246
247 if (cot_power > 0 || csc_power > 0)
248 && sin_power == 0
249 && cos_power == 0
250 && tan_power == 0
251 && sec_power == 0
252 {
253 return Some(TrigPattern::CotCscPower {
254 cot_power,
255 csc_power,
256 });
257 }
258
259 None
260}
261
262fn detect_single_function_pattern(name: &str) -> Option<TrigPattern> {
264 match name {
265 "sin" => Some(TrigPattern::SinCosPower {
266 sin_power: 1,
267 cos_power: 0,
268 }),
269 "cos" => Some(TrigPattern::SinCosPower {
270 sin_power: 0,
271 cos_power: 1,
272 }),
273 "tan" => Some(TrigPattern::TanPower { power: 1 }),
274 "cot" => Some(TrigPattern::CotPower { power: 1 }),
275 "sec" => Some(TrigPattern::SecPower { power: 1 }),
276 "csc" => Some(TrigPattern::CscPower { power: 1 }),
277 _ => None,
278 }
279}
280
281fn is_simple_var(expr: &Expression, var: &Symbol) -> bool {
283 matches!(expr, Expression::Symbol(s) if s == var)
284}
285
286fn is_elementary_trig(name: &str) -> bool {
288 matches!(name, "sin" | "cos")
289}
290
291pub fn extract_trig_function_with_coeff(expr: &Expression, var: &Symbol) -> Option<(String, i64)> {
295 match expr {
296 Expression::Function { name, args } if args.len() == 1 => {
297 if is_simple_var(&args[0], var) {
298 return Some((name.to_string(), 1));
299 }
300
301 if let Expression::Mul(factors) = &args[0] {
302 if factors.len() == 2 {
303 if let (Expression::Number(Number::Integer(m)), Expression::Symbol(s)) =
304 (&factors[0], &factors[1])
305 {
306 if s == var {
307 return Some((name.to_string(), *m));
308 }
309 }
310 if let (Expression::Symbol(s), Expression::Number(Number::Integer(m))) =
311 (&factors[0], &factors[1])
312 {
313 if s == var {
314 return Some((name.to_string(), *m));
315 }
316 }
317 }
318 }
319 None
320 }
321 _ => None,
322 }
323}