mathhook_core/functions/elementary/trigonometric/
trig_evaluation.rs1use crate::core::expression::Expression;
7use crate::core::number::Number;
8use num_bigint::BigInt;
9use num_traits::ToPrimitive;
10
11pub 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
24pub 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
37pub 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
50pub 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
59pub 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
68pub 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 println!("Input: {:?}", zero);
335 println!("Result: {:?}", result);
336
337 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 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 assert!(
376 matches!(result, Expression::Number(Number::Integer(1))),
377 "Expected cos(0) = 1 (integer), got: {:?}",
378 result
379 );
380 }
381}