Skip to main content

polydat_nodes/
math.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Trigonometric and mathematical function nodes.
5//!
6//! Standard math operations on f64 values. Use after `unit_interval`
7//! or `scale_range` to transform normalized values into waveforms,
8//! angles, or other mathematical shapes.
9
10#[polydat::polydat_node(category = Math)]
11fn sin(input: f64) -> f64 {
12    input.sin()
13}
14
15#[polydat::polydat_node(category = Math)]
16fn cos(input: f64) -> f64 {
17    input.cos()
18}
19
20#[polydat::polydat_node(category = Math)]
21fn tan(input: f64) -> f64 {
22    input.tan()
23}
24
25#[polydat::polydat_node(category = Math)]
26fn asin(input: f64) -> f64 {
27    input.asin()
28}
29
30#[polydat::polydat_node(category = Math)]
31fn acos(input: f64) -> f64 {
32    input.acos()
33}
34
35#[polydat::polydat_node(category = Math)]
36fn atan(input: f64) -> f64 {
37    input.atan()
38}
39
40#[polydat::polydat_node(category = Math)]
41fn sqrt(input: f64) -> f64 {
42    input.sqrt()
43}
44
45#[polydat::polydat_node(category = Math)]
46fn abs_f64(input: f64) -> f64 {
47    input.abs()
48}
49
50#[polydat::polydat_node(category = Math)]
51fn ln(input: f64) -> f64 {
52    input.ln()
53}
54
55#[polydat::polydat_node(category = Math)]
56fn exp(input: f64) -> f64 {
57    input.exp()
58}
59
60#[polydat::polydat_node(category = Math, simd = "reg_add_f64", simd_total)]
61fn f64_add(a: f64, b: f64) -> f64 {
62    a + b
63}
64
65#[polydat::polydat_node(category = Math, simd = "reg_sub_f64", simd_total)]
66fn f64_sub(a: f64, b: f64) -> f64 {
67    a - b
68}
69
70#[polydat::polydat_node(category = Math, simd = "reg_mul_f64", simd_total)]
71fn f64_mul(a: f64, b: f64) -> f64 {
72    a * b
73}
74
75#[polydat::polydat_node(category = Math)]
76fn f64_div(a: f64, b: f64) -> f64 {
77    if b != 0.0 { a / b } else { 0.0 }
78}
79
80#[polydat::polydat_node(category = Math)]
81fn f64_mod(a: f64, b: f64) -> f64 {
82    if b != 0.0 { a % b } else { 0.0 }
83}
84
85// --- Binary f64 math functions ---
86
87/// Two-argument arc tangent: atan2(y, x).
88///
89/// Signature: `atan2(y: f64, x: f64) -> (f64)`
90///
91/// Returns the angle in radians between the positive x-axis and the
92/// point (x, y). Output in (-pi, pi]. Use for converting Cartesian
93/// coordinates to polar angle.
94///
95/// JIT level: P3 (`JitOp::MathBinary`, extern libm call from native
96/// code).
97#[polydat::polydat_node(category = Math)]
98fn atan2(y: f64, x: f64) -> f64 {
99    y.atan2(x)
100}
101
102/// Power: base^exponent.
103#[polydat::polydat_node(category = Math)]
104fn pow(base: f64, exponent: f64) -> f64 {
105    base.powf(exponent)
106}
107
108#[cfg(any())]
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use polydat::ast::{PolydatNode, Value};
113    use std::f64::consts::PI;
114
115    #[test]
116    fn sin_known_values() {
117        let node = Sin::new();
118        let mut out = [Value::None];
119        node.eval(&[Value::F64(0.0)], &mut out);
120        assert!((out[0].as_f64() - 0.0).abs() < 1e-10);
121        node.eval(&[Value::F64(PI / 2.0)], &mut out);
122        assert!((out[0].as_f64() - 1.0).abs() < 1e-10);
123    }
124
125    #[test]
126    fn cos_known_values() {
127        let node = Cos::new();
128        let mut out = [Value::None];
129        node.eval(&[Value::F64(0.0)], &mut out);
130        assert!((out[0].as_f64() - 1.0).abs() < 1e-10);
131        node.eval(&[Value::F64(PI)], &mut out);
132        assert!((out[0].as_f64() + 1.0).abs() < 1e-10);
133    }
134
135    #[test]
136    fn sqrt_known() {
137        let node = Sqrt::new();
138        let mut out = [Value::None];
139        node.eval(&[Value::F64(4.0)], &mut out);
140        assert!((out[0].as_f64() - 2.0).abs() < 1e-10);
141    }
142
143    #[test]
144    fn atan2_quadrants() {
145        let node = Atan2::new();
146        let mut out = [Value::None];
147        // atan2(1, 0) = pi/2
148        node.eval(&[Value::F64(1.0), Value::F64(0.0)], &mut out);
149        assert!((out[0].as_f64() - PI / 2.0).abs() < 1e-10);
150    }
151
152    #[test]
153    fn pow_known() {
154        let node = Pow::new();
155        let mut out = [Value::None];
156        node.eval(&[Value::F64(2.0), Value::F64(10.0)], &mut out);
157        assert!((out[0].as_f64() - 1024.0).abs() < 1e-10);
158    }
159
160    #[test]
161    fn ln_exp_roundtrip() {
162        let node_ln = Ln::new();
163        let node_exp = Exp::new();
164        let mut out = [Value::None];
165        node_exp.eval(&[Value::F64(3.0)], &mut out);
166        let e3 = out[0].as_f64();
167        node_ln.eval(&[Value::F64(e3)], &mut out);
168        assert!((out[0].as_f64() - 3.0).abs() < 1e-10);
169    }
170
171    #[test]
172    fn compiled_matches_eval() {
173        let node = Sin::new();
174        let compiled = node.compiled_u64().unwrap();
175        let input = PI / 4.0;
176        let mut eval_out = [Value::None];
177        node.eval(&[Value::F64(input)], &mut eval_out);
178        let mut comp_out = [0u64];
179        compiled(&[input.to_bits()], &mut comp_out);
180        assert_eq!(eval_out[0].as_f64(), f64::from_bits(comp_out[0]));
181    }
182}