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