use crate::core::{Expression, Number, Symbol};
#[derive(Debug, Clone, PartialEq)]
pub enum TrigPattern {
SinCosPower {
sin_power: i64,
cos_power: i64,
},
TanSecPower {
tan_power: i64,
sec_power: i64,
},
CotCscPower {
cot_power: i64,
csc_power: i64,
},
ProductDifferentFreq {
func1: String,
m: i64,
func2: String,
n: i64,
},
TanPower {
power: i64,
},
CotPower {
power: i64,
},
SecPower {
power: i64,
},
CscPower {
power: i64,
},
}
pub fn detect_trig_pattern(expr: &Expression, var: &Symbol) -> Option<TrigPattern> {
match expr {
Expression::Pow(base, exp) => detect_power_pattern(base, exp, var),
Expression::Mul(factors) => detect_product_pattern(factors, var),
Expression::Function { name, args } if args.len() == 1 && is_simple_var(&args[0], var) => {
detect_single_function_pattern(name)
}
_ => None,
}
}
fn detect_power_pattern(base: &Expression, exp: &Expression, var: &Symbol) -> Option<TrigPattern> {
if let (Expression::Function { name, args }, Expression::Number(Number::Integer(n))) =
(base, exp)
{
if args.len() == 1 && is_simple_var(&args[0], var) {
return match name.as_ref() {
"sin" => Some(TrigPattern::SinCosPower {
sin_power: *n,
cos_power: 0,
}),
"cos" => Some(TrigPattern::SinCosPower {
sin_power: 0,
cos_power: *n,
}),
"tan" => Some(TrigPattern::TanPower { power: *n }),
"cot" => Some(TrigPattern::CotPower { power: *n }),
"sec" => Some(TrigPattern::SecPower { power: *n }),
"csc" => Some(TrigPattern::CscPower { power: *n }),
_ => None,
};
}
}
None
}
fn detect_product_pattern(factors: &[Expression], var: &Symbol) -> Option<TrigPattern> {
if factors.len() == 2 {
if let Some(pattern) = detect_product_different_freq(factors, var) {
return Some(pattern);
}
}
detect_product_same_var(factors, var)
}
fn detect_product_different_freq(factors: &[Expression], var: &Symbol) -> Option<TrigPattern> {
if let (Some((func1, m)), Some((func2, n))) = (
extract_trig_function_with_coeff(&factors[0], var),
extract_trig_function_with_coeff(&factors[1], var),
) {
if is_elementary_trig(&func1) && is_elementary_trig(&func2) {
return Some(TrigPattern::ProductDifferentFreq { func1, m, func2, n });
}
}
None
}
fn detect_product_same_var(factors: &[Expression], var: &Symbol) -> Option<TrigPattern> {
let mut sin_power = 0i64;
let mut cos_power = 0i64;
let mut tan_power = 0i64;
let mut sec_power = 0i64;
let mut cot_power = 0i64;
let mut csc_power = 0i64;
let mut other_factors = Vec::new();
for factor in factors.iter() {
match factor {
Expression::Function { name, args }
if args.len() == 1 && is_simple_var(&args[0], var) =>
{
update_trig_powers(
name,
1,
&mut sin_power,
&mut cos_power,
&mut tan_power,
&mut sec_power,
&mut cot_power,
&mut csc_power,
&mut other_factors,
factor,
);
}
Expression::Pow(base, exp) => {
if let (
Expression::Function { name, args },
Expression::Number(Number::Integer(n)),
) = (&**base, &**exp)
{
if args.len() == 1 && is_simple_var(&args[0], var) {
update_trig_powers(
name,
*n,
&mut sin_power,
&mut cos_power,
&mut tan_power,
&mut sec_power,
&mut cot_power,
&mut csc_power,
&mut other_factors,
factor,
);
} else {
other_factors.push(factor);
}
} else {
other_factors.push(factor);
}
}
_ => other_factors.push(factor),
}
}
if !other_factors.is_empty() {
return None;
}
classify_trig_powers(
sin_power, cos_power, tan_power, sec_power, cot_power, csc_power,
)
}
#[allow(clippy::too_many_arguments)]
fn update_trig_powers<'a>(
name: &str,
power: i64,
sin_power: &mut i64,
cos_power: &mut i64,
tan_power: &mut i64,
sec_power: &mut i64,
cot_power: &mut i64,
csc_power: &mut i64,
other_factors: &mut Vec<&'a Expression>,
factor: &'a Expression,
) {
match name {
"sin" => *sin_power += power,
"cos" => *cos_power += power,
"tan" => *tan_power += power,
"sec" => *sec_power += power,
"cot" => *cot_power += power,
"csc" => *csc_power += power,
_ => other_factors.push(factor),
}
}
#[allow(clippy::too_many_arguments)]
fn classify_trig_powers(
sin_power: i64,
cos_power: i64,
tan_power: i64,
sec_power: i64,
cot_power: i64,
csc_power: i64,
) -> Option<TrigPattern> {
if (sin_power > 0 || cos_power > 0)
&& tan_power == 0
&& sec_power == 0
&& cot_power == 0
&& csc_power == 0
{
return Some(TrigPattern::SinCosPower {
sin_power,
cos_power,
});
}
if (tan_power > 0 || sec_power > 0)
&& sin_power == 0
&& cos_power == 0
&& cot_power == 0
&& csc_power == 0
{
return Some(TrigPattern::TanSecPower {
tan_power,
sec_power,
});
}
if (cot_power > 0 || csc_power > 0)
&& sin_power == 0
&& cos_power == 0
&& tan_power == 0
&& sec_power == 0
{
return Some(TrigPattern::CotCscPower {
cot_power,
csc_power,
});
}
None
}
fn detect_single_function_pattern(name: &str) -> Option<TrigPattern> {
match name {
"sin" => Some(TrigPattern::SinCosPower {
sin_power: 1,
cos_power: 0,
}),
"cos" => Some(TrigPattern::SinCosPower {
sin_power: 0,
cos_power: 1,
}),
"tan" => Some(TrigPattern::TanPower { power: 1 }),
"cot" => Some(TrigPattern::CotPower { power: 1 }),
"sec" => Some(TrigPattern::SecPower { power: 1 }),
"csc" => Some(TrigPattern::CscPower { power: 1 }),
_ => None,
}
}
fn is_simple_var(expr: &Expression, var: &Symbol) -> bool {
matches!(expr, Expression::Symbol(s) if s == var)
}
fn is_elementary_trig(name: &str) -> bool {
matches!(name, "sin" | "cos")
}
pub fn extract_trig_function_with_coeff(expr: &Expression, var: &Symbol) -> Option<(String, i64)> {
match expr {
Expression::Function { name, args } if args.len() == 1 => {
if is_simple_var(&args[0], var) {
return Some((name.to_string(), 1));
}
if let Expression::Mul(factors) = &args[0] {
if factors.len() == 2 {
if let (Expression::Number(Number::Integer(m)), Expression::Symbol(s)) =
(&factors[0], &factors[1])
{
if s == var {
return Some((name.to_string(), *m));
}
}
if let (Expression::Symbol(s), Expression::Number(Number::Integer(m))) =
(&factors[0], &factors[1])
{
if s == var {
return Some((name.to_string(), *m));
}
}
}
}
None
}
_ => None,
}
}