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}