hoomd_geometry/shape/
eighteight.rs1use serde::{Deserialize, Serialize};
7
8use crate::IsPointInside;
9use hoomd_manifold::{Hyperbolic, Minkowski};
10use hoomd_vector::Metric;
11use std::f64::consts::PI;
12
13#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
20pub struct EightEight {}
21
22impl IsPointInside<Hyperbolic<3>> for EightEight {
23 #[inline]
40 fn is_point_inside(&self, point: &Hyperbolic<3>) -> bool {
41 EightEight::distance_to_boundary(point) >= 0.0
42 }
43}
44
45impl EightEight {
46 #[inline]
80 #[must_use]
81 pub fn distance_to_boundary(point: &Hyperbolic<3>) -> f64 {
82 let theta =
83 (point.coordinates()[1].atan2(point.coordinates()[0])).rem_euclid(PI / 4.0) - PI / 8.0;
84 let boost = (point.coordinates()[2]).acosh();
85 let (b_sinh, b_cosh) = (boost.sinh(), boost.cosh());
86 let xi = Self::CUSP_TO_EDGE;
87 let (xi_sinh, xi_cosh) = (xi.sinh(), xi.cosh());
88 let edge_as_diameter: Hyperbolic<3> =
90 Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
91 xi_cosh * b_sinh * (theta.cos()) - xi_sinh * b_cosh,
92 b_sinh * (theta.sin()),
93 -xi_sinh * b_sinh * (theta.cos()) + xi_cosh * b_cosh,
94 ]));
95 let flipped = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
96 -edge_as_diameter.coordinates()[0],
97 edge_as_diameter.coordinates()[1],
98 edge_as_diameter.coordinates()[2],
99 ]));
100 let sign = -(edge_as_diameter.coordinates()[0]).signum();
101 sign * (edge_as_diameter.distance(&flipped)) / 2.0
102 }
103 #[inline]
105 #[must_use]
106 pub fn boundary_points(number_of_points: usize) -> Vec<(f64, f64)> {
107 let mut coords = Vec::<(f64, f64)>::new();
108 for n in 0..number_of_points {
109 let angle = (n as f64) * 2.0 * PI / (number_of_points as f64);
110 let tile_size = EightEight::EIGHTEIGHT;
111 let eta =
112 (tile_size.tanh() / (angle.cos() - angle.sin() * (1.0 - (2.0_f64).sqrt()))).atanh();
113 let x = eta.sinh() / (1.0 + eta.cosh());
114 for k in 0..8 {
115 coords.push((
116 x * (angle + f64::from(k) * PI / 4.0).cos(),
117 x * (angle + f64::from(k) * PI / 4.0).sin(),
118 ));
119 }
120 }
121 coords
122 }
123 #[inline]
125 #[must_use]
126 pub fn gamma(eta: f64, theta: f64, point: &[f64; 3]) -> [f64; 3] {
127 let (eta_sinh_squared, two_eta_sinh, theta_sin, theta_cos) = (
128 (eta.sinh()).powi(2),
129 (2.0 * eta).sinh(),
130 theta.sin(),
131 theta.cos(),
132 );
133 [
134 (2.0 * (eta_sinh_squared) * ((theta_cos).powi(2)) + 1.0) * point[0]
135 + ((2.0 * theta).sin()) * (eta_sinh_squared) * point[1]
136 + (two_eta_sinh) * (theta_cos) * point[2],
137 ((2.0 * theta).sin()) * (eta_sinh_squared) * point[0]
138 + (2.0 * (eta_sinh_squared) * ((theta_sin).powi(2)) + 1.0) * point[1]
139 + (two_eta_sinh) * (theta_sin) * point[2],
140 (two_eta_sinh) * (theta_cos) * point[0]
141 + (two_eta_sinh) * (theta_sin) * point[1]
142 + ((2.0 * eta).cosh()) * point[2],
143 ]
144 }
145 #[inline]
148 #[must_use]
149 pub fn reorient(theta: f64, point: &[f64; 3]) -> f64 {
150 let (q_u, q_v) = (point[0] / (1.0 + point[2]), point[1] / (1.0 + point[2]));
151 let alpha = (1.0 + (PI / 4.0).cos()).sqrt();
152 let beta = (theta.cos()) * ((2.0 * ((PI / 4.0).cos())).sqrt());
153 let gamma = (theta.sin()) * ((2.0 * ((PI / 4.0).cos())).sqrt());
154 let p_x = alpha + beta * q_u + gamma * q_v;
156 let p_y = beta * q_v - gamma * q_u;
157 -2.0 * (p_y.atan2(p_x))
158 }
159 pub const EIGHTEIGHT: f64 = 2.448_452_447_678_076;
161 pub const CUSP_TO_EDGE: f64 = 1.528_570_919_480_998;
163 pub const EDGE_LENGTH: f64 = 3.057_141_838_961_997;
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use approxim::assert_relative_eq;
171 use hoomd_manifold::{Hyperbolic, HyperbolicDisk, Minkowski};
172 use rand::{SeedableRng, distr::Distribution, rngs::StdRng};
173 use std::ops::Not;
174
175 #[test]
176 fn boundary_distance() {
177 let e = Hyperbolic::<3>::from_polar_coordinates(1.0, 0.1);
179 let e_edge_distance = EightEight::distance_to_boundary(&e);
180 let e_edge_distance_numeric = 0.631_401_734_734_821;
181 assert_relative_eq!(e_edge_distance, e_edge_distance_numeric, epsilon = 1e-12);
182
183 let f = Hyperbolic::<3>::from_polar_coordinates(0.6, 0.2 + PI / 4.0);
184 let f_edge_distance = EightEight::distance_to_boundary(&f);
185 let f_edge_distance_numeric = 0.947_879_122_461_848;
186 assert_relative_eq!(f_edge_distance, f_edge_distance_numeric, epsilon = 1e-12);
187 }
188
189 #[test]
190 fn inside_is_inside() {
191 let eight_eight = EightEight {};
192 let r = 1.528_570_919_480_998;
193 let mut rng = StdRng::seed_from_u64(239);
194 let disk = HyperbolicDisk {
195 disk_radius: r.try_into().expect("hard-coded positive number"),
196 point: Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([0.0, 0.0, 1.0])),
197 };
198 let random_point: Hyperbolic<3> = disk.sample(&mut rng);
199 assert!(eight_eight.is_point_inside(&random_point));
200
201 let point_1 = Hyperbolic::<3>::from_polar_coordinates(1.52, PI / 8.0);
202 assert!(eight_eight.is_point_inside(&point_1));
203
204 let point_2 = Hyperbolic::<3>::from_polar_coordinates(2.44, PI / 4.0);
205 assert!(eight_eight.is_point_inside(&point_2));
206 }
207
208 #[test]
209 fn outside_is_outside() {
210 let eight_eight = EightEight {};
211 let point_1 = Hyperbolic::<3>::from_polar_coordinates(1.54, PI / 8.0);
212 assert!((eight_eight.is_point_inside(&point_1)).not());
213
214 let point_2 = Hyperbolic::<3>::from_polar_coordinates(2.45, PI / 4.0);
215 assert!((eight_eight.is_point_inside(&point_2)).not());
216 }
217}