amari_calculus/lie.rs
1//! Lie derivatives and Lie brackets
2//!
3//! This module provides Lie derivatives - derivatives along vector fields that measure
4//! how fields change along the flow of a vector field.
5//!
6//! ## Mathematical Background
7//!
8//! The Lie derivative generalizes the directional derivative to arbitrary tensor fields:
9//!
10//! ### Lie Bracket of Vector Fields
11//!
12//! For vector fields X and Y, the Lie bracket is:
13//! ```text
14//! [X, Y] = ∇_X Y - ∇_Y X
15//! ```
16//!
17//! In coordinates:
18//! ```text
19//! [X, Y]^i = X^j ∂_j Y^i - Y^j ∂_j X^i
20//! ```
21//!
22//! ### Lie Derivative
23//!
24//! - **Scalar field**: L_X(f) = X(f) = X^i ∂_i f (directional derivative)
25//! - **Vector field**: L_X(Y) = [X, Y]
26//! - **Multivector field**: L_X(F) extends via Leibniz rule
27//!
28//! ## Examples
29//!
30//! ```
31//! use amari_calculus::{VectorField, LieDerivative, vector_from_slice};
32//!
33//! // Define rotation field X = (-y, x, 0)
34//! let x = VectorField::<3, 0, 0>::new(|coords| {
35//! vector_from_slice(&[-coords[1], coords[0], 0.0])
36//! });
37//!
38//! // Define radial field Y = (x, y, 0)
39//! let y = VectorField::<3, 0, 0>::new(|coords| {
40//! vector_from_slice(&[coords[0], coords[1], 0.0])
41//! });
42//!
43//! let lie = LieDerivative::<3, 0, 0>::new();
44//!
45//! // Compute Lie bracket [X, Y] at origin
46//! let bracket = lie.bracket(&x, &y, &[0.0, 0.0, 0.0]);
47//! ```
48
49use crate::fields::*;
50use amari_core::Multivector;
51
52/// Lie derivative operator
53///
54/// Computes Lie derivatives and Lie brackets of fields along vector fields.
55pub struct LieDerivative<const P: usize, const Q: usize, const R: usize> {
56 /// Step size for numerical differentiation
57 h: f64,
58}
59
60impl<const P: usize, const Q: usize, const R: usize> LieDerivative<P, Q, R> {
61 /// Create new Lie derivative operator
62 ///
63 /// # Examples
64 ///
65 /// ```
66 /// use amari_calculus::LieDerivative;
67 ///
68 /// let lie = LieDerivative::<3, 0, 0>::new();
69 /// ```
70 pub fn new() -> Self {
71 Self { h: 1e-5 }
72 }
73
74 /// Set step size for numerical differentiation
75 pub fn with_step_size(mut self, h: f64) -> Self {
76 self.h = h;
77 self
78 }
79
80 /// Compute Lie derivative of scalar field along vector field: L_X(f)
81 ///
82 /// This is equivalent to the directional derivative X(f).
83 ///
84 /// # Arguments
85 ///
86 /// * `x` - Vector field along which to differentiate
87 /// * `f` - Scalar field to differentiate
88 /// * `coords` - Point at which to evaluate
89 ///
90 /// # Returns
91 ///
92 /// Scalar value L_X(f) = X^i ∂_i f
93 ///
94 /// # Examples
95 ///
96 /// ```
97 /// use amari_calculus::{ScalarField, VectorField, LieDerivative, vector_from_slice};
98 ///
99 /// // f(x, y) = x² + y²
100 /// let f = ScalarField::<3, 0, 0>::new(|coords| {
101 /// coords[0].powi(2) + coords[1].powi(2)
102 /// });
103 ///
104 /// // X = (1, 0, 0) - unit vector in x direction
105 /// let x = VectorField::<3, 0, 0>::new(|_coords| {
106 /// vector_from_slice(&[1.0, 0.0, 0.0])
107 /// });
108 ///
109 /// let lie = LieDerivative::<3, 0, 0>::new();
110 ///
111 /// // L_X(f) at (3, 4) should be ∂f/∂x = 2x = 6
112 /// let result = lie.of_scalar(&x, &f, &[3.0, 4.0, 0.0]);
113 /// ```
114 pub fn of_scalar(
115 &self,
116 x: &VectorField<P, Q, R>,
117 f: &ScalarField<P, Q, R>,
118 coords: &[f64],
119 ) -> f64 {
120 let dim = P + Q + R;
121 let x_val = x.evaluate(coords);
122 let mut result = 0.0;
123
124 // L_X(f) = X^i ∂_i f
125 for i in 0..dim {
126 let x_i = x_val.vector_component(i);
127 if let Ok(df_di) = f.partial_derivative(coords, i, self.h) {
128 result += x_i * df_di;
129 }
130 }
131
132 result
133 }
134
135 /// Compute Lie bracket of two vector fields: [X, Y]
136 ///
137 /// The Lie bracket measures the non-commutativity of the flows generated by X and Y.
138 ///
139 /// # Arguments
140 ///
141 /// * `x` - First vector field
142 /// * `y` - Second vector field
143 /// * `coords` - Point at which to evaluate
144 ///
145 /// # Returns
146 ///
147 /// Vector field [X, Y]^i = X^j ∂_j Y^i - Y^j ∂_j X^i
148 ///
149 /// # Examples
150 ///
151 /// ```
152 /// use amari_calculus::{VectorField, LieDerivative, vector_from_slice};
153 ///
154 /// // Coordinate vector fields
155 /// let e1 = VectorField::<3, 0, 0>::new(|_coords| {
156 /// vector_from_slice(&[1.0, 0.0, 0.0])
157 /// });
158 /// let e2 = VectorField::<3, 0, 0>::new(|_coords| {
159 /// vector_from_slice(&[0.0, 1.0, 0.0])
160 /// });
161 ///
162 /// let lie = LieDerivative::<3, 0, 0>::new();
163 ///
164 /// // [e1, e2] should be zero (coordinate fields commute)
165 /// let bracket = lie.bracket(&e1, &e2, &[1.0, 1.0, 0.0]);
166 /// ```
167 pub fn bracket(
168 &self,
169 x: &VectorField<P, Q, R>,
170 y: &VectorField<P, Q, R>,
171 coords: &[f64],
172 ) -> Multivector<P, Q, R> {
173 let dim = P + Q + R;
174 let x_val = x.evaluate(coords);
175 let y_val = y.evaluate(coords);
176
177 let mut bracket = Multivector::zero();
178
179 // [X, Y]^i = X^j ∂_j Y^i - Y^j ∂_j X^i
180 for i in 0..dim {
181 let mut component = 0.0;
182
183 // First term: X^j ∂_j Y^i
184 for j in 0..dim {
185 let x_j = x_val.vector_component(j);
186 if let Ok(dy_dj) = y.partial_derivative(coords, j, self.h) {
187 component += x_j * dy_dj.vector_component(i);
188 }
189 }
190
191 // Second term: -Y^j ∂_j X^i
192 for j in 0..dim {
193 let y_j = y_val.vector_component(j);
194 if let Ok(dx_dj) = x.partial_derivative(coords, j, self.h) {
195 component -= y_j * dx_dj.vector_component(i);
196 }
197 }
198
199 bracket.set_vector_component(i, component);
200 }
201
202 bracket
203 }
204
205 /// Compute Lie derivative of vector field along another vector field: L_X(Y) = [X, Y]
206 ///
207 /// This is an alias for the Lie bracket.
208 ///
209 /// # Arguments
210 ///
211 /// * `x` - Vector field along which to differentiate
212 /// * `y` - Vector field to differentiate
213 /// * `coords` - Point at which to evaluate
214 ///
215 /// # Returns
216 ///
217 /// Vector field L_X(Y) = [X, Y]
218 pub fn of_vector(
219 &self,
220 x: &VectorField<P, Q, R>,
221 y: &VectorField<P, Q, R>,
222 coords: &[f64],
223 ) -> Multivector<P, Q, R> {
224 self.bracket(x, y, coords)
225 }
226
227 /// Compute Lie derivative of multivector field along vector field: L_X(F)
228 ///
229 /// For a general multivector field, the Lie derivative extends via the Leibniz rule.
230 ///
231 /// # Arguments
232 ///
233 /// * `x` - Vector field along which to differentiate
234 /// * `f` - Multivector field to differentiate
235 /// * `coords` - Point at which to evaluate
236 ///
237 /// # Returns
238 ///
239 /// Multivector field L_X(F)
240 pub fn of_multivector(
241 &self,
242 x: &VectorField<P, Q, R>,
243 f: &MultivectorField<P, Q, R>,
244 coords: &[f64],
245 ) -> Multivector<P, Q, R> {
246 let dim = P + Q + R;
247 let x_val = x.evaluate(coords);
248
249 // L_X(F) = X^i ∂_i F (directional derivative for now)
250 // Full implementation would account for geometric product structure
251 let mut result = Multivector::zero();
252
253 for i in 0..dim {
254 let x_i = x_val.vector_component(i);
255 if let Ok(df_di) = f.partial_derivative(coords, i, self.h) {
256 result = result + df_di * x_i;
257 }
258 }
259
260 result
261 }
262}
263
264impl<const P: usize, const Q: usize, const R: usize> Default for LieDerivative<P, Q, R> {
265 fn default() -> Self {
266 Self::new()
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
275 fn test_lie_derivative_of_scalar() {
276 // f(x, y) = x² + y²
277 let f = ScalarField::<3, 0, 0>::new(|coords| coords[0].powi(2) + coords[1].powi(2));
278
279 // X = (1, 0, 0)
280 let x = VectorField::<3, 0, 0>::new(|_coords| crate::vector_from_slice(&[1.0, 0.0, 0.0]));
281
282 let lie = LieDerivative::<3, 0, 0>::new();
283 let result = lie.of_scalar(&x, &f, &[3.0, 4.0, 0.0]);
284
285 // L_X(f) = X · ∇f = (1,0,0) · (2x, 2y, 0) = 2x = 6
286 assert!(
287 (result - 6.0).abs() < 1e-4,
288 "Lie derivative should be 6.0, got {}",
289 result
290 );
291 }
292
293 #[test]
294 fn test_lie_bracket_coordinate_fields() {
295 // Coordinate vector fields commute, so bracket should be zero
296 let e1 = VectorField::<3, 0, 0>::new(|_coords| crate::vector_from_slice(&[1.0, 0.0, 0.0]));
297 let e2 = VectorField::<3, 0, 0>::new(|_coords| crate::vector_from_slice(&[0.0, 1.0, 0.0]));
298
299 let lie = LieDerivative::<3, 0, 0>::new();
300 let bracket = lie.bracket(&e1, &e2, &[1.0, 1.0, 0.0]);
301
302 // All components should be approximately zero
303 for i in 0..3 {
304 assert!(
305 bracket.vector_component(i).abs() < 1e-4,
306 "Coordinate fields should commute, got component {} = {}",
307 i,
308 bracket.vector_component(i)
309 );
310 }
311 }
312
313 #[test]
314 fn test_lie_bracket_noncommuting_fields() {
315 // X = (y, 0, 0)
316 let x =
317 VectorField::<3, 0, 0>::new(|coords| crate::vector_from_slice(&[coords[1], 0.0, 0.0]));
318
319 // Y = (0, x, 0)
320 let y =
321 VectorField::<3, 0, 0>::new(|coords| crate::vector_from_slice(&[0.0, coords[0], 0.0]));
322
323 let lie = LieDerivative::<3, 0, 0>::new();
324
325 // ∂_x Y = (0, 1, 0), ∂_y Y = (0, 0, 0)
326 // ∂_x X = (0, 0, 0), ∂_y X = (1, 0, 0)
327 // At point (2, 3, 0): X = (3, 0, 0), Y = (0, 2, 0)
328 // [X, Y]^x = X^x ∂_x Y^x + X^y ∂_y Y^x - (Y^x ∂_x X^x + Y^y ∂_y X^x)
329 // = 3*0 + 0*0 - (0*0 + 2*1)
330 // = 0 - 2 = -2
331 // [X, Y]^y = X^x ∂_x Y^y + X^y ∂_y Y^y - (Y^x ∂_x X^y + Y^y ∂_y X^y)
332 // = 3*1 + 0*0 - (0*0 + 2*0)
333 // = 3 - 0 = 3
334 let bracket = lie.bracket(&x, &y, &[2.0, 3.0, 0.0]);
335
336 assert!(
337 (bracket.vector_component(0) + 2.0).abs() < 1e-4,
338 "x-component should be -2, got {}",
339 bracket.vector_component(0)
340 );
341 assert!(
342 (bracket.vector_component(1) - 3.0).abs() < 1e-4,
343 "y-component should be 3, got {}",
344 bracket.vector_component(1)
345 );
346 }
347
348 #[test]
349 fn test_lie_bracket_antisymmetry() {
350 // [X, Y] = -[Y, X]
351 let x = VectorField::<3, 0, 0>::new(|coords| {
352 crate::vector_from_slice(&[-coords[1], coords[0], 0.0])
353 });
354 let y = VectorField::<3, 0, 0>::new(|coords| {
355 crate::vector_from_slice(&[coords[0], coords[1], 0.0])
356 });
357
358 let lie = LieDerivative::<3, 0, 0>::new();
359 let coords = [1.0, 2.0, 0.0];
360
361 let bracket_xy = lie.bracket(&x, &y, &coords);
362 let bracket_yx = lie.bracket(&y, &x, &coords);
363
364 for i in 0..3 {
365 let sum = bracket_xy.vector_component(i) + bracket_yx.vector_component(i);
366 assert!(
367 sum.abs() < 1e-4,
368 "Antisymmetry violated: [X,Y]_{} + [Y,X]_{} = {}",
369 i,
370 i,
371 sum
372 );
373 }
374 }
375}