symtropy_math/
bivector.rs1use nalgebra::{SMatrix, SVector};
5
6const MAX_BIVECTOR_COMPONENTS: usize = 36;
10
11#[derive(Clone, Copy, Debug)]
22pub struct Bivector<const D: usize> {
23 components: [f64; MAX_BIVECTOR_COMPONENTS],
24}
25
26const fn _assert_d_le_9<const D: usize>() {
28 assert!(
29 D * (D - 1) / 2 <= MAX_BIVECTOR_COMPONENTS,
30 "Bivector supports D <= 9 (max 36 components)"
31 );
32}
33
34impl<const D: usize> Bivector<D> {
35 pub const fn num_components() -> usize {
37 D * (D - 1) / 2
38 }
39
40 #[inline]
42 pub fn zero() -> Self {
43 _assert_d_le_9::<D>();
44 Self {
45 components: [0.0; MAX_BIVECTOR_COMPONENTS],
46 }
47 }
48
49 pub fn unit_plane(i: usize, j: usize) -> Self {
51 assert!(i < j && j < D, "requires i < j < D");
52 let mut bv = Self::zero();
53 bv.set(i, j, 1.0);
54 bv
55 }
56
57 #[inline]
59 fn index(i: usize, j: usize) -> usize {
60 debug_assert!(i < j && j < D);
61 i * D - i * (i + 1) / 2 + j - i - 1
62 }
63
64 #[inline]
66 pub fn get(&self, i: usize, j: usize) -> f64 {
67 self.components[Self::index(i, j)]
68 }
69
70 #[inline]
72 pub fn set(&mut self, i: usize, j: usize, value: f64) {
73 self.components[Self::index(i, j)] = value;
74 }
75
76 pub fn to_matrix(&self) -> SMatrix<f64, D, D> {
78 let mut mat = SMatrix::zeros();
79 for i in 0..D {
80 for j in (i + 1)..D {
81 let val = self.get(i, j);
82 mat[(i, j)] = val;
83 mat[(j, i)] = -val;
84 }
85 }
86 mat
87 }
88
89 pub fn from_matrix(mat: &SMatrix<f64, D, D>) -> Self {
91 let mut bv = Self::zero();
92 for i in 0..D {
93 for j in (i + 1)..D {
94 bv.set(i, j, (mat[(i, j)] - mat[(j, i)]) / 2.0);
95 }
96 }
97 bv
98 }
99
100 #[inline]
102 pub fn is_finite(&self) -> bool {
103 let n = Self::num_components();
104 for i in 0..n {
105 if !self.components[i].is_finite() {
106 return false;
107 }
108 }
109 true
110 }
111
112 #[inline]
114 pub fn norm_squared(&self) -> f64 {
115 let n = Self::num_components();
116 let mut sum = 0.0;
117 for i in 0..n {
118 sum += self.components[i] * self.components[i];
119 }
120 sum
121 }
122
123 #[inline]
125 pub fn norm(&self) -> f64 {
126 self.norm_squared().sqrt()
127 }
128
129 pub fn normalized(&self) -> Option<Self> {
131 let n = self.norm();
132 if n < 1e-15 {
133 return None;
134 }
135 Some(self.scale(1.0 / n))
136 }
137
138 #[inline]
140 pub fn scale(&self, s: f64) -> Self {
141 let mut result = *self;
142 let n = Self::num_components();
143 for i in 0..n {
144 result.components[i] *= s;
145 }
146 result
147 }
148
149 #[inline]
151 pub fn add(&self, other: &Self) -> Self {
152 let mut result = *self;
153 let n = Self::num_components();
154 for i in 0..n {
155 result.components[i] += other.components[i];
156 }
157 result
158 }
159
160 #[inline]
163 pub fn apply_to_vector(&self, v: &SVector<f64, D>) -> SVector<f64, D> {
164 self.to_matrix() * v
165 }
166}
167
168impl<const D: usize> Default for Bivector<D> {
169 fn default() -> Self {
170 Self::zero()
171 }
172}
173
174impl<const D: usize> PartialEq for Bivector<D> {
175 fn eq(&self, other: &Self) -> bool {
176 let n = Self::num_components();
177 for i in 0..n {
178 if (self.components[i] - other.components[i]).abs() > 1e-14 {
179 return false;
180 }
181 }
182 true
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn num_components() {
192 assert_eq!(Bivector::<2>::num_components(), 1);
193 assert_eq!(Bivector::<3>::num_components(), 3);
194 assert_eq!(Bivector::<4>::num_components(), 6);
195 }
196
197 #[test]
198 fn unit_plane_3d() {
199 let bv = Bivector::<3>::unit_plane(0, 1);
200 assert!((bv.get(0, 1) - 1.0).abs() < 1e-15);
201 assert!(bv.get(0, 2).abs() < 1e-15);
202 assert!(bv.get(1, 2).abs() < 1e-15);
203 }
204
205 #[test]
206 fn matrix_roundtrip() {
207 let mut bv = Bivector::<4>::zero();
208 bv.set(0, 1, 1.0);
209 bv.set(0, 3, -0.5);
210 bv.set(2, 3, 0.7);
211 let mat = bv.to_matrix();
212 let recovered = Bivector::<4>::from_matrix(&mat);
213 assert_eq!(bv, recovered);
214 }
215
216 #[test]
217 fn antisymmetric_matrix() {
218 let bv = Bivector::<3>::unit_plane(0, 2);
219 let mat = bv.to_matrix();
220 assert!((mat[(0, 2)] - 1.0).abs() < 1e-15);
221 assert!((mat[(2, 0)] + 1.0).abs() < 1e-15);
222 }
223
224 #[test]
225 fn angular_velocity_3d() {
226 let bv = Bivector::<3>::unit_plane(0, 1);
227 let v = SVector::from([1.0, 0.0, 0.0]);
228 let result = bv.apply_to_vector(&v);
229 assert!(result[0].abs() < 1e-15);
230 assert!((result[1] + 1.0).abs() < 1e-15);
231 assert!(result[2].abs() < 1e-15);
232 }
233
234 #[test]
235 fn norm_and_normalize() {
236 let mut bv = Bivector::<3>::zero();
237 bv.set(0, 1, 3.0);
238 bv.set(0, 2, 4.0);
239 assert!((bv.norm() - 5.0).abs() < 1e-14);
240 let unit = bv.normalized().unwrap();
241 assert!((unit.norm() - 1.0).abs() < 1e-14);
242 }
243
244 #[test]
245 fn four_d_double_rotation() {
246 let mut bv = Bivector::<4>::zero();
247 bv.set(0, 1, 1.0);
248 bv.set(2, 3, 1.0);
249 assert!((bv.norm_squared() - 2.0).abs() < 1e-14);
250 }
251
252 #[test]
253 fn is_copy() {
254 let a = Bivector::<3>::unit_plane(0, 1);
255 let b = a; assert_eq!(a, b);
257 }
258
259 #[test]
260 fn scale_no_alloc() {
261 let bv = Bivector::<4>::unit_plane(0, 1);
262 let scaled = bv.scale(3.0);
263 assert!((scaled.get(0, 1) - 3.0).abs() < 1e-14);
264 assert!((bv.get(0, 1) - 1.0).abs() < 1e-14);
266 }
267
268 #[test]
269 fn add_no_alloc() {
270 let a = Bivector::<3>::unit_plane(0, 1);
271 let b = Bivector::<3>::unit_plane(0, 2);
272 let c = a.add(&b);
273 assert!((c.get(0, 1) - 1.0).abs() < 1e-14);
274 assert!((c.get(0, 2) - 1.0).abs() < 1e-14);
275 }
276}