diff --git a/hoomd-bevy/katex.html b/hoomd-bevy/katex.html~trunk
similarity index 100%
rename from hoomd-bevy/katex.html
rename to hoomd-bevy/katex.html~trunk
diff --git a/hoomd-bevy/src/representation/hyperbolic_polygon.wgsl b/hoomd-bevy/src/representation/hyperbolic_polygon.wgsl
index 42c192a4d..c5d9bb694 100644
--- a/hoomd-bevy/src/representation/hyperbolic_polygon.wgsl
+++ b/hoomd-bevy/src/representation/hyperbolic_polygon.wgsl
@@ -19,7 +19,11 @@ const PI: f32 = 3.141592653589793238462643;
@group(2) @binding(4) var image_color_texture: texture_2d;
@group(2) @binding(5) var image_color_sampler: sampler;
-@group(2) @binding(6) var n_sides: f32;
+struct PolygonParams {
+ n_sides: f32,
+};
+@group(2) @binding(6)
+var polygon: PolygonParams;
struct VertexOutput {
// this is `clip position` when the struct is used as a vertex stage output
@@ -75,7 +79,7 @@ fn vertex(vertex: Vertex) -> VertexOutput {
vec4(vertex.position, 1.0)
);
out.position = mesh_functions::mesh2d_position_world_to_clip(out.world_position);
- out.n_sides = n_sides;
+ out.n_sides = polygon.n_sides;
#endif
#ifdef VERTEX_NORMALS
diff --git a/hoomd-derive/katex.html b/hoomd-derive/katex.html~trunk
similarity index 100%
rename from hoomd-derive/katex.html
rename to hoomd-derive/katex.html~trunk
diff --git a/hoomd-geometry/katex.html b/hoomd-geometry/katex.html~trunk
similarity index 100%
rename from hoomd-geometry/katex.html
rename to hoomd-geometry/katex.html~trunk
diff --git a/hoomd-gsd/katex.html b/hoomd-gsd/katex.html~trunk
similarity index 100%
rename from hoomd-gsd/katex.html
rename to hoomd-gsd/katex.html~trunk
diff --git a/hoomd-interaction/katex.html b/hoomd-interaction/katex.html~trunk
similarity index 100%
rename from hoomd-interaction/katex.html
rename to hoomd-interaction/katex.html~trunk
diff --git a/hoomd-linear-algebra/katex.html b/hoomd-linear-algebra/katex.html~trunk
similarity index 100%
rename from hoomd-linear-algebra/katex.html
rename to hoomd-linear-algebra/katex.html~trunk
diff --git a/hoomd-manifold/katex.html b/hoomd-manifold/katex.html~trunk
similarity index 100%
rename from hoomd-manifold/katex.html
rename to hoomd-manifold/katex.html~trunk
diff --git a/hoomd-manifold/src/sphere.rs b/hoomd-manifold/src/sphere.rs
index 9145b7ced..eaa91a6c5 100644
--- a/hoomd-manifold/src/sphere.rs
+++ b/hoomd-manifold/src/sphere.rs
@@ -5,14 +5,14 @@
use approxim::{approx_derive::RelativeEq, assert_relative_eq};
use rand::{
- Rng,
+ Rng, RngExt,
distr::{Distribution, Uniform},
};
use serde::{Deserialize, Serialize};
use std::f64::consts::PI;
use hoomd_utility::valid::PositiveReal;
-use hoomd_vector::{Cartesian, InnerProduct, Metric};
+use hoomd_vector::{Cartesian, InnerProduct, Metric, Quaternion, Rotate, Versor};
/// Point on the surface of a sphere.
///
@@ -53,8 +53,8 @@ impl Spherical {
assert_relative_eq!(rad, 1.0_f64, epsilon = 1e-6);
Spherical { point }
}
-
- /// Implements a stereographic projection from the N-sphere to an N-dimensional plane.
+ /// Implements a stereographic projection from the 2-sphere to an 2-dimensional
+ /// plane by projecting through the $`(0,0,1)`$ axis.
///
/// # Example
/// ```
@@ -100,7 +100,7 @@ impl Spherical<3> {
}
impl Spherical<4> {
- /// Create a 3-sphere from spherical coordinates
+ /// Create a point on a unit-radius 3-sphere from a unit quaternion.
#[inline]
#[must_use]
pub fn from_polar_coordinates(theta: f64, phi_1: f64, phi_2: f64) -> Spherical<4> {
@@ -115,6 +115,80 @@ impl Spherical<4> {
]);
Spherical::from_cartesian_coordinates(point)
}
+ /// Create a point on a unit-radius 3-sphere from a unit quaternion.
+ #[inline]
+ #[must_use]
+ pub fn from_versor(versor: Versor) -> Spherical<4> {
+ let (a, b, c, d) = versor.get_components();
+ Spherical::<4>::from_cartesian_coordinates(Cartesian::from([a, b, c, d]))
+ }
+ /// Create a versor which maps $`(1,0,0,0)`$ to the target `Spherical<4>` point.
+ /// # Example
+ /// ```
+ /// use approxim::assert_relative_eq;
+ /// use hoomd_manifold::Spherical;
+ /// use hoomd_vector::{Cartesian, Quaternion, Versor};
+ /// use std::f64::consts::PI;
+ ///
+ /// # fn main() -> Result<(), Box> {
+ /// let radius = 1.0;
+ /// let x = Spherical::<4>::from_polar_coordinates(
+ /// PI / 4.0,
+ /// PI / 8.0,
+ /// 5.0 * PI / 4.0,
+ /// );
+ /// let x_versor = x.to_versor();
+ /// let pole_versor = Quaternion::from([1.0, 0.0, 0.0, 0.0])
+ /// .to_versor()
+ /// .expect("not a null vector");
+ /// let transformation =
+ /// (*x_versor.get() * *pole_versor.get() * *x_versor.get())
+ /// .to_versor()
+ /// .expect("Hard-coded example is valid");
+ /// let mapped_pole = Spherical::<4>::from_versor(transformation);
+ ///
+ /// assert_relative_eq!(
+ /// mapped_pole.coordinates()[0],
+ /// x.coordinates()[0],
+ /// epsilon = 1e-12
+ /// );
+ /// assert_relative_eq!(
+ /// mapped_pole.coordinates()[1],
+ /// x.coordinates()[1],
+ /// epsilon = 1e-12
+ /// );
+ /// assert_relative_eq!(
+ /// mapped_pole.coordinates()[2],
+ /// x.coordinates()[2],
+ /// epsilon = 1e-12
+ /// );
+ /// assert_relative_eq!(
+ /// mapped_pole.coordinates()[3],
+ /// x.coordinates()[3],
+ /// epsilon = 1e-12
+ /// );
+ /// # Ok(())
+ /// # }
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn to_versor(&self) -> Versor {
+ let phi = self.coordinates()[3].atan2(self.coordinates()[2]);
+ let theta = ((self.coordinates()[3].powi(2) + self.coordinates()[2].powi(2)).sqrt())
+ .atan2(self.coordinates()[1]);
+ let psi = ((self.coordinates()[3].powi(2)
+ + self.coordinates()[2].powi(2)
+ + self.coordinates()[1].powi(2))
+ .sqrt())
+ .atan2(self.coordinates()[0]);
+ let n_hat = Cartesian::from([
+ theta.cos(),
+ (theta.sin()) * (phi.cos()),
+ (theta.sin()) * (phi.sin()),
+ ])
+ .to_unit_unchecked();
+ Versor::from_axis_angle(n_hat.0, psi)
+ }
}
impl Metric for Spherical<3> {
@@ -158,7 +232,8 @@ impl Metric for Spherical<4> {
#[inline]
fn distance(&self, other: &Self) -> f64 {
let arg = Cartesian::dot(&self.point, &other.point);
- arg.acos()
+ let arg_clipped = arg.clamp(-1.0, 1.0);
+ arg_clipped.acos()
}
#[inline]
fn distance_squared(&self, other: &Self) -> f64 {
@@ -206,11 +281,11 @@ impl Metric for Spherical<4> {
/// # }
/// ```
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct SphericalDisk {
+pub struct SphericalDisk {
/// Max distance away from point.
pub disk_radius: PositiveReal,
/// The center of the disk.
- pub point: Spherical<3>,
+ pub point: Spherical,
}
impl Default for Spherical {
@@ -222,7 +297,7 @@ impl Default for Spherical {
}
}
-impl Distribution> for SphericalDisk {
+impl Distribution> for SphericalDisk<3> {
#[inline]
fn sample(&self, rng: &mut R) -> Spherical<3> {
let max_angle = self.disk_radius.get();
@@ -274,6 +349,34 @@ impl Distribution> for SphericalDisk {
}
}
+impl Distribution> for SphericalDisk<4> {
+ /// Translates 3-dimensional cartesian vector named "point" along the
+ /// surface of a sphere by maximum distance of r.
+ #[inline]
+ fn sample(&self, rng: &mut R) -> Spherical<4> {
+ let max_trans = self.disk_radius.get();
+ let point = self.point;
+ // generate random unit cartesian vector
+ let v: Versor = rng.random();
+ let b_hat = v
+ .rotate(&Cartesian::from([1.0, 0.0, 0.0]))
+ .to_unit()
+ .expect("hard coded non-null vector");
+ let eta = Uniform::new(0.0, max_trans).expect("hard coded non-negative");
+ let translation_versor = Versor::from_axis_angle(b_hat.0, eta.sample(rng));
+
+ let position_versor = Quaternion::from(*point.coordinates())
+ .to_versor()
+ .expect("spherical points cannot be null");
+ let transformation =
+ ((*translation_versor.get()) * (*position_versor.get()) * (*translation_versor.get()))
+ .to_versor()
+ .expect("sperical points cannot be null");
+ let sphere_point = Spherical::<4>::from_versor(transformation);
+ Spherical::<4>::from_cartesian_coordinates(*sphere_point.point())
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -329,7 +432,7 @@ mod tests {
}
#[test]
- fn random_sphere() {
+ fn random_two_sphere() {
// Generate ten random points on the Hyperbolic
let mut rng = StdRng::seed_from_u64(42);
let d = 0.1;
@@ -350,4 +453,26 @@ mod tests {
assert!(d > distance);
}
}
+ #[test]
+ fn random_three_sphere() {
+ // Generate ten random points on the Hyperbolic
+ let mut rng = StdRng::seed_from_u64(42);
+ let d = 0.1;
+ let n_pole = Spherical::from_cartesian_coordinates(Cartesian::from([1.0, 0.0, 0.0, 0.0]));
+ for _n in 0..10 {
+ let disk = SphericalDisk {
+ disk_radius: d.try_into().expect("hard-coded positive number"),
+ point: n_pole,
+ };
+ let random_point: Spherical<4> = disk.sample(&mut rng);
+
+ // check that points remain on Sphere
+ let rho = random_point.point.norm_squared();
+ assert_relative_eq!(rho, 1.0, epsilon = 1e-12);
+
+ // check that points are within distance d of north pole
+ let distance = random_point.point().distance(n_pole.point());
+ assert!(d > distance);
+ }
+ }
}
diff --git a/hoomd-mc/katex.html b/hoomd-mc/katex.html~trunk
similarity index 100%
rename from hoomd-mc/katex.html
rename to hoomd-mc/katex.html~trunk
diff --git a/hoomd-mc/src/translate/sphere.rs b/hoomd-mc/src/translate/sphere.rs
index 9c44dfa7e..edf6930bb 100644
--- a/hoomd-mc/src/translate/sphere.rs
+++ b/hoomd-mc/src/translate/sphere.rs
@@ -3,12 +3,13 @@
//! Implement Translation moves on curved surfaces
-use rand::{Rng, distr::Distribution};
+use rand::{Rng, RngExt};
+use rand_distr::StandardNormal;
use crate::{LocalTrial, Translate};
-use hoomd_manifold::{Spherical, SphericalDisk};
+use hoomd_manifold::Spherical;
use hoomd_microstate::property::{Point, Position};
-use hoomd_vector::InnerProduct;
+use hoomd_vector::{Cartesian, InnerProduct};
impl LocalTrial>> for Translate>> {
/// Propose local trial moves for a body on the surface of a sphere
@@ -25,7 +26,7 @@ impl LocalTrial>> for Translate>> {
/// # fn main() -> Result<(), Box> {
/// let mut rng = StdRng::seed_from_u64(14);
/// let initial_point = Point::new(Spherical::from_cartesian_coordinates(
- /// [0.5_f64.sqrt(), 0.5_f64.sqrt(), 0.0].into(),
+ /// [0.5_f64.sqrt(), 0.0, 0.5_f64.sqrt()].into(),
/// ));
/// let d = 0.1;
/// let translate = Translate::with_maximum_distance(d.try_into()?);
@@ -52,16 +53,104 @@ impl LocalTrial>> for Translate>> {
body_properties: Point>,
) -> Point> {
let mut trial = body_properties;
- let disk = SphericalDisk {
- disk_radius: *self.maximum_distance(),
- point: *trial.position_mut(),
- };
- *trial.position_mut() = disk.sample(rng);
- let rescale = 1.0 / trial.position().point().norm();
- *trial.position_mut() =
- Spherical::from_cartesian_coordinates(*trial.position().point() * rescale);
+ let displacement = (self.maximum_distance().get()) * rng.sample::(StandardNormal);
+ let (sn, cs) = (displacement.sin(), displacement.cos());
+ let vec = Cartesian::<3>::from(std::array::from_fn(|_| rng.sample(StandardNormal)));
+ let proj = vec.dot(trial.position.point());
+ let tangent = Cartesian::from([
+ vec[0] - proj * trial.position.coordinates()[0],
+ vec[1] - proj * trial.position.coordinates()[1],
+ vec[2] - proj * trial.position.coordinates()[2],
+ ]);
+ let (unit, _norm) = tangent.to_unit().expect("cannot be null");
+ let new = Cartesian::from([
+ trial.position.coordinates()[0] * cs + unit.get().coordinates[0] * sn,
+ trial.position.coordinates()[1] * cs + unit.get().coordinates[1] * sn,
+ trial.position.coordinates()[2] * cs + unit.get().coordinates[2] * sn,
+ ]);
+ *trial.position_mut() = Spherical::from_cartesian_coordinates(new);
trial
+ // let mut trial = body_properties;
+ // let disk = SphericalDisk {
+ // disk_radius: *self.maximum_distance(),
+ // point: *trial.position_mut(),
+ // };
+ // trial.position_mut() = disk.sample(rng);
+ // let rescale = 1.0 / trial.position().point().norm();
+ // trial.position_mut() =
+ // Spherical::from_cartesian_coordinates(*trial.position().point() * rescale);
+ // trial
}
}
-// TODO: test
+impl LocalTrial>> for Translate>> {
+ /// Propose local trial moves for a body on the surface of a 3-sphere
+ ///
+ /// # Example
+ /// ```
+ /// use approxim::assert_relative_eq;
+ /// use hoomd_manifold::{Spherical, SphericalDisk};
+ /// use hoomd_mc::{LocalTrial, Translate};
+ /// use hoomd_microstate::property::{Point, Position};
+ /// use hoomd_vector::{Cartesian, InnerProduct, Metric, Vector};
+ /// use rand::{Rng, SeedableRng, rngs::StdRng};
+ /// use std::f64::consts::PI;
+ ///
+ /// # fn main() -> Result<(), Box> {
+ /// let mut rng = StdRng::seed_from_u64(14);
+ /// let initial_point = Point::new(Spherical::<4>::from_polar_coordinates(
+ /// PI / 4.0,
+ /// PI / 10.0,
+ /// 5.0 * PI / 4.0,
+ /// ));
+ /// let d = 0.1;
+ /// let translate = Translate::with_maximum_distance(d.try_into()?);
+ ///
+ /// let new_body_properties = translate.propose(&mut rng, initial_point);
+ ///
+ /// // Translation move keeps point on the surface of the sphere
+ /// assert_relative_eq!(
+ /// new_body_properties.position().point().norm(),
+ /// 1.0_f64,
+ /// epsilon = 1e-8
+ /// );
+ ///
+ /// // Translation move does not translate the point more than a distance d away
+ /// assert!(
+ /// d > new_body_properties
+ /// .position()
+ /// .distance(&initial_point.position())
+ /// );
+ /// # Ok(())
+ /// # }
+ /// ```
+ #[inline]
+ fn propose(
+ &self,
+ rng: &mut R,
+ body_properties: Point>,
+ ) -> Point> {
+ let mut trial = body_properties;
+ let displacement = (self.maximum_distance().get()) * rng.sample::(StandardNormal);
+ let (sn, cs) = ((displacement.abs()).sin(), (displacement.abs()).cos());
+ let vec = Cartesian::<4>::from(std::array::from_fn(|_| rng.sample(StandardNormal)));
+ let proj = vec.dot(trial.position.point());
+ let tangent = Cartesian::from([
+ vec[0] - proj * trial.position.coordinates()[0],
+ vec[1] - proj * trial.position.coordinates()[1],
+ vec[2] - proj * trial.position.coordinates()[2],
+ vec[3] - proj * trial.position.coordinates()[3],
+ ]);
+ let (unit, _norm) = tangent.to_unit().expect("cannot be null");
+ let new = Cartesian::from([
+ trial.position.coordinates()[0] * cs + unit.get().coordinates[0] * sn,
+ trial.position.coordinates()[1] * cs + unit.get().coordinates[1] * sn,
+ trial.position.coordinates()[2] * cs + unit.get().coordinates[2] * sn,
+ trial.position.coordinates()[3] * cs + unit.get().coordinates[3] * sn,
+ ]);
+ *trial.position_mut() = Spherical::from_cartesian_coordinates(new);
+ trial
+ }
+}
+
+// TODO: test code
diff --git a/hoomd-microstate/src/append.rs b/hoomd-microstate/src/append.rs
index f4ba09d2a..be50ef7dc 100644
--- a/hoomd-microstate/src/append.rs
+++ b/hoomd-microstate/src/append.rs
@@ -5,12 +5,13 @@
use hoomd_geometry::shape::Hypercuboid;
use hoomd_gsd::hoomd::{AppendError, Dimensions, Frame, HoomdGsdFile};
+use hoomd_manifold::{Hyperbolic, Spherical};
use hoomd_vector::{Angle, Cartesian, Versor};
use crate::{
AppendMicrostate, Microstate,
- boundary::{Closed, Periodic},
- property::{OrientedPoint, Point},
+ boundary::{Closed, Open, Periodic},
+ property::{OrientedHyperbolicPoint, OrientedPoint, Point},
};
impl AppendMicrostate>, X, Closed>> for HoomdGsdFile {
@@ -202,15 +203,81 @@ impl AppendMicrostate, Versor>, X, Periodic<
}
}
+impl AppendMicrostate>, X, Open> for HoomdGsdFile {
+ #[inline]
+ fn append_microstate(
+ &mut self,
+ microstate: &Microstate>, X, Open>,
+ ) -> Result, AppendError> {
+ self.append_frame(microstate.step())?
+ .configuration_box([5.0, 5.0, 5.0, 0.0, 0.0, 0.0])?
+ .configuration_dimensions(Dimensions::Three)?
+ .particles_position(microstate.iter_sites_tag_order().map(|s| {
+ let proj: Vec = s.properties.position.stereographic_projection();
+ [proj[0], proj[1], proj[2]].into()
+ }))
+ }
+}
+
+impl AppendMicrostate, X, C> for HoomdGsdFile {
+ #[inline]
+ fn append_microstate(
+ &mut self,
+ microstate: &Microstate, X, C>,
+ ) -> Result, AppendError> {
+ self.append_frame(microstate.step())?
+ .configuration_box([2.0, 2.0, 2.0, 0.0, 0.0, 0.0])?
+ .configuration_dimensions(Dimensions::Two)?
+ .particles_position(
+ microstate
+ .iter_sites_tag_order()
+ .map(|s| s.properties.position.to_poincare())
+ .map(|p| [p[0], p[1], 0.0].into()),
+ )?
+ .particles_orientation(
+ microstate
+ .iter_sites_tag_order()
+ .map(|s| s.properties.orientation.theta)
+ .map(|a| {
+ Versor::from_axis_angle(
+ [0.0, 0.0, 1.0]
+ .try_into()
+ .expect("hard-coded vector can be normalized"),
+ a,
+ )
+ }),
+ )
+ }
+}
+
+impl AppendMicrostate>, X, C> for HoomdGsdFile {
+ #[inline]
+ fn append_microstate(
+ &mut self,
+ microstate: &Microstate>, X, C>,
+ ) -> Result, AppendError> {
+ self.append_frame(microstate.step())?
+ .configuration_box([2.0, 2.0, 2.0, 0.0, 0.0, 0.0])?
+ .configuration_dimensions(Dimensions::Two)?
+ .particles_position(
+ microstate
+ .iter_sites_tag_order()
+ .map(|s| s.properties.position.to_poincare())
+ .map(|p| [p[0], p[1], 0.0].into()),
+ )
+ }
+}
+
#[cfg(test)]
mod test {
+ use approxim::assert_relative_eq;
use assert2::assert;
use std::f64::consts::PI;
use tempfile::tempdir;
use super::*;
use crate::Body;
- use hoomd_geometry::shape::Rectangle;
+ use hoomd_geometry::shape::{EightEight, Rectangle};
use hoomd_gsd::file_layer::{GsdFile, Mode};
#[test]
@@ -298,6 +365,273 @@ mod test {
Ok(())
}
+ #[test]
+ fn point_spherical_3d() -> anyhow::Result<()> {
+ let microstate = Microstate::builder()
+ .boundary(Open)
+ .bodies([
+ Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
+ 1.0, 0.0, 0.0, 0.0,
+ ]))),
+ Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
+ 0.0, 1.0, 0.0, 0.0,
+ ]))),
+ Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
+ 0.0, 0.0, 1.0, 0.0,
+ ]))),
+ ])
+ .step(1234)
+ .try_build()?;
+
+ let tmp_dir = tempdir()?;
+ let path = tmp_dir.path().join("test.gsd");
+ let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
+ hoomd_gsd_file.append_microstate(µstate)?;
+
+ drop(hoomd_gsd_file);
+
+ let gsd_file = GsdFile::open(path, Mode::Read)?;
+
+ assert!(gsd_file.n_frames() == 1);
+
+ let step = gsd_file.iter_scalars::(0, "configuration/step")?;
+ itertools::assert_equal(step, [1234]);
+
+ let positions: Vec<[f32; 3]> = gsd_file
+ .iter_arrays::(0, "particles/position")?
+ .collect();
+ assert_relative_eq!(positions[0][0], 1.0);
+ assert_relative_eq!(positions[0][1], 0.0);
+ assert_relative_eq!(positions[0][2], 0.0);
+ assert_relative_eq!(positions[1][0], 0.0);
+ assert_relative_eq!(positions[1][1], 1.0);
+ assert_relative_eq!(positions[1][2], 0.0);
+ assert_relative_eq!(positions[2][0], 0.0);
+ assert_relative_eq!(positions[2][1], 0.0);
+ assert_relative_eq!(positions[2][2], 1.0);
+ Ok(())
+ }
+
+ #[test]
+ fn point_hyperbolic_2d_open() -> anyhow::Result<()> {
+ let microstate = Microstate::builder()
+ .boundary(Open)
+ .bodies([
+ Body::point(Hyperbolic::<3>::from_polar_coordinates(1.2, 0.0)),
+ Body::point(Hyperbolic::<3>::from_polar_coordinates(0.6, 1.5)),
+ ])
+ .step(1234)
+ .try_build()?;
+
+ let tmp_dir = tempdir()?;
+ let path = tmp_dir.path().join("test.gsd");
+ let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
+ hoomd_gsd_file.append_microstate(µstate)?;
+
+ drop(hoomd_gsd_file);
+
+ let gsd_file = GsdFile::open(path, Mode::Read)?;
+
+ assert!(gsd_file.n_frames() == 1);
+
+ let step = gsd_file.iter_scalars::(0, "configuration/step")?;
+ itertools::assert_equal(step, [1234]);
+
+ let positions: Vec<[f32; 3]> = gsd_file
+ .iter_arrays::(0, "particles/position")?
+ .collect();
+ assert_relative_eq!(positions[0][0], (f32::sinh(1.2)) / (1.0 + f32::cosh(1.2)));
+ assert_relative_eq!(positions[0][1], 0.0);
+ assert_relative_eq!(
+ positions[1][0],
+ (f32::sinh(0.6) * f32::cos(1.5)) / (1.0 + f32::cosh(0.6))
+ );
+ assert_relative_eq!(
+ positions[1][1],
+ (f32::sinh(0.6) * f32::sin(1.5)) / (1.0 + f32::cosh(0.6))
+ );
+ Ok(())
+ }
+
+ #[test]
+ fn point_hyperbolic_2d_eighteight_periodic() -> anyhow::Result<()> {
+ let boundary = Periodic::new(0.6, EightEight {})?;
+ let microstate = Microstate::builder()
+ .boundary(boundary)
+ .bodies([
+ Body::point(Hyperbolic::<3>::from_polar_coordinates(0.2, 0.3)),
+ Body::point(Hyperbolic::<3>::from_polar_coordinates(0.6, 0.7)),
+ ])
+ .step(1234)
+ .try_build()?;
+
+ let tmp_dir = tempdir()?;
+ let path = tmp_dir.path().join("test.gsd");
+ let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
+ hoomd_gsd_file.append_microstate(µstate)?;
+
+ drop(hoomd_gsd_file);
+
+ let gsd_file = GsdFile::open(path, Mode::Read)?;
+
+ assert!(gsd_file.n_frames() == 1);
+
+ let step = gsd_file.iter_scalars::(0, "configuration/step")?;
+ itertools::assert_equal(step, [1234]);
+
+ let positions: Vec<[f32; 3]> = gsd_file
+ .iter_arrays::(0, "particles/position")?
+ .collect();
+ assert_relative_eq!(
+ positions[0][0],
+ (f32::sinh(0.2) * f32::cos(0.3)) / (1.0 + f32::cosh(0.2))
+ );
+ assert_relative_eq!(
+ positions[0][1],
+ (f32::sinh(0.2) * f32::sin(0.3)) / (1.0 + f32::cosh(0.2))
+ );
+ assert_relative_eq!(
+ positions[1][0],
+ (f32::sinh(0.6) * f32::cos(0.7)) / (1.0 + f32::cosh(0.6))
+ );
+ assert_relative_eq!(
+ positions[1][1],
+ (f32::sinh(0.6) * f32::sin(0.7)) / (1.0 + f32::cosh(0.6))
+ );
+ Ok(())
+ }
+
+ #[test]
+ fn oriented_point_hyperbolic_2d_open() -> anyhow::Result<()> {
+ let microstate = Microstate::builder()
+ .boundary(Open)
+ .bodies([
+ Body {
+ properties: OrientedHyperbolicPoint {
+ position: Hyperbolic::<3>::from_polar_coordinates(0.5, 0.6),
+ orientation: Angle::from(0.3),
+ },
+ sites: vec![OrientedHyperbolicPoint {
+ position: Hyperbolic::<3>::default(),
+ orientation: Angle::default(),
+ }],
+ },
+ Body {
+ properties: OrientedHyperbolicPoint {
+ position: Hyperbolic::<3>::from_polar_coordinates(0.9, 0.3),
+ orientation: Angle::from(1.2),
+ },
+ sites: vec![OrientedHyperbolicPoint {
+ position: Hyperbolic::<3>::default(),
+ orientation: Angle::default(),
+ }],
+ },
+ ])
+ .step(1234)
+ .try_build()?;
+
+ let tmp_dir = tempdir()?;
+ let path = tmp_dir.path().join("test.gsd");
+ let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
+ hoomd_gsd_file.append_microstate(µstate)?;
+
+ drop(hoomd_gsd_file);
+
+ let gsd_file = GsdFile::open(path, Mode::Read)?;
+
+ assert!(gsd_file.n_frames() == 1);
+
+ let step = gsd_file.iter_scalars::(0, "configuration/step")?;
+ itertools::assert_equal(step, [1234]);
+
+ let positions: Vec<[f32; 3]> = gsd_file
+ .iter_arrays::(0, "particles/position")?
+ .collect();
+ assert_relative_eq!(
+ positions[0][0],
+ (f32::sinh(0.5) * f32::cos(0.6)) / (1.0 + f32::cosh(0.5))
+ );
+ assert_relative_eq!(
+ positions[0][1],
+ (f32::sinh(0.5) * f32::sin(0.6)) / (1.0 + f32::cosh(0.5))
+ );
+ assert_relative_eq!(
+ positions[1][0],
+ (f32::sinh(0.9) * f32::cos(0.3)) / (1.0 + f32::cosh(0.9))
+ );
+ assert_relative_eq!(
+ positions[1][1],
+ (f32::sinh(0.9) * f32::sin(0.3)) / (1.0 + f32::cosh(0.9))
+ );
+ Ok(())
+ }
+
+ #[test]
+ fn oriented_point_hyperbolic_2d_periodic_eighteight() -> anyhow::Result<()> {
+ let boundary = Periodic::new(0.6, EightEight {})?;
+ let microstate = Microstate::builder()
+ .boundary(boundary)
+ .bodies([
+ Body {
+ properties: OrientedHyperbolicPoint {
+ position: Hyperbolic::<3>::from_polar_coordinates(0.5, 0.6),
+ orientation: Angle::from(0.3),
+ },
+ sites: vec![OrientedHyperbolicPoint {
+ position: Hyperbolic::<3>::default(),
+ orientation: Angle::default(),
+ }],
+ },
+ Body {
+ properties: OrientedHyperbolicPoint {
+ position: Hyperbolic::<3>::from_polar_coordinates(0.9, 0.3),
+ orientation: Angle::from(1.2),
+ },
+ sites: vec![OrientedHyperbolicPoint {
+ position: Hyperbolic::<3>::default(),
+ orientation: Angle::default(),
+ }],
+ },
+ ])
+ .step(1234)
+ .try_build()?;
+
+ let tmp_dir = tempdir()?;
+ let path = tmp_dir.path().join("test.gsd");
+ let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
+ hoomd_gsd_file.append_microstate(µstate)?;
+
+ drop(hoomd_gsd_file);
+
+ let gsd_file = GsdFile::open(path, Mode::Read)?;
+
+ assert!(gsd_file.n_frames() == 1);
+
+ let step = gsd_file.iter_scalars::(0, "configuration/step")?;
+ itertools::assert_equal(step, [1234]);
+
+ let positions: Vec<[f32; 3]> = gsd_file
+ .iter_arrays::(0, "particles/position")?
+ .collect();
+ assert_relative_eq!(
+ positions[0][0],
+ (f32::sinh(0.5) * f32::cos(0.6)) / (1.0 + f32::cosh(0.5))
+ );
+ assert_relative_eq!(
+ positions[0][1],
+ (f32::sinh(0.5) * f32::sin(0.6)) / (1.0 + f32::cosh(0.5))
+ );
+ assert_relative_eq!(
+ positions[1][0],
+ (f32::sinh(0.9) * f32::cos(0.3)) / (1.0 + f32::cosh(0.9))
+ );
+ assert_relative_eq!(
+ positions[1][1],
+ (f32::sinh(0.9) * f32::sin(0.3)) / (1.0 + f32::cosh(0.9))
+ );
+ Ok(())
+ }
+
#[test]
fn oriented_point_closed_rectangle_2d() -> anyhow::Result<()> {
let boundary = Closed(Rectangle {
diff --git a/hoomd-microstate/src/property/point.rs b/hoomd-microstate/src/property/point.rs
index df19713c4..c289cfac6 100644
--- a/hoomd-microstate/src/property/point.rs
+++ b/hoomd-microstate/src/property/point.rs
@@ -222,7 +222,7 @@ impl Transform>> for Point> {
/// Move `Point>` properties from the local body frame to the
/// system frame.
///
- /// All positions on the 3-sphere are associated with some $`SO(4)`$
+ /// All positions on the 3-sphere are associated with some $`SU(2)`$
/// transformation which translates the origin to that position. The local
/// body frame is the frame in which the body position is the origin. The
/// position of the sites in the system frame is obtained by applying the
@@ -230,6 +230,7 @@ impl Transform>> for Point> {
/// local body frame.
#[inline]
fn transform(&self, site_properties: &Point>) -> Point> {
+ // TODO: implement quaternion representation of SO(4)
let body_point = self.position.coordinates();
let body_phi_1 = (body_point[2].powi(2) + body_point[1].powi(2))
.sqrt()
diff --git a/hoomd-rand/katex.html b/hoomd-rand/katex.html~trunk
similarity index 100%
rename from hoomd-rand/katex.html
rename to hoomd-rand/katex.html~trunk
diff --git a/hoomd-simulation/katex.html b/hoomd-simulation/katex.html~trunk
similarity index 100%
rename from hoomd-simulation/katex.html
rename to hoomd-simulation/katex.html~trunk
diff --git a/hoomd-spatial/katex.html b/hoomd-spatial/katex.html~trunk
similarity index 100%
rename from hoomd-spatial/katex.html
rename to hoomd-spatial/katex.html~trunk
diff --git a/hoomd-utility/katex.html b/hoomd-utility/katex.html~trunk
similarity index 100%
rename from hoomd-utility/katex.html
rename to hoomd-utility/katex.html~trunk
diff --git a/hoomd-vector/katex.html b/hoomd-vector/katex.html~trunk
similarity index 100%
rename from hoomd-vector/katex.html
rename to hoomd-vector/katex.html~trunk
diff --git a/hoomd-vector/src/quaternion.rs b/hoomd-vector/src/quaternion.rs
index ed0ecfd4e..94094a019 100644
--- a/hoomd-vector/src/quaternion.rs
+++ b/hoomd-vector/src/quaternion.rs
@@ -542,6 +542,20 @@ impl Versor {
})
}
+ /// Get the components of a [`Versor`].
+ #[inline]
+ #[must_use]
+ pub fn get_components(&self) -> (f64, f64, f64, f64) {
+ let quaternion = self.get();
+ let vec_components = quaternion.vector;
+ (
+ quaternion.scalar,
+ vec_components[0],
+ vec_components[1],
+ vec_components[2],
+ )
+ }
+
/// Normalize the versor.
///
/// Nominally, all [`Versor`] instances retain a unit norm. Due to limited
diff --git a/prek.toml b/prek.toml
index 47067d757..2a2c4e585 100644
--- a/prek.toml
+++ b/prek.toml
@@ -39,14 +39,14 @@ hooks = [
[[repos]]
repo = "https://github.com/rhysd/actionlint"
-rev = "v1.7.11"
+rev = "v1.7.12"
hooks = [
{ id = "actionlint" }
]
[[repos]]
repo = "https://github.com/mit-d/check-unicode"
-rev = "v0.5.0"
+rev = "v0.6.0"
hooks = [
{ id = "check-unicode",
args = [