amari_calculus/fields/
vector_field.rs1use crate::{CalculusError, CalculusResult};
4use amari_core::Multivector;
5
6#[derive(Clone)]
24pub struct VectorField<const P: usize, const Q: usize, const R: usize> {
25 function: fn(&[f64]) -> Multivector<P, Q, R>,
27 dim: usize,
29}
30
31impl<const P: usize, const Q: usize, const R: usize> VectorField<P, Q, R> {
32 pub fn new(function: fn(&[f64]) -> Multivector<P, Q, R>) -> Self {
49 Self {
50 function,
51 dim: P + Q + R,
52 }
53 }
54
55 pub fn with_dimension(function: fn(&[f64]) -> Multivector<P, Q, R>, dim: usize) -> Self {
57 Self { function, dim }
58 }
59
60 pub fn evaluate(&self, coords: &[f64]) -> Multivector<P, Q, R> {
70 (self.function)(coords)
71 }
72
73 pub fn dimension(&self) -> usize {
75 self.dim
76 }
77
78 pub fn partial_derivative(
86 &self,
87 coords: &[f64],
88 axis: usize,
89 h: f64,
90 ) -> CalculusResult<Multivector<P, Q, R>> {
91 if axis >= self.dim {
92 return Err(CalculusError::InvalidDimension {
93 expected: self.dim,
94 got: axis,
95 });
96 }
97
98 let mut coords_plus = coords.to_vec();
99 let mut coords_minus = coords.to_vec();
100
101 coords_plus[axis] += h;
102 coords_minus[axis] -= h;
103
104 let f_plus = self.evaluate(&coords_plus);
105 let f_minus = self.evaluate(&coords_minus);
106
107 let result = (f_plus - f_minus) * (1.0 / (2.0 * h));
109
110 Ok(result)
111 }
112
113 }
117
118impl<const P: usize, const Q: usize, const R: usize> std::fmt::Debug for VectorField<P, Q, R> {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 f.debug_struct("VectorField")
121 .field("dim", &self.dim)
122 .field("signature", &format!("Cl({},{},{})", P, Q, R))
123 .finish()
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_vector_field_evaluation() {
133 let f = VectorField::<3, 0, 0>::new(|coords| {
135 crate::vector_from_slice(&[coords[0], coords[1], coords[2]])
136 });
137
138 let v = f.evaluate(&[1.0, 2.0, 3.0]);
139 let expected = crate::vector_from_slice::<3, 0, 0>(&[1.0, 2.0, 3.0]);
140
141 for i in 0..3 {
143 assert!(
144 (v.vector_component(i) - expected.vector_component(i)).abs() < 1e-10,
145 "Component {} mismatch",
146 i
147 );
148 }
149 }
150
151 #[test]
152 fn test_vector_field_combination() {
153 let _f = VectorField::<3, 0, 0>::new(|coords| {
156 crate::vector_from_slice(&[coords[0], coords[1], 0.0])
157 });
158
159 let _g = VectorField::<3, 0, 0>::new(|coords| {
161 crate::vector_from_slice(&[coords[1], coords[0], 0.0])
162 });
163
164 let h = VectorField::<3, 0, 0>::new(|coords| {
166 crate::vector_from_slice(&[coords[0] + coords[1], coords[1] + coords[0], 0.0])
167 });
168
169 let v = h.evaluate(&[2.0, 3.0, 0.0]);
170 assert!((v.vector_component(0) - 5.0).abs() < 1e-10);
172 assert!((v.vector_component(1) - 5.0).abs() < 1e-10);
173 }
174
175 #[test]
176 fn test_partial_derivative() {
177 let f = VectorField::<3, 0, 0>::new(|coords| {
180 crate::vector_from_slice(&[coords[0].powi(2), coords[1].powi(2), 0.0])
181 });
182
183 let df_dx = f.partial_derivative(&[3.0, 4.0, 0.0], 0, 1e-5).unwrap();
184 let df_dy = f.partial_derivative(&[3.0, 4.0, 0.0], 1, 1e-5).unwrap();
185
186 assert!(
188 (df_dx.vector_component(0) - 6.0).abs() < 1e-4,
189 "∂F_x/∂x should be 6.0"
190 );
191 assert!(
192 df_dx.vector_component(1).abs() < 1e-4,
193 "∂F_y/∂x should be 0.0"
194 );
195
196 assert!(
198 df_dy.vector_component(0).abs() < 1e-4,
199 "∂F_x/∂y should be 0.0"
200 );
201 assert!(
202 (df_dy.vector_component(1) - 8.0).abs() < 1e-4,
203 "∂F_y/∂y should be 8.0"
204 );
205 }
206}