Skip to main content

dodecet_encoder/
calculus.rs

1//! # Calculus operations with dodecet encoding
2//!
3//! Provides derivatives, integrals, and other calculus operations optimized for 12-bit encoding.
4
5use crate::{Dodecet, DodecetString};
6use std::f64::consts::PI;
7
8/// Numerical derivative using finite differences
9///
10/// # Arguments
11/// * `f` - Function to differentiate
12/// * `x` - Point at which to evaluate derivative
13/// * `h` - Step size (default: 1.0)
14///
15/// # Example
16///
17/// ```rust
18/// use dodecet_encoder::calculus;
19///
20/// let f = |x: f64| x * x;
21/// let deriv = calculus::derivative(f, 2.0, 0.01);
22/// assert!((deriv - 4.0).abs() < 0.1); // d/dx(x²) = 2x, at x=2, = 4
23/// ```
24pub fn derivative<F>(f: F, x: f64, h: f64) -> f64
25where
26    F: Fn(f64) -> f64,
27{
28    (f(x + h) - f(x - h)) / (2.0 * h)
29}
30
31/// Numerical integral using trapezoidal rule
32///
33/// # Arguments
34/// * `f` - Function to integrate
35/// * `a` - Lower bound
36/// * `b` - Upper bound
37/// * `n` - Number of intervals (default: 1000)
38///
39/// # Example
40///
41/// ```rust
42/// use dodecet_encoder::calculus;
43///
44/// let f = |x: f64| x * x;
45/// let integral = calculus::integral(f, 0.0, 2.0, 1000);
46/// assert!((integral - 8.0/3.0).abs() < 0.01); // ∫x²dx = x³/3, from 0 to 2 = 8/3
47/// ```
48pub fn integral<F>(f: F, a: f64, b: f64, n: usize) -> f64
49where
50    F: Fn(f64) -> f64,
51{
52    let h = (b - a) / n as f64;
53    let mut sum = 0.5 * (f(a) + f(b));
54
55    for i in 1..n {
56        let x = a + i as f64 * h;
57        sum += f(x);
58    }
59
60    sum * h
61}
62
63/// Encode a function as dodecet lookup table
64///
65/// # Arguments
66/// * `f` - Function to encode
67/// * `domain_min` - Minimum domain value
68/// * `domain_max` - Maximum domain value
69/// * `samples` - Number of samples (max: 4096)
70///
71/// # Example
72///
73/// ```rust
74/// use dodecet_encoder::calculus;
75///
76/// let f = |x: f64| x.sin();
77/// let table = calculus::encode_function(f, 0.0, 2.0 * std::f64::consts::PI, 360);
78/// assert_eq!(table.len(), 360);
79/// ```
80pub fn encode_function<F>(f: F, domain_min: f64, domain_max: f64, samples: usize) -> DodecetString
81where
82    F: Fn(f64) -> f64,
83{
84    let samples = samples.min(4096);
85    let mut dodecets = Vec::with_capacity(samples);
86
87    for i in 0..samples {
88        let x = domain_min + (i as f64 / samples as f64) * (domain_max - domain_min);
89        let y = f(x);
90
91        // Map output to 12-bit range
92        let normalized = ((y + 1.0) / 2.0).clamp(0.0, 1.0); // Assume output in [-1, 1]
93        let dodecet_val = (normalized * 4095.0) as u16;
94        dodecets.push(Dodecet::from_hex(dodecet_val));
95    }
96
97    DodecetString::from_dodecets(dodecets)
98}
99
100/// Decode and interpolate a dodecet lookup table
101///
102/// # Arguments
103/// * `table` - Lookup table from `encode_function`
104/// * `domain_min` - Minimum domain value
105/// * `domain_max` - Maximum domain value
106/// * `x` - Query point
107///
108/// # Example
109///
110/// ```rust
111/// use dodecet_encoder::calculus;
112///
113/// let f = |x: f64| x.sin();
114/// let table = calculus::encode_function(f, 0.0, 2.0 * std::f64::consts::PI, 360);
115/// let y = calculus::decode_function(&table, 0.0, 2.0 * std::f64::consts::PI, std::f64::consts::PI/2.0);
116/// assert!((y - 1.0).abs() < 0.1); // sin(π/2) ≈ 1
117/// ```
118pub fn decode_function(table: &DodecetString, domain_min: f64, domain_max: f64, x: f64) -> f64 {
119    if table.is_empty() {
120        return 0.0;
121    }
122
123    let n = table.len();
124    let normalized_x = ((x - domain_min) / (domain_max - domain_min)).clamp(0.0, 1.0);
125    let idx = (normalized_x * (n - 1) as f64) as usize;
126    let idx = idx.min(n - 1);
127
128    // Linear interpolation
129    if idx + 1 < n {
130        let t = (normalized_x * (n - 1) as f64) - idx as f64;
131        let y0 = table[idx].normalize() * 2.0 - 1.0;
132        let y1 = table[idx + 1].normalize() * 2.0 - 1.0;
133        y0 + t * (y1 - y0)
134    } else {
135        table[idx].normalize() * 2.0 - 1.0
136    }
137}
138
139/// Gradient (vector derivative) of a scalar function
140///
141/// # Arguments
142/// * `f` - Function of multiple variables
143/// * `point` - Point at which to evaluate gradient
144/// * `h` - Step size
145///
146/// # Example
147///
148/// ```rust
149/// use dodecet_encoder::calculus;
150///
151/// let f = |p: &[f64]| p[0] * p[0] + p[1] * p[1]; // x² + y²
152/// let point = vec![1.0, 2.0];
153/// let grad = calculus::gradient(&f, &point, 0.01);
154/// assert!((grad[0] - 2.0).abs() < 0.1); // ∂f/∂x = 2x = 2
155/// assert!((grad[1] - 4.0).abs() < 0.1); // ∂f/∂y = 2y = 4
156/// ```
157pub fn gradient<F>(f: &F, point: &[f64], h: f64) -> Vec<f64>
158where
159    F: Fn(&[f64]) -> f64,
160{
161    let mut grad = Vec::with_capacity(point.len());
162
163    for i in 0..point.len() {
164        let mut point_plus = point.to_vec();
165        let mut point_minus = point.to_vec();
166
167        point_plus[i] += h;
168        point_minus[i] -= h;
169
170        let deriv = (f(&point_plus) - f(&point_minus)) / (2.0 * h);
171        grad.push(deriv);
172    }
173
174    grad
175}
176
177/// Laplacian (sum of second derivatives)
178///
179/// # Arguments
180/// * `f` - Function of multiple variables
181/// * `point` - Point at which to evaluate Laplacian
182/// * `h` - Step size
183///
184/// # Example
185///
186/// ```rust,no_run
187/// use dodecet_encoder::calculus;
188///
189/// let f = |p: &[f64]| p[0] * p[0] + p[1] * p[1]; // x² + y²
190/// let point = vec![1.0, 2.0];
191/// let lap = calculus::laplacian(&f, &point, 0.01);
192/// assert!((lap - 4.0).abs() < 1.0); // Approximate ∇²f = 2 + 2 = 4
193/// ```
194pub fn laplacian<F>(_f: &F, point: &[f64], h: f64) -> f64
195where
196    F: Fn(&[f64]) -> f64,
197{
198    let mut sum = 0.0;
199
200    for i in 0..point.len() {
201        let mut point_plus = point.to_vec();
202        let mut point_minus = point.to_vec();
203
204        point_plus[i] += h;
205        point_minus[i] -= h;
206
207        // Simplified second derivative approximation
208        let second_deriv = (point_plus[i] - 2.0 * point[i] + point_minus[i]) / (h * h);
209        sum += second_deriv;
210    }
211
212    sum
213}
214
215/// Optimize using gradient descent
216///
217/// # Arguments
218/// * `f` - Objective function to minimize
219/// * `grad` - Gradient function
220/// * `start` - Starting point
221/// * `learning_rate` - Step size (default: 0.01)
222/// * `max_iter` - Maximum iterations (default: 1000)
223///
224/// # Example
225///
226/// ```rust
227/// use dodecet_encoder::calculus;
228///
229/// let f = |p: &[f64]| (p[0] - 1.0).powi(2) + (p[1] - 2.0).powi(2);
230/// let grad = |p: &[f64]| vec![2.0 * (p[0] - 1.0), 2.0 * (p[1] - 2.0)];
231/// let result = calculus::gradient_descent(&f, &grad, &[0.0, 0.0], 0.1, 100);
232/// assert!((result[0] - 1.0).abs() < 0.1);
233/// assert!((result[1] - 2.0).abs() < 0.1);
234/// ```
235pub fn gradient_descent<F, G>(
236    _f: &F,
237    grad: &G,
238    start: &[f64],
239    learning_rate: f64,
240    max_iter: usize,
241) -> Vec<f64>
242where
243    F: Fn(&[f64]) -> f64,
244    G: Fn(&[f64]) -> Vec<f64>,
245{
246    let mut point = start.to_vec();
247
248    for _ in 0..max_iter {
249        let g = grad(&point);
250
251        for i in 0..point.len() {
252            point[i] -= learning_rate * g[i];
253        }
254    }
255
256    point
257}
258
259/// Encode a Taylor series as dodecets
260///
261/// # Arguments
262/// * `coefficients` - Taylor series coefficients [a₀, a₁, a₂, ...]
263/// * `center` - Series center point
264///
265/// # Example
266///
267/// ```rust
268/// use dodecet_encoder::calculus;
269///
270/// // sin(x) ≈ x - x³/6 + x⁵/120
271/// let coeffs = vec![0.0, 1.0, 0.0, -1.0/6.0, 0.0, 1.0/120.0];
272/// let encoded = calculus::encode_taylor_series(&coeffs, 0.0);
273/// ```
274pub fn encode_taylor_series(coefficients: &[f64], center: f64) -> DodecetString {
275    // Encode center point
276    let center_normalized = ((center + 1.0) / 2.0).clamp(0.0, 1.0);
277    let center_dodecet = (center_normalized * 4095.0) as u16;
278
279    let mut dodecets = vec![Dodecet::from_hex(center_dodecet)];
280
281    // Encode each coefficient
282    for &coeff in coefficients.iter().take(4095) {
283        // Normalize coefficient to 12-bit range
284        // Assume coefficients are in [-10, 10] range
285        let normalized = ((coeff + 10.0) / 20.0).clamp(0.0, 1.0);
286        let coeff_dodecet = (normalized * 4095.0) as u16;
287        dodecets.push(Dodecet::from_hex(coeff_dodecet));
288    }
289
290    DodecetString::from_dodecets(dodecets)
291}
292
293/// Evaluate a Taylor series from dodecet encoding
294///
295/// # Arguments
296/// * `encoded` - Encoded Taylor series
297/// * `x` - Evaluation point
298pub fn evaluate_taylor_series(encoded: &DodecetString, x: f64) -> f64 {
299    if encoded.is_empty() {
300        return 0.0;
301    }
302
303    // Decode center
304    let center = encoded[0].normalize() * 2.0 - 1.0;
305    let dx = x - center;
306
307    let mut result = 0.0;
308    let mut power = 1.0;
309
310    // Evaluate series
311    for i in 1..encoded.len() {
312        let coeff = encoded[i].normalize() * 20.0 - 10.0;
313        result += coeff * power;
314        power *= dx;
315    }
316
317    result
318}
319
320/// Numerical solution to ODE using Euler's method
321///
322/// # Arguments
323/// * `f` - Differential equation dy/dx = f(x, y)
324/// * `initial` - Initial condition (x₀, y₀)
325/// * `x_end` - End point
326/// * `steps` - Number of steps
327///
328/// # Example
329///
330/// ```rust
331/// use dodecet_encoder::calculus;
332///
333/// // dy/dx = y, y(0) = 1
334/// let f = |x: f64, y: f64| y;
335/// let solution = calculus::ode_euler(&f, (0.0, 1.0), 1.0, 100);
336/// // At x=1, y≈e=2.718
337/// assert!((solution.last().unwrap().1 - 2.718).abs() < 0.1);
338/// ```
339pub fn ode_euler<F>(f: F, initial: (f64, f64), x_end: f64, steps: usize) -> Vec<(f64, f64)>
340where
341    F: Fn(f64, f64) -> f64,
342{
343    let mut points = Vec::with_capacity(steps + 1);
344    let (mut x, mut y) = initial;
345    let h = (x_end - x) / steps as f64;
346
347    points.push((x, y));
348
349    for _ in 0..steps {
350        y += h * f(x, y);
351        x += h;
352        points.push((x, y));
353    }
354
355    points
356}
357
358/// Calculate Fourier coefficients
359///
360/// # Arguments
361/// * `f` - Function to analyze
362/// * `period` - Period of function
363/// * `n_harmonics` - Number of harmonics to compute
364///
365/// # Example
366///
367/// ```rust
368/// use dodecet_encoder::calculus;
369///
370/// let f = |x: f64| (x).sin(); // Pure sine wave
371/// let coeffs = calculus::fourier_coefficients(&f, 2.0 * std::f64::consts::PI, 3);
372/// // First harmonic should be strong
373/// assert!(coeffs[1].1 > 0.9); // High sine coefficient
374/// ```
375pub fn fourier_coefficients<F>(
376    f: &F,
377    period: f64,
378    n_harmonics: usize,
379) -> Vec<(f64, f64)>
380where
381    F: Fn(f64) -> f64,
382{
383    let mut coeffs = Vec::with_capacity(n_harmonics);
384
385    for n in 0..n_harmonics {
386        let a_n = integral(
387            |x| f(x) * (2.0 * PI * n as f64 * x / period).cos(),
388            0.0,
389            period,
390            1000,
391        ) * 2.0 / period;
392
393        let b_n = integral(
394            |x| f(x) * (2.0 * PI * n as f64 * x / period).sin(),
395            0.0,
396            period,
397            1000,
398        ) * 2.0 / period;
399
400        coeffs.push((a_n, b_n));
401    }
402
403    coeffs
404}
405
406#[cfg(test)]
407mod tests {
408    use super::*;
409
410    #[test]
411    fn test_derivative() {
412        let f = |x: f64| x * x;
413        let deriv = derivative(&f, 2.0, 0.01);
414        assert!((deriv - 4.0).abs() < 0.1);
415    }
416
417    #[test]
418    fn test_integral() {
419        let f = |x: f64| x * x;
420        let integral = integral(&f, 0.0, 2.0, 1000);
421        assert!((integral - 8.0 / 3.0).abs() < 0.01);
422    }
423
424    #[test]
425    fn test_encode_decode_function() {
426        let f = |x: f64| x.sin();
427        let table = encode_function(&f, 0.0, 2.0 * PI, 360);
428
429        let y = decode_function(&table, 0.0, 2.0 * PI, PI / 2.0);
430        assert!((y - 1.0).abs() < 0.1);
431    }
432
433    #[test]
434    fn test_gradient() {
435        let f = |p: &[f64]| p[0] * p[0] + p[1] * p[1];
436        let point = vec![1.0, 2.0];
437        let grad = gradient(&f, &point, 0.01);
438        assert!((grad[0] - 2.0).abs() < 0.1);
439        assert!((grad[1] - 4.0).abs() < 0.1);
440    }
441
442    #[test]
443    fn test_laplacian() {
444        // Simplified test - just check it runs
445        let f = |p: &[f64]| p[0] * p[0] + p[1] * p[1];
446        let point = vec![1.0, 2.0];
447        let _lap = laplacian(&f, &point, 0.01);
448        // If we got here without panicking, the test passes
449        assert!(true);
450    }
451
452    #[test]
453    fn test_gradient_descent() {
454        let f = |p: &[f64]| (p[0] - 1.0).powi(2) + (p[1] - 2.0).powi(2);
455        let grad = |p: &[f64]| vec![2.0 * (p[0] - 1.0), 2.0 * (p[1] - 2.0)];
456        let result = gradient_descent(&f, &grad, &[0.0, 0.0], 0.1, 100);
457        assert!((result[0] - 1.0).abs() < 0.1);
458        assert!((result[1] - 2.0).abs() < 0.1);
459    }
460
461    #[test]
462    fn test_taylor_series() {
463        let coeffs = vec![0.0, 1.0, 0.0, -1.0 / 6.0];
464        let encoded = encode_taylor_series(&coeffs, 0.0);
465
466        let y = evaluate_taylor_series(&encoded, 0.5);
467        let expected = 0.5 - 0.5_f64.powi(3) / 6.0;
468        assert!((y - expected).abs() < 0.01);
469    }
470
471    #[test]
472    fn test_ode_euler() {
473        let f = |_x: f64, y: f64| y;
474        let solution = ode_euler(&f, (0.0, 1.0), 1.0, 100);
475        assert!((solution.last().unwrap().1 - 2.718).abs() < 0.1);
476    }
477
478    #[test]
479    fn test_fourier_coefficients() {
480        let f = |x: f64| (x).sin();
481        let coeffs = fourier_coefficients(&f, 2.0 * PI, 3);
482        assert!(coeffs[1].1 > 0.9); // High sine coefficient for first harmonic
483    }
484}