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}