1use arrayvec::ArrayVec;
11use std::f64::consts::PI;
12
13use crate::{
14 boundary::{
15 Error, GenerateGhosts, MAX_GHOSTS, MaximumAllowableInteractionRange, Periodic, Wrap,
16 },
17 property::{Orientation, OrientedHyperbolicPoint, Point, Position},
18};
19use hoomd_geometry::shape::TwelveTwelve;
20use hoomd_manifold::{Hyperbolic, Minkowski};
21use hoomd_vector::Angle;
22
23impl MaximumAllowableInteractionRange for TwelveTwelve {
24 #[inline]
28 fn maximum_allowable_interaction_range(&self) -> f64 {
29 TwelveTwelve::CUSP_TO_EDGE
30 }
31}
32
33impl Wrap<Point<Hyperbolic<3>>> for Periodic<TwelveTwelve> {
34 #[inline]
40 #[expect(clippy::too_many_lines, reason = "complicated function")]
41 fn wrap(&self, properties: Point<Hyperbolic<3>>) -> Result<Point<Hyperbolic<3>>, Error> {
42 let mut properties = properties;
43 let r = properties.position_mut();
44 let r_coords = r.coordinates();
45 let angle = r_coords[1].atan2(r_coords[0]).rem_euclid(2.0 * PI);
46
47 let d = TwelveTwelve::distance_to_boundary(r);
49 if d >= 0.0 {
50 Ok(properties)
51 } else if d > -self.maximum_interaction_range {
52 let nearest_vertex_number =
54 (((angle + (PI / 12.0)).rem_euclid(PI * 2.0)) / (PI / 6.0)).floor();
55
56 let (vertex_boost, vertex_angle) = (
58 TwelveTwelve::TWELVETWELVE,
59 (nearest_vertex_number * PI / 6.0).rem_euclid(PI * 2.0),
60 );
61 let (v_cosh, v_sinh, v_cos, v_sin) = (
62 (-vertex_boost).cosh(),
63 (-vertex_boost).sinh(),
64 (-vertex_angle).cos(),
65 (-vertex_angle).sin(),
66 );
67 let transformed_point = Minkowski::from([
68 r_coords[0] * v_cosh * v_cos - r_coords[1] * v_cosh * v_sin + r_coords[2] * v_sinh,
69 r_coords[0] * v_sin + r_coords[1] * v_cos,
70 r_coords[0] * v_sinh * v_cos - r_coords[1] * v_sinh * v_sin + r_coords[2] * v_cosh,
71 ]);
72 let trans_angle =
74 transformed_point.coordinates[1].atan2(transformed_point.coordinates[0]);
75 let sector = (((trans_angle + (PI / 12.0)).rem_euclid(2.0 * PI)) / (PI / 6.0)).floor();
77
78 let eta = TwelveTwelve::CUSP_TO_EDGE;
80 let wrapped: [f64; 3];
81 match sector {
82 7.0 => {
83 let theta_1 = (nearest_vertex_number + 5.0).rem_euclid(12.0);
84 wrapped = TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
85 }
86 8.0 => {
87 let theta_1 = (nearest_vertex_number + 5.0).rem_euclid(12.0);
88 let wrapped_1 =
89 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
90 let theta_2 = (theta_1 + 5.0).rem_euclid(12.0);
91 wrapped = TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
92 }
93 9.0 => {
94 let theta_1 = (nearest_vertex_number + 5.0).rem_euclid(12.0);
95 let wrapped_1 =
96 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
97 let theta_2 = (theta_1 + 5.0).rem_euclid(12.0);
98 let wrapped_2 =
99 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
100 let theta_3 = (theta_2 + 5.0).rem_euclid(12.0);
101 wrapped = TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
102 }
103 10.0 => {
104 let theta_1 = (nearest_vertex_number + 5.0).rem_euclid(12.0);
105 let wrapped_1 =
106 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
107 let theta_2 = (theta_1 + 5.0).rem_euclid(12.0);
108 let wrapped_2 =
109 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
110 let theta_3 = (theta_2 + 5.0).rem_euclid(12.0);
111 let wrapped_3 =
112 TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
113 let theta_4 = (theta_3 + 5.0).rem_euclid(12.0);
114 wrapped = TwelveTwelve::gamma(eta, theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3);
115 }
116 11.0 => {
117 let theta_1 = (nearest_vertex_number + 5.0).rem_euclid(12.0);
118 let wrapped_1 =
119 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
120 let theta_2 = (theta_1 + 5.0).rem_euclid(12.0);
121 let wrapped_2 =
122 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
123 let theta_3 = (theta_2 + 5.0).rem_euclid(12.0);
124 let wrapped_3 =
125 TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
126 let theta_4 = (theta_3 + 5.0).rem_euclid(12.0);
127 let wrapped_4 =
128 TwelveTwelve::gamma(eta, theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3);
129 let theta_5 = (theta_4 + 5.0).rem_euclid(12.0);
130 wrapped = TwelveTwelve::gamma(eta, theta_5 * PI / 6.0 + PI / 12.0, &wrapped_4);
131 }
132 5.0 => {
133 let theta_1 = (nearest_vertex_number + 6.0).rem_euclid(12.0);
134 wrapped = TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
135 }
136 4.0 => {
137 let theta_1 = (nearest_vertex_number + 6.0).rem_euclid(12.0);
138 let wrapped_1 =
139 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
140 let theta_2 = (theta_1 - 5.0).rem_euclid(12.0);
141 wrapped = TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
142 }
143 3.0 => {
144 let theta_1 = (nearest_vertex_number + 6.0).rem_euclid(12.0);
145 let wrapped_1 =
146 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
147 let theta_2 = (theta_1 - 5.0).rem_euclid(12.0);
148 let wrapped_2 =
149 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
150 let theta_3 = (theta_2 - 5.0).rem_euclid(12.0);
151 wrapped = TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
152 }
153 2.0 => {
154 let theta_1 = (nearest_vertex_number + 6.0).rem_euclid(12.0);
155 let wrapped_1 =
156 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
157 let theta_2 = (theta_1 - 5.0).rem_euclid(12.0);
158 let wrapped_2 =
159 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
160 let theta_3 = (theta_2 - 5.0).rem_euclid(12.0);
161 let wrapped_3 =
162 TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
163 let theta_4 = (theta_3 - 5.0).rem_euclid(12.0);
164 wrapped = TwelveTwelve::gamma(eta, theta_4 * PI / 6.0 - PI / 12.0, &wrapped_3);
165 }
166 1.0 => {
167 let theta_1 = (nearest_vertex_number + 6.0).rem_euclid(12.0);
168 let wrapped_1 =
169 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
170 let theta_2 = (theta_1 - 5.0).rem_euclid(12.0);
171 let wrapped_2 =
172 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
173 let theta_3 = (theta_2 - 5.0).rem_euclid(12.0);
174 let wrapped_3 =
175 TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
176 let theta_4 = (theta_3 - 5.0).rem_euclid(12.0);
177 let wrapped_4 =
178 TwelveTwelve::gamma(eta, theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3);
179 let theta_5 = (theta_4 - 5.0).rem_euclid(12.0);
180 wrapped = TwelveTwelve::gamma(eta, theta_5 * PI / 6.0 + 12.0, &wrapped_4);
181 }
182 0.0 => {
183 if transformed_point.coordinates[1] >= 0.0 {
184 let theta_1 = (nearest_vertex_number + 6.0).rem_euclid(12.0);
185 let wrapped_1 =
186 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
187 let theta_2 = (theta_1 - 5.0).rem_euclid(12.0);
188 let wrapped_2 =
189 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
190 let theta_3 = (theta_2 - 5.0).rem_euclid(12.0);
191 let wrapped_3 =
192 TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
193 let theta_4 = (theta_3 - 5.0).rem_euclid(12.0);
194 let wrapped_4 =
195 TwelveTwelve::gamma(eta, theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3);
196 let theta_5 = (theta_4 - 5.0).rem_euclid(12.0);
197 let wrapped_5 =
198 TwelveTwelve::gamma(eta, theta_5 * PI / 6.0 + 12.0, &wrapped_4);
199 let theta_6 = (theta_5 - 5.0).rem_euclid(12.0);
200 wrapped =
201 TwelveTwelve::gamma(eta, theta_6 * PI / 6.0 + PI / 12.0, &wrapped_5);
202 } else {
203 let theta_1 = (nearest_vertex_number + 5.0).rem_euclid(12.0);
204 let wrapped_1 =
205 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
206 let theta_2 = (theta_1 + 5.0).rem_euclid(12.0);
207 let wrapped_2 =
208 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
209 let theta_3 = (theta_2 + 5.0).rem_euclid(12.0);
210 let wrapped_3 =
211 TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
212 let theta_4 = (theta_3 + 5.0).rem_euclid(12.0);
213 let wrapped_4 =
214 TwelveTwelve::gamma(eta, theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3);
215 let theta_5 = (theta_4 + 5.0).rem_euclid(12.0);
216 let wrapped_5 =
217 TwelveTwelve::gamma(eta, theta_5 * PI / 6.0 + PI / 12.0, &wrapped_4);
218 let theta_6 = (theta_5 + 5.0).rem_euclid(12.0);
219 wrapped =
220 TwelveTwelve::gamma(eta, theta_6 * PI / 6.0 + PI / 12.0, &wrapped_5);
221 }
222 }
223 _ => return Err(Error::CannotWrapProperties),
224 }
225 let wrapped_hyperbolic =
226 Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from(wrapped));
227 *r = wrapped_hyperbolic;
228 Ok(properties)
229 } else {
230 Err(Error::CannotWrapProperties)
231 }
232 }
233}
234
235impl Wrap<OrientedHyperbolicPoint<3, Angle>> for Periodic<TwelveTwelve> {
236 #[inline]
239 #[allow(clippy::too_many_lines, reason = "complicated function")]
240 fn wrap(
241 &self,
242 properties: OrientedHyperbolicPoint<3, Angle>,
243 ) -> Result<OrientedHyperbolicPoint<3, Angle>, Error> {
244 let original_orientation = properties.orientation.theta;
245 let mut properties = properties;
246 let r = properties.position_mut();
247 let r_coords = r.coordinates();
248 let angle = r_coords[1].atan2(r_coords[0]).rem_euclid(2.0 * PI);
249
250 let d = TwelveTwelve::distance_to_boundary(r);
251 if d >= 0.0 {
252 Ok(properties)
253 } else if d > -self.maximum_interaction_range {
254 let nearest_vertex_number =
256 (((angle + (PI / 12.0)).rem_euclid(PI * 2.0)) / (PI / 6.0)).floor();
257
258 let (vertex_boost, vertex_angle) = (
260 TwelveTwelve::TWELVETWELVE,
261 (nearest_vertex_number * PI / 6.0).rem_euclid(PI * 2.0),
262 );
263 let (v_cosh, v_sinh, v_cos, v_sin) = (
264 (-vertex_boost).cosh(),
265 (-vertex_boost).sinh(),
266 (-vertex_angle).cos(),
267 (-vertex_angle).sin(),
268 );
269 let transformed_point = Minkowski::from([
270 r_coords[0] * v_cosh * v_cos - r_coords[1] * v_cosh * v_sin + r_coords[2] * v_sinh,
271 r_coords[0] * v_sin + r_coords[1] * v_cos,
272 r_coords[0] * v_sinh * v_cos - r_coords[1] * v_sinh * v_sin + r_coords[2] * v_cosh,
273 ]);
274 let trans_angle =
276 transformed_point.coordinates[1].atan2(transformed_point.coordinates[0]);
277 let sector = (((trans_angle + (PI / 12.0)).rem_euclid(2.0 * PI)) / (PI / 6.0)).floor();
279
280 let eta = TwelveTwelve::CUSP_TO_EDGE;
282 let wrapped: [f64; 3];
283 let relative_angle: f64;
284 match sector {
285 7.0 => {
286 let theta_1 = (nearest_vertex_number + 5.0).rem_euclid(12.0);
287 wrapped = TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
288 relative_angle =
289 TwelveTwelve::reorient(theta_1 * PI / 6.0 + PI / 12.0, r_coords);
290 }
291 8.0 => {
292 let theta_1 = (nearest_vertex_number + 5.0).rem_euclid(12.0);
293 let wrapped_1 =
294 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
295 let relative_angle_1 =
296 TwelveTwelve::reorient(theta_1 * PI / 6.0 + PI / 12.0, r_coords);
297 let theta_2 = (theta_1 + 5.0).rem_euclid(12.0);
298 wrapped = TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
299 relative_angle =
300 TwelveTwelve::reorient(theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1)
301 + relative_angle_1;
302 }
303 9.0 => {
304 let theta_1 = (nearest_vertex_number + 5.0).rem_euclid(12.0);
305 let wrapped_1 =
306 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
307 let relative_angle_1 =
308 TwelveTwelve::reorient(theta_1 * PI / 6.0 + PI / 12.0, r_coords);
309 let theta_2 = (theta_1 + 5.0).rem_euclid(12.0);
310 let wrapped_2 =
311 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
312 let relative_angle_2 =
313 TwelveTwelve::reorient(theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
314 let theta_3 = (theta_2 + 5.0).rem_euclid(12.0);
315 wrapped = TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
316 relative_angle =
317 TwelveTwelve::reorient(theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2)
318 + relative_angle_1
319 + relative_angle_2;
320 }
321 10.0 => {
322 let theta_1 = (nearest_vertex_number + 5.0).rem_euclid(12.0);
323 let wrapped_1 =
324 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
325 let relative_angle_1 =
326 TwelveTwelve::reorient(theta_1 * PI / 6.0 + PI / 12.0, r_coords);
327 let theta_2 = (theta_1 + 5.0).rem_euclid(12.0);
328 let wrapped_2 =
329 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
330 let relative_angle_2 =
331 TwelveTwelve::reorient(theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
332 let theta_3 = (theta_2 + 5.0).rem_euclid(12.0);
333 let wrapped_3 =
334 TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
335 let relative_angle_3 =
336 TwelveTwelve::reorient(theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
337 let theta_4 = (theta_3 + 5.0).rem_euclid(12.0);
338 wrapped = TwelveTwelve::gamma(eta, theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3);
339 relative_angle =
340 TwelveTwelve::reorient(theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3)
341 + relative_angle_1
342 + relative_angle_2
343 + relative_angle_3;
344 }
345 11.0 => {
346 let theta_1 = (nearest_vertex_number + 5.0).rem_euclid(12.0);
347 let wrapped_1 =
348 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
349 let relative_angle_1 =
350 TwelveTwelve::reorient(theta_1 * PI / 6.0 + PI / 12.0, r_coords);
351 let theta_2 = (theta_1 + 5.0).rem_euclid(12.0);
352 let wrapped_2 =
353 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
354 let relative_angle_2 =
355 TwelveTwelve::reorient(theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
356 let theta_3 = (theta_2 + 5.0).rem_euclid(12.0);
357 let wrapped_3 =
358 TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
359 let relative_angle_3 =
360 TwelveTwelve::reorient(theta_3 * PI / 6.0 + PI / 12.0, &wrapped_3);
361 let theta_4 = (theta_3 + 5.0).rem_euclid(12.0);
362 let wrapped_4 =
363 TwelveTwelve::gamma(eta, theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3);
364 let relative_angle_4 =
365 TwelveTwelve::reorient(theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3);
366 let theta_5 = (theta_4 + 5.0).rem_euclid(12.0);
367 wrapped = TwelveTwelve::gamma(eta, theta_5 * PI / 6.0 + PI / 12.0, &wrapped_4);
368 relative_angle =
369 TwelveTwelve::reorient(theta_5 * PI / 6.0 + PI / 12.0, &wrapped_4)
370 + relative_angle_1
371 + relative_angle_2
372 + relative_angle_3
373 + relative_angle_4;
374 }
375 5.0 => {
376 let theta_1 = (nearest_vertex_number + 6.0).rem_euclid(12.0);
377 wrapped = TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
378 relative_angle =
379 TwelveTwelve::reorient(theta_1 * PI / 6.0 + PI / 12.0, r_coords);
380 }
381 4.0 => {
382 let theta_1 = (nearest_vertex_number + 6.0).rem_euclid(12.0);
383 let wrapped_1 =
384 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
385 let relative_angle_1 =
386 TwelveTwelve::reorient(theta_1 * PI / 6.0 + PI / 12.0, r_coords);
387 let theta_2 = (theta_1 - 5.0).rem_euclid(12.0);
388 wrapped = TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
389 relative_angle =
390 TwelveTwelve::reorient(theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1)
391 + relative_angle_1;
392 }
393 3.0 => {
394 let theta_1 = (nearest_vertex_number + 6.0).rem_euclid(12.0);
395 let wrapped_1 =
396 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
397 let relative_angle_1 =
398 TwelveTwelve::reorient(theta_1 * PI / 6.0 + PI / 12.0, r_coords);
399 let theta_2 = (theta_1 - 5.0).rem_euclid(12.0);
400 let wrapped_2 =
401 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
402 let relative_angle_2 =
403 TwelveTwelve::reorient(theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
404 let theta_3 = (theta_2 - 5.0).rem_euclid(12.0);
405 wrapped = TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
406 relative_angle =
407 TwelveTwelve::reorient(theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2)
408 + relative_angle_1
409 + relative_angle_2;
410 }
411 2.0 => {
412 let theta_1 = (nearest_vertex_number + 6.0).rem_euclid(12.0);
413 let wrapped_1 =
414 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
415 let relative_angle_1 =
416 TwelveTwelve::reorient(theta_1 * PI / 6.0 + PI / 12.0, r_coords);
417 let theta_2 = (theta_1 - 5.0).rem_euclid(12.0);
418 let wrapped_2 =
419 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
420 let relative_angle_2 =
421 TwelveTwelve::reorient(theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
422 let theta_3 = (theta_2 - 5.0).rem_euclid(12.0);
423 let wrapped_3 =
424 TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
425 let relative_angle_3 =
426 TwelveTwelve::reorient(theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
427 let theta_4 = (theta_3 - 5.0).rem_euclid(12.0);
428 wrapped = TwelveTwelve::gamma(eta, theta_4 * PI / 6.0 - PI / 12.0, &wrapped_3);
429 relative_angle =
430 TwelveTwelve::reorient(theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3)
431 + relative_angle_1
432 + relative_angle_2
433 + relative_angle_3;
434 }
435 1.0 => {
436 let theta_1 = (nearest_vertex_number + 6.0).rem_euclid(12.0);
437 let wrapped_1 =
438 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
439 let relative_angle_1 =
440 TwelveTwelve::reorient(theta_1 * PI / 6.0 + PI / 12.0, r_coords);
441 let theta_2 = (theta_1 - 5.0).rem_euclid(12.0);
442 let wrapped_2 =
443 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
444 let relative_angle_2 =
445 TwelveTwelve::reorient(theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
446 let theta_3 = (theta_2 - 5.0).rem_euclid(12.0);
447 let wrapped_3 =
448 TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
449 let relative_angle_3 =
450 TwelveTwelve::reorient(theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
451 let theta_4 = (theta_3 - 5.0).rem_euclid(12.0);
452 let wrapped_4 =
453 TwelveTwelve::gamma(eta, theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3);
454 let relative_angle_4 =
455 TwelveTwelve::reorient(theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3);
456 let theta_5 = (theta_4 - 5.0).rem_euclid(12.0);
457 wrapped = TwelveTwelve::gamma(eta, theta_5 * PI / 6.0 + PI / 12.0, &wrapped_4);
458 relative_angle =
459 TwelveTwelve::reorient(theta_5 * PI / 6.0 + PI / 12.0, &wrapped_4)
460 + relative_angle_1
461 + relative_angle_2
462 + relative_angle_3
463 + relative_angle_4;
464 }
465 0.0 => {
466 if transformed_point.coordinates[1] >= 0.0 {
467 let theta_1 = (nearest_vertex_number + 6.0).rem_euclid(12.0);
468 let wrapped_1 =
469 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
470 let relative_angle_1 =
471 TwelveTwelve::reorient(theta_1 * PI / 6.0 + PI / 12.0, r_coords);
472 let theta_2 = (theta_1 - 5.0).rem_euclid(12.0);
473 let wrapped_2 =
474 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
475 let relative_angle_2 =
476 TwelveTwelve::reorient(theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
477 let theta_3 = (theta_2 - 5.0).rem_euclid(12.0);
478 let wrapped_3 =
479 TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
480 let relative_angle_3 =
481 TwelveTwelve::reorient(theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
482 let theta_4 = (theta_3 - 5.0).rem_euclid(12.0);
483 let wrapped_4 =
484 TwelveTwelve::gamma(eta, theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3);
485 let relative_angle_4 =
486 TwelveTwelve::reorient(theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3);
487 let theta_5 = (theta_4 - 5.0).rem_euclid(12.0);
488 let wrapped_5 =
489 TwelveTwelve::gamma(eta, theta_5 * PI / 6.0 + PI / 12.0, &wrapped_4);
490 let relative_angle_5 =
491 TwelveTwelve::reorient(theta_5 * PI / 6.0 + PI / 12.0, &wrapped_4);
492 let theta_6 = (theta_5 - 5.0).rem_euclid(12.0);
493 wrapped =
494 TwelveTwelve::gamma(eta, theta_6 * PI / 6.0 + PI / 12.0, &wrapped_5);
495 relative_angle =
496 TwelveTwelve::reorient(theta_6 * PI / 6.0 + PI / 12.0, &wrapped_5)
497 + relative_angle_1
498 + relative_angle_2
499 + relative_angle_3
500 + relative_angle_4
501 + relative_angle_5;
502 } else {
503 let theta_1 = (nearest_vertex_number + 5.0).rem_euclid(12.0);
504 let wrapped_1 =
505 TwelveTwelve::gamma(eta, theta_1 * PI / 6.0 + PI / 12.0, r_coords);
506 let relative_angle_1 =
507 TwelveTwelve::reorient(theta_1 * PI / 6.0 + PI / 12.0, r_coords);
508 let theta_2 = (theta_1 + 5.0).rem_euclid(12.0);
509 let wrapped_2 =
510 TwelveTwelve::gamma(eta, theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
511 let relative_angle_2 =
512 TwelveTwelve::reorient(theta_2 * PI / 6.0 + PI / 12.0, &wrapped_1);
513 let theta_3 = (theta_2 + 5.0).rem_euclid(12.0);
514 let wrapped_3 =
515 TwelveTwelve::gamma(eta, theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
516 let relative_angle_3 =
517 TwelveTwelve::reorient(theta_3 * PI / 6.0 + PI / 12.0, &wrapped_2);
518 let theta_4 = (theta_3 + 5.0).rem_euclid(12.0);
519 let wrapped_4 =
520 TwelveTwelve::gamma(eta, theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3);
521 let relative_angle_4 =
522 TwelveTwelve::reorient(theta_4 * PI / 6.0 + PI / 12.0, &wrapped_3);
523 let theta_5 = (theta_4 + 5.0).rem_euclid(12.0);
524 let wrapped_5 =
525 TwelveTwelve::gamma(eta, theta_5 * PI / 6.0 + PI / 12.0, &wrapped_4);
526 let relative_angle_5 =
527 TwelveTwelve::reorient(theta_5 * PI / 6.0 + PI / 12.0, &wrapped_4);
528 let theta_6 = (theta_5 + 5.0).rem_euclid(12.0);
529 wrapped =
530 TwelveTwelve::gamma(eta, theta_6 * PI / 6.0 + PI / 12.0, &wrapped_5);
531 relative_angle =
532 TwelveTwelve::reorient(theta_6 * PI / 6.0 + PI / 12.0, &wrapped_5)
533 + relative_angle_1
534 + relative_angle_2
535 + relative_angle_3
536 + relative_angle_4
537 + relative_angle_5;
538 }
539 }
540 _ => return Err(Error::CannotWrapProperties),
541 }
542 let wrapped_hyperbolic =
543 Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from(wrapped));
544 let mut final_orientation = relative_angle + original_orientation;
545 while final_orientation > PI {
546 final_orientation -= 2.0 * PI;
547 }
548 while final_orientation <= -PI {
549 final_orientation += 2.0 * PI;
550 }
551 *r = wrapped_hyperbolic;
552 *properties.orientation_mut() = Angle::from(final_orientation);
553 Ok(properties)
554 } else {
555 Err(Error::CannotWrapProperties)
556 }
557 }
558}
559
560impl GenerateGhosts<Point<Hyperbolic<3>>> for Periodic<TwelveTwelve> {
561 #[inline]
562 fn maximum_interaction_range(&self) -> f64 {
563 self.maximum_interaction_range
564 }
565 #[inline]
567 fn generate_ghosts(
568 &self,
569 site_properties: &Point<Hyperbolic<3>>,
570 ) -> ArrayVec<Point<Hyperbolic<3>>, MAX_GHOSTS> {
571 let mut result = ArrayVec::new();
572 let r = site_properties.position();
573
574 let eta = TwelveTwelve::CUSP_TO_EDGE;
576 let gamma_pt = |theta: f64, point: &[f64; 3]| {
577 let ghost = TwelveTwelve::gamma(eta, theta, point);
578 let new_hyperbolic = Hyperbolic::from_minkowski_coordinates(Minkowski::from(ghost));
579 let mut new_site = *site_properties;
580 *new_site.position_mut() = new_hyperbolic;
581 new_site
582 };
583 let angle = (r.coordinates()[1].atan2(r.coordinates()[0])).rem_euclid(2.0 * PI);
585 let nearest_vertex_number =
586 (((angle + (PI / 12.0)).rem_euclid(PI * 2.0)) / (PI / 6.0)).floor();
587 let coords = *r.coordinates();
588
589 let theta_1a = (nearest_vertex_number + 6.0).rem_euclid(12.0);
590 let ghost_1a = gamma_pt(theta_1a * PI / 6.0 + PI / 12.0, &coords);
591 result.push(ghost_1a);
592 let theta_1b = (nearest_vertex_number + 5.0).rem_euclid(12.0);
593 let ghost_1b = gamma_pt(theta_1b * PI / 6.0 + PI / 12.0, &coords);
594 result.push(ghost_1b);
595
596 let theta_2a = (theta_1a - 5.0).rem_euclid(12.0);
597 let ghost_2a = gamma_pt(
598 theta_2a * PI / 6.0 + PI / 12.0,
599 ghost_1a.position.coordinates(),
600 );
601 result.push(ghost_2a);
602 let theta_2b = (theta_1b + 5.0).rem_euclid(12.0);
603 let ghost_2b = gamma_pt(
604 theta_2b * PI / 6.0 + PI / 12.0,
605 ghost_1b.position.coordinates(),
606 );
607 result.push(ghost_2b);
608
609 let theta_3a = (theta_2a - 5.0).rem_euclid(12.0);
610 let ghost_3a = gamma_pt(
611 theta_3a * PI / 6.0 + PI / 12.0,
612 ghost_2a.position.coordinates(),
613 );
614 result.push(ghost_3a);
615 let theta_3b = (theta_2b + 5.0).rem_euclid(12.0);
616 let ghost_3b = gamma_pt(
617 theta_3b * PI / 6.0 + PI / 12.0,
618 ghost_2b.position.coordinates(),
619 );
620 result.push(ghost_3b);
621
622 let theta_4a = (theta_3a - 5.0).rem_euclid(12.0);
623 let ghost_4a = gamma_pt(
624 theta_4a * PI / 6.0 + PI / 12.0,
625 ghost_3a.position.coordinates(),
626 );
627 result.push(ghost_4a);
628 let theta_4b = (theta_3b + 5.0).rem_euclid(12.0);
629 let ghost_4b = gamma_pt(
630 theta_4b * PI / 6.0 + PI / 12.0,
631 ghost_3b.position.coordinates(),
632 );
633 result.push(ghost_4b);
634
635 let theta_5a = (theta_4a - 5.0).rem_euclid(12.0);
636 let ghost_5a = gamma_pt(
637 theta_5a * PI / 6.0 + PI / 12.0,
638 ghost_4a.position.coordinates(),
639 );
640 result.push(ghost_5a);
641 let theta_5b = (theta_4b + 5.0).rem_euclid(12.0);
642 let ghost_5b = gamma_pt(
643 theta_5b * PI / 6.0 + PI / 12.0,
644 ghost_4b.position.coordinates(),
645 );
646 result.push(ghost_5b);
647
648 let theta_6 = (theta_5a - 5.0).rem_euclid(12.0);
649 let ghost_6 = gamma_pt(
650 theta_6 * PI / 6.0 + PI / 12.0,
651 ghost_5a.position.coordinates(),
652 );
653 result.push(ghost_6);
654
655 result
656 }
657}
658
659impl GenerateGhosts<OrientedHyperbolicPoint<3, Angle>> for Periodic<TwelveTwelve> {
660 #[inline]
661 fn maximum_interaction_range(&self) -> f64 {
662 self.maximum_interaction_range
663 }
664 #[inline]
666 #[expect(clippy::too_many_lines, reason = "complicated function")]
667 fn generate_ghosts(
668 &self,
669 site_properties: &OrientedHyperbolicPoint<3, Angle>,
670 ) -> ArrayVec<OrientedHyperbolicPoint<3, Angle>, MAX_GHOSTS> {
671 let mut result = ArrayVec::new();
672 let r = site_properties.position();
673
674 let eta = TwelveTwelve::CUSP_TO_EDGE;
676 let gamma_pt = |theta: f64, point: &[f64; 3]| {
677 let ghost = TwelveTwelve::gamma(eta, theta, point);
678 let new_hyperbolic = Hyperbolic::from_minkowski_coordinates(Minkowski::from(ghost));
679 let mut new_site = *site_properties;
680 *new_site.position_mut() = new_hyperbolic;
681 new_site
682 };
683 let angle = (r.coordinates()[1].atan2(r.coordinates()[0])).rem_euclid(2.0 * PI);
685 let nearest_vertex_number =
686 (((angle + (PI / 12.0)).rem_euclid(PI * 2.0)) / (PI / 6.0)).floor();
687 let coords = *r.coordinates();
688 let orientation = site_properties.orientation.theta;
689
690 let theta_1a = (nearest_vertex_number + 6.0).rem_euclid(12.0);
691 let ghost_1a = gamma_pt(theta_1a * PI / 6.0 + PI / 12.0, &coords);
692 let rel_angle_1a = TwelveTwelve::reorient(theta_1a * PI / 6.0 + PI / 12.0, &coords);
693 let orientation_1a = rel_angle_1a + orientation;
694 let mut final_orientation = orientation_1a;
695 while final_orientation > PI {
696 final_orientation -= 2.0 * PI;
697 }
698 while final_orientation <= -PI {
699 final_orientation += 2.0 * PI;
700 }
701 let mut new_site_1a = *site_properties;
702 *new_site_1a.position_mut() = ghost_1a.position;
703 *new_site_1a.orientation_mut() = Angle::from(final_orientation);
704 result.push(new_site_1a);
705
706 let theta_1b = (nearest_vertex_number + 5.0).rem_euclid(12.0);
707 let ghost_1b = gamma_pt(theta_1b * PI / 6.0 + PI / 12.0, &coords);
708 let rel_angle_1b = TwelveTwelve::reorient(theta_1b * PI / 6.0 + PI / 12.0, &coords);
709 let orientation_1b = orientation + rel_angle_1b;
710 let mut final_orientation = orientation_1b;
711 while final_orientation > PI {
712 final_orientation -= 2.0 * PI;
713 }
714 while final_orientation <= -PI {
715 final_orientation += 2.0 * PI;
716 }
717 let mut new_site_1b = *site_properties;
718 *new_site_1b.position_mut() = ghost_1b.position;
719 *new_site_1b.orientation_mut() = Angle::from(final_orientation);
720 result.push(new_site_1b);
721
722 let theta_2a = (theta_1a - 5.0).rem_euclid(12.0);
723 let ghost_2a = gamma_pt(
724 theta_2a * PI / 6.0 + PI / 12.0,
725 ghost_1a.position.coordinates(),
726 );
727 let rel_angle_2a = TwelveTwelve::reorient(
728 theta_2a * PI / 6.0 + PI / 12.0,
729 ghost_1a.position.coordinates(),
730 );
731 let orientation_2a = orientation_1a + rel_angle_2a;
732 let mut final_orientation = orientation_2a;
733 while final_orientation > PI {
734 final_orientation -= 2.0 * PI;
735 }
736 while final_orientation <= -PI {
737 final_orientation += 2.0 * PI;
738 }
739 let mut new_site_2a = *site_properties;
740 *new_site_2a.position_mut() = ghost_2a.position;
741 *new_site_2a.orientation_mut() = Angle::from(final_orientation);
742 result.push(new_site_2a);
743
744 let theta_2b = (theta_1b + 5.0).rem_euclid(12.0);
745 let ghost_2b = gamma_pt(
746 theta_2b * PI / 6.0 + PI / 12.0,
747 ghost_1b.position.coordinates(),
748 );
749 let rel_angle_2b = TwelveTwelve::reorient(
750 theta_2b * PI / 6.0 + PI / 12.0,
751 ghost_1b.position.coordinates(),
752 );
753 let orientation_2b = orientation_1b + rel_angle_2b;
754 let mut final_orientation = orientation_2b;
755 while final_orientation > PI {
756 final_orientation -= 2.0 * PI;
757 }
758 while final_orientation <= -PI {
759 final_orientation += 2.0 * PI;
760 }
761 let mut new_site_2b = *site_properties;
762 *new_site_2b.position_mut() = ghost_2b.position;
763 *new_site_2b.orientation_mut() = Angle::from(final_orientation);
764 result.push(new_site_2b);
765
766 let theta_3a = (theta_2a - 5.0).rem_euclid(12.0);
767 let ghost_3a = gamma_pt(
768 theta_3a * PI / 6.0 + PI / 12.0,
769 ghost_2a.position.coordinates(),
770 );
771 let rel_angle_3a = TwelveTwelve::reorient(
772 theta_3a * PI / 6.0 + PI / 12.0,
773 ghost_2a.position.coordinates(),
774 );
775 let orientation_3a = orientation_2a + rel_angle_3a;
776 let mut final_orientation = orientation_3a;
777 while final_orientation > PI {
778 final_orientation -= 2.0 * PI;
779 }
780 while final_orientation <= -PI {
781 final_orientation += 2.0 * PI;
782 }
783 let mut new_site_3a = *site_properties;
784 *new_site_3a.position_mut() = ghost_3a.position;
785 *new_site_3a.orientation_mut() = Angle::from(final_orientation);
786 result.push(new_site_3a);
787
788 let theta_3b = (theta_2b + 5.0).rem_euclid(12.0);
789 let ghost_3b = gamma_pt(
790 theta_3b * PI / 6.0 + PI / 12.0,
791 ghost_2b.position.coordinates(),
792 );
793 let rel_angle_3b = TwelveTwelve::reorient(
794 theta_3b * PI / 6.0 + PI / 12.0,
795 ghost_2b.position.coordinates(),
796 );
797 let orientation_3b = rel_angle_3b + orientation_2b;
798 let mut final_orientation = orientation_3b;
799 while final_orientation > PI {
800 final_orientation -= 2.0 * PI;
801 }
802 while final_orientation <= -PI {
803 final_orientation += 2.0 * PI;
804 }
805 let mut new_site_3b = *site_properties;
806 *new_site_3b.position_mut() = ghost_3b.position;
807 *new_site_3b.orientation_mut() = Angle::from(final_orientation);
808 result.push(new_site_3b);
809
810 let theta_4a = (theta_3a - 5.0).rem_euclid(12.0);
811 let ghost_4a = gamma_pt(
812 theta_4a * PI / 6.0 + PI / 12.0,
813 ghost_3a.position.coordinates(),
814 );
815 let rel_angle_4a = TwelveTwelve::reorient(
816 theta_4a * PI / 6.0 + PI / 12.0,
817 ghost_3a.position.coordinates(),
818 );
819 let orientation_4a = orientation_3a + rel_angle_4a;
820 let mut final_orientation = orientation_4a;
821 while final_orientation > PI {
822 final_orientation -= 2.0 * PI;
823 }
824 while final_orientation <= -PI {
825 final_orientation += 2.0 * PI;
826 }
827 let mut new_site_4a = *site_properties;
828 *new_site_4a.position_mut() = ghost_4a.position;
829 *new_site_4a.orientation_mut() = Angle::from(final_orientation);
830 result.push(new_site_4a);
831
832 let theta_4b = (theta_3b + 5.0).rem_euclid(12.0);
833 let ghost_4b = gamma_pt(
834 theta_4b * PI / 6.0 + PI / 12.0,
835 ghost_3b.position.coordinates(),
836 );
837 let rel_angle_4b = TwelveTwelve::reorient(
838 theta_4b * PI / 6.0 + PI / 12.0,
839 ghost_3b.position.coordinates(),
840 );
841 let orientation_4b = orientation_3b + rel_angle_4b;
842 let mut final_orientation = orientation_4b;
843 while final_orientation > PI {
844 final_orientation -= 2.0 * PI;
845 }
846 while final_orientation <= -PI {
847 final_orientation += 2.0 * PI;
848 }
849 let mut new_site_4b = *site_properties;
850 *new_site_4b.position_mut() = ghost_4b.position;
851 *new_site_4b.orientation_mut() = Angle::from(final_orientation);
852 result.push(new_site_4b);
853
854 let theta_5a = (theta_4a - 5.0).rem_euclid(12.0);
855 let ghost_5a = gamma_pt(
856 theta_5a * PI / 6.0 + PI / 12.0,
857 ghost_4a.position.coordinates(),
858 );
859 let rel_angle_5a = TwelveTwelve::reorient(
860 theta_5a * PI / 6.0 + PI / 12.0,
861 ghost_4a.position.coordinates(),
862 );
863 let orientation_5a = orientation_4a + rel_angle_5a;
864 let mut final_orientation = orientation_5a;
865 while final_orientation > PI {
866 final_orientation -= 2.0 * PI;
867 }
868 while final_orientation <= -PI {
869 final_orientation += 2.0 * PI;
870 }
871 let mut new_site_5a = *site_properties;
872 *new_site_5a.position_mut() = ghost_5a.position;
873 *new_site_5a.orientation_mut() = Angle::from(final_orientation);
874 result.push(new_site_5a);
875
876 let theta_5b = (theta_4b + 5.0).rem_euclid(12.0);
877 let ghost_5b = gamma_pt(
878 theta_5b * PI / 6.0 + PI / 12.0,
879 ghost_4b.position.coordinates(),
880 );
881 let rel_angle_5b = TwelveTwelve::reorient(
882 theta_5b * PI / 6.0 + PI / 12.0,
883 ghost_4b.position.coordinates(),
884 );
885 let orientation_5b = orientation_4b + rel_angle_5b;
886 let mut final_orientation = orientation_5b;
887 while final_orientation > PI {
888 final_orientation -= 2.0 * PI;
889 }
890 while final_orientation <= -PI {
891 final_orientation += 2.0 * PI;
892 }
893 let mut new_site_5b = *site_properties;
894 *new_site_5b.position_mut() = ghost_5b.position;
895 *new_site_5b.orientation_mut() = Angle::from(final_orientation);
896 result.push(new_site_5b);
897
898 let theta_6 = (theta_5a - 5.0).rem_euclid(12.0);
899 let ghost_6 = gamma_pt(
900 theta_6 * PI / 6.0 + PI / 12.0,
901 ghost_5a.position.coordinates(),
902 );
903 let rel_angle_6 = TwelveTwelve::reorient(
904 theta_6 * PI / 6.0 + PI / 12.0,
905 ghost_5a.position.coordinates(),
906 );
907 let orientation_6 = orientation_5a + rel_angle_6;
908 let mut final_orientation = orientation_6;
909 while final_orientation > PI {
910 final_orientation -= 2.0 * PI;
911 }
912 while final_orientation <= -PI {
913 final_orientation += 2.0 * PI;
914 }
915 let mut nnew_site_6 = *site_properties;
916 *nnew_site_6.position_mut() = ghost_6.position;
917 *nnew_site_6.orientation_mut() = Angle::from(final_orientation);
918 result.push(nnew_site_6);
919
920 result
921 }
922}
923
924#[cfg(test)]
926mod tests {
927 use super::*;
928 use crate::property::Point;
929 use approxim::assert_relative_eq;
930 use hoomd_manifold::{Hyperbolic, HyperbolicDisk};
931 use hoomd_vector::Metric;
932 use rand::{RngExt, SeedableRng, distr::Distribution, rngs::StdRng};
933 use std::f64::consts::PI;
934
935 #[test]
936 fn doesnt_wrap_if_inside() {
937 let r = TwelveTwelve::CUSP_TO_EDGE;
938 let mut rng = StdRng::seed_from_u64(239);
939 let disk = HyperbolicDisk {
940 disk_radius: r.try_into().expect("hard-coded positive number"),
941 point: Hyperbolic::from_minkowski_coordinates(Minkowski::from([0.0, 0.0, 1.0])),
942 };
943 for _ in 0..250 {
944 let random_point: Hyperbolic<3> = disk.sample(&mut rng);
945 let random_point = Point::new(random_point);
946
947 let periodic = Periodic::new(0.5, TwelveTwelve {}).expect("hard-coded positive number");
948 let wrapped_point = periodic.wrap(random_point).expect("hard-coded");
949 assert_eq!(
950 random_point.position.coordinates(),
951 wrapped_point.position.coordinates()
952 );
953 }
954 }
955
956 #[test]
957 fn wraps_to_opposite_edge() {
958 let mut rng = StdRng::seed_from_u64(54321);
959 let side = f64::from(rng.random_range(0..12));
960 let boost = TwelveTwelve::CUSP_TO_EDGE + 0.5;
961 let offset = PI / 12.0;
962 let point = Hyperbolic::<3>::from_polar_coordinates(boost, side * PI / 6.0 + offset);
963 let point = Point::new(point);
964 let periodic = Periodic::new(1.0, TwelveTwelve {}).expect("hard-coded positive number");
965 let wrapped_point = periodic.wrap(point).expect("hard-coded");
966
967 let wrapped_side = (side + 6.0).rem_euclid(12.0);
968 let octant = (((wrapped_point.position.coordinates()[1]
969 .atan2(wrapped_point.position.coordinates()[0]))
970 / (PI / 6.0))
971 .floor())
972 .rem_euclid(12.0);
973
974 assert_eq!(wrapped_side, octant);
976
977 let new_boost = 2.0
979 * (TwelveTwelve::TWELVETWELVE.tanh() * ((PI / 6.0).sin())
980 / (2.0 * ((PI / 12.0).sin())))
981 .atanh()
982 - boost;
983 let ans = Hyperbolic::<3>::from_polar_coordinates(
984 new_boost,
985 (wrapped_side + 1.0) * (PI / 6.0) - offset,
986 );
987 assert_relative_eq!(ans, wrapped_point.position, epsilon = 1e-12);
988 assert_relative_eq!(
989 -TwelveTwelve::distance_to_boundary(&point.position),
990 TwelveTwelve::distance_to_boundary(&wrapped_point.position),
991 epsilon = 1e-12
992 );
993 }
994
995 #[test]
996 fn wraps_to_opposite_edge_distance() {
997 let mut rng = StdRng::seed_from_u64(1);
998 let side = f64::from(rng.random_range(0..12));
999 let boost = TwelveTwelve::CUSP_TO_EDGE + 0.2;
1000 let offset = PI / 12.0 + rng.random::<f64>() * (PI / 24.0);
1001 let point = Hyperbolic::<3>::from_polar_coordinates(boost, side * PI / 6.0 + offset);
1002 let point = Point::new(point);
1003 let periodic = Periodic::new(1.0, TwelveTwelve {}).expect("hard-coded positive number");
1004 let wrapped_point = periodic.wrap(point).expect("hard-coded");
1005
1006 assert_relative_eq!(
1008 -TwelveTwelve::distance_to_boundary(&point.position),
1009 TwelveTwelve::distance_to_boundary(&wrapped_point.position),
1010 epsilon = 1e-12
1011 );
1012 }
1013
1014 #[test]
1015 fn consistency_edge() {
1016 let mut rng = StdRng::seed_from_u64(5212);
1017 let side = f64::from(rng.random_range(0..12));
1018 let boost = TwelveTwelve::CUSP_TO_EDGE + 0.5;
1019 let offset = PI / 12.0 + 0.15 * rng.random::<f64>();
1020 let point = Hyperbolic::<3>::from_polar_coordinates(boost, side * PI / 6.0 + offset);
1021 let point = Point::new(point);
1022 let periodic = Periodic::new(1.0, TwelveTwelve {}).expect("hard-coded positive number");
1023 let wrapped_point = periodic.wrap(point).expect("hard-coded");
1024 let wrapped_poincare = wrapped_point.position.to_poincare();
1025
1026 let (q_u, q_v) = (
1028 point.position.to_poincare()[0],
1029 point.position.to_poincare()[1],
1030 );
1031 let phi = (side + 6.0).rem_euclid(12.0) * PI / 6.0 + PI / 12.0;
1032 let a = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1033 let b = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi.cos());
1034 let c = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi.sin());
1035 let pref = 1.0 / ((b * q_v - c * q_u).powi(2) + (a + b * q_u + c * q_v).powi(2));
1036 let ans = [
1037 pref * (q_u * (a.powi(2) + b.powi(2) - c.powi(2))
1038 + 2.0 * b * c * q_v
1039 + a * b * (1.0 + q_u.powi(2) + q_v.powi(2))),
1040 pref * (q_v * (a.powi(2) - b.powi(2) + c.powi(2))
1041 + 2.0 * b * c * q_u
1042 + a * c * (1.0 + q_u.powi(2) + q_v.powi(2))),
1043 ];
1044
1045 assert_relative_eq!(ans[0], wrapped_poincare[0], epsilon = 1e-12);
1046 assert_relative_eq!(ans[1], wrapped_poincare[1], epsilon = 1e-12);
1047 }
1048
1049 #[test]
1050 #[allow(clippy::too_many_lines, reason = "complicated function")]
1051 fn wraps_vertex() {
1052 let boost = TwelveTwelve::TWELVETWELVE + 0.4;
1053 let point = Hyperbolic::<3>::from_polar_coordinates(boost, PI / 2.0 - 0.0001);
1054 let point = Point::new(point);
1055 let periodic = Periodic::new(0.5, TwelveTwelve {}).expect("hard-coded positive number");
1056 let wrapped_point = periodic.wrap(point).expect("hard-coded");
1057 let wrapped_poincare = wrapped_point.position.to_poincare();
1058 let point_poincare = point.position.to_poincare();
1059
1060 let phi8 = 8.0 * PI / 6.0 + PI / 12.0;
1061 let a8 = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1062 let b8 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi8.cos());
1063 let c8 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi8.sin());
1064 let pref_8 = 1.0
1065 / ((b8 * point_poincare[1] - c8 * point_poincare[0]).powi(2)
1066 + (a8 + b8 * point_poincare[0] + c8 * point_poincare[1]).powi(2));
1067 let ans_8 = [
1068 pref_8
1069 * (point_poincare[0] * (a8.powi(2) + b8.powi(2) - c8.powi(2))
1070 + 2.0 * b8 * c8 * point_poincare[1]
1071 + a8 * b8 * (1.0 + point_poincare[0].powi(2) + point_poincare[1].powi(2))),
1072 pref_8
1073 * (point_poincare[1] * (a8.powi(2) - b8.powi(2) + c8.powi(2))
1074 + 2.0 * b8 * c8 * point_poincare[0]
1075 + a8 * c8 * (1.0 + point_poincare[0].powi(2) + point_poincare[1].powi(2))),
1076 ];
1077 let phi1 = PI / 6.0 + PI / 12.0;
1078 let a1 = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1079 let b1 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi1.cos());
1080 let c1 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi1.sin());
1081 let pref_1 = 1.0
1082 / ((b1 * ans_8[1] - c1 * ans_8[0]).powi(2)
1083 + (a1 + b1 * ans_8[0] + c1 * ans_8[1]).powi(2));
1084 let ans_1 = [
1085 pref_1
1086 * (ans_8[0] * (a1.powi(2) + b1.powi(2) - c1.powi(2))
1087 + 2.0 * b1 * c1 * ans_8[1]
1088 + a1 * b1 * (1.0 + ans_8[0].powi(2) + ans_8[1].powi(2))),
1089 pref_1
1090 * (ans_8[1] * (a1.powi(2) - b1.powi(2) + c1.powi(2))
1091 + 2.0 * b1 * c1 * ans_8[0]
1092 + a1 * c1 * (1.0 + ans_8[0].powi(2) + ans_8[1].powi(2))),
1093 ];
1094 let phi6 = PI + PI / 12.0;
1095 let a6 = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1096 let b6 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi6.cos());
1097 let c6 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi6.sin());
1098 let pref_6 = 1.0
1099 / ((b6 * ans_1[1] - c6 * ans_1[0]).powi(2)
1100 + (a6 + b6 * ans_1[0] + c6 * ans_1[1]).powi(2));
1101 let ans_6 = [
1102 pref_6
1103 * (ans_1[0] * (a6.powi(2) + b6.powi(2) - c6.powi(2))
1104 + 2.0 * b6 * c6 * ans_1[1]
1105 + a6 * b6 * (1.0 + ans_1[0].powi(2) + ans_1[1].powi(2))),
1106 pref_6
1107 * (ans_1[1] * (a6.powi(2) - b6.powi(2) + c6.powi(2))
1108 + 2.0 * b6 * c6 * ans_1[0]
1109 + a6 * c6 * (1.0 + ans_1[0].powi(2) + ans_1[1].powi(2))),
1110 ];
1111 let phi11 = 11.0 * PI / 6.0 + PI / 12.0;
1112 let a11 = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1113 let b11 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi11.cos());
1114 let c11 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi11.sin());
1115 let pref_11 = 1.0
1116 / ((b11 * ans_6[1] - c11 * ans_6[0]).powi(2)
1117 + (a11 + b11 * ans_6[0] + c11 * ans_6[1]).powi(2));
1118 let ans_11 = [
1119 pref_11
1120 * (ans_6[0] * (a11.powi(2) + b11.powi(2) - c11.powi(2))
1121 + 2.0 * b11 * c11 * ans_6[1]
1122 + a11 * b11 * (1.0 + ans_6[0].powi(2) + ans_6[1].powi(2))),
1123 pref_11
1124 * (ans_6[1] * (a11.powi(2) - b11.powi(2) + c11.powi(2))
1125 + 2.0 * b11 * c11 * ans_6[0]
1126 + a11 * c11 * (1.0 + ans_6[0].powi(2) + ans_6[1].powi(2))),
1127 ];
1128 let phi4 = 4.0 * PI / 6.0 + PI / 12.0;
1129 let a4 = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1130 let b4 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi4.cos());
1131 let c4 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi4.sin());
1132 let pref_4 = 1.0
1133 / ((b4 * ans_11[1] - c4 * ans_11[0]).powi(2)
1134 + (a4 + b4 * ans_11[0] + c4 * ans_11[1]).powi(2));
1135 let ans_4 = [
1136 pref_4
1137 * (ans_11[0] * (a4.powi(2) + b4.powi(2) - c4.powi(2))
1138 + 2.0 * b4 * c4 * ans_11[1]
1139 + a4 * b4 * (1.0 + ans_11[0].powi(2) + ans_11[1].powi(2))),
1140 pref_4
1141 * (ans_11[1] * (a4.powi(2) - b4.powi(2) + c4.powi(2))
1142 + 2.0 * b4 * c4 * ans_11[0]
1143 + a4 * c4 * (1.0 + ans_11[0].powi(2) + ans_11[1].powi(2))),
1144 ];
1145 let phi9 = 9.0 * PI / 6.0 + PI / 12.0;
1146 let a9 = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1147 let b9 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi9.cos());
1148 let c9 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi9.sin());
1149 let pref_9 = 1.0
1150 / ((b9 * ans_4[1] - c9 * ans_4[0]).powi(2)
1151 + (a9 + b9 * ans_4[0] + c9 * ans_4[1]).powi(2));
1152 let ans_9 = [
1153 pref_9
1154 * (ans_4[0] * (a9.powi(2) + b9.powi(2) - c9.powi(2))
1155 + 2.0 * b9 * c9 * ans_4[1]
1156 + a9 * b9 * (1.0 + ans_4[0].powi(2) + ans_4[1].powi(2))),
1157 pref_9
1158 * (ans_4[1] * (a9.powi(2) - b9.powi(2) + c9.powi(2))
1159 + 2.0 * b9 * c9 * ans_4[0]
1160 + a9 * c9 * (1.0 + ans_4[0].powi(2) + ans_4[1].powi(2))),
1161 ];
1162
1163 assert_relative_eq!(ans_9[0], wrapped_poincare[0], epsilon = 1e-10);
1164 assert_relative_eq!(ans_9[1], wrapped_poincare[1], epsilon = 1e-10);
1165 }
1166
1167 #[test]
1168 fn wraps_vertex_non_center() {
1169 let offset_boost: f64 = 0.4;
1170 let v: f64 = TwelveTwelve::TWELVETWELVE;
1171 let point = Hyperbolic::from_minkowski_coordinates(
1172 [
1173 (v.sinh()) * (offset_boost.cosh()),
1174 -offset_boost.sinh(),
1175 (v.cosh()) * (offset_boost.cosh()),
1176 ]
1177 .into(),
1178 );
1179 let point = Point::new(point);
1180 let periodic = Periodic::new(0.5, TwelveTwelve {}).expect("hard-coded positive number");
1181 let wrapped_point = periodic.wrap(point).expect("hard-coded");
1182
1183 let wrapped_poincare = wrapped_point.position.to_poincare();
1184 let point_poincare = point.position.to_poincare();
1185
1186 let phi5 = 5.0 * PI / 6.0 + PI / 12.0;
1187 let a5 = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1188 let b5 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi5.cos());
1189 let c5 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi5.sin());
1190 let pref_5 = 1.0
1191 / ((b5 * point_poincare[1] - c5 * point_poincare[0]).powi(2)
1192 + (a5 + b5 * point_poincare[0] + c5 * point_poincare[1]).powi(2));
1193 let ans_5 = [
1194 pref_5
1195 * (point_poincare[0] * (a5.powi(2) + b5.powi(2) - c5.powi(2))
1196 + 2.0 * b5 * c5 * point_poincare[1]
1197 + a5 * b5 * (1.0 + point_poincare[0].powi(2) + point_poincare[1].powi(2))),
1198 pref_5
1199 * (point_poincare[1] * (a5.powi(2) - b5.powi(2) + c5.powi(2))
1200 + 2.0 * b5 * c5 * point_poincare[0]
1201 + a5 * c5 * (1.0 + point_poincare[0].powi(2) + point_poincare[1].powi(2))),
1202 ];
1203 let phi10 = 10.0 * PI / 6.0 + PI / 12.0;
1204 let a10 = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1205 let b10 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi10.cos());
1206 let c10 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi10.sin());
1207 let pref_10 = 1.0
1208 / ((b10 * ans_5[1] - c10 * ans_5[0]).powi(2)
1209 + (a10 + b10 * ans_5[0] + c10 * ans_5[1]).powi(2));
1210 let ans_10 = [
1211 pref_10
1212 * (ans_5[0] * (a10.powi(2) + b10.powi(2) - c10.powi(2))
1213 + 2.0 * b10 * c10 * ans_5[1]
1214 + a10 * b10 * (1.0 + ans_5[0].powi(2) + ans_5[1].powi(2))),
1215 pref_10
1216 * (ans_5[1] * (a10.powi(2) - b10.powi(2) + c10.powi(2))
1217 + 2.0 * b10 * c10 * ans_5[0]
1218 + a10 * c10 * (1.0 + ans_5[0].powi(2) + ans_5[1].powi(2))),
1219 ];
1220 let phi3 = 3.0 * PI / 6.0 + PI / 12.0;
1221 let a3 = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1222 let b3 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi3.cos());
1223 let c3 = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi3.sin());
1224 let pref_3 = 1.0
1225 / ((b3 * ans_10[1] - c3 * ans_10[0]).powi(2)
1226 + (a3 + b3 * ans_10[0] + c3 * ans_10[1]).powi(2));
1227 let ans_3 = [
1228 pref_3
1229 * (ans_10[0] * (a3.powi(2) + b3.powi(2) - c3.powi(2))
1230 + 2.0 * b3 * c3 * ans_10[1]
1231 + a3 * b3 * (1.0 + ans_10[0].powi(2) + ans_10[1].powi(2))),
1232 pref_3
1233 * (ans_10[1] * (a3.powi(2) - b3.powi(2) + c3.powi(2))
1234 + 2.0 * b3 * c3 * ans_10[0]
1235 + a3 * c3 * (1.0 + ans_10[0].powi(2) + ans_10[1].powi(2))),
1236 ];
1237
1238 assert_relative_eq!(ans_3[0], wrapped_poincare[0], epsilon = 1e-12);
1239 assert_relative_eq!(ans_3[1], wrapped_poincare[1], epsilon = 1e-12);
1240 }
1241
1242 #[test]
1243 fn ghost_near_side() {
1244 let mut rng = StdRng::seed_from_u64(4593);
1245 let side = f64::from(rng.random_range(0..12));
1246 let offset = 0.4;
1247 let boost = TwelveTwelve::CUSP_TO_EDGE - offset;
1248 let point = Hyperbolic::<3>::from_polar_coordinates(boost, PI / 12.0 + side * PI / 6.0);
1249 let point = Point::new(point);
1250
1251 let periodic = Periodic::new(1.0, TwelveTwelve {}).expect("hard-coded positive number");
1252
1253 let ghost_array = periodic.generate_ghosts(&point);
1254 let ghost = match side {
1255 8.0 => ghost_array[0],
1256 _ => ghost_array[1],
1257 };
1258
1259 let ans = Hyperbolic::<3>::from_polar_coordinates(
1260 TwelveTwelve::CUSP_TO_EDGE + offset,
1261 (side + 6.0).rem_euclid(12.0) * PI / 6.0 + PI / 12.0,
1262 );
1263
1264 assert_relative_eq!(ans, ghost.position, epsilon = 1e-12);
1265 assert_relative_eq!(
1266 TwelveTwelve::distance_to_boundary(&ghost.position),
1267 -TwelveTwelve::distance_to_boundary(&point.position),
1268 epsilon = 1e-12
1269 );
1270 }
1271
1272 #[test]
1273 fn ghost_near_vertex() {
1274 let offset_boost = 0.3;
1275 let v: f64 = TwelveTwelve::TWELVETWELVE;
1276 let point = Hyperbolic::<3>::from_polar_coordinates(v - offset_boost, 0.0);
1277 let point = Point::new(point);
1278 let periodic = Periodic::new(0.5, TwelveTwelve {}).expect("hard-coded positive number");
1279
1280 let ghost_array: ArrayVec<Point<Hyperbolic<3>>, 12> = periodic.generate_ghosts(&point);
1281 let ghost_10 = ghost_array[10];
1282
1283 let ans_10 = Hyperbolic::<3>::from_polar_coordinates(v + offset_boost, PI);
1284 assert_relative_eq!(ans_10, ghost_10.position, epsilon = 1e-10);
1285
1286 let ghost_4 = ghost_array[4];
1287 let ans_4 = Hyperbolic::from_minkowski_coordinates(
1288 [
1289 -offset_boost.sinh(),
1290 -(v.sinh()) * (offset_boost.cosh()),
1291 (v.cosh()) * (offset_boost.cosh()),
1292 ]
1293 .into(),
1294 );
1295 assert_relative_eq!(ans_4, ghost_4.position, epsilon = 1e-10);
1296
1297 let ghost_5 = ghost_array[5];
1298 let ans_5 = Hyperbolic::from_minkowski_coordinates(
1299 [
1300 -offset_boost.sinh(),
1301 (v.sinh()) * (offset_boost.cosh()),
1302 (v.cosh()) * (offset_boost.cosh()),
1303 ]
1304 .into(),
1305 );
1306 assert_relative_eq!(ans_5, ghost_5.position, epsilon = 1e-10);
1307 }
1308
1309 #[test]
1310 #[allow(clippy::too_many_lines, reason = "complicated function")]
1311 fn consistency_vertex() {
1312 let offset_boost = 0.3;
1313 let offset_angle = 0.1;
1314 let edge_boost: f64 = TwelveTwelve::TWELVETWELVE;
1315 let point =
1316 Hyperbolic::<3>::from_polar_coordinates(edge_boost - offset_boost, offset_angle);
1317 let point = Point::new(point);
1318 let periodic = Periodic::new(0.5, TwelveTwelve {}).expect("hard-coded positive number");
1319
1320 let ghost_array: ArrayVec<Point<Hyperbolic<3>>, 12> = periodic.generate_ghosts(&point);
1321
1322 let ghost_2_poincare = ghost_array[2].position.to_poincare();
1324 let (q_u, q_v) = (
1325 point.position.to_poincare()[0],
1326 point.position.to_poincare()[1],
1327 );
1328 let phi1 = PI / 6.0 + PI / 12.0;
1329 let phi6 = PI + PI / 12.0;
1330 let a = (1.0 + (PI / 6.0).cos() + 2.0 * ((PI / 6.0).cos()) * ((phi1 - phi6).cos()))
1331 / (1.0 - (PI / 6.0).cos());
1332 let d = (2.0 * ((PI / 6.0).cos()) * (phi1 - phi6).sin()) / (1.0 - (PI / 6.0).cos());
1333 let b = ((2.0 * ((PI / 6.0).cos()) * (1.0 + (PI / 6.0).cos())).sqrt())
1334 * (phi6.cos() + phi1.cos())
1335 / (1.0 - (PI / 6.0).cos());
1336 let c = ((2.0 * ((PI / 6.0).cos()) * (1.0 + (PI / 6.0).cos())).sqrt())
1337 * (phi6.sin() + phi1.sin())
1338 / (1.0 - (PI / 6.0).cos());
1339 let pref = 1.0 / ((b * q_v - c * q_u - d).powi(2) + (a + b * q_u + c * q_v).powi(2));
1340 let ans_2 = [
1341 pref * ((a * b - c * d) * (1.0 + q_u.powi(2) + q_v.powi(2))
1342 + q_u * (a.powi(2) + b.powi(2) - c.powi(2) - d.powi(2))
1343 + 2.0 * b * c * q_v
1344 - 2.0 * a * d * q_v),
1345 pref * ((a * c + b * d) * (1.0 + q_u.powi(2) + q_v.powi(2))
1346 + q_v * (a.powi(2) - b.powi(2) + c.powi(2) - d.powi(2))
1347 + 2.0 * b * c * q_u
1348 + 2.0 * a * d * q_u),
1349 ];
1350 assert_relative_eq!(ans_2[0], ghost_2_poincare[0], epsilon = 1e-12);
1351 assert_relative_eq!(ans_2[1], ghost_2_poincare[1], epsilon = 1e-12);
1352
1353 let ghost_3_poincare = ghost_array[3].position.to_poincare();
1354 let phi5 = 5.0 * PI / 6.0 + PI / 12.0;
1355 let phi10 = 10.0 * PI / 6.0 + PI / 12.0;
1356 let a = (1.0 + (PI / 6.0).cos() + 2.0 * ((PI / 6.0).cos()) * ((phi5 - phi10).cos()))
1357 / (1.0 - (PI / 6.0).cos());
1358 let d = (2.0 * ((PI / 6.0).cos()) * (phi10 - phi5).sin()) / (1.0 - (PI / 6.0).cos());
1359 let b = ((2.0 * ((PI / 6.0).cos()) * (1.0 + (PI / 6.0).cos())).sqrt())
1360 * (phi10.cos() + phi5.cos())
1361 / (1.0 - (PI / 6.0).cos());
1362 let c = ((2.0 * ((PI / 6.0).cos()) * (1.0 + (PI / 6.0).cos())).sqrt())
1363 * (phi10.sin() + phi5.sin())
1364 / (1.0 - (PI / 6.0).cos());
1365 let pref = 1.0 / ((b * q_v - c * q_u - d).powi(2) + (a + b * q_u + c * q_v).powi(2));
1366 let ans_3 = [
1367 pref * ((a * b - c * d) * (1.0 + q_u.powi(2) + q_v.powi(2))
1368 + q_u * (a.powi(2) + b.powi(2) - c.powi(2) - d.powi(2))
1369 + 2.0 * b * c * q_v
1370 - 2.0 * a * d * q_v),
1371 pref * ((a * c + b * d) * (1.0 + q_u.powi(2) + q_v.powi(2))
1372 + q_v * (a.powi(2) - b.powi(2) + c.powi(2) - d.powi(2))
1373 + 2.0 * b * c * q_u
1374 + 2.0 * a * d * q_u),
1375 ];
1376 assert_relative_eq!(ans_3[0], ghost_3_poincare[0], epsilon = 1e-12);
1377 assert_relative_eq!(ans_3[1], ghost_3_poincare[1], epsilon = 1e-12);
1378
1379 let ghost_4_poincare = ghost_array[4].position.to_poincare();
1381 let phi8 = 8.0 * PI / 6.0 + PI / 12.0;
1382 let a = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1383 let b = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi8.cos());
1384 let c = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi8.sin());
1385 let pref = 1.0
1386 / ((b * ans_2[1] - c * ans_2[0]).powi(2) + (a + b * ans_2[0] + c * ans_2[1]).powi(2));
1387 let ans_4 = [
1388 pref * (ans_2[0] * (a.powi(2) + b.powi(2) - c.powi(2))
1389 + 2.0 * b * c * ans_2[1]
1390 + a * b * (1.0 + ans_2[0].powi(2) + ans_2[1].powi(2))),
1391 pref * (ans_2[1] * (a.powi(2) - b.powi(2) + c.powi(2))
1392 + 2.0 * b * c * ans_2[0]
1393 + a * c * (1.0 + ans_2[0].powi(2) + ans_2[1].powi(2))),
1394 ];
1395 assert_relative_eq!(ans_4[0], ghost_4_poincare[0], epsilon = 1e-12);
1396 assert_relative_eq!(ans_4[1], ghost_4_poincare[1], epsilon = 1e-12);
1397
1398 let ghost_5_poincare = ghost_array[5].position.to_poincare();
1399 let phi3 = 3.0 * PI / 6.0 + PI / 12.0;
1400 let a = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1401 let b = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi3.cos());
1402 let c = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi3.sin());
1403 let pref = 1.0
1404 / ((b * ans_3[1] - c * ans_3[0]).powi(2) + (a + b * ans_3[0] + c * ans_3[1]).powi(2));
1405 let ans_5 = [
1406 pref * (ans_3[0] * (a.powi(2) + b.powi(2) - c.powi(2))
1407 + 2.0 * b * c * ans_3[1]
1408 + a * b * (1.0 + ans_3[0].powi(2) + ans_3[1].powi(2))),
1409 pref * (ans_3[1] * (a.powi(2) - b.powi(2) + c.powi(2))
1410 + 2.0 * b * c * ans_3[0]
1411 + a * c * (1.0 + ans_3[0].powi(2) + ans_3[1].powi(2))),
1412 ];
1413 assert_relative_eq!(ans_5[0], ghost_5_poincare[0], epsilon = 1e-12);
1414 assert_relative_eq!(ans_5[1], ghost_5_poincare[1], epsilon = 1e-12);
1415
1416 let ghost_6_poincare = ghost_array[6].position.to_poincare();
1418 let a = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1419 let b = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi3.cos());
1420 let c = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi3.sin());
1421 let pref = 1.0
1422 / ((b * ans_4[1] - c * ans_4[0]).powi(2) + (a + b * ans_4[0] + c * ans_4[1]).powi(2));
1423 let ans_6 = [
1424 pref * (ans_4[0] * (a.powi(2) + b.powi(2) - c.powi(2))
1425 + 2.0 * b * c * ans_4[1]
1426 + a * b * (1.0 + ans_4[0].powi(2) + ans_4[1].powi(2))),
1427 pref * (ans_4[1] * (a.powi(2) - b.powi(2) + c.powi(2))
1428 + 2.0 * b * c * ans_4[0]
1429 + a * c * (1.0 + ans_4[0].powi(2) + ans_4[1].powi(2))),
1430 ];
1431 assert_relative_eq!(ans_6[0], ghost_6_poincare[0], epsilon = 1e-12);
1432 assert_relative_eq!(ans_6[1], ghost_6_poincare[1], epsilon = 1e-12);
1433
1434 let ghost_7_poincare = ghost_array[7].position.to_poincare();
1435 let a = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1436 let b = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi8.cos());
1437 let c = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi8.sin());
1438 let pref = 1.0
1439 / ((b * ans_5[1] - c * ans_5[0]).powi(2) + (a + b * ans_5[0] + c * ans_5[1]).powi(2));
1440 let ans_7 = [
1441 pref * (ans_5[0] * (a.powi(2) + b.powi(2) - c.powi(2))
1442 + 2.0 * b * c * ans_5[1]
1443 + a * b * (1.0 + ans_5[0].powi(2) + ans_5[1].powi(2))),
1444 pref * (ans_5[1] * (a.powi(2) - b.powi(2) + c.powi(2))
1445 + 2.0 * b * c * ans_5[0]
1446 + a * c * (1.0 + ans_5[0].powi(2) + ans_5[1].powi(2))),
1447 ];
1448 assert_relative_eq!(ans_7[0], ghost_7_poincare[0], epsilon = 1e-12);
1449 assert_relative_eq!(ans_7[1], ghost_7_poincare[1], epsilon = 1e-12);
1450
1451 let ghost_8_poincare = ghost_array[8].position.to_poincare();
1453 let a = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1454 let b = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi10.cos());
1455 let c = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi10.sin());
1456 let pref = 1.0
1457 / ((b * ans_6[1] - c * ans_6[0]).powi(2) + (a + b * ans_6[0] + c * ans_6[1]).powi(2));
1458 let ans_8 = [
1459 pref * (ans_6[0] * (a.powi(2) + b.powi(2) - c.powi(2))
1460 + 2.0 * b * c * ans_6[1]
1461 + a * b * (1.0 + ans_6[0].powi(2) + ans_6[1].powi(2))),
1462 pref * (ans_6[1] * (a.powi(2) - b.powi(2) + c.powi(2))
1463 + 2.0 * b * c * ans_6[0]
1464 + a * c * (1.0 + ans_6[0].powi(2) + ans_6[1].powi(2))),
1465 ];
1466 assert_relative_eq!(ans_8[0], ghost_8_poincare[0], epsilon = 1e-12);
1467 assert_relative_eq!(ans_8[1], ghost_8_poincare[1], epsilon = 1e-12);
1468
1469 let ghost_9_poincare = ghost_array[9].position.to_poincare();
1470 let a = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1471 let b = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi1.cos());
1472 let c = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi1.sin());
1473 let pref = 1.0
1474 / ((b * ans_7[1] - c * ans_7[0]).powi(2) + (a + b * ans_7[0] + c * ans_7[1]).powi(2));
1475 let ans_9 = [
1476 pref * (ans_7[0] * (a.powi(2) + b.powi(2) - c.powi(2))
1477 + 2.0 * b * c * ans_7[1]
1478 + a * b * (1.0 + ans_7[0].powi(2) + ans_7[1].powi(2))),
1479 pref * (ans_7[1] * (a.powi(2) - b.powi(2) + c.powi(2))
1480 + 2.0 * b * c * ans_7[0]
1481 + a * c * (1.0 + ans_7[0].powi(2) + ans_7[1].powi(2))),
1482 ];
1483 assert_relative_eq!(ans_9[0], ghost_9_poincare[0], epsilon = 1e-12);
1484 assert_relative_eq!(ans_9[1], ghost_9_poincare[1], epsilon = 1e-12);
1485
1486 let ghost_10_poincare = ghost_array[10].position.to_poincare();
1488 let a = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1489 let b = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi5.cos());
1490 let c = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi5.sin());
1491 let pref = 1.0
1492 / ((b * ans_8[1] - c * ans_8[0]).powi(2) + (a + b * ans_8[0] + c * ans_8[1]).powi(2));
1493 let ans_10 = [
1494 pref * (ans_8[0] * (a.powi(2) + b.powi(2) - c.powi(2))
1495 + 2.0 * b * c * ans_8[1]
1496 + a * b * (1.0 + ans_8[0].powi(2) + ans_8[1].powi(2))),
1497 pref * (ans_8[1] * (a.powi(2) - b.powi(2) + c.powi(2))
1498 + 2.0 * b * c * ans_8[0]
1499 + a * c * (1.0 + ans_8[0].powi(2) + ans_8[1].powi(2))),
1500 ];
1501 assert_relative_eq!(ans_10[0], ghost_10_poincare[0], epsilon = 1e-12);
1502 assert_relative_eq!(ans_10[1], ghost_10_poincare[1], epsilon = 1e-12);
1503
1504 let ghost_1_poincare = ghost_array[1].position.to_poincare();
1506 let a = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1507 let b = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi5.cos());
1508 let c = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi5.sin());
1509 let pref = 1.0 / ((b * q_v - c * q_u).powi(2) + (a + b * q_u + c * q_v).powi(2));
1510 let ans_1 = [
1511 pref * (q_u * (a.powi(2) + b.powi(2) - c.powi(2))
1512 + 2.0 * b * c * q_v
1513 + a * b * (1.0 + q_u.powi(2) + q_v.powi(2))),
1514 pref * (q_v * (a.powi(2) - b.powi(2) + c.powi(2))
1515 + 2.0 * b * c * q_u
1516 + a * c * (1.0 + q_u.powi(2) + q_v.powi(2))),
1517 ];
1518 assert_relative_eq!(ans_1[0], ghost_1_poincare[0], epsilon = 1e-12);
1519 assert_relative_eq!(ans_1[1], ghost_1_poincare[1], epsilon = 1e-12);
1520
1521 let ghost_0_poincare = ghost_array[0].position.to_poincare();
1522 let a = ((1.0 + (PI / 6.0).cos()) / (1.0 - (PI / 6.0).cos())).sqrt();
1523 let b = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi6.cos());
1524 let c = ((2.0 * ((PI / 6.0).cos())) / (1.0 - (PI / 6.0).cos())).sqrt() * (phi6.sin());
1525 let pref = 1.0 / ((b * q_v - c * q_u).powi(2) + (a + b * q_u + c * q_v).powi(2));
1526 let ans_0 = [
1527 pref * (q_u * (a.powi(2) + b.powi(2) - c.powi(2))
1528 + 2.0 * b * c * q_v
1529 + a * b * (1.0 + q_u.powi(2) + q_v.powi(2))),
1530 pref * (q_v * (a.powi(2) - b.powi(2) + c.powi(2))
1531 + 2.0 * b * c * q_u
1532 + a * c * (1.0 + q_u.powi(2) + q_v.powi(2))),
1533 ];
1534 assert_relative_eq!(ans_0[0], ghost_0_poincare[0], epsilon = 1e-12);
1535 assert_relative_eq!(ans_0[1], ghost_0_poincare[1], epsilon = 1e-12);
1536 }
1537
1538 #[test]
1539 fn wraps_orientation() {
1540 let angle_offset: f64 = 0.1;
1541 let boost = ((TwelveTwelve::TWELVETWELVE).tanh() * ((PI / 6.0).sin())
1542 / ((angle_offset).sin() + (PI / 6.0 - angle_offset).sin()))
1543 .atanh()
1544 + 0.1;
1545 let point = Hyperbolic::<3>::from_polar_coordinates(boost, angle_offset + PI / 6.0);
1546 let tangent = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1547 &Hyperbolic::<3>::from_polar_coordinates(TwelveTwelve::TWELVETWELVE, PI / 6.0),
1548 &point,
1549 );
1550 let oriented_point = OrientedHyperbolicPoint {
1551 position: point,
1552 orientation: Angle::from(13.0 * PI / 12.0 + tangent),
1553 };
1554 let periodic = Periodic::new(0.5, TwelveTwelve {}).expect("hard-coded positive number");
1555 let wrapped_point = periodic.wrap(oriented_point).expect("hard-coded");
1556
1557 let answer = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1558 &Hyperbolic::<3>::from_polar_coordinates(TwelveTwelve::TWELVETWELVE, 8.0 * PI / 6.0),
1559 &wrapped_point.position,
1560 );
1561
1562 assert_relative_eq!(
1564 wrapped_point.orientation.theta,
1565 5.0 * PI / 12.0 + answer,
1566 epsilon = 1e-12
1567 );
1568 }
1569
1570 #[test]
1571 fn wraps_nearby_orientations() {
1572 let offset = 0.000_001;
1573 let boost = TwelveTwelve::CUSP_TO_EDGE + 0.4;
1574 let point_1 =
1575 Hyperbolic::<3>::from_polar_coordinates(boost, 7.0 * PI / 6.0 + PI / 12.0 - offset);
1576 let point_2 =
1577 Hyperbolic::<3>::from_polar_coordinates(boost, 7.0 * PI / 6.0 + PI / 12.0 + offset);
1578
1579 let oriented_point_1 = OrientedHyperbolicPoint {
1580 position: point_1,
1581 orientation: Angle::from(PI),
1582 };
1583 let oriented_point_2 = OrientedHyperbolicPoint {
1584 position: point_2,
1585 orientation: Angle::from(PI),
1586 };
1587 let periodic = Periodic::new(0.5, TwelveTwelve {}).expect("hard-coded positive number");
1588 let wrapped_point_1 = periodic.wrap(oriented_point_1).expect("hard-coded");
1589 let wrapped_point_2 = periodic.wrap(oriented_point_2).expect("hard-coded");
1590
1591 assert_relative_eq!(
1593 wrapped_point_1.position.coordinates()[0],
1594 wrapped_point_2.position.coordinates()[0],
1595 epsilon = 1e-4
1596 );
1597 assert_relative_eq!(
1598 wrapped_point_1.position.coordinates()[1],
1599 wrapped_point_2.position.coordinates()[1],
1600 epsilon = 1e-4
1601 );
1602 assert_relative_eq!(
1603 wrapped_point_1.position.coordinates()[2],
1604 wrapped_point_2.position.coordinates()[2],
1605 epsilon = 1e-4
1606 );
1607
1608 assert_relative_eq!(
1610 wrapped_point_1.orientation.theta.rem_euclid(2.0 * PI),
1611 wrapped_point_2.orientation.theta.rem_euclid(2.0 * PI),
1612 epsilon = 1e-4
1613 );
1614 }
1615
1616 #[test]
1617 fn wraps_vertex_orientation() {
1618 let boost = TwelveTwelve::TWELVETWELVE + 0.5;
1619 let ve = TwelveTwelve::TWELVETWELVE;
1620 let point = Hyperbolic::<3>::from_polar_coordinates(boost, PI / 6.0);
1621
1622 let (int_1, _or1) =
1623 OrientedHyperbolicPoint::<3, Angle>::intersection_point(PI / 12.0, ve - boost);
1624 let intersection = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1625 (ve.cosh()) * (int_1.sinh()) * ((PI / 6.0).cos()) * ((PI / 12.0).cos())
1626 - (int_1.sinh()) * ((PI / 6.0).sin()) * ((PI / 12.0).sin())
1627 + (ve.sinh()) * (int_1.cosh()) * ((PI / 6.0).cos()),
1628 (ve.cosh()) * (int_1.sinh()) * ((PI / 6.0).sin()) * ((PI / 12.0).cos())
1629 + (int_1.sinh()) * ((PI / 6.0).cos()) * ((PI / 12.0).sin())
1630 + (ve.sinh()) * (int_1.cosh()) * ((PI / 6.0).sin()),
1631 (ve.sinh()) * (int_1.sinh()) * ((PI / 12.0).cos()) + (ve.cosh()) * (int_1.cosh()),
1632 ]));
1633 let pt_v_to_int = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1634 &Hyperbolic::<3>::from_polar_coordinates(ve, PI / 6.0),
1635 &intersection,
1636 );
1637 let pt_int_to_pnt =
1638 OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(&intersection, &point);
1639
1640 let oriented_point = OrientedHyperbolicPoint {
1641 position: point,
1642 orientation: Angle::from(pt_v_to_int + pt_int_to_pnt + 3.0 * PI / 12.0),
1643 };
1644 let periodic = Periodic::new(0.5, TwelveTwelve {}).expect("hard-coded positive number");
1645 let wrapped_point = periodic.wrap(oriented_point).expect("hard-coded");
1646
1647 let new_intersection = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1649 (ve.cosh()) * (int_1.sinh()) * ((7.0 * PI / 6.0).cos()) * ((13.0 * PI / 12.0).cos())
1650 - (int_1.sinh()) * ((7.0 * PI / 6.0).sin()) * ((13.0 * PI / 12.0).sin())
1651 + (ve.sinh()) * (int_1.cosh()) * ((7.0 * PI / 6.0).cos()),
1652 (ve.cosh()) * (int_1.sinh()) * ((7.0 * PI / 6.0).sin()) * ((13.0 * PI / 12.0).cos())
1653 + (int_1.sinh()) * ((7.0 * PI / 6.0).cos()) * ((13.0 * PI / 12.0).sin())
1654 + (ve.sinh()) * (int_1.cosh()) * ((7.0 * PI / 6.0).sin()),
1655 (ve.sinh()) * (int_1.sinh()) * ((13.0 * PI / 12.0).cos())
1656 + (ve.cosh()) * (int_1.cosh()),
1657 ]));
1658 let pt_v_to_n_int = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1659 &Hyperbolic::<3>::from_polar_coordinates(ve, 7.0 * PI / 6.0),
1660 &new_intersection,
1661 );
1662 let pt_n_int_to_w_pnt = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1663 &new_intersection,
1664 &wrapped_point.position,
1665 );
1666 assert_relative_eq!(
1667 3.0 * PI / 12.0 + pt_v_to_n_int + pt_n_int_to_w_pnt,
1668 wrapped_point.orientation.theta,
1669 epsilon = 1e-10
1670 );
1671 }
1672
1673 #[test]
1674 fn wraps_orientation_vertex_non_center() {
1675 let offset_boost: f64 = 0.2;
1676 let angle_offset = 0.01;
1677 let v: f64 = TwelveTwelve::TWELVETWELVE;
1678 let point = Hyperbolic::from_minkowski_coordinates(
1679 [
1680 (v.sinh()) * (offset_boost.cosh())
1681 + (v.cosh()) * (offset_boost.sinh()) * ((angle_offset + 3.0 * PI / 2.0).cos()),
1682 (offset_boost.sinh()) * ((angle_offset + 3.0 * PI / 2.0).sin()),
1683 (v.cosh()) * (offset_boost.cosh())
1684 + (v.sinh()) * (offset_boost.sinh()) * ((angle_offset + 3.0 * PI / 2.0).cos()),
1685 ]
1686 .into(),
1687 );
1688
1689 let (int_1, _or1) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
1690 PI / 12.0 + angle_offset,
1691 offset_boost,
1692 );
1693
1694 let intersection = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1695 (v.cosh()) * (int_1.sinh()) * ((17.0 * PI / 12.0).cos()) + (v.sinh()) * (int_1.cosh()),
1696 (int_1.sinh()) * ((17.0 * PI / 12.0).sin()),
1697 (v.sinh()) * (int_1.sinh()) * ((17.0 * PI / 12.0).cos()) + (v.cosh()) * (int_1.cosh()),
1698 ]));
1699 let pt_v_to_int = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1700 &Hyperbolic::<3>::from_polar_coordinates(v, 0.0),
1701 &intersection,
1702 );
1703 let pt_int_to_pnt =
1704 OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(&intersection, &point);
1705
1706 let oriented_point = OrientedHyperbolicPoint {
1707 position: point,
1708 orientation: Angle::from(pt_v_to_int + pt_int_to_pnt + 17.0 * PI / 12.0),
1709 };
1710
1711 let periodic = Periodic::new(0.5, TwelveTwelve {}).expect("hard-coded positive number");
1712 let wrapped_point = periodic.wrap(oriented_point).expect("hard-coded");
1713
1714 let new_intersection = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1716 (v.cosh()) * (int_1.sinh()) * ((PI / 2.0).cos()) * ((11.0 * PI / 12.0).cos())
1717 - (int_1.sinh()) * ((PI / 2.0).sin()) * ((11.0 * PI / 12.0).sin())
1718 + (v.sinh()) * (int_1.cosh()) * ((PI / 2.0).cos()),
1719 (v.cosh()) * (int_1.sinh()) * ((PI / 2.0).sin()) * ((11.0 * PI / 12.0).cos())
1720 + (int_1.sinh()) * ((PI / 2.0).cos()) * ((11.0 * PI / 12.0).sin())
1721 + (v.sinh()) * (int_1.cosh()) * ((PI / 2.0).sin()),
1722 (v.sinh()) * (int_1.sinh()) * ((11.0 * PI / 12.0).cos()) + (v.cosh()) * (int_1.cosh()),
1723 ]));
1724 let pt_v_to_n_int = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1725 &Hyperbolic::<3>::from_polar_coordinates(v, PI / 2.0),
1726 &new_intersection,
1727 );
1728 let pt_n_int_to_w_pnt = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1729 &new_intersection,
1730 &wrapped_point.position,
1731 );
1732
1733 let mut answer_orientation = 17.0 * PI / 12.0 + pt_v_to_n_int + pt_n_int_to_w_pnt;
1734 while answer_orientation > PI {
1735 answer_orientation -= 2.0 * PI;
1736 }
1737 while answer_orientation <= -PI {
1738 answer_orientation += 2.0 * PI;
1739 }
1740 assert_relative_eq!(
1741 answer_orientation,
1742 wrapped_point.orientation.theta,
1743 epsilon = 1e-12
1744 );
1745 }
1746
1747 #[test]
1748 #[allow(clippy::too_many_lines, reason = "complicated function")]
1749 fn ghost_near_zeroth_vertex_orientation() {
1750 let offset_boost = 0.3;
1751 let v: f64 = TwelveTwelve::TWELVETWELVE;
1752 let point = Hyperbolic::<3>::from_polar_coordinates(v - offset_boost, 0.1);
1753 let point = OrientedHyperbolicPoint {
1754 position: point,
1755 orientation: Angle::from(0.0),
1756 };
1757
1758 let point_in_center = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1760 (v.cosh()) * point.position.coordinates()[0]
1761 - (v.sinh()) * point.position.coordinates()[2],
1762 point.position.coordinates()[1],
1763 -(v.sinh()) * point.position.coordinates()[0]
1764 + (v.cosh()) * point.position.coordinates()[2],
1765 ]));
1766 let (angle_c, boost_c) = (
1767 point_in_center.coordinates()[1]
1768 .atan2(point_in_center.coordinates()[0])
1769 .rem_euclid(2.0 * PI),
1770 (point_in_center.coordinates()[2]).acosh(),
1771 );
1772 let (int_o, _o_o) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
1773 angle_c - 11.0 * PI / 12.0,
1774 boost_c,
1775 );
1776
1777 let intersection_o = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1778 (v.cosh()) * (int_o.sinh()) * ((11.0 * PI / 12.0).cos()) + (v.sinh()) * (int_o.cosh()),
1779 (int_o.sinh()) * ((11.0 * PI / 12.0).sin()),
1780 (v.sinh()) * (int_o.sinh()) * ((11.0 * PI / 12.0).cos()) + (v.cosh()) * (int_o.cosh()),
1781 ]));
1782
1783 let int_to_v_o = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1784 &Hyperbolic::<3>::from_polar_coordinates(v, 0.0),
1785 &intersection_o,
1786 );
1787 let tang_o = 11.0 * PI / 12.0 + int_to_v_o;
1788 let relative_orientation_upper =
1789 (OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1790 &point.position,
1791 &intersection_o,
1792 ) - tang_o)
1793 .rem_euclid(2.0 * PI);
1794
1795 let (int_ol, _o_ol) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
1796 13.0 * PI / 12.0 - angle_c,
1797 boost_c,
1798 );
1799
1800 let intersection_ol = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1801 (v.cosh()) * (int_ol.sinh()) * ((13.0 * PI / 12.0).cos())
1802 + (v.sinh()) * (int_ol.cosh()),
1803 (int_ol.sinh()) * ((13.0 * PI / 12.0).sin()),
1804 (v.sinh()) * (int_ol.sinh()) * ((13.0 * PI / 12.0).cos())
1805 + (v.cosh()) * (int_ol.cosh()),
1806 ]));
1807
1808 let int_to_v_ol = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1809 &Hyperbolic::<3>::from_polar_coordinates(v, 0.0),
1810 &intersection_ol,
1811 );
1812 let tang_ol = 13.0 * PI / 12.0 + int_to_v_ol;
1813 let relative_orientation_lower =
1814 (OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1815 &point.position,
1816 &intersection_ol,
1817 ) - tang_ol)
1818 .rem_euclid(2.0 * PI);
1819
1820 let periodic = Periodic::new(0.5, TwelveTwelve {}).expect("hard-coded positive number");
1821
1822 let ghost_array = periodic.generate_ghosts(&point);
1823
1824 let ghost_0 = ghost_array[0];
1825 let ghost_0_center = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1826 (v.cosh()) * ((7.0 * PI / 6.0).cos()) * ghost_0.position.coordinates()[0]
1827 + (v.cosh()) * ((7.0 * PI / 6.0).sin()) * ghost_0.position.coordinates()[1]
1828 - (v.sinh()) * ghost_0.position.coordinates()[2],
1829 -((7.0 * PI / 6.0).sin()) * ghost_0.position.coordinates()[0]
1830 + ((7.0 * PI / 6.0).cos()) * ghost_0.position.coordinates()[1],
1831 (v.cosh()) * ghost_0.position.coordinates()[2]
1832 - (v.sinh()) * ((7.0 * PI / 6.0).cos()) * ghost_0.position.coordinates()[0]
1833 - (v.sinh()) * ((7.0 * PI / 6.0).sin()) * ghost_0.position.coordinates()[1],
1834 ]));
1835 let (angle_0, boost_0) = (
1836 ghost_0_center.coordinates()[1].atan2(ghost_0_center.coordinates()[0]),
1837 (ghost_0_center.coordinates()[2]).acosh(),
1838 );
1839 let (int_0, _o_0) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
1840 13.0 * PI / 12.0 - angle_0,
1841 boost_0,
1842 );
1843 let int_c = Hyperbolic::<3>::from_polar_coordinates(int_0, 13.0 * PI / 12.0);
1845 let int_c_plus = Hyperbolic::<3>::from_polar_coordinates(int_0 + 0.05, 13.0 * PI / 12.0);
1846 let int_c_minus = Hyperbolic::<3>::from_polar_coordinates(int_0 - 0.05, 13.0 * PI / 12.0);
1847 assert!(ghost_0_center.distance(&int_c) < ghost_0_center.distance(&int_c_minus));
1848 assert!(ghost_0_center.distance(&int_c) < ghost_0_center.distance(&int_c_plus));
1849
1850 let intersection_0 = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1851 (v.cosh()) * ((7.0 * PI / 6.0).cos()) * (int_0.sinh()) * ((13.0 * PI / 12.0).cos())
1852 - ((7.0 * PI / 6.0).sin()) * (int_0.sinh()) * ((13.0 * PI / 12.0).sin())
1853 + (v.sinh()) * ((7.0 * PI / 6.0).cos()) * (int_0.cosh()),
1854 (v.cosh()) * ((7.0 * PI / 6.0).sin()) * (int_0.sinh()) * ((13.0 * PI / 12.0).cos())
1855 + ((7.0 * PI / 6.0).cos()) * (int_0.sinh()) * ((13.0 * PI / 12.0).sin())
1856 + (v.sinh()) * ((7.0 * PI / 6.0).sin()) * (int_0.cosh()),
1857 (v.sinh()) * (int_0.sinh()) * ((13.0 * PI / 12.0).cos()) + (v.cosh()) * (int_0.cosh()),
1858 ]));
1859
1860 let blip = 0.05;
1862 let intersection_plus = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1863 (v.cosh())
1864 * ((7.0 * PI / 6.0).cos())
1865 * ((int_0 + blip).sinh())
1866 * ((13.0 * PI / 12.0).cos())
1867 - ((7.0 * PI / 6.0).sin()) * ((int_0 + blip).sinh()) * ((13.0 * PI / 12.0).sin())
1868 + (v.sinh()) * ((7.0 * PI / 6.0).cos()) * ((int_0 + blip).cosh()),
1869 (v.cosh())
1870 * ((7.0 * PI / 6.0).sin())
1871 * ((int_0 + blip).sinh())
1872 * ((13.0 * PI / 12.0).cos())
1873 + ((7.0 * PI / 6.0).cos()) * ((int_0 + blip).sinh()) * ((13.0 * PI / 12.0).sin())
1874 + (v.sinh()) * ((7.0 * PI / 6.0).sin()) * ((int_0 + blip).cosh()),
1875 (v.sinh()) * ((int_0 + blip).sinh()) * ((13.0 * PI / 12.0).cos())
1876 + (v.cosh()) * ((int_0 + blip).cosh()),
1877 ]));
1878 let intersection_minus = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1879 (v.cosh())
1880 * ((7.0 * PI / 6.0).cos())
1881 * ((int_0 - blip).sinh())
1882 * ((13.0 * PI / 12.0).cos())
1883 - ((7.0 * PI / 6.0).sin()) * ((int_0 - blip).sinh()) * ((13.0 * PI / 12.0).sin())
1884 + (v.sinh()) * ((7.0 * PI / 6.0).cos()) * ((int_0 - blip).cosh()),
1885 (v.cosh())
1886 * ((7.0 * PI / 6.0).sin())
1887 * ((int_0 - blip).sinh())
1888 * ((13.0 * PI / 12.0).cos())
1889 + ((7.0 * PI / 6.0).cos()) * ((int_0 - blip).sinh()) * ((13.0 * PI / 12.0).sin())
1890 + (v.sinh()) * ((7.0 * PI / 6.0).sin()) * ((int_0 - blip).cosh()),
1891 (v.sinh()) * ((int_0 - blip).sinh()) * ((13.0 * PI / 12.0).cos())
1892 + (v.cosh()) * ((int_0 - blip).cosh()),
1893 ]));
1894 assert!(
1895 ghost_0.position.distance(&intersection_0)
1896 < ghost_0.position.distance(&intersection_minus)
1897 );
1898 assert!(
1899 ghost_0.position.distance(&intersection_0)
1900 < ghost_0.position.distance(&intersection_plus)
1901 );
1902
1903 let int_to_ghost = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1904 &intersection_0,
1905 &ghost_0.position,
1906 );
1907 let v_to_int = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1908 &Hyperbolic::<3>::from_polar_coordinates(v, 7.0 * PI / 6.0),
1909 &intersection_0,
1910 );
1911 let tang_0 = 3.0 * PI / 12.0 + v_to_int;
1912 let ans_0 = (relative_orientation_upper + tang_0 + int_to_ghost).rem_euclid(2.0 * PI);
1913 assert_relative_eq!(
1914 ans_0.rem_euclid(2.0 * PI),
1915 ghost_0.orientation.theta.rem_euclid(2.0 * PI),
1916 epsilon = 1e-12
1917 );
1918
1919 let ghost_1 = ghost_array[1];
1922 let ghost_1_center = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1923 (v.cosh()) * ((5.0 * PI / 6.0).cos()) * ghost_1.position.coordinates()[0]
1924 + (v.cosh()) * ((5.0 * PI / 6.0).sin()) * ghost_1.position.coordinates()[1]
1925 - (v.sinh()) * ghost_1.position.coordinates()[2],
1926 -((5.0 * PI / 6.0).sin()) * ghost_1.position.coordinates()[0]
1927 + ((5.0 * PI / 6.0).cos()) * ghost_1.position.coordinates()[1],
1928 (v.cosh()) * ghost_1.position.coordinates()[2]
1929 - (v.sinh()) * ((5.0 * PI / 6.0).cos()) * ghost_1.position.coordinates()[0]
1930 - (v.sinh()) * ((5.0 * PI / 6.0).sin()) * ghost_1.position.coordinates()[1],
1931 ]));
1932 let (angle_1, boost_1) = (
1933 ghost_1_center.coordinates()[1].atan2(ghost_1_center.coordinates()[0]),
1934 (ghost_1_center.coordinates()[2]).acosh(),
1935 );
1936 let (int_1, _o_1) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
1937 angle_1 - 11.0 * PI / 12.0,
1938 boost_1,
1939 );
1940 let intersection_1 = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1941 (v.cosh()) * ((5.0 * PI / 6.0).cos()) * (int_1.sinh()) * ((11.0 * PI / 12.0).cos())
1942 - ((5.0 * PI / 6.0).sin()) * (int_1.sinh()) * ((11.0 * PI / 12.0).sin())
1943 + (v.sinh()) * ((5.0 * PI / 6.0).cos()) * (int_1.cosh()),
1944 (v.cosh()) * ((5.0 * PI / 6.0).sin()) * (int_1.sinh()) * ((11.0 * PI / 12.0).cos())
1945 + ((5.0 * PI / 6.0).cos()) * (int_1.sinh()) * ((11.0 * PI / 12.0).sin())
1946 + (v.sinh()) * ((5.0 * PI / 6.0).sin()) * (int_1.cosh()),
1947 (v.sinh()) * (int_1.sinh()) * ((11.0 * PI / 12.0).cos()) + (v.cosh()) * (int_1.cosh()),
1948 ]));
1949 let int_to_ghost_1 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1950 &intersection_1,
1951 &ghost_1.position,
1952 );
1953 let v_to_int_1 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1954 &Hyperbolic::<3>::from_polar_coordinates(v, 5.0 * PI / 6.0),
1955 &intersection_1,
1956 );
1957 let tang_1 = 21.0 * PI / 12.0 + v_to_int_1;
1958 let ans_1 = (relative_orientation_lower + tang_1 + int_to_ghost_1).rem_euclid(2.0 * PI);
1959 assert_relative_eq!(ans_1, ghost_1.orientation.theta, epsilon = 1e-12);
1960
1961 let ghost_2 = ghost_array[2];
1963 let ghost_2_center = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1964 (v.cosh()) * ((PI / 3.0).cos()) * ghost_2.position.coordinates()[0]
1965 + (v.cosh()) * ((PI / 3.0).sin()) * ghost_2.position.coordinates()[1]
1966 - (v.sinh()) * ghost_2.position.coordinates()[2],
1967 -((PI / 3.0).sin()) * ghost_2.position.coordinates()[0]
1968 + ((PI / 3.0).cos()) * ghost_2.position.coordinates()[1],
1969 (v.cosh()) * ghost_2.position.coordinates()[2]
1970 - (v.sinh()) * ((PI / 3.0).cos()) * ghost_2.position.coordinates()[0]
1971 - (v.sinh()) * ((PI / 3.0).sin()) * ghost_2.position.coordinates()[1],
1972 ]));
1973 let (angle_2, boost_2) = (
1974 ghost_2_center.coordinates()[1].atan2(ghost_2_center.coordinates()[0]),
1975 (ghost_2_center.coordinates()[2]).acosh(),
1976 );
1977 let (int_2, _o_1) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
1978 angle_2 - 15.0 * PI / 12.0,
1979 boost_2,
1980 );
1981 let intersection_2 = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
1982 (v.cosh()) * ((PI / 3.0).cos()) * (int_2.sinh()) * ((15.0 * PI / 12.0).cos())
1983 - ((PI / 3.0).sin()) * (int_2.sinh()) * ((15.0 * PI / 12.0).sin())
1984 + (v.sinh()) * ((PI / 3.0).cos()) * (int_2.cosh()),
1985 (v.cosh()) * ((PI / 3.0).sin()) * (int_2.sinh()) * ((15.0 * PI / 12.0).cos())
1986 + ((PI / 3.0).cos()) * (int_2.sinh()) * ((15.0 * PI / 12.0).sin())
1987 + (v.sinh()) * ((PI / 3.0).sin()) * (int_2.cosh()),
1988 (v.sinh()) * (int_2.sinh()) * ((15.0 * PI / 12.0).cos()) + (v.cosh()) * (int_2.cosh()),
1989 ]));
1990 let int_to_ghost_2 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1991 &intersection_2,
1992 &ghost_2.position,
1993 );
1994 let v_to_int_2 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
1995 &Hyperbolic::<3>::from_polar_coordinates(v, PI / 3.0),
1996 &intersection_2,
1997 );
1998 let tang_2 = 19.0 * PI / 12.0 + v_to_int_2;
1999 let ans_2 = (relative_orientation_upper + tang_2 + int_to_ghost_2).rem_euclid(2.0 * PI);
2000 assert_relative_eq!(
2001 ans_2.rem_euclid(2.0 * PI),
2002 ghost_2.orientation.theta.rem_euclid(2.0 * PI),
2003 epsilon = 1e-12
2004 );
2005
2006 let ghost_3 = ghost_array[3];
2008 let ghost_3_center = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2009 (v.cosh()) * ((10.0 * PI / 6.0).cos()) * ghost_3.position.coordinates()[0]
2010 + (v.cosh()) * ((10.0 * PI / 6.0).sin()) * ghost_3.position.coordinates()[1]
2011 - (v.sinh()) * ghost_3.position.coordinates()[2],
2012 -((10.0 * PI / 6.0).sin()) * ghost_3.position.coordinates()[0]
2013 + ((10.0 * PI / 6.0).cos()) * ghost_3.position.coordinates()[1],
2014 (v.cosh()) * ghost_3.position.coordinates()[2]
2015 - (v.sinh()) * ((10.0 * PI / 6.0).cos()) * ghost_3.position.coordinates()[0]
2016 - (v.sinh()) * ((10.0 * PI / 6.0).sin()) * ghost_3.position.coordinates()[1],
2017 ]));
2018 let (angle_3, boost_3) = (
2019 ghost_3_center.coordinates()[1].atan2(ghost_3_center.coordinates()[0]),
2020 (ghost_3_center.coordinates()[2]).acosh(),
2021 );
2022 let (int_3, _o_1) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
2023 9.0 * PI / 12.0 - angle_3,
2024 boost_3,
2025 );
2026 let intersection_3 = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2027 (v.cosh()) * ((10.0 * PI / 6.0).cos()) * (int_3.sinh()) * ((9.0 * PI / 12.0).cos())
2028 - ((10.0 * PI / 6.0).sin()) * (int_3.sinh()) * ((9.0 * PI / 12.0).sin())
2029 + (v.sinh()) * ((10.0 * PI / 6.0).cos()) * (int_3.cosh()),
2030 (v.cosh()) * ((10.0 * PI / 6.0).sin()) * (int_3.sinh()) * ((9.0 * PI / 12.0).cos())
2031 + ((10.0 * PI / 6.0).cos()) * (int_3.sinh()) * ((9.0 * PI / 12.0).sin())
2032 + (v.sinh()) * ((10.0 * PI / 6.0).sin()) * (int_3.cosh()),
2033 (v.sinh()) * (int_3.sinh()) * ((9.0 * PI / 12.0).cos()) + (v.cosh()) * (int_3.cosh()),
2034 ]));
2035 let int_to_ghost_3 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2036 &intersection_3,
2037 &ghost_3.position,
2038 );
2039 let v_to_int_3 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2040 &Hyperbolic::<3>::from_polar_coordinates(v, 10.0 * PI / 6.0),
2041 &intersection_3,
2042 );
2043 let tang_3 = 5.0 * PI / 12.0 + v_to_int_3;
2044 let ans_3 = (relative_orientation_lower + tang_3 + int_to_ghost_3).rem_euclid(2.0 * PI);
2045 assert_relative_eq!(
2046 ans_3.rem_euclid(2.0 * PI),
2047 ghost_3.orientation.theta.rem_euclid(2.0 * PI),
2048 epsilon = 1e-12
2049 );
2050
2051 let ghost_4 = ghost_array[4];
2053 let ghost_4_center = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2054 (v.cosh()) * ((9.0 * PI / 6.0).cos()) * ghost_4.position.coordinates()[0]
2055 + (v.cosh()) * ((9.0 * PI / 6.0).sin()) * ghost_4.position.coordinates()[1]
2056 - (v.sinh()) * ghost_4.position.coordinates()[2],
2057 -((9.0 * PI / 6.0).sin()) * ghost_4.position.coordinates()[0]
2058 + ((9.0 * PI / 6.0).cos()) * ghost_4.position.coordinates()[1],
2059 (v.cosh()) * ghost_4.position.coordinates()[2]
2060 - (v.sinh()) * ((9.0 * PI / 6.0).cos()) * ghost_4.position.coordinates()[0]
2061 - (v.sinh()) * ((9.0 * PI / 6.0).sin()) * ghost_4.position.coordinates()[1],
2062 ]));
2063 let (angle_4, boost_4) = (
2064 ghost_4_center.coordinates()[1]
2065 .atan2(ghost_4_center.coordinates()[0])
2066 .rem_euclid(2.0 * PI),
2067 (ghost_4_center.coordinates()[2]).acosh(),
2068 );
2069 let (int_4, _o_1) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
2070 angle_4 - 17.0 * PI / 12.0,
2071 boost_4,
2072 );
2073 let intersection_4 = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2074 (v.cosh()) * ((9.0 * PI / 6.0).cos()) * (int_4.sinh()) * ((17.0 * PI / 12.0).cos())
2075 - ((9.0 * PI / 6.0).sin()) * (int_4.sinh()) * ((17.0 * PI / 12.0).sin())
2076 + (v.sinh()) * ((9.0 * PI / 6.0).cos()) * (int_4.cosh()),
2077 (v.cosh()) * ((9.0 * PI / 6.0).sin()) * (int_4.sinh()) * ((17.0 * PI / 12.0).cos())
2078 + ((9.0 * PI / 6.0).cos()) * (int_4.sinh()) * ((17.0 * PI / 12.0).sin())
2079 + (v.sinh()) * ((9.0 * PI / 6.0).sin()) * (int_4.cosh()),
2080 (v.sinh()) * (int_4.sinh()) * ((17.0 * PI / 12.0).cos()) + (v.cosh()) * (int_4.cosh()),
2081 ]));
2082 let int_to_ghost_4 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2083 &intersection_4,
2084 &ghost_4.position,
2085 );
2086 let v_to_int_4 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2087 &Hyperbolic::<3>::from_polar_coordinates(v, 9.0 * PI / 6.0),
2088 &intersection_4,
2089 );
2090 let tang_4 = 11.0 * PI / 12.0 + v_to_int_4;
2091 let ans_4 = (relative_orientation_upper + tang_4 + int_to_ghost_4).rem_euclid(2.0 * PI);
2092 assert_relative_eq!(
2093 ans_4.rem_euclid(2.0 * PI),
2094 ghost_4.orientation.theta.rem_euclid(2.0 * PI),
2095 epsilon = 1e-12
2096 );
2097
2098 let ghost_5 = ghost_array[5];
2100 let ghost_5_center = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2101 (v.cosh()) * ((3.0 * PI / 6.0).cos()) * ghost_5.position.coordinates()[0]
2102 + (v.cosh()) * ((3.0 * PI / 6.0).sin()) * ghost_5.position.coordinates()[1]
2103 - (v.sinh()) * ghost_5.position.coordinates()[2],
2104 -((3.0 * PI / 6.0).sin()) * ghost_5.position.coordinates()[0]
2105 + ((3.0 * PI / 6.0).cos()) * ghost_5.position.coordinates()[1],
2106 (v.cosh()) * ghost_5.position.coordinates()[2]
2107 - (v.sinh()) * ((3.0 * PI / 6.0).cos()) * ghost_5.position.coordinates()[0]
2108 - (v.sinh()) * ((3.0 * PI / 6.0).sin()) * ghost_5.position.coordinates()[1],
2109 ]));
2110 let (angle_5, boost_5) = (
2111 ghost_5_center.coordinates()[1].atan2(ghost_5_center.coordinates()[0]),
2112 (ghost_5_center.coordinates()[2]).acosh(),
2113 );
2114 let (int_5, _o_1) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
2115 7.0 * PI / 12.0 - angle_5,
2116 boost_5,
2117 );
2118 let intersection_5 = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2119 (v.cosh()) * ((3.0 * PI / 6.0).cos()) * (int_5.sinh()) * ((7.0 * PI / 12.0).cos())
2120 - ((3.0 * PI / 6.0).sin()) * (int_5.sinh()) * ((7.0 * PI / 12.0).sin())
2121 + (v.sinh()) * ((3.0 * PI / 6.0).cos()) * (int_5.cosh()),
2122 (v.cosh()) * ((3.0 * PI / 6.0).sin()) * (int_5.sinh()) * ((7.0 * PI / 12.0).cos())
2123 + ((3.0 * PI / 6.0).cos()) * (int_5.sinh()) * ((7.0 * PI / 12.0).sin())
2124 + (v.sinh()) * ((3.0 * PI / 6.0).sin()) * (int_5.cosh()),
2125 (v.sinh()) * (int_5.sinh()) * ((7.0 * PI / 12.0).cos()) + (v.cosh()) * (int_5.cosh()),
2126 ]));
2127 let int_to_ghost_5 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2128 &intersection_5,
2129 &ghost_5.position,
2130 );
2131 let v_to_int_5 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2132 &Hyperbolic::<3>::from_polar_coordinates(v, 3.0 * PI / 6.0),
2133 &intersection_5,
2134 );
2135 let tang_5 = 13.0 * PI / 12.0 + v_to_int_5;
2136 let ans_5 = (relative_orientation_lower + tang_5 + int_to_ghost_5).rem_euclid(2.0 * PI);
2137 assert_relative_eq!(
2138 ans_5.rem_euclid(2.0 * PI),
2139 ghost_5.orientation.theta.rem_euclid(2.0 * PI),
2140 epsilon = 1e-12
2141 );
2142
2143 let ghost_6 = ghost_array[6];
2145 let ghost_6_center = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2146 (v.cosh()) * ((4.0 * PI / 6.0).cos()) * ghost_6.position.coordinates()[0]
2147 + (v.cosh()) * ((4.0 * PI / 6.0).sin()) * ghost_6.position.coordinates()[1]
2148 - (v.sinh()) * ghost_6.position.coordinates()[2],
2149 -((4.0 * PI / 6.0).sin()) * ghost_6.position.coordinates()[0]
2150 + ((4.0 * PI / 6.0).cos()) * ghost_6.position.coordinates()[1],
2151 (v.cosh()) * ghost_6.position.coordinates()[2]
2152 - (v.sinh()) * ((4.0 * PI / 6.0).cos()) * ghost_6.position.coordinates()[0]
2153 - (v.sinh()) * ((4.0 * PI / 6.0).sin()) * ghost_6.position.coordinates()[1],
2154 ]));
2155 let (angle_6, boost_6) = (
2156 ghost_6_center.coordinates()[1]
2157 .atan2(ghost_6_center.coordinates()[0])
2158 .rem_euclid(2.0 * PI),
2159 (ghost_6_center.coordinates()[2]).acosh(),
2160 );
2161 let (int_6, _o_1) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
2162 angle_6 - 19.0 * PI / 12.0,
2163 boost_6,
2164 );
2165 let intersection_6 = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2166 (v.cosh()) * ((4.0 * PI / 6.0).cos()) * (int_6.sinh()) * ((19.0 * PI / 12.0).cos())
2167 - ((4.0 * PI / 6.0).sin()) * (int_6.sinh()) * ((19.0 * PI / 12.0).sin())
2168 + (v.sinh()) * ((4.0 * PI / 6.0).cos()) * (int_6.cosh()),
2169 (v.cosh()) * ((4.0 * PI / 6.0).sin()) * (int_6.sinh()) * ((19.0 * PI / 12.0).cos())
2170 + ((4.0 * PI / 6.0).cos()) * (int_6.sinh()) * ((19.0 * PI / 12.0).sin())
2171 + (v.sinh()) * ((4.0 * PI / 6.0).sin()) * (int_6.cosh()),
2172 (v.sinh()) * (int_6.sinh()) * ((19.0 * PI / 12.0).cos()) + (v.cosh()) * (int_6.cosh()),
2173 ]));
2174 let int_to_ghost_6 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2175 &intersection_6,
2176 &ghost_6.position,
2177 );
2178 let v_to_int_6 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2179 &Hyperbolic::<3>::from_polar_coordinates(v, 4.0 * PI / 6.0),
2180 &intersection_6,
2181 );
2182 let tang_6 = 3.0 * PI / 12.0 + v_to_int_6;
2183 let ans_6 = (relative_orientation_upper + tang_6 + int_to_ghost_6).rem_euclid(2.0 * PI);
2184 assert_relative_eq!(
2185 ans_6.rem_euclid(2.0 * PI),
2186 ghost_6.orientation.theta.rem_euclid(2.0 * PI),
2187 epsilon = 1e-12
2188 );
2189
2190 let ghost_7 = ghost_array[7];
2192 let ghost_7_center = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2193 (v.cosh()) * ((8.0 * PI / 6.0).cos()) * ghost_7.position.coordinates()[0]
2194 + (v.cosh()) * ((8.0 * PI / 6.0).sin()) * ghost_7.position.coordinates()[1]
2195 - (v.sinh()) * ghost_7.position.coordinates()[2],
2196 -((8.0 * PI / 6.0).sin()) * ghost_7.position.coordinates()[0]
2197 + ((8.0 * PI / 6.0).cos()) * ghost_7.position.coordinates()[1],
2198 (v.cosh()) * ghost_7.position.coordinates()[2]
2199 - (v.sinh()) * ((8.0 * PI / 6.0).cos()) * ghost_7.position.coordinates()[0]
2200 - (v.sinh()) * ((8.0 * PI / 6.0).sin()) * ghost_7.position.coordinates()[1],
2201 ]));
2202 let (angle_7, boost_7) = (
2203 ghost_7_center.coordinates()[1].atan2(ghost_7_center.coordinates()[0]),
2204 (ghost_7_center.coordinates()[2]).acosh(),
2205 );
2206 let (int_7, _o_1) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
2207 5.0 * PI / 12.0 - angle_7,
2208 boost_7,
2209 );
2210 let intersection_7 = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2211 (v.cosh()) * ((8.0 * PI / 6.0).cos()) * (int_7.sinh()) * ((5.0 * PI / 12.0).cos())
2212 - ((8.0 * PI / 6.0).sin()) * (int_7.sinh()) * ((5.0 * PI / 12.0).sin())
2213 + (v.sinh()) * ((8.0 * PI / 6.0).cos()) * (int_7.cosh()),
2214 (v.cosh()) * ((8.0 * PI / 6.0).sin()) * (int_7.sinh()) * ((5.0 * PI / 12.0).cos())
2215 + ((8.0 * PI / 6.0).cos()) * (int_7.sinh()) * ((5.0 * PI / 12.0).sin())
2216 + (v.sinh()) * ((8.0 * PI / 6.0).sin()) * (int_7.cosh()),
2217 (v.sinh()) * (int_7.sinh()) * ((5.0 * PI / 12.0).cos()) + (v.cosh()) * (int_7.cosh()),
2218 ]));
2219 let int_to_ghost_7 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2220 &intersection_7,
2221 &ghost_7.position,
2222 );
2223 let v_to_int_7 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2224 &Hyperbolic::<3>::from_polar_coordinates(v, 8.0 * PI / 6.0),
2225 &intersection_7,
2226 );
2227 let tang_7 = 21.0 * PI / 12.0 + v_to_int_7;
2228 let ans_7 = (relative_orientation_lower + tang_7 + int_to_ghost_7).rem_euclid(2.0 * PI);
2229 assert_relative_eq!(
2230 ans_7.rem_euclid(2.0 * PI),
2231 ghost_7.orientation.theta.rem_euclid(2.0 * PI),
2232 epsilon = 1e-12
2233 );
2234
2235 let ghost_8 = ghost_array[8];
2237 let ghost_8_center = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2238 (v.cosh()) * ((11.0 * PI / 6.0).cos()) * ghost_8.position.coordinates()[0]
2239 + (v.cosh()) * ((11.0 * PI / 6.0).sin()) * ghost_8.position.coordinates()[1]
2240 - (v.sinh()) * ghost_8.position.coordinates()[2],
2241 -((11.0 * PI / 6.0).sin()) * ghost_8.position.coordinates()[0]
2242 + ((11.0 * PI / 6.0).cos()) * ghost_8.position.coordinates()[1],
2243 (v.cosh()) * ghost_8.position.coordinates()[2]
2244 - (v.sinh()) * ((11.0 * PI / 6.0).cos()) * ghost_8.position.coordinates()[0]
2245 - (v.sinh()) * ((11.0 * PI / 6.0).sin()) * ghost_8.position.coordinates()[1],
2246 ]));
2247 let (angle_8, boost_8) = (
2248 ghost_8_center.coordinates()[1]
2249 .atan2(ghost_8_center.coordinates()[0])
2250 .rem_euclid(2.0 * PI),
2251 (ghost_8_center.coordinates()[2]).acosh(),
2252 );
2253 let (int_8, _o_1) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
2254 angle_8 - 21.0 * PI / 12.0,
2255 boost_8,
2256 );
2257 let intersection_8 = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2258 (v.cosh()) * ((11.0 * PI / 6.0).cos()) * (int_8.sinh()) * ((21.0 * PI / 12.0).cos())
2259 - ((11.0 * PI / 6.0).sin()) * (int_8.sinh()) * ((21.0 * PI / 12.0).sin())
2260 + (v.sinh()) * ((11.0 * PI / 6.0).cos()) * (int_8.cosh()),
2261 (v.cosh()) * ((11.0 * PI / 6.0).sin()) * (int_8.sinh()) * ((21.0 * PI / 12.0).cos())
2262 + ((11.0 * PI / 6.0).cos()) * (int_8.sinh()) * ((21.0 * PI / 12.0).sin())
2263 + (v.sinh()) * ((11.0 * PI / 6.0).sin()) * (int_8.cosh()),
2264 (v.sinh()) * (int_8.sinh()) * ((21.0 * PI / 12.0).cos()) + (v.cosh()) * (int_8.cosh()),
2265 ]));
2266 let int_to_ghost_8 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2267 &intersection_8,
2268 &ghost_8.position,
2269 );
2270 let v_to_int_8 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2271 &Hyperbolic::<3>::from_polar_coordinates(v, 11.0 * PI / 6.0),
2272 &intersection_8,
2273 );
2274 let tang_8 = 19.0 * PI / 12.0 + v_to_int_8;
2275 let ans_8 = (relative_orientation_upper + tang_8 + int_to_ghost_8).rem_euclid(2.0 * PI);
2276 assert_relative_eq!(
2277 ans_8.rem_euclid(2.0 * PI),
2278 ghost_8.orientation.theta.rem_euclid(2.0 * PI),
2279 epsilon = 1e-10
2280 );
2281
2282 let ghost_9 = ghost_array[9];
2284 let ghost_9_center = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2285 (v.cosh()) * ((PI / 6.0).cos()) * ghost_9.position.coordinates()[0]
2286 + (v.cosh()) * ((PI / 6.0).sin()) * ghost_9.position.coordinates()[1]
2287 - (v.sinh()) * ghost_9.position.coordinates()[2],
2288 -((PI / 6.0).sin()) * ghost_9.position.coordinates()[0]
2289 + ((PI / 6.0).cos()) * ghost_9.position.coordinates()[1],
2290 (v.cosh()) * ghost_9.position.coordinates()[2]
2291 - (v.sinh()) * ((PI / 6.0).cos()) * ghost_9.position.coordinates()[0]
2292 - (v.sinh()) * ((PI / 6.0).sin()) * ghost_9.position.coordinates()[1],
2293 ]));
2294 let (angle_9, boost_9) = (
2295 ghost_9_center.coordinates()[1].atan2(ghost_9_center.coordinates()[0]),
2296 (ghost_9_center.coordinates()[2]).acosh(),
2297 );
2298 let (int_9, _o_1) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
2299 3.0 * PI / 12.0 - angle_9,
2300 boost_9,
2301 );
2302 let intersection_9 = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2303 (v.cosh()) * ((PI / 6.0).cos()) * (int_9.sinh()) * ((3.0 * PI / 12.0).cos())
2304 - ((PI / 6.0).sin()) * (int_9.sinh()) * ((3.0 * PI / 12.0).sin())
2305 + (v.sinh()) * ((PI / 6.0).cos()) * (int_9.cosh()),
2306 (v.cosh()) * ((PI / 6.0).sin()) * (int_9.sinh()) * ((3.0 * PI / 12.0).cos())
2307 + ((PI / 6.0).cos()) * (int_9.sinh()) * ((3.0 * PI / 12.0).sin())
2308 + (v.sinh()) * ((PI / 6.0).sin()) * (int_9.cosh()),
2309 (v.sinh()) * (int_9.sinh()) * ((3.0 * PI / 12.0).cos()) + (v.cosh()) * (int_9.cosh()),
2310 ]));
2311 let int_to_ghost_9 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2312 &intersection_9,
2313 &ghost_9.position,
2314 );
2315 let v_to_int_9 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2316 &Hyperbolic::<3>::from_polar_coordinates(v, PI / 6.0),
2317 &intersection_9,
2318 );
2319 let tang_9 = 5.0 * PI / 12.0 + v_to_int_9;
2320 let ans_9 = (relative_orientation_lower + tang_9 + int_to_ghost_9).rem_euclid(2.0 * PI);
2321 assert_relative_eq!(
2322 ans_9.rem_euclid(2.0 * PI),
2323 ghost_9.orientation.theta.rem_euclid(2.0 * PI),
2324 epsilon = 1e-10
2325 );
2326
2327 let ghost_10 = ghost_array[10];
2329 let ghost_10_center = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2330 (v.cosh()) * ((PI).cos()) * ghost_10.position.coordinates()[0]
2331 + (v.cosh()) * ((PI).sin()) * ghost_10.position.coordinates()[1]
2332 - (v.sinh()) * ghost_10.position.coordinates()[2],
2333 -((PI).sin()) * ghost_10.position.coordinates()[0]
2334 + ((PI).cos()) * ghost_10.position.coordinates()[1],
2335 (v.cosh()) * ghost_10.position.coordinates()[2]
2336 - (v.sinh()) * ((PI).cos()) * ghost_10.position.coordinates()[0]
2337 - (v.sinh()) * ((PI).sin()) * ghost_10.position.coordinates()[1],
2338 ]));
2339 let (angle_10, boost_10) = (
2340 ghost_10_center.coordinates()[1]
2341 .atan2(ghost_10_center.coordinates()[0])
2342 .rem_euclid(2.0 * PI),
2343 (ghost_10_center.coordinates()[2]).acosh(),
2344 );
2345 let (int_10, _o_1) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
2346 angle_10 - 23.0 * PI / 12.0,
2347 boost_10,
2348 );
2349 let intersection_10 = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2350 (v.cosh()) * ((PI).cos()) * (int_10.sinh()) * ((23.0 * PI / 12.0).cos())
2351 - ((PI).sin()) * (int_10.sinh()) * ((23.0 * PI / 12.0).sin())
2352 + (v.sinh()) * ((PI).cos()) * (int_10.cosh()),
2353 (v.cosh()) * ((PI).sin()) * (int_10.sinh()) * ((23.0 * PI / 12.0).cos())
2354 + ((PI).cos()) * (int_10.sinh()) * ((23.0 * PI / 12.0).sin())
2355 + (v.sinh()) * ((PI).sin()) * (int_10.cosh()),
2356 (v.sinh()) * (int_10.sinh()) * ((23.0 * PI / 12.0).cos())
2357 + (v.cosh()) * (int_10.cosh()),
2358 ]));
2359 let int_to_ghost_10 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2360 &intersection_10,
2361 &ghost_10.position,
2362 );
2363 let v_to_int_10 = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2364 &Hyperbolic::<3>::from_polar_coordinates(v, PI),
2365 &intersection_10,
2366 );
2367 let tang_10 = 11.0 * PI / 12.0 + v_to_int_10;
2368 let ans_10 = (relative_orientation_upper + tang_10 + int_to_ghost_10).rem_euclid(2.0 * PI);
2369 assert_relative_eq!(
2370 ans_10.rem_euclid(2.0 * PI),
2371 ghost_10.orientation.theta.rem_euclid(2.0 * PI),
2372 epsilon = 1e-10
2373 );
2374
2375 let (int_10b, _o_1) = OrientedHyperbolicPoint::<3, Angle>::intersection_point(
2376 1.0 * PI / 12.0 - angle_10,
2377 boost_10,
2378 );
2379 let intersection_10b = Hyperbolic::<3>::from_minkowski_coordinates(Minkowski::from([
2380 (v.cosh()) * ((PI).cos()) * (int_10b.sinh()) * ((1.0 * PI / 12.0).cos())
2381 - ((PI).sin()) * (int_10b.sinh()) * ((1.0 * PI / 12.0).sin())
2382 + (v.sinh()) * ((PI).cos()) * (int_10b.cosh()),
2383 (v.cosh()) * ((PI).sin()) * (int_10b.sinh()) * ((1.0 * PI / 12.0).cos())
2384 + ((PI).cos()) * (int_10b.sinh()) * ((1.0 * PI / 12.0).sin())
2385 + (v.sinh()) * ((PI).sin()) * (int_10b.cosh()),
2386 (v.sinh()) * (int_10b.sinh()) * ((1.0 * PI / 12.0).cos())
2387 + (v.cosh()) * (int_10b.cosh()),
2388 ]));
2389 let int_to_ghost_10b = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2390 &intersection_10b,
2391 &ghost_10.position,
2392 );
2393 let v_to_int_10b = OrientedHyperbolicPoint::<3, Angle>::parallel_transport_angle(
2394 &Hyperbolic::<3>::from_polar_coordinates(v, PI),
2395 &intersection_10b,
2396 );
2397 let tang_10b = 13.0 * PI / 12.0 + v_to_int_10b;
2398 let ans_10b =
2399 (relative_orientation_lower + tang_10b + int_to_ghost_10b).rem_euclid(2.0 * PI);
2400 assert_relative_eq!(
2401 ans_10b.rem_euclid(2.0 * PI),
2402 ghost_10.orientation.theta.rem_euclid(2.0 * PI),
2403 epsilon = 1e-10
2404 );
2405 }
2406}