qualia_core_db/solvers/calculus/
exterior.rs1use super::analysis::{AnalysisError, LinearMap, Vector};
4
5pub const EXTERIOR3_COMPONENTS: usize = 8;
6
7#[repr(C)]
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct Exterior3 {
10 pub coefficients: [f64; EXTERIOR3_COMPONENTS],
12}
13
14impl Exterior3 {
15 pub const fn zero() -> Self {
16 Self {
17 coefficients: [0.0; EXTERIOR3_COMPONENTS],
18 }
19 }
20
21 pub const fn scalar(value: f64) -> Self {
22 let mut coefficients = [0.0; EXTERIOR3_COMPONENTS];
23 coefficients[0] = value;
24 Self { coefficients }
25 }
26
27 pub const fn basis(mask: usize, value: f64) -> Self {
28 let mut coefficients = [0.0; EXTERIOR3_COMPONENTS];
29 if mask < EXTERIOR3_COMPONENTS {
30 coefficients[mask] = value;
31 }
32 Self { coefficients }
33 }
34
35 pub fn validate(&self) -> Result<(), AnalysisError> {
36 if self.coefficients.iter().all(|value| value.is_finite()) {
37 Ok(())
38 } else {
39 Err(AnalysisError::NonFinite)
40 }
41 }
42
43 pub fn grade(self, grade: u32) -> Self {
44 let mut result = Self::zero();
45 for mask in 0..EXTERIOR3_COMPONENTS {
46 if mask.count_ones() == grade {
47 result.coefficients[mask] = self.coefficients[mask];
48 }
49 }
50 result
51 }
52
53 pub fn add(self, other: Self) -> Self {
54 let mut result = Self::zero();
55 for mask in 0..EXTERIOR3_COMPONENTS {
56 result.coefficients[mask] = self.coefficients[mask] + other.coefficients[mask];
57 }
58 result
59 }
60
61 pub fn scale(self, scalar: f64) -> Result<Self, AnalysisError> {
62 if !scalar.is_finite() {
63 return Err(AnalysisError::NonFinite);
64 }
65 let mut result = self;
66 for value in &mut result.coefficients {
67 *value *= scalar;
68 }
69 Ok(result)
70 }
71
72 pub fn wedge(self, other: Self) -> Result<Self, AnalysisError> {
73 self.validate()?;
74 other.validate()?;
75 let mut result = Self::zero();
76 for left in 0..EXTERIOR3_COMPONENTS {
77 for right in 0..EXTERIOR3_COMPONENTS {
78 if left & right != 0 {
79 continue;
80 }
81 let (mask, sign) = wedge_basis(left, right);
82 result.coefficients[mask] +=
83 sign * self.coefficients[left] * other.coefficients[right];
84 }
85 }
86 Ok(result)
87 }
88
89 pub fn interior(self, vector: Vector<3>) -> Result<Self, AnalysisError> {
90 self.validate()?;
91 vector.validate()?;
92 let mut result = Self::zero();
93 for mask in 0..EXTERIOR3_COMPONENTS {
94 let coefficient = self.coefficients[mask];
95 for axis in 0..3 {
96 let bit = 1usize << axis;
97 if mask & bit == 0 {
98 continue;
99 }
100 let lower = (mask & (bit - 1)).count_ones();
101 let sign = if lower & 1 == 0 { 1.0 } else { -1.0 };
102 result.coefficients[mask ^ bit] += sign * vector.data[axis] * coefficient;
103 }
104 }
105 Ok(result)
106 }
107
108 pub fn hodge_star(self) -> Result<Self, AnalysisError> {
110 self.validate()?;
111 let mut result = Self::zero();
112 for mask in 0..EXTERIOR3_COMPONENTS {
113 let complement = 0b111 ^ mask;
114 let (_, sign) = wedge_basis(mask, complement);
115 result.coefficients[complement] += sign * self.coefficients[mask];
116 }
117 Ok(result)
118 }
119}
120
121fn wedge_basis(left: usize, right: usize) -> (usize, f64) {
122 let mut inversions = 0_u32;
123 for left_axis in 0..3 {
124 if left & (1 << left_axis) == 0 {
125 continue;
126 }
127 inversions += (right & ((1 << left_axis) - 1)).count_ones();
128 }
129 (left | right, if inversions & 1 == 0 { 1.0 } else { -1.0 })
130}
131
132pub fn permutation_parity(permutation: &[usize]) -> Result<i8, AnalysisError> {
133 for (index, value) in permutation.iter().enumerate() {
134 if *value >= permutation.len()
135 || permutation[index + 1..].iter().any(|other| other == value)
136 {
137 return Err(AnalysisError::InvalidDomain);
138 }
139 }
140 let mut inversions = 0usize;
141 for left in 0..permutation.len() {
142 for right in left + 1..permutation.len() {
143 inversions += usize::from(permutation[left] > permutation[right]);
144 }
145 }
146 Ok(if inversions & 1 == 0 { 1 } else { -1 })
147}
148
149pub fn determinant_from_wedge(columns: [Vector<3>; 3]) -> Result<f64, AnalysisError> {
150 let mut forms = [Exterior3::zero(); 3];
151 for column in 0..3 {
152 for row in 0..3 {
153 forms[column].coefficients[1 << row] = columns[column].data[row];
154 }
155 }
156 Ok(forms[0].wedge(forms[1])?.wedge(forms[2])?.coefficients[0b111])
157}
158
159pub fn pullback_one_form(
160 derivative: &LinearMap<3, 3>,
161 one_form: Vector<3>,
162) -> Result<Vector<3>, AnalysisError> {
163 derivative.transpose().apply(one_form)
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn wedge_is_graded_commutative_and_associative() {
172 let e1 = Exterior3::basis(0b001, 1.0);
173 let e2 = Exterior3::basis(0b010, 1.0);
174 let e3 = Exterior3::basis(0b100, 1.0);
175 assert_eq!(e1.wedge(e1).unwrap(), Exterior3::zero());
176 assert_eq!(
177 e1.wedge(e2).unwrap(),
178 e2.wedge(e1).unwrap().scale(-1.0).unwrap()
179 );
180 assert_eq!(
181 e1.wedge(e2).unwrap().wedge(e3).unwrap(),
182 e1.wedge(e2.wedge(e3).unwrap()).unwrap()
183 );
184 }
185
186 #[test]
187 fn determinant_and_hodge_identities_hold() {
188 let columns = [
189 Vector::new([2.0, 0.0, 0.0]),
190 Vector::new([1.0, 3.0, 0.0]),
191 Vector::new([0.0, 2.0, 4.0]),
192 ];
193 assert_eq!(determinant_from_wedge(columns).unwrap(), 24.0);
194
195 for mask in 0..8 {
196 let blade = Exterior3::basis(mask, 1.0);
197 let twice = blade.hodge_star().unwrap().hodge_star().unwrap();
198 let grade = mask.count_ones();
199 let sign = if (grade * (3 - grade)) & 1 == 0 {
200 1.0
201 } else {
202 -1.0
203 };
204 assert_eq!(twice, blade.scale(sign).unwrap());
205 }
206 }
207
208 #[test]
209 fn permutation_and_pullback_are_correct() {
210 assert_eq!(permutation_parity(&[2, 0, 1]), Ok(1));
211 assert_eq!(permutation_parity(&[1, 0, 2]), Ok(-1));
212 assert_eq!(
213 permutation_parity(&[0, 0, 1]),
214 Err(AnalysisError::InvalidDomain)
215 );
216
217 let derivative = LinearMap::new([[2.0, 0.0, 0.0], [1.0, 3.0, 0.0], [0.0, 0.0, 4.0]]);
218 let pulled = pullback_one_form(&derivative, Vector::new([1.0, 2.0, 3.0])).unwrap();
219 assert_eq!(pulled, Vector::new([4.0, 6.0, 12.0]));
220 }
221}