hoomd_vector/lib.rs
1// Copyright (c) 2024-2026 The Regents of the University of Michigan.
2// Part of hoomd-rs, released under the BSD 3-Clause License.
3
4#![doc(
5 html_favicon_url = "https://raw.githubusercontent.com/glotzerlab/hoomd-rs/7352214172a490cc716492e9724ff42720a0018a/doc/theme/favicon.svg"
6)]
7#![doc(
8 html_logo_url = "https://raw.githubusercontent.com/glotzerlab/hoomd-rs/7352214172a490cc716492e9724ff42720a0018a/doc/theme/favicon.svg"
9)]
10
11//! Vector and quaternion math.
12//!
13//! `hoomd_vector` implements vector math types and operations used in scientific
14//! computations, specifically those used in the HOOMD molecular simulation software
15//! suite. Its API is firmly rooted in mathematical principles. Users in
16//! other fields may find `hoomd_vector` useful outside the context of `HOOMD`.
17//!
18//! ## Vectors
19//!
20//! The [`Vector`] trait describes any type that is a member of a metric vector
21//! space. Write code with a [`Vector`] trait bound when you can express the
22//! computation with vector arithmetic and a distance metric. Your generic code can
23//! then be invoked on vector types with any dimension or representation (e.g.
24//! spherical coordinates).
25//!
26//! ```
27//! use hoomd_vector::Vector;
28//!
29//! fn some_function<V: Vector>(a: &V, b: &V, c: &V) -> f64 {
30//! (*a + *b).distance(&c)
31//! }
32//! ```
33//!
34//! The [`InnerProduct`] subtrait of [`Vector`] describes any type that is a member of
35//! an inner product space. [`InnerProduct`] implements vector norms and dot products.
36//!
37//! ```
38//! use hoomd_vector::InnerProduct;
39//!
40//! fn some_other_function<V: InnerProduct>(a: &V, b: &V) -> f64 {
41//! a.dot(b) / (a.norm_squared())
42//! }
43//! ```
44//!
45//! Require additional trait bounds to perform more specific operations, such as [`Cross`]:
46//! ```
47//! use hoomd_vector::{Cross, InnerProduct};
48//!
49//! fn triple<V: InnerProduct + Cross>(a: &V, b: &V, c: &V) -> f64 {
50//! a.dot(&b.cross(c))
51//! }
52//! ```
53//!
54//! Use the provided [`Cartesian`] type to concretely represent N-dimensional
55//! vectors, or when your algorithm requires Cartesian coordinates:
56//!
57//! ```
58//! use hoomd_vector::{Cartesian, InnerProduct};
59//!
60//! let a = Cartesian::from([1.0, 2.0]);
61//! let b = Cartesian::from([-2.0, 1.0]);
62//!
63//! let product = a.dot(&b);
64//! assert_eq!(product, 0.0);
65//!
66//! let x = a[0];
67//! let y = a[1];
68//! ```
69//!
70//! ## Quaternions
71//!
72//! Quaternions are generalized complex numbers and a convenient way to describe the motion
73//! of rotating bodies. The [`Quaternion`] type describes a single quaternion and implements
74//! the associated algebra.
75//!
76//! ```
77//! use hoomd_vector::Quaternion;
78//!
79//! let a = Quaternion::from([1.0, -2.0, 6.0, -4.0]);
80//! let b = Quaternion::from([-2.0, 6.0, 4.0, 1.0]);
81//!
82//! let norm = a.norm();
83//! assert_eq!(norm, 57.0_f64.sqrt());
84//!
85//! let sum = a + b;
86//! assert_eq!(sum, [-1.0, 4.0, 10.0, -3.0].into());
87//!
88//! let product = a * b;
89//! assert_eq!(product, [-10.0, 32.0, -30.0, -35.0].into());
90//! ```
91//!
92//! A **unit quaternion** (called a [`Versor`] in mathematics) can represent a 3D rotation.
93//!
94//! ```
95//! use hoomd_vector::{Quaternion, Versor};
96//!
97//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
98//! let q = Quaternion::from([3.0, 0.0, 0.0, 4.0]);
99//! let v = q.to_versor()?;
100//! assert_eq!(*v.get(), [3.0 / 5.0, 0.0, 0.0, 4.0 / 5.0].into());
101//! # Ok(())
102//! # }
103//! ```
104//!
105//! ## Rotations
106//!
107//! A [`Rotation`] describes a transformation from one orthonormal basis to
108//! another. A type that implements [`Rotation`] has an
109//! [`identity`](Rotation::identity). Instances of that type have an
110//! [`inverse`](Rotation::inverted) and can be [`combined`](Rotation::combine)
111//! with other rotations.
112//!
113//! Through the [`Rotate<V>`] trait, a [`Rotation`] can rotate a vector.
114//!
115//! As with [`Vector`], you can implement methods that operate on generic types:
116//! ```
117//! use hoomd_vector::{Rotate, Vector};
118//!
119//! fn rotate_and_translate<R: Rotate<V>, V: Vector>(r: &R, a: &V, b: &V) -> V {
120//! r.rotate(a) + *b
121//! }
122//! ```
123//!
124//! [`Angle`] implements rotations on [`Cartesian<2>`] vectors.
125//! ```
126//! use approxim::assert_relative_eq;
127//! use hoomd_vector::{Angle, Cartesian, Rotate, Rotation};
128//! use std::f64::consts::PI;
129//!
130//! let v = Cartesian::from([-1.0, 0.0]);
131//! let a = Angle::from(PI / 2.0);
132//! let rotated = a.rotate(&v);
133//! assert_relative_eq!(rotated, [0.0, -1.0].into());
134//! ```
135//!
136//! [`Versor`] implements rotations on [`Cartesian<3>`] vectors.
137//! ```
138//! use approxim::assert_relative_eq;
139//! use hoomd_vector::{Cartesian, Rotate, Rotation, Versor};
140//! use std::f64::consts::PI;
141//!
142//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
143//! let a = Cartesian::from([-1.0, 0.0, 0.0]);
144//! let v = Versor::from_axis_angle([0.0, 0.0, 1.0].try_into()?, PI / 2.0);
145//! let b = v.rotate(&a);
146//! assert_relative_eq!(b, [0.0, -1.0, 0.0].into());
147//! # Ok(())
148//! # }
149//! ```
150//!
151//! Convert to a [`RotationMatrix`] when you need to rotate many vectors by the same
152//! rotation. [`RotationMatrix::rotate`] is typically several times faster than
153//! [`Versor::rotate`].
154//!
155//! # Random distributions
156//!
157//! `hoomd_vector` interoperates with [`rand`] to generate random vectors and rotations.
158//!
159//! The [`StandardUniform`](rand::distr::StandardUniform) distribution randomly samples
160//! rotations uniformly from the set of all vectors or rotations.
161//!
162//! - Vectors are uniformly sampled from the `[-1,1]` hypercube
163//! - Angles are uniformly sampled from the half-open interval `[0, 2π)`
164//! - Versors are uniformly sampled from the surface of the `3-Sphere`, which doubly
165//! covers `SO(3)`, the manifold of rotations in three dimensions.
166//!
167//! ```
168//! use hoomd_vector::{Angle, Cartesian, Versor};
169//! use rand::{RngExt, SeedableRng, rngs::StdRng};
170//!
171//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
172//! let mut rng = StdRng::seed_from_u64(1);
173//! let vector: Cartesian<3> = rng.random();
174//! let angle: Angle = rng.random();
175//! let versor: Versor = rng.random();
176//! # Ok(())
177//! # }
178//! ```
179//!
180//! The [`Ball`](crate::distribution::Ball) distribution samples vectors from
181//! the interior of an `n-Ball`, the set of all points whose distance from the origin is
182//! in `[0, 1)`.
183//!
184//! ```
185//! use hoomd_vector::{Cartesian, distribution::Ball};
186//! use rand::{Rng, SeedableRng, distr::Distribution, rngs::StdRng};
187//!
188//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
189//! let mut rng = StdRng::seed_from_u64(1);
190//! let ball = Ball {
191//! radius: 3.0.try_into()?,
192//! };
193//! let v: Cartesian<3> = ball.sample(&mut rng);
194//! # Ok(())
195//! # }
196//! ```
197//!
198//! # Complete documentation
199//!
200//! `hoomd-vector` is is a part of *hoomd-rs*. Read the [complete documentation]
201//! for more information.
202//!
203//! [complete documentation]: https://hoomd-rs.readthedocs.io
204
205mod angle;
206mod cartesian;
207pub mod distribution;
208mod quaternion;
209
210pub use angle::Angle;
211pub use cartesian::{Cartesian, RotationMatrix};
212pub use quaternion::{Quaternion, Versor};
213
214use serde::{Deserialize, Serialize};
215use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
216use thiserror::Error;
217
218/// Enumerate possible sources of error in fallible vector math operations.
219#[non_exhaustive]
220#[derive(Error, PartialEq, Debug)]
221pub enum Error {
222 /// Attempted converting a value to a vector with a dimension not equal to the value's length.
223 #[error("source length does not match the target dimensions")]
224 InvalidVectorLength,
225
226 /// Attempted to normalize a vector with an invalid magnitude.
227 #[error("cannot normalize the 0 vector")]
228 InvalidVectorMagnitude,
229
230 /// Attempted to normalize a quaternion with an invalid magnitude.
231 #[error("cannot normalize the 0 quaternion")]
232 InvalidQuaternionMagnitude,
233}
234
235/// Operate on elements of a metric vector space.
236///
237/// Specifically, [`Vector`] defines methods that can be performed on any vector in a metric vector
238/// space. Note that this is not an inner product space by default, and calculations requiring an
239/// inner product should use the [`InnerProduct`] subtrait.
240///
241/// ## Vector Operations
242///
243/// The following examples demonstrate vector operations applied to the following
244/// vectors:
245///
246/// ```
247/// use hoomd_vector::Cartesian;
248///
249/// # fn main() {
250/// let mut a = Cartesian::from([1.0, 2.0]);
251/// let mut b = Cartesian::from([4.0, 8.0]);
252/// # }
253/// ```
254///
255/// Vector addition:
256///
257/// ```
258/// # use hoomd_vector::Cartesian;
259/// # fn main() {
260/// # let mut a = Cartesian::from([1.0, 2.0]);
261/// # let mut b = Cartesian::from([4.0, 8.0]);
262/// let c = a + b;
263/// assert_eq!(c, [5.0, 10.0].into())
264/// # }
265/// ```
266///
267/// ```
268/// # use hoomd_vector::Cartesian;
269/// # fn main() {
270/// # let mut a = Cartesian::from([1.0, 2.0]);
271/// # let mut b = Cartesian::from([4.0, 8.0]);
272/// a += b;
273/// assert_eq!(a, [5.0, 10.0].into())
274/// # }
275/// ```
276///
277/// Vector subtraction:
278///
279/// ```
280/// # use hoomd_vector::Cartesian;
281/// # fn main() {
282/// # let mut a = Cartesian::from([1.0, 2.0]);
283/// # let mut b = Cartesian::from([4.0, 8.0]);
284/// let c = b - a;
285/// assert_eq!(c, [3.0, 6.0].into())
286/// # }
287/// ```
288///
289/// ```
290/// # use hoomd_vector::Cartesian;
291/// # fn main() {
292/// # let mut a = Cartesian::from([1.0, 2.0]);
293/// # let mut b = Cartesian::from([4.0, 8.0]);
294/// b -= a;
295/// assert_eq!(b, [3.0, 6.0].into())
296/// # }
297/// ```
298///
299/// Multiplication of a vector by a scalar:
300///
301/// ```
302/// # use hoomd_vector::Cartesian;
303/// # fn main() {
304/// # let mut a = Cartesian::from([1.0, 2.0]);
305/// # let mut b = Cartesian::from([4.0, 8.0]);
306/// let c = a * 2.0;
307/// assert_eq!(c, [2.0, 4.0].into())
308/// # }
309/// ```
310///
311/// ```
312/// # use hoomd_vector::Cartesian;
313/// # fn main() {
314/// # let mut a = Cartesian::from([1.0, 2.0]);
315/// # let mut b = Cartesian::from([4.0, 8.0]);
316/// a *= 2.0;
317/// assert_eq!(a, [2.0, 4.0].into())
318/// # }
319/// ```
320///
321/// Division of a vector by a scalar:
322///
323/// ```
324/// # use hoomd_vector::Cartesian;
325/// # fn main() {
326/// # let mut a = Cartesian::from([1.0, 2.0]);
327/// # let mut b = Cartesian::from([4.0, 8.0]);
328/// let c = b / 2.0;
329/// assert_eq!(c, [2.0, 4.0].into())
330/// # }
331/// ```
332///
333/// ```
334/// # use hoomd_vector::Cartesian;
335/// # fn main() {
336/// # let mut a = Cartesian::from([1.0, 2.0]);
337/// # let mut b = Cartesian::from([4.0, 8.0]);
338/// b /= 2.0;
339/// assert_eq!(b, [2.0, 4.0].into())
340/// # }
341/// ```
342///
343/// Negation:
344///
345/// ```
346/// # use hoomd_vector::Cartesian;
347/// # fn main() {
348/// # let mut a = Cartesian::from([1.0, 2.0]);
349/// # let mut b = Cartesian::from([4.0, 8.0]);
350/// let mut c = -a;
351/// assert_eq!(c, [-1.0, -2.0].into());
352/// # }
353/// ```
354///
355/// Equality:
356///
357/// ```
358/// # use hoomd_vector::Cartesian;
359/// # fn main() {
360/// # let mut a = Cartesian::from([1.0, 2.0]);
361/// # let mut b = Cartesian::from([4.0, 8.0]);
362/// assert!(a != b)
363/// # }
364/// ```
365pub trait Vector:
366 Add<Self, Output = Self>
367 + AddAssign
368 + Copy
369 + Div<f64, Output = Self>
370 + DivAssign<f64>
371 + PartialEq
372 + Metric
373 + Mul<f64, Output = Self>
374 + MulAssign<f64>
375 + Sub<Self, Output = Self>
376 + SubAssign
377 + Neg<Output = Self>
378{
379}
380
381/// The vector wedge product.
382///
383/// The result of a vector wedge product is a *[bivector]*. Mathematically,
384/// bivectors are different from vectors. In practice, *hoomd-rs* uses
385/// follows standard physics practices, where torques are bivectors:
386/// * When the inputs are 2D vectors ([`Cartesian<2>`]), the result is a scalar.
387/// * When the inputs are 3D vectors ([`Cartesian<3>`]), the result is another
388/// [`Cartesian<3>`].
389///
390/// [bivector]: https://en.wikipedia.org/wiki/Bivector
391pub trait Wedge {
392 /// Type of the bivector result.
393 type Bivector;
394
395 /// Compute the wedge product of two vectors.
396 ///
397 /// ```math
398 /// \textbf{A}=\textbf{a}\wedge{\textbf{b}}
399 /// ```
400 ///
401 /// # Examples
402 ///
403 /// 2D:
404 /// ```
405 /// use hoomd_vector::{Cartesian, Wedge};
406 ///
407 /// let a = Cartesian::from([2.0, 1.0]);
408 /// let b = Cartesian::from([3.0, 1.0]);
409 ///
410 /// assert_eq!(a.wedge(&b), -1.0);
411 /// ```
412 ///
413 /// 3D:
414 /// ```
415 /// use hoomd_vector::{Cartesian, Wedge};
416 ///
417 /// let a = Cartesian::from([1.0, 0.0, 0.0]);
418 /// let b = Cartesian::from([0.0, 1.0, 0.0]);
419 /// assert_eq!(a.wedge(&b), [0.0, 0.0, 1.0].into());
420 /// ```
421 fn wedge(&self, other: &Self) -> Self::Bivector;
422}
423
424/// The vector outer product.
425pub trait Outer {
426 /// Result type.
427 type Tensor;
428
429 /// Compute the outer product of two vectors.
430 ///
431 /// ```math
432 /// a \otimes b = \begin{bmatrix} a_0
433 /// \\ a_1
434 /// \\ \vdots
435 /// \\ a_{n}
436 /// \end{bmatrix}
437 /// \begin{bmatrix}
438 /// b_0 & b_1 & \dots & b_{n}
439 /// \end{bmatrix}
440 /// =
441 /// \begin{bmatrix}
442 /// a_0 b_0 & a_0 b_1 & \dots & a_0 b_n \\
443 /// a_1 b_0 & a_1 b_1 & \dots & a_1 b_n \\
444 /// \vdots & \vdots & \ddots & \vdots \\
445 /// a_n b_0 & a_n b_1 & \dots & a_n b_n
446 /// \end{bmatrix}
447 /// ```
448 ///
449 /// # Example
450 /// ```
451 /// use hoomd_linear_algebra::matrix::Matrix;
452 /// use hoomd_vector::{Cartesian, Outer};
453 ///
454 /// let a = Cartesian::from([2.0, 1.0]);
455 /// let b = Cartesian::from([4.0, 3.0]);
456 ///
457 /// let m = Matrix {
458 /// rows: [[8.0, 6.0], [4.0, 3.0]],
459 /// };
460 /// assert_eq!(a.outer(&b), m);
461 /// ```
462 fn outer(&self, other: &Self) -> Self::Tensor;
463}
464
465/// Operates on elements of a metric space.
466///
467/// [`Metric`] implements a distance metric between points.
468pub trait Metric {
469 /// Compute the squared distance between two vectors belonging to a metric space.
470 ///
471 /// # Example
472 /// ```
473 /// use hoomd_vector::{Cartesian, Metric};
474 ///
475 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
476 /// let x = Cartesian::from([0.0, 1.0, 1.0]);
477 /// let y = Cartesian::from([1.0, 0.0, 0.0]);
478 /// assert_eq!(3.0, x.distance_squared(&y));
479 /// # Ok(())
480 /// # }
481 /// ```
482 fn distance_squared(&self, other: &Self) -> f64;
483
484 /// Return the number of dimensions in this vector space.
485 ///
486 /// # Example
487 /// ```
488 /// use hoomd_vector::{Cartesian, Metric};
489 ///
490 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
491 /// assert_eq!(2, Cartesian::<2>::n_dimensions());
492 /// assert_eq!(3, Cartesian::<3>::n_dimensions());
493 /// # Ok(())
494 /// # }
495 /// ```
496 fn n_dimensions() -> usize;
497
498 /// Compute the distance between two vectors belonging to a metric space.
499 /// # Example
500 /// ```
501 /// use hoomd_vector::{Cartesian, Metric};
502 ///
503 /// let x = Cartesian::from([0.0, 0.0]);
504 /// let y = Cartesian::from([3.0, 4.0]);
505 /// assert_eq!(5.0, x.distance(&y));
506 /// ```
507 fn distance(&self, other: &Self) -> f64;
508}
509
510/// Operate on elements of an inner product space.
511///
512/// The [`InnerProduct`] subtrait defines additional methods that can be performed on any vector
513/// in an inner product space, specifically vector norms and inner products.
514pub trait InnerProduct: Vector {
515 /// Compute the vector dot product between two vectors.
516 ///
517 /// ```math
518 /// c = \vec{a} \cdot \vec{b}
519 /// ```
520 ///
521 /// # Example
522 /// ```
523 /// use hoomd_vector::{Cartesian, InnerProduct};
524 ///
525 /// # fn main() {
526 /// let a = Cartesian::from([1.0, 2.0]);
527 /// let b = Cartesian::from([3.0, 4.0]);
528 /// let c = a.dot(&b);
529 /// assert_eq!(c, 11.0);
530 /// # }
531 /// ```
532 #[must_use]
533 fn dot(&self, other: &Self) -> f64;
534
535 /// Compute the squared norm of the vector.
536 ///
537 /// ```math
538 /// \left| \vec{v} \right|^2
539 /// ```
540 ///
541 /// # Example
542 /// ```
543 /// use hoomd_vector::{Cartesian, InnerProduct};
544 ///
545 /// # fn main() {
546 /// let v = Cartesian::from([2.0, 4.0]);
547 /// let norm_squared = v.norm_squared();
548 /// assert_eq!(norm_squared, 20.0);
549 /// # }
550 /// ```
551 #[must_use]
552 #[inline]
553 fn norm_squared(&self) -> f64 {
554 self.dot(self)
555 }
556
557 /// Compute the norm of the vector.
558 ///
559 /// ```math
560 /// \left| \vec{v} \right|
561 /// ```
562 ///
563 /// <div class="warning">
564 ///
565 /// Computing the norm calls `sqrt`. Prefer
566 /// [`norm_squared`](InnerProduct::norm_squared) when possible.
567 ///
568 /// </div>
569 ///
570 /// # Example
571 /// ```
572 /// use hoomd_vector::{Cartesian, InnerProduct};
573 ///
574 /// # fn main() {
575 /// let v = Cartesian::from([3.0, 4.0]);
576 /// let norm = v.norm();
577 /// assert_eq!(norm, 5.0);
578 /// # }
579 /// ```
580 #[must_use]
581 #[inline]
582 fn norm(&self) -> f64 {
583 self.norm_squared().sqrt()
584 }
585
586 /// Create a vector of unit length pointing in the same direction as the given vector.
587 ///
588 /// Returns a tuple containing unit vector along with the original vector's norm:
589 /// ```math
590 /// \frac{\vec{v}}{|\vec{v}|}
591 /// ```
592 ///
593 /// # Example
594 ///
595 /// ```
596 /// use hoomd_vector::{Cartesian, InnerProduct, Unit};
597 ///
598 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
599 /// let a = Cartesian::from([3.0, 4.0]);
600 /// let (unit, norm) = a.to_unit()?;
601 /// assert_eq!(*unit.get(), [3.0 / 5.0, 4.0 / 5.0].into());
602 /// assert_eq!(norm, 5.0);
603 /// # Ok(())
604 /// # }
605 /// ```
606 ///
607 /// # Errors
608 ///
609 /// [`Error::InvalidVectorMagnitude`] when `self` is the 0 vector.
610 #[inline]
611 fn to_unit(self) -> Result<(Unit<Self>, f64), Error> {
612 let norm = self.norm();
613 if norm == 0.0 {
614 Err(Error::InvalidVectorMagnitude)
615 } else {
616 Ok((Unit(self / norm), norm))
617 }
618 }
619
620 /// Create a vector of unit length pointing in the same direction as the given vector.
621 ///
622 /// Returns a tuple containing unit vector along with the original vector's norm:
623 /// ```math
624 /// \frac{\vec{v}}{|\vec{v}|}
625 /// ```
626 ///
627 /// # Example
628 ///
629 /// ```
630 /// use hoomd_vector::{Cartesian, InnerProduct, Unit};
631 ///
632 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
633 /// let a = Cartesian::from([3.0, 4.0]);
634 /// let (unit, norm) = a.to_unit_unchecked();
635 /// assert_eq!(*unit.get(), [3.0 / 5.0, 4.0 / 5.0].into());
636 /// assert_eq!(norm, 5.0);
637 /// # Ok(())
638 /// # }
639 /// ```
640 ///
641 /// # Panics
642 ///
643 /// Divide by 0 when `self` is the 0 vector.
644 #[inline]
645 fn to_unit_unchecked(self) -> (Unit<Self>, f64) {
646 let norm = self.norm();
647 (Unit(self / norm), norm)
648 }
649
650 /// Project one vector onto another.
651 /// ```math
652 /// \left(\frac{\vec{a} \cdot \vec{b}}{|\vec{b}|^2}\right) \vec{b}
653 /// ```
654 /// where `self` is $`\vec{a}`$.
655 /// # Example
656 /// ```
657 /// use hoomd_vector::{Cartesian, InnerProduct, Vector};
658 /// let a = Cartesian::from([1.0, 2.0]);
659 /// let b = Cartesian::from([4.0, 0.0]);
660 /// let c = a.project(&b);
661 /// assert_eq!(c, [1.0, 0.0].into());
662 /// ```
663 #[inline]
664 #[must_use]
665 fn project(&self, b: &Self) -> Self {
666 *b * self.dot(b) / b.norm_squared()
667 }
668
669 /// Create a unit vector in the space.
670 ///
671 /// Each vector space defines its own default unit vector.
672 ///
673 /// # Example
674 /// ```
675 /// use hoomd_vector::{Cartesian, InnerProduct};
676 ///
677 /// let u = Cartesian::<2>::default_unit();
678 /// assert_eq!(*u.get(), [0.0, 1.0].into());
679 /// ```
680 fn default_unit() -> Unit<Self>;
681}
682
683/// A [`Vector`] with magnitude 1.0.
684#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
685pub struct Unit<V>(V);
686
687impl<V> Unit<V> {
688 /// Get the unit vector.
689 #[inline]
690 pub fn get(&self) -> &V {
691 &self.0
692 }
693}
694
695/// The vector cross product.
696///
697/// The result of a vector cross product is in the same vector space as the
698/// operands.
699pub trait Cross {
700 /// Compute the cross product (right-handed) of two vectors:
701 ///
702 /// ```math
703 /// \vec{c} = \vec{a} × \vec{b}
704 /// ```
705 ///
706 /// # Example
707 /// ```
708 /// use hoomd_vector::{Cartesian, Cross, Vector};
709 ///
710 /// # fn main() {
711 /// let a = Cartesian::from([1.0, 0.0, 0.0]);
712 /// let b = Cartesian::from([0.0, 1.0, 0.0]);
713 /// let c = a.cross(&b);
714 /// assert_eq!(c, [0.0, 0.0, 1.0].into());
715 /// # }
716 /// ```
717 #[must_use]
718 fn cross(&self, other: &Self) -> Self;
719}
720
721/// Applies the rotation operation to vectors.
722///
723/// The [`Rotate`] trait describes a type that can rotate a given vector. The rotated vector has the
724/// same magnitude, but possibly a different direction.
725///
726/// Types that implement [`Rotate`] may or _may not_ implement [`Rotation`].
727pub trait Rotate<V: Vector> {
728 /// Type of the related rotation matrix
729 type Matrix: Rotate<V>;
730
731 /// Rotate a vector.
732 ///
733 /// ```math
734 /// \vec{b} = R(\vec{a})
735 /// ```
736 ///
737 /// # Example
738 /// ```
739 /// use approxim::assert_relative_eq;
740 /// use hoomd_vector::{Angle, Cartesian, Rotate, Rotation};
741 ///
742 /// let v = Cartesian::from([-1.0, 0.0]);
743 /// let a = Angle::from(std::f64::consts::PI / 2.0);
744 /// let rotated = a.rotate(&v);
745 /// assert_relative_eq!(rotated, [0.0, -1.0].into());
746 /// ```
747 #[must_use]
748 fn rotate(&self, vector: &V) -> V;
749}
750
751/// Describes the transformation from one orthonormal basis to another.
752///
753/// A [`Rotation`] represents a single rotation operation. Rotations change the direction of a vector
754/// while keeping its magnitude constant. To maintain generality, this documentation shows rotations
755/// mathematically as _functions_:
756/// ```math
757/// \vec{b} = R(\vec{a})
758/// ```
759///
760/// All types that implement [`Rotation`] _should_ implement [`Rotate`] for at least one vector type.
761pub trait Rotation: Copy {
762 /// The identity rotation.
763 /// ```math
764 /// \vec{a} = I(\vec{a})
765 /// ```
766 #[must_use]
767 fn identity() -> Self;
768
769 /// Inverse the rotation.
770 /// ```math
771 /// \vec{a} = R^{-1}(R(\vec{a}))
772 /// ```
773 ///
774 /// # Example
775 /// ```
776 /// # use hoomd_vector::{Rotation};
777 /// # fn inverse<R: Rotation>(r: R) {
778 /// let r_inverse = r.inverted();
779 /// # }
780 /// ```
781 #[must_use]
782 fn inverted(self) -> Self;
783
784 /// Combine two rotations.
785 ///
786 /// The resulting rotation `R_ab` will rotate by **first** `R_b` _followed by_ a
787 /// rotation of `R_a`.
788 ///
789 /// ```math
790 /// R_{ab}(\vec{v})= R_a(R_b(\vec{v}))
791 /// ```
792 ///
793 /// # Example
794 /// ```
795 /// # use hoomd_vector::{Rotation};
796 /// # fn inverse<R: Rotation>(R_a: &R, R_b: &R) {
797 /// let R_ab = R_a.combine(R_b);
798 /// # }
799 /// ```
800 #[must_use]
801 fn combine(&self, other: &Self) -> Self;
802}
803
804/// Get the relative position and orientation given pairs of positions and orientations.
805///
806/// # Example
807///
808/// ```
809/// use approxim::assert_relative_eq;
810/// use hoomd_vector::{self, Angle, Cartesian};
811/// use std::f64::consts::PI;
812///
813/// let r_a = Cartesian::from([1.0, -2.0]);
814/// let o_a = Angle::from(PI / 2.0);
815///
816/// let r_b = Cartesian::from([2.0, -1.0]);
817/// let o_b = Angle::from(PI);
818///
819/// let (v_ab, o_ab) =
820/// hoomd_vector::pair_system_to_local(&r_a, &o_a, &r_b, &o_b);
821/// assert_relative_eq!(v_ab[0], 1.0);
822/// assert_relative_eq!(v_ab[1], -1.0);
823/// assert_relative_eq!(o_ab.theta, PI / 2.0);
824/// ```
825#[inline]
826pub fn pair_system_to_local<V, R>(r_a: &V, o_a: &R, r_b: &V, o_b: &R) -> (V, R)
827where
828 V: Vector,
829 R: Rotation + Rotate<V>,
830{
831 let r_ab = *r_b - *r_a;
832 let o_a_inverted = o_a.inverted();
833 let v_ij = o_a_inverted.rotate(&r_ab);
834 let o_ij = o_a_inverted.combine(o_b);
835 (v_ij, o_ij)
836}
837
838#[cfg(test)]
839mod tests {
840 use super::*;
841 use approxim::assert_relative_eq;
842 use assert2::check;
843 use rand::{RngExt, SeedableRng, rngs::StdRng};
844
845 fn compute_add_generic<T>(a: T, b: T) -> T
846 where
847 T: Vector,
848 {
849 a + b
850 }
851
852 #[test]
853 fn add_generic() {
854 let a = Cartesian::from([1.0, 2.0, 3.0]);
855 let b = Cartesian::from([4.0, 5.0, 6.0]);
856 let c = compute_add_generic(a, b);
857 check!(c == [5.0, 7.0, 9.0].into());
858 }
859
860 #[test]
861 fn test_pair_system_to_local_2d() {
862 let mut rng = StdRng::seed_from_u64(1);
863
864 for _ in 0..1_000 {
865 let o_a: Angle = rng.random();
866 let o_b: Angle = rng.random();
867
868 let r_a: Cartesian<2> = rng.random();
869 let r_b: Cartesian<2> = rng.random();
870
871 let c_in_b: Cartesian<2> = rng.random();
872
873 // Test self-consistency by locating c in both a's and b's reference frames.
874 // Check that they are equivalent in the global frame.
875 let (v_ij, o_ij) = pair_system_to_local(&r_a, &o_a, &r_b, &o_b);
876 let c_in_a = v_ij + o_ij.rotate(&c_in_b);
877
878 assert_relative_eq!(
879 r_a + o_a.rotate(&c_in_a),
880 r_b + o_b.rotate(&c_in_b),
881 epsilon = 4.0 * f64::EPSILON
882 );
883
884 let (v_ji, o_ji) = pair_system_to_local(&r_b, &o_b, &r_a, &o_a);
885 assert_relative_eq!(
886 v_ji + o_ji.rotate(&c_in_a),
887 c_in_b,
888 epsilon = 4.0 * f64::EPSILON
889 );
890 }
891 }
892
893 #[test]
894 fn test_pair_system_to_local_3d() {
895 let mut rng = StdRng::seed_from_u64(1);
896
897 for _ in 0..1_000 {
898 let o_a: Versor = rng.random();
899 let o_b: Versor = rng.random();
900
901 let r_a: Cartesian<3> = rng.random();
902 let r_b: Cartesian<3> = rng.random();
903
904 let c_in_b: Cartesian<3> = rng.random();
905
906 // Test self-consistency by locating c in both a's and b's reference frames.
907 // Check that they are equivalent in the global frame.
908 let (v_ij, o_ij) = pair_system_to_local(&r_a, &o_a, &r_b, &o_b);
909 let c_in_a = v_ij + o_ij.rotate(&c_in_b);
910
911 assert_relative_eq!(
912 r_a + o_a.rotate(&c_in_a),
913 r_b + o_b.rotate(&c_in_b),
914 epsilon = 10.0 * f64::EPSILON
915 );
916
917 let (v_ji, o_ji) = pair_system_to_local(&r_b, &o_b, &r_a, &o_a);
918 assert_relative_eq!(
919 v_ji + o_ji.rotate(&c_in_a),
920 c_in_b,
921 epsilon = 10.0 * f64::EPSILON
922 );
923 }
924 }
925}