amari_calculus/
derivative.rs

1//! Vector derivative operator ∇ = e^i ∂_i
2
3use crate::{fields::*, CoordinateSystem};
4use amari_core::Multivector;
5
6/// Vector derivative operator ∇
7///
8/// The fundamental differential operator in geometric calculus that combines
9/// gradient, divergence, and curl into a single geometric operation.
10///
11/// ## Mathematical Background
12///
13/// The vector derivative is defined as:
14/// ```text
15/// ∇ = e^i ∂_i  (sum over basis vectors)
16/// ```
17///
18/// When applied to fields:
19/// - Scalar field: ∇f = gradient
20/// - Vector field: ∇·F = divergence, ∇∧F = curl
21/// - General: ∇F = ∇·F + ∇∧F (full geometric derivative)
22pub struct VectorDerivative<const P: usize, const Q: usize, const R: usize> {
23    /// Coordinate system (reserved for future use with non-Cartesian coordinates)
24    _coordinates: CoordinateSystem,
25    /// Step size for numerical differentiation
26    h: f64,
27}
28
29impl<const P: usize, const Q: usize, const R: usize> VectorDerivative<P, Q, R> {
30    /// Create new vector derivative operator
31    ///
32    /// # Arguments
33    ///
34    /// * `coordinates` - Coordinate system (Cartesian, spherical, etc.)
35    ///
36    /// # Examples
37    ///
38    /// ```
39    /// use amari_calculus::{VectorDerivative, CoordinateSystem};
40    ///
41    /// let nabla = VectorDerivative::<3, 0, 0>::new(CoordinateSystem::Cartesian);
42    /// ```
43    pub fn new(coordinates: CoordinateSystem) -> Self {
44        Self {
45            _coordinates: coordinates,
46            h: 1e-5, // Default step size
47        }
48    }
49
50    /// Set step size for numerical differentiation
51    pub fn with_step_size(mut self, h: f64) -> Self {
52        self.h = h;
53        self
54    }
55
56    /// Compute gradient of scalar field: ∇f
57    ///
58    /// Returns a vector field representing the gradient.
59    pub fn gradient(&self, f: &ScalarField<P, Q, R>, coords: &[f64]) -> Multivector<P, Q, R> {
60        let dim = P + Q + R;
61        let mut components = vec![0.0; dim];
62
63        // Compute partial derivatives along each axis
64        for (i, component) in components.iter_mut().enumerate().take(dim) {
65            *component = f.partial_derivative(coords, i, self.h).unwrap_or(0.0);
66        }
67
68        crate::vector_from_slice(&components)
69    }
70
71    /// Compute divergence of vector field: ∇·F
72    ///
73    /// Returns a scalar representing the divergence.
74    pub fn divergence(&self, f: &VectorField<P, Q, R>, coords: &[f64]) -> f64 {
75        let dim = P + Q + R;
76        let mut div = 0.0;
77
78        // Sum of partial derivatives: ∂F_i/∂x_i
79        for i in 0..dim {
80            if let Ok(df_dxi) = f.partial_derivative(coords, i, self.h) {
81                // Extract i-th component of the derivative
82                div += df_dxi.vector_component(i);
83            }
84        }
85
86        div
87    }
88
89    /// Compute curl of vector field: ∇∧F
90    ///
91    /// Returns a bivector representing the curl.
92    pub fn curl(&self, f: &VectorField<P, Q, R>, coords: &[f64]) -> Multivector<P, Q, R> {
93        // For 3D: curl = (∂F_z/∂y - ∂F_y/∂z, ∂F_x/∂z - ∂F_z/∂x, ∂F_y/∂x - ∂F_x/∂y)
94        // Represented as bivector (e_2∧e_3, e_3∧e_1, e_1∧e_2)
95
96        let dim = P + Q + R;
97        if dim != 3 {
98            // Curl only well-defined in 3D for now
99            return Multivector::zero();
100        }
101
102        // Compute partial derivatives
103        let df_dx = f
104            .partial_derivative(coords, 0, self.h)
105            .unwrap_or(Multivector::zero());
106        let df_dy = f
107            .partial_derivative(coords, 1, self.h)
108            .unwrap_or(Multivector::zero());
109        let df_dz = f
110            .partial_derivative(coords, 2, self.h)
111            .unwrap_or(Multivector::zero());
112
113        // Extract components (basis: e_1, e_2, e_3 → indices 0, 1, 2)
114        let _fx_x = df_dx.vector_component(0); // ∂F_x/∂x (not used in curl)
115        let fy_x = df_dx.vector_component(1); // ∂F_y/∂x
116        let fz_x = df_dx.vector_component(2); // ∂F_z/∂x
117
118        let fx_y = df_dy.vector_component(0); // ∂F_x/∂y
119        let _fy_y = df_dy.vector_component(1); // ∂F_y/∂y (not used in curl)
120        let fz_y = df_dy.vector_component(2); // ∂F_z/∂y
121
122        let fx_z = df_dz.vector_component(0); // ∂F_x/∂z
123        let fy_z = df_dz.vector_component(1); // ∂F_y/∂z
124        let _fz_z = df_dz.vector_component(2); // ∂F_z/∂z (not used in curl)
125
126        // Curl components (bivector representation)
127        // In 3D: e_1∧e_2, e_1∧e_3, e_2∧e_3
128        // Traditional curl: (∂F_z/∂y - ∂F_y/∂z, ∂F_x/∂z - ∂F_z/∂x, ∂F_y/∂x - ∂F_x/∂y)
129        // Maps to bivectors: e_2∧e_3, e_3∧e_1, e_1∧e_2
130
131        let mut curl = Multivector::zero();
132        curl.set_bivector_component(2, fz_y - fy_z); // e_2∧e_3 (index 2)
133        curl.set_bivector_component(1, fx_z - fz_x); // e_1∧e_3 (index 1)
134        curl.set_bivector_component(0, fy_x - fx_y); // e_1∧e_2 (index 0)
135
136        curl
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_gradient() {
146        // f(x, y) = x² + y²
147        // ∇f = (2x, 2y, 0)
148        let f = ScalarField::<3, 0, 0>::new(|coords| coords[0].powi(2) + coords[1].powi(2));
149
150        let nabla = VectorDerivative::<3, 0, 0>::new(CoordinateSystem::Cartesian);
151        let grad = nabla.gradient(&f, &[3.0, 4.0, 0.0]);
152
153        // Check components
154        assert!(
155            (grad.vector_component(0) - 6.0).abs() < 1e-4,
156            "∂f/∂x should be 6.0"
157        );
158        assert!(
159            (grad.vector_component(1) - 8.0).abs() < 1e-4,
160            "∂f/∂y should be 8.0"
161        );
162    }
163
164    #[test]
165    fn test_divergence() {
166        // F(x, y, z) = (x, y, z)
167        // ∇·F = 1 + 1 + 1 = 3
168        let f = VectorField::<3, 0, 0>::new(|coords| {
169            crate::vector_from_slice(&[coords[0], coords[1], coords[2]])
170        });
171
172        let nabla = VectorDerivative::<3, 0, 0>::new(CoordinateSystem::Cartesian);
173        let div = nabla.divergence(&f, &[1.0, 1.0, 1.0]);
174
175        assert!(
176            (div - 3.0).abs() < 1e-4,
177            "Divergence should be 3.0, got {}",
178            div
179        );
180    }
181
182    #[test]
183    fn test_curl_of_rotation_field() {
184        // F(x, y, z) = (-y, x, 0) - rotation around z-axis
185        // ∇×F = (0, 0, 2) in traditional notation
186        // In GA: bivector e_1∧e_2 with magnitude 2
187        let f = VectorField::<3, 0, 0>::new(|coords| {
188            crate::vector_from_slice(&[-coords[1], coords[0], 0.0])
189        });
190
191        let nabla = VectorDerivative::<3, 0, 0>::new(CoordinateSystem::Cartesian);
192        let curl = nabla.curl(&f, &[0.0, 0.0, 0.0]);
193
194        // Check e_1∧e_2 component (index 1|2 = 3)
195        let curl_z = curl.get(1 | 2);
196        assert!(
197            (curl_z - 2.0).abs() < 1e-4,
198            "Curl z-component should be 2.0, got {}",
199            curl_z
200        );
201    }
202}