Skip to main content

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}