mathhook_core/functions/elementary/trigonometric/
trig_evaluation.rs

1//! Trigonometric function evaluation
2//!
3//! Provides numerical and symbolic evaluation for circular trigonometric functions.
4//! Handles special values, periodicity, and numerical computation.
5
6use crate::core::expression::Expression;
7use crate::core::number::Number;
8use num_bigint::BigInt;
9use num_traits::ToPrimitive;
10
11/// Evaluates sine function
12pub fn sin(arg: &Expression) -> Expression {
13    if let Some(exact) = try_exact_sin(arg) {
14        return exact;
15    }
16
17    if let Some(num_val) = try_numeric_sin(arg) {
18        return num_val;
19    }
20
21    Expression::function("sin", vec![arg.clone()])
22}
23
24/// Evaluates cosine function
25pub fn cos(arg: &Expression) -> Expression {
26    if let Some(exact) = try_exact_cos(arg) {
27        return exact;
28    }
29
30    if let Some(num_val) = try_numeric_cos(arg) {
31        return num_val;
32    }
33
34    Expression::function("cos", vec![arg.clone()])
35}
36
37/// Evaluates tangent function
38pub fn tan(arg: &Expression) -> Expression {
39    if let Some(exact) = try_exact_tan(arg) {
40        return exact;
41    }
42
43    if let Some(num_val) = try_numeric_tan(arg) {
44        return num_val;
45    }
46
47    Expression::function("tan", vec![arg.clone()])
48}
49
50/// Wrapper function pointer for sin (matches ElementaryProperties signature)
51pub fn sin_evaluator(args: &[Expression]) -> Expression {
52    if args.len() == 1 {
53        sin(&args[0])
54    } else {
55        Expression::function("sin", args.to_vec())
56    }
57}
58
59/// Wrapper function pointer for cos (matches ElementaryProperties signature)
60pub fn cos_evaluator(args: &[Expression]) -> Expression {
61    if args.len() == 1 {
62        cos(&args[0])
63    } else {
64        Expression::function("cos", args.to_vec())
65    }
66}
67
68/// Wrapper function pointer for tan (matches ElementaryProperties signature)
69pub fn tan_evaluator(args: &[Expression]) -> Expression {
70    if args.len() == 1 {
71        tan(&args[0])
72    } else {
73        Expression::function("tan", args.to_vec())
74    }
75}
76
77fn try_exact_sin(arg: &Expression) -> Option<Expression> {
78    if arg.is_zero() {
79        return Some(Expression::integer(0));
80    }
81
82    if matches!(
83        arg,
84        Expression::Constant(crate::core::constants::MathConstant::Pi)
85    ) {
86        return Some(Expression::integer(0));
87    }
88
89    if is_pi_over_2(arg) {
90        return Some(Expression::integer(1));
91    }
92
93    if is_pi_over_6(arg) {
94        return Some(Expression::rational(1, 2));
95    }
96
97    if is_pi_over_4(arg) {
98        return Some(Expression::mul(vec![
99            Expression::pow(Expression::integer(2), Expression::rational(1, 2)),
100            Expression::rational(1, 2),
101        ]));
102    }
103
104    if is_pi_over_3(arg) {
105        return Some(Expression::mul(vec![
106            Expression::pow(Expression::integer(3), Expression::rational(1, 2)),
107            Expression::rational(1, 2),
108        ]));
109    }
110
111    None
112}
113
114fn try_exact_cos(arg: &Expression) -> Option<Expression> {
115    if arg.is_zero() {
116        return Some(Expression::integer(1));
117    }
118
119    if matches!(
120        arg,
121        Expression::Constant(crate::core::constants::MathConstant::Pi)
122    ) {
123        return Some(Expression::integer(-1));
124    }
125
126    if is_pi_over_2(arg) {
127        return Some(Expression::integer(0));
128    }
129
130    if is_pi_over_6(arg) {
131        return Some(Expression::mul(vec![
132            Expression::pow(Expression::integer(3), Expression::rational(1, 2)),
133            Expression::rational(1, 2),
134        ]));
135    }
136
137    if is_pi_over_4(arg) {
138        return Some(Expression::mul(vec![
139            Expression::pow(Expression::integer(2), Expression::rational(1, 2)),
140            Expression::rational(1, 2),
141        ]));
142    }
143
144    if is_pi_over_3(arg) {
145        return Some(Expression::rational(1, 2));
146    }
147
148    None
149}
150
151fn try_exact_tan(arg: &Expression) -> Option<Expression> {
152    if arg.is_zero() {
153        return Some(Expression::integer(0));
154    }
155
156    if matches!(
157        arg,
158        Expression::Constant(crate::core::constants::MathConstant::Pi)
159    ) {
160        return Some(Expression::integer(0));
161    }
162
163    if is_pi_over_2(arg) {
164        return Some(Expression::function("tan", vec![arg.clone()]));
165    }
166
167    if is_pi_over_4(arg) {
168        return Some(Expression::integer(1));
169    }
170
171    if is_pi_over_6(arg) {
172        return Some(Expression::pow(
173            Expression::integer(3),
174            Expression::rational(-1, 2),
175        ));
176    }
177
178    if is_pi_over_3(arg) {
179        return Some(Expression::pow(
180            Expression::integer(3),
181            Expression::rational(1, 2),
182        ));
183    }
184
185    None
186}
187
188fn try_numeric_sin(arg: &Expression) -> Option<Expression> {
189    match arg {
190        Expression::Number(Number::Integer(n)) => Some(Expression::float((*n as f64).sin())),
191        Expression::Number(Number::Rational(r)) => {
192            let val = r.as_ref().to_f64().unwrap();
193            Some(Expression::float(val.sin()))
194        }
195        Expression::Number(Number::Float(f)) => Some(Expression::float(f.sin())),
196        _ => None,
197    }
198}
199
200fn try_numeric_cos(arg: &Expression) -> Option<Expression> {
201    match arg {
202        Expression::Number(Number::Integer(n)) => Some(Expression::float((*n as f64).cos())),
203        Expression::Number(Number::Rational(r)) => {
204            let val = r.as_ref().to_f64().unwrap();
205            Some(Expression::float(val.cos()))
206        }
207        Expression::Number(Number::Float(f)) => Some(Expression::float(f.cos())),
208        _ => None,
209    }
210}
211
212fn try_numeric_tan(arg: &Expression) -> Option<Expression> {
213    match arg {
214        Expression::Number(Number::Integer(n)) => Some(Expression::float((*n as f64).tan())),
215        Expression::Number(Number::Rational(r)) => {
216            let val = r.as_ref().to_f64().unwrap();
217            Some(Expression::float(val.tan()))
218        }
219        Expression::Number(Number::Float(f)) => Some(Expression::float(f.tan())),
220        _ => None,
221    }
222}
223
224fn is_pi_over_2(expr: &Expression) -> bool {
225    if let Expression::Mul(terms) = expr {
226        if terms.len() == 2 {
227            let (rational_idx, _pi_idx) = if matches!(
228                &terms[0],
229                Expression::Constant(crate::core::constants::MathConstant::Pi)
230            ) {
231                (1, 0)
232            } else if matches!(
233                &terms[1],
234                Expression::Constant(crate::core::constants::MathConstant::Pi)
235            ) {
236                (0, 1)
237            } else {
238                return false;
239            };
240
241            if let Expression::Number(Number::Rational(r)) = &terms[rational_idx] {
242                return r.numer() == &BigInt::from(1) && r.denom() == &BigInt::from(2);
243            }
244        }
245    }
246    false
247}
248
249fn is_pi_over_3(expr: &Expression) -> bool {
250    if let Expression::Mul(terms) = expr {
251        if terms.len() == 2 {
252            let (rational_idx, _pi_idx) = if matches!(
253                &terms[0],
254                Expression::Constant(crate::core::constants::MathConstant::Pi)
255            ) {
256                (1, 0)
257            } else if matches!(
258                &terms[1],
259                Expression::Constant(crate::core::constants::MathConstant::Pi)
260            ) {
261                (0, 1)
262            } else {
263                return false;
264            };
265
266            if let Expression::Number(Number::Rational(r)) = &terms[rational_idx] {
267                return r.numer() == &BigInt::from(1) && r.denom() == &BigInt::from(3);
268            }
269        }
270    }
271    false
272}
273
274fn is_pi_over_4(expr: &Expression) -> bool {
275    if let Expression::Mul(terms) = expr {
276        if terms.len() == 2 {
277            let (rational_idx, _pi_idx) = if matches!(
278                &terms[0],
279                Expression::Constant(crate::core::constants::MathConstant::Pi)
280            ) {
281                (1, 0)
282            } else if matches!(
283                &terms[1],
284                Expression::Constant(crate::core::constants::MathConstant::Pi)
285            ) {
286                (0, 1)
287            } else {
288                return false;
289            };
290
291            if let Expression::Number(Number::Rational(r)) = &terms[rational_idx] {
292                return r.numer() == &BigInt::from(1) && r.denom() == &BigInt::from(4);
293            }
294        }
295    }
296    false
297}
298
299fn is_pi_over_6(expr: &Expression) -> bool {
300    if let Expression::Mul(terms) = expr {
301        if terms.len() == 2 {
302            let (rational_idx, _pi_idx) = if matches!(
303                &terms[0],
304                Expression::Constant(crate::core::constants::MathConstant::Pi)
305            ) {
306                (1, 0)
307            } else if matches!(
308                &terms[1],
309                Expression::Constant(crate::core::constants::MathConstant::Pi)
310            ) {
311                (0, 1)
312            } else {
313                return false;
314            };
315
316            if let Expression::Number(Number::Rational(r)) = &terms[rational_idx] {
317                return r.numer() == &BigInt::from(1) && r.denom() == &BigInt::from(6);
318            }
319        }
320    }
321    false
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    #[test]
329    fn test_sin_of_zero_returns_zero() {
330        let zero = Expression::integer(0);
331        let result = sin(&zero);
332
333        // Debug: print what we got
334        println!("Input: {:?}", zero);
335        println!("Result: {:?}", result);
336
337        // The result should be Expression::integer(0), not a function wrapper
338        assert!(
339            matches!(result, Expression::Number(Number::Integer(0))),
340            "Expected sin(0) = 0 (integer), got: {:?}",
341            result
342        );
343    }
344
345    #[test]
346    fn test_sin_of_one_returns_float() {
347        let one = Expression::integer(1);
348        let result = sin(&one);
349
350        println!("Input: {:?}", one);
351        println!("Result: {:?}", result);
352
353        // The result should be a float (0.8414...)
354        match &result {
355            Expression::Number(Number::Float(f)) => {
356                assert!(
357                    (*f - 0.8414709848078965).abs() < 1e-10,
358                    "Expected sin(1) ~ 0.841, got: {}",
359                    f
360                );
361            }
362            _ => panic!("Expected sin(1) to return Float, got: {:?}", result),
363        }
364    }
365
366    #[test]
367    fn test_cos_of_zero_returns_one() {
368        let zero = Expression::integer(0);
369        let result = cos(&zero);
370
371        println!("Input: {:?}", zero);
372        println!("Result: {:?}", result);
373
374        // cos(0) should return 1
375        assert!(
376            matches!(result, Expression::Number(Number::Integer(1))),
377            "Expected cos(0) = 1 (integer), got: {:?}",
378            result
379        );
380    }
381}