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/// Operates on elements on a metric space.
382///
383/// [`Metric`] implements a distance metric between points.
384pub trait Metric {
385    /// Compute the squared distance between two vectors belonging to a metric space.
386    ///
387    /// # Example
388    /// ```
389    /// use hoomd_vector::{Cartesian, Metric};
390    ///
391    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
392    /// let x = Cartesian::from([0.0, 1.0, 1.0]);
393    /// let y = Cartesian::from([1.0, 0.0, 0.0]);
394    /// assert_eq!(3.0, x.distance_squared(&y));
395    /// # Ok(())
396    /// # }
397    /// ```
398    fn distance_squared(&self, other: &Self) -> f64;
399
400    /// Return the number of dimensions in this vector space.
401    ///
402    /// # Example
403    /// ```
404    /// use hoomd_vector::{Cartesian, Metric};
405    ///
406    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
407    /// let vec2 = Cartesian::<2>::default();
408    /// let vec3 = Cartesian::<3>::default();
409    /// assert_eq!(2, vec2.n_dimensions());
410    /// assert_eq!(3, vec3.n_dimensions());
411    /// # Ok(())
412    /// # }
413    /// ```
414    fn n_dimensions(&self) -> usize;
415
416    /// Compute the distance between two vectors belonging to a metric space.
417    /// # Example
418    /// ```
419    /// use hoomd_vector::{Cartesian, Metric};
420    ///
421    /// let x = Cartesian::from([0.0, 0.0]);
422    /// let y = Cartesian::from([3.0, 4.0]);
423    /// assert_eq!(5.0, x.distance(&y));
424    /// ```
425    fn distance(&self, other: &Self) -> f64;
426}
427
428/// Operate on elements of an inner product space.
429///
430/// The [`InnerProduct`] subtrait defines additional methods that can be performed on any vector
431/// in an inner product space, specifically vector norms and inner products.
432pub trait InnerProduct: Vector {
433    /// Compute the vector dot product between two vectors.
434    ///
435    /// ```math
436    /// c = \vec{a} \cdot \vec{b}
437    /// ```
438    ///
439    /// # Example
440    /// ```
441    /// use hoomd_vector::{Cartesian, InnerProduct};
442    ///
443    /// # fn main() {
444    /// let a = Cartesian::from([1.0, 2.0]);
445    /// let b = Cartesian::from([3.0, 4.0]);
446    /// let c = a.dot(&b);
447    /// assert_eq!(c, 11.0);
448    /// # }
449    /// ```
450    #[must_use]
451    fn dot(&self, other: &Self) -> f64;
452
453    /// Compute the squared norm of the vector.
454    ///
455    /// ```math
456    /// \left| \vec{v} \right|^2
457    /// ```
458    ///
459    /// # Example
460    /// ```
461    /// use hoomd_vector::{Cartesian, InnerProduct};
462    ///
463    /// # fn main() {
464    /// let v = Cartesian::from([2.0, 4.0]);
465    /// let norm_squared = v.norm_squared();
466    /// assert_eq!(norm_squared, 20.0);
467    /// # }
468    /// ```
469    #[must_use]
470    #[inline]
471    fn norm_squared(&self) -> f64 {
472        self.dot(self)
473    }
474
475    /// Compute the norm of the vector.
476    ///
477    /// ```math
478    /// \left| \vec{v} \right|
479    /// ```
480    ///
481    /// <div class="warning">
482    ///
483    /// Computing the norm calls `sqrt`. Prefer
484    /// [`norm_squared`](InnerProduct::norm_squared) when possible.
485    ///
486    /// </div>
487    ///
488    /// # Example
489    /// ```
490    /// use hoomd_vector::{Cartesian, InnerProduct};
491    ///
492    /// # fn main() {
493    /// let v = Cartesian::from([3.0, 4.0]);
494    /// let norm = v.norm();
495    /// assert_eq!(norm, 5.0);
496    /// # }
497    /// ```
498    #[must_use]
499    #[inline]
500    fn norm(&self) -> f64 {
501        self.norm_squared().sqrt()
502    }
503
504    /// Create a vector of unit length pointing in the same direction as the given vector.
505    ///
506    /// Returns a tuple containing unit vector along with the original vector's norm:
507    /// ```math
508    /// \frac{\vec{v}}{|\vec{v}|}
509    /// ```
510    ///
511    /// # Example
512    ///
513    /// ```
514    /// use hoomd_vector::{Cartesian, InnerProduct, Unit};
515    ///
516    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
517    /// let a = Cartesian::from([3.0, 4.0]);
518    /// let (unit, norm) = a.to_unit()?;
519    /// assert_eq!(*unit.get(), [3.0 / 5.0, 4.0 / 5.0].into());
520    /// assert_eq!(norm, 5.0);
521    /// # Ok(())
522    /// # }
523    /// ```
524    ///
525    /// # Errors
526    ///
527    /// [`Error::InvalidVectorMagnitude`] when `self` is the 0 vector.
528    #[inline]
529    fn to_unit(self) -> Result<(Unit<Self>, f64), Error> {
530        let norm = self.norm();
531        if norm == 0.0 {
532            Err(Error::InvalidVectorMagnitude)
533        } else {
534            Ok((Unit(self / norm), norm))
535        }
536    }
537
538    /// Create a vector of unit length pointing in the same direction as the given vector.
539    ///
540    /// Returns a tuple containing unit vector along with the original vector's norm:
541    /// ```math
542    /// \frac{\vec{v}}{|\vec{v}|}
543    /// ```
544    ///
545    /// # Example
546    ///
547    /// ```
548    /// use hoomd_vector::{Cartesian, InnerProduct, Unit};
549    ///
550    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
551    /// let a = Cartesian::from([3.0, 4.0]);
552    /// let (unit, norm) = a.to_unit_unchecked();
553    /// assert_eq!(*unit.get(), [3.0 / 5.0, 4.0 / 5.0].into());
554    /// assert_eq!(norm, 5.0);
555    /// # Ok(())
556    /// # }
557    /// ```
558    ///
559    /// # Panics
560    ///
561    /// Divide by 0 when `self` is the 0 vector.
562    #[inline]
563    fn to_unit_unchecked(self) -> (Unit<Self>, f64) {
564        let norm = self.norm();
565        (Unit(self / norm), norm)
566    }
567
568    /// Project one vector onto another.
569    /// ```math
570    /// \left(\frac{\vec{a} \cdot \vec{b}}{|\vec{b}|^2}\right) \vec{b}
571    /// ```
572    /// where `self` is $`\vec{a}`$.
573    /// # Example
574    /// ```
575    /// use hoomd_vector::{Cartesian, InnerProduct, Vector};
576    /// let a = Cartesian::from([1.0, 2.0]);
577    /// let b = Cartesian::from([4.0, 0.0]);
578    /// let c = a.project(&b);
579    /// assert_eq!(c, [1.0, 0.0].into());
580    /// ```
581    #[inline]
582    #[must_use]
583    fn project(&self, b: &Self) -> Self {
584        *b * self.dot(b) / b.norm_squared()
585    }
586
587    /// Create a unit vector in the space.
588    ///
589    /// Each vector space defines its own default unit vector.
590    ///
591    /// # Example
592    /// ```
593    /// use hoomd_vector::{Cartesian, InnerProduct};
594    ///
595    /// let u = Cartesian::<2>::default_unit();
596    /// assert_eq!(*u.get(), [0.0, 1.0].into());
597    /// ```
598    fn default_unit() -> Unit<Self>;
599}
600
601/// A [`Vector`] with magnitude 1.0.
602#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
603pub struct Unit<V>(V);
604
605impl<V> Unit<V> {
606    /// Get the unit vector.
607    #[inline]
608    pub fn get(&self) -> &V {
609        &self.0
610    }
611}
612
613/// A vector space where the cross product is defined.
614pub trait Cross {
615    /// Perform the cross product.
616    /// Compute the cross product (right-handed) of two vectors:
617    ///
618    /// ```math
619    /// \vec{c} = \vec{a} × \vec{b}
620    /// ```
621    ///
622    /// # Example
623    /// ```
624    /// use hoomd_vector::{Cartesian, Cross, Vector};
625    ///
626    /// # fn main() {
627    /// let a = Cartesian::from([1.0, 0.0, 0.0]);
628    /// let b = Cartesian::from([0.0, 1.0, 0.0]);
629    /// let c = a.cross(&b);
630    /// assert_eq!(c, [0.0, 0.0, 1.0].into());
631    /// # }
632    /// ```
633    #[must_use]
634    fn cross(&self, other: &Self) -> Self;
635}
636
637/// Applies the rotation operation to vectors.
638///
639/// The [`Rotate`] trait describes a type that can rotate a given vector. The rotated vector has the
640/// same magnitude, but possibly a different direction.
641///
642/// Types that implement [`Rotate`] may or _may not_ implement [`Rotation`].
643pub trait Rotate<V: Vector> {
644    /// Type of the related rotation matrix
645    type Matrix: Rotate<V>;
646
647    /// Rotate a vector.
648    ///
649    /// ```math
650    /// \vec{b} = R(\vec{a})
651    /// ```
652    ///
653    /// # Example
654    /// ```
655    /// use approxim::assert_relative_eq;
656    /// use hoomd_vector::{Angle, Cartesian, Rotate, Rotation};
657    ///
658    /// let v = Cartesian::from([-1.0, 0.0]);
659    /// let a = Angle::from(std::f64::consts::PI / 2.0);
660    /// let rotated = a.rotate(&v);
661    /// assert_relative_eq!(rotated, [0.0, -1.0].into());
662    /// ```
663    #[must_use]
664    fn rotate(&self, vector: &V) -> V;
665}
666
667/// Describes the transformation from one orthonormal basis to another.
668///
669/// A [`Rotation`] represents a single rotation operation. Rotations change the direction of a vector
670/// while keeping its magnitude constant. To maintain generality, this documentation shows rotations
671/// mathematically as _functions_:
672/// ```math
673/// \vec{b} = R(\vec{a})
674/// ```
675///
676/// All types that implement [`Rotation`] _should_ implement [`Rotate`] for at least one vector type.
677pub trait Rotation: Copy {
678    /// The identity rotation.
679    /// ```math
680    /// \vec{a} = I(\vec{a})
681    /// ```
682    #[must_use]
683    fn identity() -> Self;
684
685    /// Inverse the rotation.
686    /// ```math
687    /// \vec{a} = R^{-1}(R(\vec{a}))
688    /// ```
689    ///
690    /// # Example
691    /// ```
692    /// # use hoomd_vector::{Rotation};
693    /// # fn inverse<R: Rotation>(r: R) {
694    /// let r_inverse = r.inverted();
695    /// # }
696    /// ```
697    #[must_use]
698    fn inverted(self) -> Self;
699
700    /// Combine two rotations.
701    ///
702    /// The resulting rotation `R_ab` will rotate by **first** `R_b` _followed by_ a
703    /// rotation of `R_a`.
704    ///
705    /// ```math
706    /// R_{ab}(\vec{v})= R_a(R_b(\vec{v}))
707    /// ```
708    ///
709    /// # Example
710    /// ```
711    /// # use hoomd_vector::{Rotation};
712    /// # fn inverse<R: Rotation>(R_a: &R, R_b: &R) {
713    /// let R_ab = R_a.combine(R_b);
714    /// # }
715    /// ```
716    #[must_use]
717    fn combine(&self, other: &Self) -> Self;
718}
719
720/// Get the relative position and orientation given pairs of positions and orientations.
721///
722/// # Example
723///
724/// ```
725/// use approxim::assert_relative_eq;
726/// use hoomd_vector::{self, Angle, Cartesian};
727/// use std::f64::consts::PI;
728///
729/// let r_a = Cartesian::from([1.0, -2.0]);
730/// let o_a = Angle::from(PI / 2.0);
731///
732/// let r_b = Cartesian::from([2.0, -1.0]);
733/// let o_b = Angle::from(PI);
734///
735/// let (v_ab, o_ab) =
736///     hoomd_vector::pair_system_to_local(&r_a, &o_a, &r_b, &o_b);
737/// assert_relative_eq!(v_ab[0], 1.0);
738/// assert_relative_eq!(v_ab[1], -1.0);
739/// assert_relative_eq!(o_ab.theta, PI / 2.0);
740/// ```
741#[inline]
742pub fn pair_system_to_local<V, R>(r_a: &V, o_a: &R, r_b: &V, o_b: &R) -> (V, R)
743where
744    V: Vector,
745    R: Rotation + Rotate<V>,
746{
747    let r_ab = *r_b - *r_a;
748    let o_a_inverted = o_a.inverted();
749    let v_ij = o_a_inverted.rotate(&r_ab);
750    let o_ij = o_a_inverted.combine(o_b);
751    (v_ij, o_ij)
752}
753
754#[cfg(test)]
755mod tests {
756    use super::*;
757    use approxim::assert_relative_eq;
758    use assert2::check;
759    use rand::{RngExt, SeedableRng, rngs::StdRng};
760
761    fn compute_add_generic<T>(a: T, b: T) -> T
762    where
763        T: Vector,
764    {
765        a + b
766    }
767
768    #[test]
769    fn add_generic() {
770        let a = Cartesian::from([1.0, 2.0, 3.0]);
771        let b = Cartesian::from([4.0, 5.0, 6.0]);
772        let c = compute_add_generic(a, b);
773        check!(c == [5.0, 7.0, 9.0].into());
774    }
775
776    #[test]
777    fn test_pair_system_to_local_2d() {
778        let mut rng = StdRng::seed_from_u64(1);
779
780        for _ in 0..1_000 {
781            let o_a: Angle = rng.random();
782            let o_b: Angle = rng.random();
783
784            let r_a: Cartesian<2> = rng.random();
785            let r_b: Cartesian<2> = rng.random();
786
787            let c_in_b: Cartesian<2> = rng.random();
788
789            // Test self-consistency by locating c in both a's and b's reference frames.
790            // Check that they are equivalent in the global frame.
791            let (v_ij, o_ij) = pair_system_to_local(&r_a, &o_a, &r_b, &o_b);
792            let c_in_a = v_ij + o_ij.rotate(&c_in_b);
793
794            assert_relative_eq!(
795                r_a + o_a.rotate(&c_in_a),
796                r_b + o_b.rotate(&c_in_b),
797                epsilon = 4.0 * f64::EPSILON
798            );
799
800            let (v_ji, o_ji) = pair_system_to_local(&r_b, &o_b, &r_a, &o_a);
801            assert_relative_eq!(
802                v_ji + o_ji.rotate(&c_in_a),
803                c_in_b,
804                epsilon = 4.0 * f64::EPSILON
805            );
806        }
807    }
808
809    #[test]
810    fn test_pair_system_to_local_3d() {
811        let mut rng = StdRng::seed_from_u64(1);
812
813        for _ in 0..1_000 {
814            let o_a: Versor = rng.random();
815            let o_b: Versor = rng.random();
816
817            let r_a: Cartesian<3> = rng.random();
818            let r_b: Cartesian<3> = rng.random();
819
820            let c_in_b: Cartesian<3> = rng.random();
821
822            // Test self-consistency by locating c in both a's and b's reference frames.
823            // Check that they are equivalent in the global frame.
824            let (v_ij, o_ij) = pair_system_to_local(&r_a, &o_a, &r_b, &o_b);
825            let c_in_a = v_ij + o_ij.rotate(&c_in_b);
826
827            assert_relative_eq!(
828                r_a + o_a.rotate(&c_in_a),
829                r_b + o_b.rotate(&c_in_b),
830                epsilon = 10.0 * f64::EPSILON
831            );
832
833            let (v_ji, o_ji) = pair_system_to_local(&r_b, &o_b, &r_a, &o_a);
834            assert_relative_eq!(
835                v_ji + o_ji.rotate(&c_in_a),
836                c_in_b,
837                epsilon = 10.0 * f64::EPSILON
838            );
839        }
840    }
841}