1use crate::error::{ensure_finite, ensure_finite_output};
12use crate::{Matrix3, PykepError, Result, Vector3};
13
14fn validate_vector(parameter: &'static str, vector: &Vector3) -> Result<()> {
15 for &value in vector {
16 ensure_finite(parameter, value)?;
17 }
18 Ok(())
19}
20
21pub fn dot(left: &Vector3, right: &Vector3) -> Result<f64> {
29 validate_vector("left", left)?;
30 validate_vector("right", right)?;
31 ensure_finite_output(
32 "dot",
33 left[0].mul_add(right[0], left[1].mul_add(right[1], left[2] * right[2])),
34 )
35}
36
37pub fn norm(vector: &Vector3) -> Result<f64> {
45 validate_vector("vector", vector)?;
46 ensure_finite_output("norm", vector[0].hypot(vector[1]).hypot(vector[2]))
47}
48
49pub fn normalize(vector: &Vector3) -> Result<Vector3> {
56 let magnitude = norm(vector)?;
57 if magnitude == 0.0 {
58 return Err(PykepError::SingularGeometry {
59 operation: "normalize",
60 });
61 }
62 let result = [
63 vector[0] / magnitude,
64 vector[1] / magnitude,
65 vector[2] / magnitude,
66 ];
67 validate_vector("normalized_vector", &result)?;
68 Ok(result)
69}
70
71pub fn cross(left: &Vector3, right: &Vector3) -> Result<Vector3> {
79 validate_vector("left", left)?;
80 validate_vector("right", right)?;
81 let result = [
82 left[1].mul_add(right[2], -(left[2] * right[1])),
83 left[2].mul_add(right[0], -(left[0] * right[2])),
84 left[0].mul_add(right[1], -(left[1] * right[0])),
85 ];
86 for &value in &result {
87 ensure_finite_output("cross", value)?;
88 }
89 Ok(result)
90}
91
92pub fn skew(vector: &Vector3) -> Result<Matrix3> {
100 validate_vector("vector", vector)?;
101 Ok([
102 [0.0, -vector[2], vector[1]],
103 [vector[2], 0.0, -vector[0]],
104 [-vector[1], vector[0], 0.0],
105 ])
106}
107
108fn paired_batch<R>(
109 left: &[Vector3],
110 right: &[Vector3],
111 workers: usize,
112 operation: fn(&Vector3, &Vector3) -> Result<R>,
113) -> Result<Vec<R>>
114where
115 R: Send,
116{
117 if left.len() != right.len() {
118 return Err(PykepError::DimensionMismatch {
119 expected: left.len(),
120 actual: right.len(),
121 });
122 }
123 let inputs: Vec<_> = left.iter().zip(right).collect();
124 crate::batch::try_map(&inputs, workers, |(left, right)| operation(left, right))
125}
126
127pub fn dot_batch(left: &[Vector3], right: &[Vector3], workers: usize) -> Result<Vec<f64>> {
134 paired_batch(left, right, workers, dot)
135}
136
137pub fn norm_batch(vectors: &[Vector3], workers: usize) -> Result<Vec<f64>> {
144 crate::batch::try_map(vectors, workers, norm)
145}
146
147pub fn normalize_batch(vectors: &[Vector3], workers: usize) -> Result<Vec<Vector3>> {
154 crate::batch::try_map(vectors, workers, normalize)
155}
156
157pub fn cross_batch(left: &[Vector3], right: &[Vector3], workers: usize) -> Result<Vec<Vector3>> {
164 paired_batch(left, right, workers, cross)
165}
166
167pub fn skew_batch(vectors: &[Vector3], workers: usize) -> Result<Vec<Matrix3>> {
174 crate::batch::try_map(vectors, workers, skew)
175}
176
177#[must_use]
179pub fn identity<const N: usize>() -> [[f64; N]; N] {
180 let mut result = [[0.0; N]; N];
181 for (index, row) in result.iter_mut().enumerate() {
182 row[index] = 1.0;
183 }
184 result
185}
186
187pub fn matrix_multiply<const M: usize, const K: usize, const N: usize>(
193 left: &[[f64; K]; M],
194 right: &[[f64; N]; K],
195) -> Result<[[f64; N]; M]> {
196 let mut result = [[0.0; N]; M];
197 for row in 0..M {
198 for column in 0..N {
199 let mut value = 0.0;
200 for inner in 0..K {
201 ensure_finite("left", left[row][inner])?;
202 ensure_finite("right", right[inner][column])?;
203 value = left[row][inner].mul_add(right[inner][column], value);
204 }
205 result[row][column] = ensure_finite_output("matrix_multiply", value)?;
206 }
207 }
208 Ok(result)
209}
210
211pub fn matrix_vector_multiply<const M: usize, const N: usize>(
217 matrix: &[[f64; N]; M],
218 vector: &[f64; N],
219) -> Result<[f64; M]> {
220 let mut result = [0.0; M];
221 for row in 0..M {
222 let mut value = 0.0;
223 for column in 0..N {
224 ensure_finite("matrix", matrix[row][column])?;
225 ensure_finite("vector", vector[column])?;
226 value = matrix[row][column].mul_add(vector[column], value);
227 }
228 result[row] = ensure_finite_output("matrix_vector_multiply", value)?;
229 }
230 Ok(result)
231}
232
233#[must_use]
235pub fn transpose<const M: usize, const N: usize>(matrix: &[[f64; N]; M]) -> [[f64; M]; N] {
236 let mut result = [[0.0; M]; N];
237 for (row, values) in matrix.iter().enumerate() {
238 for (column, &value) in values.iter().enumerate() {
239 result[column][row] = value;
240 }
241 }
242 result
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn vector_operations_match_right_handed_geometry() {
251 let x = [1.0, 0.0, 0.0];
252 let y = [0.0, 1.0, 0.0];
253 assert_eq!(dot(&x, &y), Ok(0.0));
254 assert_eq!(norm(&[3.0, 4.0, 12.0]), Ok(13.0));
255 assert_eq!(cross(&x, &y), Ok([0.0, 0.0, 1.0]));
256 assert_eq!(
257 matrix_vector_multiply(&skew(&x).unwrap(), &y),
258 cross(&x, &y)
259 );
260 }
261
262 #[test]
263 fn normalization_is_stable_for_large_finite_vectors() {
264 let value = normalize(&[1e308, 1e308, 0.0]).unwrap();
265 let expected = core::f64::consts::FRAC_1_SQRT_2;
266 assert!((value[0] - expected).abs() < 2e-16);
267 assert!((value[1] - expected).abs() < 2e-16);
268 }
269
270 #[test]
271 fn zero_and_non_finite_vectors_are_rejected() {
272 assert!(matches!(
273 normalize(&[0.0; 3]),
274 Err(PykepError::SingularGeometry { .. })
275 ));
276 assert!(dot(&[f64::NAN, 0.0, 0.0], &[1.0; 3]).is_err());
277 }
278
279 #[test]
280 fn fixed_matrix_operations_preserve_layout() {
281 let left = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
282 let right = [[7.0, 8.0], [9.0, 10.0], [11.0, 12.0]];
283 assert_eq!(
284 matrix_multiply(&left, &right),
285 Ok([[58.0, 64.0], [139.0, 154.0]])
286 );
287 assert_eq!(transpose(&left), [[1.0, 4.0], [2.0, 5.0], [3.0, 6.0]]);
288 assert_eq!(
289 identity::<3>(),
290 [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
291 );
292 }
293
294 #[test]
295 fn ordered_vector_batches_match_scalar_operations() {
296 let left = [[1.0, 2.0, 3.0], [-2.0, 0.5, 4.0]];
297 let right = [[4.0, -1.0, 0.5], [1.0, 3.0, -2.0]];
298 assert_eq!(
299 dot_batch(&left, &right, 2).unwrap(),
300 left.iter()
301 .zip(&right)
302 .map(|(left, right)| dot(left, right).unwrap())
303 .collect::<Vec<_>>()
304 );
305 assert_eq!(
306 norm_batch(&left, 2).unwrap(),
307 left.iter()
308 .map(|value| norm(value).unwrap())
309 .collect::<Vec<_>>()
310 );
311 assert_eq!(
312 normalize_batch(&left, 2).unwrap(),
313 left.iter()
314 .map(|value| normalize(value).unwrap())
315 .collect::<Vec<_>>()
316 );
317 assert_eq!(
318 cross_batch(&left, &right, 2).unwrap(),
319 left.iter()
320 .zip(&right)
321 .map(|(left, right)| cross(left, right).unwrap())
322 .collect::<Vec<_>>()
323 );
324 assert_eq!(
325 skew_batch(&left, 2).unwrap(),
326 left.iter()
327 .map(|value| skew(value).unwrap())
328 .collect::<Vec<_>>()
329 );
330 assert!(dot_batch(&left, &right[..1], 2).is_err());
331 }
332}