1use crate::error::LocationOutOfBoundsError;
17use crate::helpers::{binary_search, i64_to_i32, invert_angular, next_moment, poly};
18use crate::rata_die::{Moment, RataDie};
19use core::f64::consts::PI;
20#[allow(unused_imports)]
21use core_maths::*;
22
23fn div_euclid_f64(n: f64, d: f64) -> f64 {
26 debug_assert!(d > 0.0);
27 let (a, b) = (n / d, n % d);
28 if n >= 0.0 || b == 0.0 {
29 a
30 } else {
31 a - 1.0
32 }
33}
34
35#[derive(Debug, Copy, Clone, Default)]
36#[allow(clippy::exhaustive_structs)] pub struct Location {
41 pub latitude: f64,
43 pub longitude: f64,
45 pub elevation: f64,
47 pub zone: f64,
49}
50
51#[allow(dead_code)]
53pub const MECCA: Location = Location {
54 latitude: 6427.0 / 300.0,
55 longitude: 11947.0 / 300.0,
56 elevation: 298.0,
57 zone: (1_f64 / 8_f64),
58};
59
60pub const MEAN_SYNODIC_MONTH: f64 = 29.530588861;
66
67pub const J2000: Moment = Moment::new(730120.5);
69
70pub const MEAN_TROPICAL_YEAR: f64 = 365.242189;
75
76pub const MIN_UTC_OFFSET: f64 = -0.5;
78
79pub const MAX_UTC_OFFSET: f64 = 14.0 / 24.0;
81
82pub const WINTER: f64 = 270.0;
84
85pub const NEW_MOON_ZERO: Moment = Moment::new(11.458922815770109);
87
88impl Location {
89 #[allow(dead_code)] pub fn try_new(
93 latitude: f64,
94 longitude: f64,
95 elevation: f64,
96 zone: f64,
97 ) -> Result<Location, LocationOutOfBoundsError> {
98 if !(-90.0..=90.0).contains(&latitude) {
99 return Err(LocationOutOfBoundsError::Latitude(latitude));
100 }
101 if !(-180.0..=180.0).contains(&longitude) {
102 return Err(LocationOutOfBoundsError::Longitude(longitude));
103 }
104 if !(MIN_UTC_OFFSET..=MAX_UTC_OFFSET).contains(&zone) {
105 return Err(LocationOutOfBoundsError::Offset(
106 zone,
107 MIN_UTC_OFFSET,
108 MAX_UTC_OFFSET,
109 ));
110 }
111 Ok(Location {
112 latitude,
113 longitude,
114 elevation,
115 zone,
116 })
117 }
118
119 pub const fn new_unchecked(
121 latitude: f64,
122 longitude: f64,
123 elevation: f64,
124 zone: f64,
125 ) -> Location {
126 Location {
127 latitude,
128 longitude,
129 elevation,
130 zone,
131 }
132 }
133
134 #[allow(dead_code)]
136 pub fn longitude(&self) -> f64 {
137 self.longitude
138 }
139
140 #[allow(dead_code)]
142 pub fn latitude(&self) -> f64 {
143 self.latitude
144 }
145
146 #[allow(dead_code)]
148 pub fn elevation(&self) -> f64 {
149 self.elevation
150 }
151
152 #[allow(dead_code)]
154 pub fn zone(&self) -> f64 {
155 self.zone
156 }
157
158 pub fn zone_from_longitude(longitude: f64) -> f64 {
163 longitude / (360.0)
164 }
165
166 #[allow(dead_code)]
171 pub fn standard_from_local(standard_time: Moment, location: Location) -> Moment {
172 Self::standard_from_universal(
173 Self::universal_from_local(standard_time, location),
174 location,
175 )
176 }
177
178 pub fn universal_from_local(local_time: Moment, location: Location) -> Moment {
183 local_time - Self::zone_from_longitude(location.longitude)
184 }
185
186 #[allow(dead_code)] pub fn local_from_universal(universal_time: Moment, location: Location) -> Moment {
192 universal_time + Self::zone_from_longitude(location.longitude)
193 }
194
195 pub fn universal_from_standard(standard_moment: Moment, location: Location) -> Moment {
203 debug_assert!(location.zone > MIN_UTC_OFFSET && location.zone < MAX_UTC_OFFSET, "UTC offset {0} was not within the possible range of offsets (see astronomy::MIN_UTC_OFFSET and astronomy::MAX_UTC_OFFSET)", location.zone);
204 standard_moment - location.zone
205 }
206 #[allow(dead_code)]
214 pub fn standard_from_universal(standard_time: Moment, location: Location) -> Moment {
215 debug_assert!(location.zone > MIN_UTC_OFFSET && location.zone < MAX_UTC_OFFSET, "UTC offset {0} was not within the possible range of offsets (see astronomy::MIN_UTC_OFFSET and astronomy::MAX_UTC_OFFSET)", location.zone);
216 standard_time + location.zone
217 }
218}
219
220#[derive(Debug)]
221#[allow(clippy::exhaustive_structs)] pub struct Astronomical;
225
226impl Astronomical {
227 pub fn ephemeris_correction(moment: Moment) -> f64 {
235 let year = moment.inner() / 365.2425;
237 let year_int = (if year > 0.0 { year + 1.0 } else { year }) as i32;
239 let fixed_mid_year = crate::iso::fixed_from_iso(year_int, 7, 1);
240 let c = ((fixed_mid_year.to_i64_date() as f64) - 693596.0) / 36525.0;
241 let y2000 = (year_int - 2000) as f64;
242 let y1700 = (year_int - 1700) as f64;
243 let y1600 = (year_int - 1600) as f64;
244 let y1000 = ((year_int - 1000) as f64) / 100.0;
245 let y0 = year_int as f64 / 100.0;
246 let y1820 = ((year_int - 1820) as f64) / 100.0;
247
248 if (2051..=2150).contains(&year_int) {
249 (-20.0
250 + 32.0 * (((year_int - 1820) * (year_int - 1820)) as f64 / 10000.0)
251 + 0.5628 * (2150 - year_int) as f64)
252 / 86400.0
253 } else if (2006..=2050).contains(&year_int) {
254 (62.92 + 0.32217 * y2000 + 0.005589 * y2000 * y2000) / 86400.0
255 } else if (1987..=2005).contains(&year_int) {
256 (63.86 + 0.3345 * y2000 - 0.060374 * y2000 * y2000
258 + 0.0017275 * y2000 * y2000 * y2000
259 + 0.000651814 * y2000 * y2000 * y2000 * y2000
260 + 0.00002373599 * y2000 * y2000 * y2000 * y2000 * y2000)
261 / 86400.0
262 } else if (1900..=1986).contains(&year_int) {
263 -0.00002 + 0.000297 * c + 0.025184 * c * c - 0.181133 * c * c * c
265 + 0.553040 * c * c * c * c
266 - 0.861938 * c * c * c * c * c
267 + 0.677066 * c * c * c * c * c * c
268 - 0.212591 * c * c * c * c * c * c * c
269 } else if (1800..=1899).contains(&year_int) {
270 -0.000009
272 + 0.003844 * c
273 + 0.083563 * c * c
274 + 0.865736 * c * c * c
275 + 4.867575 * c * c * c * c
276 + 15.845535 * c * c * c * c * c
277 + 31.332267 * c * c * c * c * c * c
278 + 38.291999 * c * c * c * c * c * c * c
279 + 28.316289 * c * c * c * c * c * c * c * c
280 + 11.636204 * c * c * c * c * c * c * c * c * c
281 + 2.043794 * c * c * c * c * c * c * c * c * c * c
282 } else if (1700..=1799).contains(&year_int) {
283 (8.118780842 - 0.005092142 * y1700 + 0.003336121 * y1700 * y1700
285 - 0.0000266484 * y1700 * y1700 * y1700)
286 / 86400.0
287 } else if (1600..=1699).contains(&year_int) {
288 (120.0 - 0.9808 * y1600 - 0.01532 * y1600 * y1600
290 + 0.000140272128 * y1600 * y1600 * y1600)
291 / 86400.0
292 } else if (500..=1599).contains(&year_int) {
293 (1574.2 - 556.01 * y1000 + 71.23472 * y1000 * y1000 + 0.319781 * y1000 * y1000 * y1000
295 - 0.8503463 * y1000 * y1000 * y1000 * y1000
296 - 0.005050998 * y1000 * y1000 * y1000 * y1000 * y1000
297 + 0.0083572073 * y1000 * y1000 * y1000 * y1000 * y1000 * y1000)
298 / 86400.0
299 } else if (-499..=499).contains(&year_int) {
300 (10583.6 - 1014.41 * y0 + 33.78311 * y0 * y0
302 - 5.952053 * y0 * y0 * y0
303 - 0.1798452 * y0 * y0 * y0 * y0
304 + 0.022174192 * y0 * y0 * y0 * y0 * y0
305 + 0.0090316521 * y0 * y0 * y0 * y0 * y0 * y0)
306 / 86400.0
307 } else {
308 (-20.0 + 32.0 * y1820 * y1820) / 86400.0
309 }
310 }
311
312 pub fn dynamical_from_universal(universal: Moment) -> Moment {
317 universal + Self::ephemeris_correction(universal)
319 }
320
321 pub fn universal_from_dynamical(dynamical: Moment) -> Moment {
326 dynamical - Self::ephemeris_correction(dynamical)
328 }
329
330 pub fn julian_centuries(moment: Moment) -> f64 {
336 let intermediate = Self::dynamical_from_universal(moment);
337 (intermediate - J2000) / 36525.0
338 }
339
340 pub fn equation_of_time(moment: Moment) -> f64 {
349 let c = Self::julian_centuries(moment);
350 let lambda = poly(c, &[280.46645, 36000.76983, 0.0003032]);
351 let anomaly = poly(c, &[357.52910, 35999.05030, -0.0001559, -0.00000048]);
352 let eccentricity = poly(c, &[0.016708617, -0.000042037, -0.0000001236]);
353 let varepsilon = Self::obliquity(moment);
354 let y = (varepsilon / 2.0).to_radians().tan();
355 let y = y * y;
356 let equation = (y * (2.0 * lambda).to_radians().sin()
357 - 2.0 * eccentricity * anomaly.to_radians().sin()
358 + 4.0
359 * eccentricity
360 * y
361 * anomaly.to_radians().sin()
362 * (2.0 * lambda).to_radians().cos()
363 - 0.5 * y * y * (4.0 * lambda).to_radians().sin()
364 - 1.25 * eccentricity * eccentricity * (2.0 * anomaly).to_radians().sin())
365 / (2.0 * PI);
366
367 equation.signum() * equation.abs().min(12.0 / 24.0)
368 }
369
370 pub fn dusk(date: f64, location: Location, alpha: f64) -> Option<Moment> {
376 let evening = false;
377 let moment_of_depression = Self::moment_of_depression(
378 Moment::new(date + (18.0 / 24.0)),
379 location,
380 alpha,
381 evening,
382 )?;
383 Some(Location::standard_from_local(
384 moment_of_depression,
385 location,
386 ))
387 }
388
389 pub fn obliquity(moment: Moment) -> f64 {
395 let c = Self::julian_centuries(moment);
396 let angle = 23.0 + 26.0 / 60.0 + 21.448 / 3600.0;
397 let coefs = &[0.0, -46.8150 / 3600.0, -0.00059 / 3600.0, 0.001813 / 3600.0];
398 angle + poly(c, coefs)
399 }
400
401 pub fn declination(moment: Moment, beta: f64, lambda: f64) -> f64 {
408 let varepsilon = Self::obliquity(moment);
409 (beta.to_radians().sin() * varepsilon.to_radians().cos()
410 + beta.to_radians().cos() * varepsilon.to_radians().sin() * lambda.to_radians().sin())
411 .asin()
412 .to_degrees()
413 .rem_euclid(360.0)
414 }
415
416 pub fn right_ascension(moment: Moment, beta: f64, lambda: f64) -> f64 {
424 let varepsilon = Self::obliquity(moment);
425
426 let y = lambda.to_radians().sin() * varepsilon.to_radians().cos()
427 - beta.to_radians().tan() * varepsilon.to_radians().sin();
428 let x = lambda.to_radians().cos();
429
430 y.atan2(x).to_degrees().rem_euclid(360.0)
432 }
433
434 pub fn local_from_apparent(moment: Moment, location: Location) -> Moment {
439 moment - Self::equation_of_time(Location::universal_from_local(moment, location))
440 }
441
442 pub fn approx_moment_of_depression(
450 moment: Moment,
451 location: Location,
452 alpha: f64,
453 early: bool, ) -> Option<Moment> {
455 let date = moment.as_rata_die().to_f64_date().floor();
456 let alt = if alpha >= 0.0 {
457 if early {
458 date
459 } else {
460 date + 1.0
461 }
462 } else {
463 date + 12.0 / 24.0
464 };
465
466 let value = if Self::sine_offset(moment, location, alpha).abs() > 1.0 {
467 Self::sine_offset(Moment::new(alt), location, alpha)
468 } else {
469 Self::sine_offset(moment, location, alpha)
470 };
471
472 if value.abs() <= 1.0 {
473 let offset =
474 (value.asin().to_degrees().rem_euclid(360.0) / 360.0 + 0.5).rem_euclid(1.0) - 0.5;
475
476 let moment = Moment::new(
477 date + if early {
478 (6.0 / 24.0) - offset
479 } else {
480 (18.0 / 24.0) + offset
481 },
482 );
483 Some(Self::local_from_apparent(moment, location))
484 } else {
485 None
486 }
487 }
488
489 pub fn moment_of_depression(
497 approx: Moment,
498 location: Location,
499 alpha: f64,
500 early: bool, ) -> Option<Moment> {
502 let moment = Self::approx_moment_of_depression(approx, location, alpha, early)?;
503 if (approx - moment).abs() < 30.0 {
504 Some(moment)
505 } else {
506 Self::moment_of_depression(moment, location, alpha, early)
507 }
508 }
509
510 pub fn refraction(location: Location) -> f64 {
515 let h = location.elevation.max(0.0);
517 let earth_r = 6.372e6; let dip = (earth_r / (earth_r + h)).acos().to_degrees();
519
520 (34.0 / 60.0) + dip + ((19.0 / 3600.0) * h.sqrt())
521 }
522
523 pub fn nth_new_moon(n: i32) -> Moment {
531 let n0 = 24724.0;
533 let k = (n as f64) - n0;
534 let c = k / 1236.85;
535 let approx = J2000
536 + (5.09766 + (MEAN_SYNODIC_MONTH * 1236.85 * c) + (0.00015437 * c * c)
537 - (0.00000015 * c * c * c)
538 + (0.00000000073 * c * c * c * c));
539 let e = 1.0 - (0.002516 * c) - (0.0000074 * c * c);
540 let solar_anomaly =
541 2.5534 + (1236.85 * 29.10535670 * c) - (0.0000014 * c * c) - (0.00000011 * c * c * c);
542 let lunar_anomaly = 201.5643
543 + (385.81693528 * 1236.85 * c)
544 + (0.0107582 * c * c)
545 + (0.00001238 * c * c * c)
546 - (0.000000058 * c * c * c * c);
547 let moon_argument = 160.7108 + (390.67050284 * 1236.85 * c)
548 - (0.0016118 * c * c)
549 - (0.00000227 * c * c * c)
550 + (0.000000011 * c * c * c * c);
551 let omega =
552 124.7746 + (-1.56375588 * 1236.85 * c) + (0.0020672 * c * c) + (0.00000215 * c * c * c);
553
554 let mut st = (
555 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
556 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
557 );
558 let [v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23] = [
559 -0.40720, 0.17241, 0.01608, 0.01039, 0.00739, -0.00514, 0.00208, -0.00111, -0.00057,
560 0.00056, -0.00042, 0.00042, 0.00038, -0.00024, -0.00007, 0.00004, 0.00004, 0.00003,
561 0.00003, -0.00003, 0.00003, -0.00002, -0.00002, 0.00002,
562 ];
563 let [x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23] = [
564 0.0, 1.0, 0.0, 0.0, -1.0, 1.0, 2.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, -1.0, 2.0, 0.0, 3.0,
565 1.0, 0.0, 1.0, -1.0, -1.0, 1.0, 0.0,
566 ];
567 let [y0, y1, y2, y3, y4, y5, y6, y7, y8, y9, y10, y11, y12, y13, y14, y15, y16, y17, y18, y19, y20, y21, y22, y23] = [
568 1.0, 0.0, 2.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 2.0, 3.0, 0.0, 0.0, 2.0, 1.0, 2.0, 0.0,
569 1.0, 2.0, 1.0, 1.0, 1.0, 3.0, 4.0,
570 ];
571 let [z0, z1, z2, z3, z4, z5, z6, z7, z8, z9, z10, z11, z12, z13, z14, z15, z16, z17, z18, z19, z20, z21, z22, z23] = [
572 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, -2.0, 2.0, 0.0, 0.0, 2.0, -2.0, 0.0, 0.0, -2.0, 0.0,
573 -2.0, 2.0, 2.0, 2.0, -2.0, 0.0, 0.0,
574 ];
575
576 let mut at = (
577 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
578 );
579 let [i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12] = [
580 251.88, 251.83, 349.42, 84.66, 141.74, 207.14, 154.84, 34.52, 207.19, 291.34, 161.72,
581 239.56, 331.55,
582 ];
583 let [j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12] = [
584 0.016321, 26.651886, 36.412478, 18.206239, 53.303771, 2.453732, 7.306860, 27.261239,
585 0.121824, 1.844379, 24.198154, 25.513099, 3.592518,
586 ];
587 let [l0, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12] = [
588 0.000165, 0.000164, 0.000126, 0.000110, 0.000062, 0.000060, 0.000056, 0.000047,
589 0.000042, 0.000040, 0.000037, 0.000035, 0.000023,
590 ];
591
592 let mut correction = -0.00017 * omega.to_radians().sin();
593
594 st.0 = v0
596 * (x0 * solar_anomaly + y0 * lunar_anomaly + z0 * moon_argument)
597 .to_radians()
598 .sin();
599 st.1 = v1
600 * e
601 * (x1 * solar_anomaly + y1 * lunar_anomaly + z1 * moon_argument)
602 .to_radians()
603 .sin();
604 st.2 = v2
605 * (x2 * solar_anomaly + y2 * lunar_anomaly + z2 * moon_argument)
606 .to_radians()
607 .sin();
608 st.3 = v3
609 * (x3 * solar_anomaly + y3 * lunar_anomaly + z3 * moon_argument)
610 .to_radians()
611 .sin();
612 st.4 = v4
613 * e
614 * (x4 * solar_anomaly + y4 * lunar_anomaly + z4 * moon_argument)
615 .to_radians()
616 .sin();
617 st.5 = v5
618 * e
619 * (x5 * solar_anomaly + y5 * lunar_anomaly + z5 * moon_argument)
620 .to_radians()
621 .sin();
622 st.6 = v6
623 * e
624 * e
625 * (x6 * solar_anomaly + y6 * lunar_anomaly + z6 * moon_argument)
626 .to_radians()
627 .sin();
628 st.7 = v7
629 * (x7 * solar_anomaly + y7 * lunar_anomaly + z7 * moon_argument)
630 .to_radians()
631 .sin();
632 st.8 = v8
633 * (x8 * solar_anomaly + y8 * lunar_anomaly + z8 * moon_argument)
634 .to_radians()
635 .sin();
636 st.9 = v9
637 * e
638 * (x9 * solar_anomaly + y9 * lunar_anomaly + z9 * moon_argument)
639 .to_radians()
640 .sin();
641 st.10 = v10
642 * (x10 * solar_anomaly + y10 * lunar_anomaly + z10 * moon_argument)
643 .to_radians()
644 .sin();
645 st.11 = v11
646 * e
647 * (x11 * solar_anomaly + y11 * lunar_anomaly + z11 * moon_argument)
648 .to_radians()
649 .sin();
650 st.12 = v12
651 * e
652 * (x12 * solar_anomaly + y12 * lunar_anomaly + z12 * moon_argument)
653 .to_radians()
654 .sin();
655 st.13 = v13
656 * e
657 * (x13 * solar_anomaly + y13 * lunar_anomaly + z13 * moon_argument)
658 .to_radians()
659 .sin();
660 st.14 = v14
661 * (x14 * solar_anomaly + y14 * lunar_anomaly + z14 * moon_argument)
662 .to_radians()
663 .sin();
664 st.15 = v15
665 * (x15 * solar_anomaly + y15 * lunar_anomaly + z15 * moon_argument)
666 .to_radians()
667 .sin();
668 st.16 = v16
669 * (x16 * solar_anomaly + y16 * lunar_anomaly + z16 * moon_argument)
670 .to_radians()
671 .sin();
672 st.17 = v17
673 * (x17 * solar_anomaly + y17 * lunar_anomaly + z17 * moon_argument)
674 .to_radians()
675 .sin();
676 st.18 = v18
677 * (x18 * solar_anomaly + y18 * lunar_anomaly + z18 * moon_argument)
678 .to_radians()
679 .sin();
680 st.19 = v19
681 * (x19 * solar_anomaly + y19 * lunar_anomaly + z19 * moon_argument)
682 .to_radians()
683 .sin();
684 st.20 = v20
685 * (x20 * solar_anomaly + y20 * lunar_anomaly + z20 * moon_argument)
686 .to_radians()
687 .sin();
688 st.21 = v21
689 * (x21 * solar_anomaly + y21 * lunar_anomaly + z21 * moon_argument)
690 .to_radians()
691 .sin();
692 st.22 = v22
693 * (x22 * solar_anomaly + y22 * lunar_anomaly + z22 * moon_argument)
694 .to_radians()
695 .sin();
696 st.23 = v23
697 * (x23 * solar_anomaly + y23 * lunar_anomaly + z23 * moon_argument)
698 .to_radians()
699 .sin();
700
701 let sum = st.0
702 + st.1
703 + st.2
704 + st.3
705 + st.4
706 + st.5
707 + st.6
708 + st.7
709 + st.8
710 + st.9
711 + st.10
712 + st.11
713 + st.12
714 + st.13
715 + st.14
716 + st.15
717 + st.16
718 + st.17
719 + st.18
720 + st.19
721 + st.20
722 + st.21
723 + st.22
724 + st.23;
725
726 correction += sum;
727 let extra = 0.000325
728 * (299.77 + (132.8475848 * c) - (0.009173 * c * c))
729 .to_radians()
730 .sin();
731
732 at.0 = l0 * (i0 + j0 * k).to_radians().sin();
733 at.1 = l1 * (i1 + j1 * k).to_radians().sin();
734 at.2 = l2 * (i2 + j2 * k).to_radians().sin();
735 at.3 = l3 * (i3 + j3 * k).to_radians().sin();
736 at.4 = l4 * (i4 + j4 * k).to_radians().sin();
737 at.5 = l5 * (i5 + j5 * k).to_radians().sin();
738 at.6 = l6 * (i6 + j6 * k).to_radians().sin();
739 at.7 = l7 * (i7 + j7 * k).to_radians().sin();
740 at.8 = l8 * (i8 + j8 * k).to_radians().sin();
741 at.9 = l9 * (i9 + j9 * k).to_radians().sin();
742 at.10 = l10 * (i10 + j10 * k).to_radians().sin();
743 at.11 = l11 * (i11 + j11 * k).to_radians().sin();
744 at.12 = l12 * (i12 + j12 * k).to_radians().sin();
745
746 let additional = at.0
747 + at.1
748 + at.2
749 + at.3
750 + at.4
751 + at.5
752 + at.6
753 + at.7
754 + at.8
755 + at.9
756 + at.10
757 + at.11
758 + at.12;
759 Self::universal_from_dynamical(approx + correction + extra + additional)
760 }
761
762 #[allow(dead_code)] pub fn sidereal_from_moment(moment: Moment) -> f64 {
770 let c = (moment - J2000) / 36525.0;
771 let coefficients = &[
772 (280.46061837),
773 (36525.0 * 360.98564736629),
774 (0.000387933),
775 (-1.0 / 38710000.0),
776 ];
777
778 let angle = poly(c, coefficients);
779
780 angle.rem_euclid(360.0)
781 }
782
783 pub fn lunar_latitude(julian_centuries: f64) -> f64 {
793 let c = julian_centuries;
794 let l = Self::mean_lunar_longitude(c);
795 let d = Self::lunar_elongation(c);
796 let ms = Self::solar_anomaly(c);
797 let ml = Self::lunar_anomaly(c);
798 let f = Self::moon_node(c);
799 let e = 1.0 - (0.002516 * c) - (0.0000074 * c * c);
800
801 let mut ct = (
802 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
803 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
804 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
805 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
806 );
807
808 let [w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15, w16, w17, w18, w19, w20, w21, w22, w23, w24, w25, w26, w27, w28, w29, w30, w31, w32, w33, w34, w35, w36, w37, w38, w39, w40, w41, w42, w43, w44, w45, w46, w47, w48, w49, w50, w51, w52, w53, w54, w55, w56, w57, w58, w59] = [
809 0.0, 0.0, 0.0, 2.0, 2.0, 2.0, 2.0, 0.0, 2.0, 0.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0,
810 0.0, 4.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 4.0, 4.0, 0.0, 4.0, 2.0, 2.0,
811 2.0, 2.0, 0.0, 2.0, 2.0, 2.0, 2.0, 4.0, 2.0, 2.0, 0.0, 2.0, 1.0, 1.0, 0.0, 2.0, 1.0,
812 2.0, 0.0, 4.0, 4.0, 1.0, 4.0, 1.0, 4.0, 2.0,
813 ];
814
815 let [x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, x30, x31, x32, x33, x34, x35, x36, x37, x38, x39, x40, x41, x42, x43, x44, x45, x46, x47, x48, x49, x50, x51, x52, x53, x54, x55, x56, x57, x58, x59] = [
816 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 1.0, -1.0, -1.0,
817 -1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
818 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, -1.0, -2.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0,
819 0.0, -1.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, -1.0, -2.0,
820 ];
821
822 let [y0, y1, y2, y3, y4, y5, y6, y7, y8, y9, y10, y11, y12, y13, y14, y15, y16, y17, y18, y19, y20, y21, y22, y23, y24, y25, y26, y27, y28, y29, y30, y31, y32, y33, y34, y35, y36, y37, y38, y39, y40, y41, y42, y43, y44, y45, y46, y47, y48, y49, y50, y51, y52, y53, y54, y55, y56, y57, y58, y59] = [
823 0.0, 1.0, 1.0, 0.0, -1.0, -1.0, 0.0, 2.0, 1.0, 2.0, 0.0, -2.0, 1.0, 0.0, -1.0, 0.0,
824 -1.0, -1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 3.0, 0.0, -1.0, 1.0, -2.0,
825 0.0, 2.0, 1.0, -2.0, 3.0, 2.0, -3.0, -1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0,
826 -2.0, -1.0, 1.0, -2.0, 2.0, -2.0, -1.0, 1.0, 1.0, -1.0, 0.0, 0.0,
827 ];
828
829 let [z0, z1, z2, z3, z4, z5, z6, z7, z8, z9, z10, z11, z12, z13, z14, z15, z16, z17, z18, z19, z20, z21, z22, z23, z24, z25, z26, z27, z28, z29, z30, z31, z32, z33, z34, z35, z36, z37, z38, z39, z40, z41, z42, z43, z44, z45, z46, z47, z48, z49, z50, z51, z52, z53, z54, z55, z56, z57, z58, z59] = [
830 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0,
831 -1.0, -1.0, -1.0, 1.0, 3.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -3.0, 1.0,
832 -3.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 3.0, -1.0, -1.0, 1.0,
833 -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0,
834 ];
835
836 let [v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59] = [
837 5128122.0, 280602.0, 277693.0, 173237.0, 55413.0, 46271.0, 32573.0, 17198.0, 9266.0,
838 8822.0, 8216.0, 4324.0, 4200.0, -3359.0, 2463.0, 2211.0, 2065.0, -1870.0, 1828.0,
839 -1794.0, -1749.0, -1565.0, -1491.0, -1475.0, -1410.0, -1344.0, -1335.0, 1107.0, 1021.0,
840 833.0, 777.0, 671.0, 607.0, 596.0, 491.0, -451.0, 439.0, 422.0, 421.0, -366.0, -351.0,
841 331.0, 315.0, 302.0, -283.0, -229.0, 223.0, 223.0, -220.0, -220.0, -185.0, 181.0,
842 -177.0, 176.0, 166.0, -164.0, 132.0, -119.0, 115.0, 107.0,
843 ];
844
845 ct.0 = v0 * (w0 * d + x0 * ms + y0 * ml + z0 * f).to_radians().sin();
847 ct.1 = v1 * (w1 * d + x1 * ms + y1 * ml + z1 * f).to_radians().sin();
848 ct.2 = v2 * (w2 * d + x2 * ms + y2 * ml + z2 * f).to_radians().sin();
849 ct.3 = v3 * (w3 * d + x3 * ms + y3 * ml + z3 * f).to_radians().sin();
850 ct.4 = v4 * (w4 * d + x4 * ms + y4 * ml + z4 * f).to_radians().sin();
851 ct.5 = v5 * (w5 * d + x5 * ms + y5 * ml + z5 * f).to_radians().sin();
852 ct.6 = v6 * (w6 * d + x6 * ms + y6 * ml + z6 * f).to_radians().sin();
853 ct.7 = v7 * (w7 * d + x7 * ms + y7 * ml + z7 * f).to_radians().sin();
854 ct.8 = v8 * (w8 * d + x8 * ms + y8 * ml + z8 * f).to_radians().sin();
855 ct.9 = v9 * (w9 * d + x9 * ms + y9 * ml + z9 * f).to_radians().sin();
856 ct.10 = v10 * e * (w10 * d + x10 * ms + y10 * ml + z10 * f).to_radians().sin();
857 ct.11 = v11 * (w11 * d + x11 * ms + y11 * ml + z11 * f).to_radians().sin();
858 ct.12 = v12 * (w12 * d + x12 * ms + y12 * ml + z12 * f).to_radians().sin();
859 ct.13 = v13 * e * (w13 * d + x13 * ms + y13 * ml + z13 * f).to_radians().sin();
860 ct.14 = v14 * e * (w14 * d + x14 * ms + y14 * ml + z14 * f).to_radians().sin();
861 ct.15 = v15 * e * (w15 * d + x15 * ms + y15 * ml + z15 * f).to_radians().sin();
862 ct.16 = v16 * e * (w16 * d + x16 * ms + y16 * ml + z16 * f).to_radians().sin();
863 ct.17 = v17 * e * (w17 * d + x17 * ms + y17 * ml + z17 * f).to_radians().sin();
864 ct.18 = v18 * (w18 * d + x18 * ms + y18 * ml + z18 * f).to_radians().sin();
865 ct.19 = v19 * e * (w19 * d + x19 * ms + y19 * ml + z19 * f).to_radians().sin();
866 ct.20 = v20 * (w20 * d + x20 * ms + y20 * ml + z20 * f).to_radians().sin();
867 ct.21 = v21 * e * (w21 * d + x21 * ms + y21 * ml + z21 * f).to_radians().sin();
868 ct.22 = v22 * (w22 * d + x22 * ms + y22 * ml + z22 * f).to_radians().sin();
869 ct.23 = v23 * e * (w23 * d + x23 * ms + y23 * ml + z23 * f).to_radians().sin();
870 ct.24 = v24 * e * (w24 * d + x24 * ms + y24 * ml + z24 * f).to_radians().sin();
871 ct.25 = v25 * e * (w25 * d + x25 * ms + y25 * ml + z25 * f).to_radians().sin();
872 ct.26 = v26 * (w26 * d + x26 * ms + y26 * ml + z26 * f).to_radians().sin();
873 ct.27 = v27 * (w27 * d + x27 * ms + y27 * ml + z27 * f).to_radians().sin();
874 ct.28 = v28 * (w28 * d + x28 * ms + y28 * ml + z28 * f).to_radians().sin();
875 ct.29 = v29 * (w29 * d + x29 * ms + y29 * ml + z29 * f).to_radians().sin();
876 ct.30 = v30 * (w30 * d + x30 * ms + y30 * ml + z30 * f).to_radians().sin();
877 ct.31 = v31 * (w31 * d + x31 * ms + y31 * ml + z31 * f).to_radians().sin();
878 ct.32 = v32 * (w32 * d + x32 * ms + y32 * ml + z32 * f).to_radians().sin();
879 ct.33 = v33 * (w33 * d + x33 * ms + y33 * ml + z33 * f).to_radians().sin();
880 ct.34 = v34 * e * (w34 * d + x34 * ms + y34 * ml + z34 * f).to_radians().sin();
881 ct.35 = v35 * (w35 * d + x35 * ms + y35 * ml + z35 * f).to_radians().sin();
882 ct.36 = v36 * (w36 * d + x36 * ms + y36 * ml + z36 * f).to_radians().sin();
883 ct.37 = v37 * (w37 * d + x37 * ms + y37 * ml + z37 * f).to_radians().sin();
884 ct.38 = v38 * (w38 * d + x38 * ms + y38 * ml + z38 * f).to_radians().sin();
885 ct.39 = v39 * e * (w39 * d + x39 * ms + y39 * ml + z39 * f).to_radians().sin();
886 ct.40 = v40 * e * (w40 * d + x40 * ms + y40 * ml + z40 * f).to_radians().sin();
887 ct.41 = v41 * (w41 * d + x41 * ms + y41 * ml + z41 * f).to_radians().sin();
888 ct.42 = v42 * e * (w42 * d + x42 * ms + y42 * ml + z42 * f).to_radians().sin();
889 ct.43 = v43 * e * e * (w43 * d + x43 * ms + y43 * ml + z43 * f).to_radians().sin();
890 ct.44 = v44 * (w44 * d + x44 * ms + y44 * ml + z44 * f).to_radians().sin();
891 ct.45 = v45 * e * (w45 * d + x45 * ms + y45 * ml + z45 * f).to_radians().sin();
892 ct.46 = v46 * e * (w46 * d + x46 * ms + y46 * ml + z46 * f).to_radians().sin();
893 ct.47 = v47 * e * (w47 * d + x47 * ms + y47 * ml + z47 * f).to_radians().sin();
894 ct.48 = v48 * e * (w48 * d + x48 * ms + y48 * ml + z48 * f).to_radians().sin();
895 ct.49 = v49 * e * (w49 * d + x49 * ms + y49 * ml + z49 * f).to_radians().sin();
896 ct.50 = v50 * (w50 * d + x50 * ms + y50 * ml + z50 * f).to_radians().sin();
897 ct.51 = v51 * e * (w51 * d + x51 * ms + y51 * ml + z51 * f).to_radians().sin();
898 ct.52 = v52 * e * (w52 * d + x52 * ms + y52 * ml + z52 * f).to_radians().sin();
899 ct.53 = v53 * (w53 * d + x53 * ms + y53 * ml + z53 * f).to_radians().sin();
900 ct.54 = v54 * e * (w54 * d + x54 * ms + y54 * ml + z54 * f).to_radians().sin();
901 ct.55 = v55 * (w55 * d + x55 * ms + y55 * ml + z55 * f).to_radians().sin();
902 ct.56 = v56 * (w56 * d + x56 * ms + y56 * ml + z56 * f).to_radians().sin();
903 ct.57 = v57 * (w57 * d + x57 * ms + y57 * ml + z57 * f).to_radians().sin();
904 ct.58 = v58 * e * (w58 * d + x58 * ms + y58 * ml + z58 * f).to_radians().sin();
905 ct.59 = v59 * e * e * (w59 * d + x59 * ms + y59 * ml + z59 * f).to_radians().sin();
906
907 let mut correction = ct.0
908 + ct.1
909 + ct.2
910 + ct.3
911 + ct.4
912 + ct.5
913 + ct.6
914 + ct.7
915 + ct.8
916 + ct.9
917 + ct.10
918 + ct.11
919 + ct.12
920 + ct.13
921 + ct.14
922 + ct.15
923 + ct.16
924 + ct.17
925 + ct.18
926 + ct.19
927 + ct.20
928 + ct.21
929 + ct.22
930 + ct.23
931 + ct.24
932 + ct.25
933 + ct.26
934 + ct.27
935 + ct.28
936 + ct.29
937 + ct.30
938 + ct.31
939 + ct.32
940 + ct.33
941 + ct.34
942 + ct.35
943 + ct.36
944 + ct.37
945 + ct.38
946 + ct.39
947 + ct.40
948 + ct.41
949 + ct.42
950 + ct.43
951 + ct.44
952 + ct.45
953 + ct.46
954 + ct.47
955 + ct.48
956 + ct.49
957 + ct.50
958 + ct.51
959 + ct.52
960 + ct.53
961 + ct.54
962 + ct.55
963 + ct.56
964 + ct.57
965 + ct.58
966 + ct.59;
967
968 correction /= 1_000_000.0;
969
970 let venus = (175.0
971 * ((119.75 + c * 131.849 + f).to_radians().sin()
972 + (119.75 + c * 131.849 - f).to_radians().sin()))
973 / 1_000_000.0;
974
975 let flat_earth = (-2235.0 * l.to_radians().sin()
976 + 127.0 * (l - ml).to_radians().sin()
977 + -115.0 * (l + ml).to_radians().sin())
978 / 1_000_000.0;
979
980 let extra = (382.0 * (313.45 + (c * 481266.484)).to_radians().sin()) / 1_000_000.0;
981
982 correction + venus + flat_earth + extra
983 }
984
985 pub fn lunar_longitude(julian_centuries: f64) -> f64 {
994 let c = julian_centuries;
995 let l = Self::mean_lunar_longitude(c);
996 let d = Self::lunar_elongation(c);
997 let ms = Self::solar_anomaly(c);
998 let ml = Self::lunar_anomaly(c);
999 let f = Self::moon_node(c);
1000 let e = 1.0 - (0.002516 * c) - (0.0000074 * c * c);
1001
1002 let mut ct = (
1003 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1004 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1005 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1006 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1007 );
1008
1009 let [v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58] = [
1010 6288774.0, 1274027.0, 658314.0, 213618.0, -185116.0, -114332.0, 58793.0, 57066.0,
1011 53322.0, 45758.0, -40923.0, -34720.0, -30383.0, 15327.0, -12528.0, 10980.0, 10675.0,
1012 10034.0, 8548.0, -7888.0, -6766.0, -5163.0, 4987.0, 4036.0, 3994.0, 3861.0, 3665.0,
1013 -2689.0, -2602.0, 2390.0, -2348.0, 2236.0, -2120.0, -2069.0, 2048.0, -1773.0, -1595.0,
1014 1215.0, -1110.0, -892.0, -810.0, 759.0, -713.0, -700.0, 691.0, 596.0, 549.0, 537.0,
1015 520.0, -487.0, -399.0, -381.0, 351.0, -340.0, 330.0, 327.0, -323.0, 299.0, 294.0,
1016 ];
1017 let [w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15, w16, w17, w18, w19, w20, w21, w22, w23, w24, w25, w26, w27, w28, w29, w30, w31, w32, w33, w34, w35, w36, w37, w38, w39, w40, w41, w42, w43, w44, w45, w46, w47, w48, w49, w50, w51, w52, w53, w54, w55, w56, w57, w58] = [
1018 0.0, 2.0, 2.0, 0.0, 0.0, 0.0, 2.0, 2.0, 2.0, 2.0, 0.0, 1.0, 0.0, 2.0, 0.0, 0.0, 4.0,
1019 0.0, 4.0, 2.0, 2.0, 1.0, 1.0, 2.0, 2.0, 4.0, 2.0, 0.0, 2.0, 2.0, 1.0, 2.0, 0.0, 0.0,
1020 2.0, 2.0, 2.0, 4.0, 0.0, 3.0, 2.0, 4.0, 0.0, 2.0, 2.0, 2.0, 4.0, 0.0, 4.0, 1.0, 2.0,
1021 0.0, 1.0, 3.0, 4.0, 2.0, 0.0, 1.0, 2.0,
1022 ];
1023 let [x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, x30, x31, x32, x33, x34, x35, x36, x37, x38, x39, x40, x41, x42, x43, x44, x45, x46, x47, x48, x49, x50, x51, x52, x53, x54, x55, x56, x57, x58] = [
1024 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, -1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0,
1025 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, -1.0, 0.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0, -2.0, 1.0, 2.0,
1026 -2.0, 0.0, 0.0, -1.0, 0.0, 0.0, 1.0, -1.0, 2.0, 2.0, 1.0, -1.0, 0.0, 0.0, -1.0, 0.0,
1027 1.0, 0.0, 1.0, 0.0, 0.0, -1.0, 2.0, 1.0, 0.0,
1028 ];
1029 let [y0, y1, y2, y3, y4, y5, y6, y7, y8, y9, y10, y11, y12, y13, y14, y15, y16, y17, y18, y19, y20, y21, y22, y23, y24, y25, y26, y27, y28, y29, y30, y31, y32, y33, y34, y35, y36, y37, y38, y39, y40, y41, y42, y43, y44, y45, y46, y47, y48, y49, y50, y51, y52, y53, y54, y55, y56, y57, y58] = [
1030 1.0, -1.0, 0.0, 2.0, 0.0, 0.0, -2.0, -1.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 1.0,
1031 -1.0, 3.0, -2.0, -1.0, 0.0, -1.0, 0.0, 1.0, 2.0, 0.0, -3.0, -2.0, -1.0, -2.0, 1.0, 0.0,
1032 2.0, 0.0, -1.0, 1.0, 0.0, -1.0, 2.0, -1.0, 1.0, -2.0, -1.0, -1.0, -2.0, 0.0, 1.0, 4.0,
1033 0.0, -2.0, 0.0, 2.0, 1.0, -2.0, -3.0, 2.0, 1.0, -1.0, 3.0,
1034 ];
1035 let [z0, z1, z2, z3, z4, z5, z6, z7, z8, z9, z10, z11, z12, z13, z14, z15, z16, z17, z18, z19, z20, z21, z22, z23, z24, z25, z26, z27, z28, z29, z30, z31, z32, z33, z34, z35, z36, z37, z38, z39, z40, z41, z42, z43, z44, z45, z46, z47, z48, z49, z50, z51, z52, z53, z54, z55, z56, z57, z58] = [
1036 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.0, 2.0, -2.0, 0.0,
1037 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1038 0.0, -2.0, 2.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.0, 0.0, 0.0, 0.0, 0.0, -2.0,
1039 -2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1040 ];
1041
1042 ct.0 = v0 * (w0 * d + x0 * ms + y0 * ml + z0 * f).to_radians().sin();
1044 ct.1 = v1 * (w1 * d + x1 * ms + y1 * ml + z1 * f).to_radians().sin();
1045 ct.2 = v2 * (w2 * d + x2 * ms + y2 * ml + z2 * f).to_radians().sin();
1046 ct.3 = v3 * (w3 * d + x3 * ms + y3 * ml + z3 * f).to_radians().sin();
1047 ct.4 = v4 * e * (w4 * d + x4 * ms + y4 * ml + z4 * f).to_radians().sin();
1048 ct.5 = v5 * (w5 * d + x5 * ms + y5 * ml + z5 * f).to_radians().sin();
1049 ct.6 = v6 * (w6 * d + x6 * ms + y6 * ml + z6 * f).to_radians().sin();
1050 ct.7 = v7 * e * (w7 * d + x7 * ms + y7 * ml + z7 * f).to_radians().sin();
1051 ct.8 = v8 * (w8 * d + x8 * ms + y8 * ml + z8 * f).to_radians().sin();
1052 ct.9 = v9 * e * (w9 * d + x9 * ms + y9 * ml + z9 * f).to_radians().sin();
1053 ct.10 = v10 * e * (w10 * d + x10 * ms + y10 * ml + z10 * f).to_radians().sin();
1054 ct.11 = v11 * (w11 * d + x11 * ms + y11 * ml + z11 * f).to_radians().sin();
1055 ct.12 = v12 * e * (w12 * d + x12 * ms + y12 * ml + z12 * f).to_radians().sin();
1056 ct.13 = v13 * (w13 * d + x13 * ms + y13 * ml + z13 * f).to_radians().sin();
1057 ct.14 = v14 * (w14 * d + x14 * ms + y14 * ml + z14 * f).to_radians().sin();
1058 ct.15 = v15 * (w15 * d + x15 * ms + y15 * ml + z15 * f).to_radians().sin();
1059 ct.16 = v16 * (w16 * d + x16 * ms + y16 * ml + z16 * f).to_radians().sin();
1060 ct.17 = v17 * (w17 * d + x17 * ms + y17 * ml + z17 * f).to_radians().sin();
1061 ct.18 = v18 * (w18 * d + x18 * ms + y18 * ml + z18 * f).to_radians().sin();
1062 ct.19 = v19 * e * (w19 * d + x19 * ms + y19 * ml + z19 * f).to_radians().sin();
1063 ct.20 = v20 * e * (w20 * d + x20 * ms + y20 * ml + z20 * f).to_radians().sin();
1064 ct.21 = v21 * (w21 * d + x21 * ms + y21 * ml + z21 * f).to_radians().sin();
1065 ct.22 = v22 * e * (w22 * d + x22 * ms + y22 * ml + z22 * f).to_radians().sin();
1066 ct.23 = v23 * e * (w23 * d + x23 * ms + y23 * ml + z23 * f).to_radians().sin();
1067 ct.24 = v24 * (w24 * d + x24 * ms + y24 * ml + z24 * f).to_radians().sin();
1068 ct.25 = v25 * (w25 * d + x25 * ms + y25 * ml + z25 * f).to_radians().sin();
1069 ct.26 = v26 * (w26 * d + x26 * ms + y26 * ml + z26 * f).to_radians().sin();
1070 ct.27 = v27 * e * (w27 * d + x27 * ms + y27 * ml + z27 * f).to_radians().sin();
1071 ct.28 = v28 * (w28 * d + x28 * ms + y28 * ml + z28 * f).to_radians().sin();
1072 ct.29 = v29 * e * (w29 * d + x29 * ms + y29 * ml + z29 * f).to_radians().sin();
1073 ct.30 = v30 * (w30 * d + x30 * ms + y30 * ml + z30 * f).to_radians().sin();
1074 ct.31 = v31 * e * e * (w31 * d + x31 * ms + y31 * ml + z31 * f).to_radians().sin();
1075 ct.32 = v32 * e * (w32 * d + x32 * ms + y32 * ml + z32 * f).to_radians().sin();
1076 ct.33 = v33 * e * e * (w33 * d + x33 * ms + y33 * ml + z33 * f).to_radians().sin();
1077 ct.34 = v34 * e * e * (w34 * d + x34 * ms + y34 * ml + z34 * f).to_radians().sin();
1078 ct.35 = v35 * (w35 * d + x35 * ms + y35 * ml + z35 * f).to_radians().sin();
1079 ct.36 = v36 * (w36 * d + x36 * ms + y36 * ml + z36 * f).to_radians().sin();
1080 ct.37 = v37 * e * (w37 * d + x37 * ms + y37 * ml + z37 * f).to_radians().sin();
1081 ct.38 = v38 * (w38 * d + x38 * ms + y38 * ml + z38 * f).to_radians().sin();
1082 ct.39 = v39 * (w39 * d + x39 * ms + y39 * ml + z39 * f).to_radians().sin();
1083 ct.40 = v40 * e * (w40 * d + x40 * ms + y40 * ml + z40 * f).to_radians().sin();
1084 ct.41 = v41 * e * (w41 * d + x41 * ms + y41 * ml + z41 * f).to_radians().sin();
1085 ct.42 = v42 * e * e * (w42 * d + x42 * ms + y42 * ml + z42 * f).to_radians().sin();
1086 ct.43 = v43 * e * e * (w43 * d + x43 * ms + y43 * ml + z43 * f).to_radians().sin();
1087 ct.44 = v44 * e * (w44 * d + x44 * ms + y44 * ml + z44 * f).to_radians().sin();
1088 ct.45 = v45 * e * (w45 * d + x45 * ms + y45 * ml + z45 * f).to_radians().sin();
1089 ct.46 = v46 * (w46 * d + x46 * ms + y46 * ml + z46 * f).to_radians().sin();
1090 ct.47 = v47 * (w47 * d + x47 * ms + y47 * ml + z47 * f).to_radians().sin();
1091 ct.48 = v48 * e * (w48 * d + x48 * ms + y48 * ml + z48 * f).to_radians().sin();
1092 ct.49 = v49 * (w49 * d + x49 * ms + y49 * ml + z49 * f).to_radians().sin();
1093 ct.50 = v50 * e * (w50 * d + x50 * ms + y50 * ml + z50 * f).to_radians().sin();
1094 ct.51 = v51 * (w51 * d + x51 * ms + y51 * ml + z51 * f).to_radians().sin();
1095 ct.52 = v52 * e * (w52 * d + x52 * ms + y52 * ml + z52 * f).to_radians().sin();
1096 ct.53 = v53 * (w53 * d + x53 * ms + y53 * ml + z53 * f).to_radians().sin();
1097 ct.54 = v54 * (w54 * d + x54 * ms + y54 * ml + z54 * f).to_radians().sin();
1098 ct.55 = v55 * e * (w55 * d + x55 * ms + y55 * ml + z55 * f).to_radians().sin();
1099 ct.56 = v56 * e * e * (w56 * d + x56 * ms + y56 * ml + z56 * f).to_radians().sin();
1100 ct.57 = v57 * e * (w57 * d + x57 * ms + y57 * ml + z57 * f).to_radians().sin();
1101 ct.58 = v58 * (w58 * d + x58 * ms + y58 * ml + z58 * f).to_radians().sin();
1102
1103 let mut correction = ct.0
1104 + ct.1
1105 + ct.2
1106 + ct.3
1107 + ct.4
1108 + ct.5
1109 + ct.6
1110 + ct.7
1111 + ct.8
1112 + ct.9
1113 + ct.10
1114 + ct.11
1115 + ct.12
1116 + ct.13
1117 + ct.14
1118 + ct.15
1119 + ct.16
1120 + ct.17
1121 + ct.18
1122 + ct.19
1123 + ct.20
1124 + ct.21
1125 + ct.22
1126 + ct.23
1127 + ct.24
1128 + ct.25
1129 + ct.26
1130 + ct.27
1131 + ct.28
1132 + ct.29
1133 + ct.30
1134 + ct.31
1135 + ct.32
1136 + ct.33
1137 + ct.34
1138 + ct.35
1139 + ct.36
1140 + ct.37
1141 + ct.38
1142 + ct.39
1143 + ct.40
1144 + ct.41
1145 + ct.42
1146 + ct.43
1147 + ct.44
1148 + ct.45
1149 + ct.46
1150 + ct.47
1151 + ct.48
1152 + ct.49
1153 + ct.50
1154 + ct.51
1155 + ct.52
1156 + ct.53
1157 + ct.54
1158 + ct.55
1159 + ct.56
1160 + ct.57
1161 + ct.58;
1162
1163 correction /= 1000000.0;
1164 let venus = 3958.0 / 1000000.0 * (119.75 + c * 131.849).to_radians().sin();
1165 let jupiter = 318.0 / 1000000.0 * (53.09 + c * 479264.29).to_radians().sin();
1166 let flat_earth = 1962.0 / 1000000.0 * (l - f).to_radians().sin();
1167 (l + correction + venus + jupiter + flat_earth + Self::nutation(julian_centuries))
1168 .rem_euclid(360.0)
1169 }
1170
1171 fn mean_lunar_longitude(c: f64) -> f64 {
1177 let n = 218.3164477
1179 + c * (481267.88123421 - 0.0015786 * c + c * c / 538841.0 - c * c * c / 65194000.0);
1180
1181 n.rem_euclid(360.0)
1182 }
1183
1184 pub fn phasis_on_or_after(
1189 date: RataDie,
1190 location: Location,
1191 lunar_phase: Option<f64>,
1192 ) -> RataDie {
1193 let lunar_phase =
1194 lunar_phase.unwrap_or_else(|| Self::calculate_new_moon_at_or_before(date));
1195 let age = date.to_f64_date() - lunar_phase;
1196 let tau = if age <= 4.0 || Self::visible_crescent((date - 1).as_moment(), location) {
1197 lunar_phase + 29.0 } else {
1199 date.to_f64_date()
1200 };
1201 next_moment(Moment::new(tau), location, Self::visible_crescent)
1202 }
1203
1204 pub fn phasis_on_or_before(
1210 date: RataDie,
1211 location: Location,
1212 lunar_phase: Option<f64>,
1213 ) -> RataDie {
1214 let lunar_phase =
1215 lunar_phase.unwrap_or_else(|| Self::calculate_new_moon_at_or_before(date));
1216 let age = date.to_f64_date() - lunar_phase;
1217 let tau = if age <= 3.0 && !Self::visible_crescent((date).as_moment(), location) {
1218 lunar_phase - 30.0 } else {
1220 lunar_phase
1221 };
1222 next_moment(Moment::new(tau), location, Self::visible_crescent)
1223 }
1224
1225 pub fn calculate_new_moon_at_or_before(date: RataDie) -> f64 {
1227 Self::lunar_phase_at_or_before(0.0, date.as_moment())
1228 .inner()
1229 .floor()
1230 }
1231
1232 pub fn month_length(date: RataDie, location: Location) -> u8 {
1239 let moon = Self::phasis_on_or_after(date + 1, location, None);
1240 let prev = Self::phasis_on_or_before(date, location, None);
1241
1242 debug_assert!(moon > prev);
1243 debug_assert!(moon - prev < u8::MAX.into());
1244 (moon - prev) as u8
1245 }
1246
1247 fn lunar_elongation(c: f64) -> f64 {
1253 (297.85019021 + 445267.1114034 * c - 0.0018819 * c * c + c * c * c / 545868.0
1255 - c * c * c * c / 113065000.0)
1256 .rem_euclid(360.0)
1257 }
1258
1259 pub fn lunar_altitude(moment: Moment, location: Location) -> f64 {
1265 let phi = location.latitude;
1266 let psi = location.longitude;
1267 let c = Self::julian_centuries(moment);
1268 let lambda = Self::lunar_longitude(c);
1269 let beta = Self::lunar_latitude(c);
1270 let alpha = Self::right_ascension(moment, beta, lambda);
1271 let delta = Self::declination(moment, beta, lambda);
1272 let theta0 = Self::sidereal_from_moment(moment);
1273 let cap_h = (theta0 + psi - alpha).rem_euclid(360.0);
1274
1275 let altitude = (phi.to_radians().sin() * delta.to_radians().sin()
1276 + phi.to_radians().cos() * delta.to_radians().cos() * cap_h.to_radians().cos())
1277 .asin()
1278 .to_degrees();
1279
1280 (altitude + 180.0).rem_euclid(360.0) - 180.0
1281 }
1282
1283 #[allow(dead_code)]
1289 pub fn lunar_distance(moment: Moment) -> f64 {
1290 let c = Self::julian_centuries(moment);
1291 let cap_d = Self::lunar_elongation(c);
1292 let cap_m = Self::solar_anomaly(c);
1293 let cap_m_prime = Self::lunar_anomaly(c);
1294 let cap_f = Self::moon_node(c);
1295 let cap_e = 1.0 - (0.002516 * c) - (0.0000074 * c * c);
1296
1297 let args_lunar_elongation = [
1298 0.0, 2.0, 2.0, 0.0, 0.0, 0.0, 2.0, 2.0, 2.0, 2.0, 0.0, 1.0, 0.0, 2.0, 0.0, 0.0, 4.0,
1299 0.0, 4.0, 2.0, 2.0, 1.0, 1.0, 2.0, 2.0, 4.0, 2.0, 0.0, 2.0, 2.0, 1.0, 2.0, 0.0, 0.0,
1300 2.0, 2.0, 2.0, 4.0, 0.0, 3.0, 2.0, 4.0, 0.0, 2.0, 2.0, 2.0, 4.0, 0.0, 4.0, 1.0, 2.0,
1301 0.0, 1.0, 3.0, 4.0, 2.0, 0.0, 1.0, 2.0, 2.0,
1302 ];
1303
1304 let args_solar_anomaly = [
1305 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, -1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0,
1306 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, -1.0, 0.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0, -2.0, 1.0, 2.0,
1307 -2.0, 0.0, 0.0, -1.0, 0.0, 0.0, 1.0, -1.0, 2.0, 2.0, 1.0, -1.0, 0.0, 0.0, -1.0, 0.0,
1308 1.0, 0.0, 1.0, 0.0, 0.0, -1.0, 2.0, 1.0, 0.0, 0.0,
1309 ];
1310
1311 let args_lunar_anomaly = [
1312 1.0, -1.0, 0.0, 2.0, 0.0, 0.0, -2.0, -1.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 1.0,
1313 -1.0, 3.0, -2.0, -1.0, 0.0, -1.0, 0.0, 1.0, 2.0, 0.0, -3.0, -2.0, -1.0, -2.0, 1.0, 0.0,
1314 2.0, 0.0, -1.0, 1.0, 0.0, -1.0, 2.0, -1.0, 1.0, -2.0, -1.0, -1.0, -2.0, 0.0, 1.0, 4.0,
1315 0.0, -2.0, 0.0, 2.0, 1.0, -2.0, -3.0, 2.0, 1.0, -1.0, 3.0, -1.0,
1316 ];
1317
1318 let args_moon_node = [
1319 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.0, 2.0, -2.0, 0.0,
1320 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1321 0.0, -2.0, 2.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.0, 0.0, 0.0, 0.0, 0.0, -2.0,
1322 -2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.0,
1323 ];
1324
1325 let cosine_coeff = [
1326 -20905355.0,
1327 -3699111.0,
1328 -2955968.0,
1329 -569925.0,
1330 48888.0,
1331 -3149.0,
1332 246158.0,
1333 -152138.0,
1334 -170733.0,
1335 -204586.0,
1336 -129620.0,
1337 108743.0,
1338 104755.0,
1339 10321.0,
1340 0.0,
1341 79661.0,
1342 -34782.0,
1343 -23210.0,
1344 -21636.0,
1345 24208.0,
1346 30824.0,
1347 -8379.0,
1348 -16675.0,
1349 -12831.0,
1350 -10445.0,
1351 -11650.0,
1352 14403.0,
1353 -7003.0,
1354 0.0,
1355 10056.0,
1356 6322.0,
1357 -9884.0,
1358 5751.0,
1359 0.0,
1360 -4950.0,
1361 4130.0,
1362 0.0,
1363 -3958.0,
1364 0.0,
1365 3258.0,
1366 2616.0,
1367 -1897.0,
1368 -2117.0,
1369 2354.0,
1370 0.0,
1371 0.0,
1372 -1423.0,
1373 -1117.0,
1374 -1571.0,
1375 -1739.0,
1376 0.0,
1377 -4421.0,
1378 0.0,
1379 0.0,
1380 0.0,
1381 0.0,
1382 1165.0,
1383 0.0,
1384 0.0,
1385 8752.0,
1386 ];
1387
1388 let correction: f64 = cosine_coeff
1389 .iter()
1390 .zip(args_lunar_elongation.iter())
1391 .zip(args_solar_anomaly.iter())
1392 .zip(args_lunar_anomaly.iter())
1393 .zip(args_moon_node.iter())
1394 .map(|((((&v, &w), &x), &y), &z)| {
1395 v * cap_e.powf(x.abs())
1396 * (w * cap_d + x * cap_m + y * cap_m_prime + z * cap_f)
1397 .to_radians()
1398 .cos()
1399 })
1400 .sum();
1401
1402 385000560.0 + correction
1403 }
1404
1405 pub fn lunar_parallax(lunar_altitude_val: f64, moment: Moment) -> f64 {
1412 let cap_delta = Self::lunar_distance(moment);
1413 let alt = 6378140.0 / cap_delta;
1414 let arg = alt * lunar_altitude_val.to_radians().cos();
1415 arg.asin().to_degrees().rem_euclid(360.0)
1416 }
1417
1418 fn topocentric_lunar_altitude(moment: Moment, location: Location) -> f64 {
1423 let lunar_altitude = Self::lunar_altitude(moment, location);
1424 lunar_altitude - Self::lunar_parallax(lunar_altitude, moment)
1425 }
1426
1427 fn observed_lunar_altitude(moment: Moment, location: Location) -> f64 {
1431 let r = Self::topocentric_lunar_altitude(moment, location);
1432 let y = Self::refraction(location);
1433 let z = 16.0 / 60.0;
1434
1435 r + y + z
1436 }
1437
1438 fn solar_anomaly(c: f64) -> f64 {
1445 (357.5291092 + 35999.0502909 * c - 0.0001536 * c * c + c * c * c / 24490000.0)
1447 .rem_euclid(360.0)
1448 }
1449
1450 fn lunar_anomaly(c: f64) -> f64 {
1457 (134.9633964 + 477198.8675055 * c + 0.0087414 * c * c + c * c * c / 69699.0
1459 - c * c * c * c / 14712000.0)
1460 .rem_euclid(360.0)
1461 }
1462
1463 fn moon_node(c: f64) -> f64 {
1470 (93.2720950 + 483202.0175233 * c - 0.0036539 * c * c - c * c * c / 3526000.0
1472 + c * c * c * c / 863310000.0)
1473 .rem_euclid(360.0)
1474 }
1475
1476 #[allow(dead_code)] fn moonset(date: Moment, location: Location) -> Option<Moment> {
1483 let moment = Location::universal_from_standard(date, location);
1484 let waxing = Self::lunar_phase(date, Self::julian_centuries(date)) < 180.0;
1485 let alt = Self::observed_lunar_altitude(moment, location);
1486 let lat = location.latitude;
1487 let offset = alt / (4.0 * (90.0 - lat.abs()));
1488
1489 let approx = if waxing {
1490 if offset > 0.0 {
1491 moment + offset
1492 } else {
1493 moment + 1.0 + offset
1494 }
1495 } else {
1496 moment - offset + 0.5
1497 };
1498
1499 let set = Moment::new(binary_search(
1500 approx.inner() - (6.0 / 24.0),
1501 approx.inner() + (6.0 / 24.0),
1502 |x| Self::observed_lunar_altitude(Moment::new(x), location) < 0.0,
1503 1.0 / 24.0 / 60.0,
1504 ));
1505
1506 if set < moment + 1.0 {
1507 let std = Moment::new(
1508 Location::standard_from_universal(set, location)
1509 .inner()
1510 .max(date.inner()),
1511 );
1512 debug_assert!(std >= date, "std should not be less than date");
1513 if std < date {
1514 return None;
1515 }
1516 Some(std)
1517 } else {
1518 None
1519 }
1520 }
1521
1522 #[allow(dead_code)]
1528 pub fn sunset(date: Moment, location: Location) -> Option<Moment> {
1529 let alpha = Self::refraction(location) + (16.0 / 60.0);
1530 Self::dusk(date.inner(), location, alpha)
1531 }
1532
1533 pub fn moonlag(date: Moment, location: Location) -> Option<f64> {
1539 if let Some(sun) = Self::sunset(date, location) {
1540 if let Some(moon) = Self::moonset(date, location) {
1541 Some(moon - sun)
1542 } else {
1543 Some(1.0)
1544 }
1545 } else {
1546 None
1547 }
1548 }
1549
1550 fn nutation(julian_centuries: f64) -> f64 {
1556 let c = julian_centuries;
1558 let a = 124.90 - 1934.134 * c + 0.002063 * c * c;
1559 let b = 201.11 + 72001.5377 * c + 0.00057 * c * c;
1560 -0.004778 * a.to_radians().sin() - 0.0003667 * b.to_radians().sin()
1561 }
1562
1563 pub fn lunar_phase(moment: Moment, julian_centuries: f64) -> f64 {
1569 let t0 = NEW_MOON_ZERO;
1570 let maybe_n = i64_to_i32(div_euclid_f64(moment - t0, MEAN_SYNODIC_MONTH).round() as i64);
1571 debug_assert!(
1572 maybe_n.is_ok(),
1573 "Lunar phase moment should be in range of i32"
1574 );
1575 let n = maybe_n.unwrap_or_else(|e| e.saturate());
1576 let a = (Self::lunar_longitude(julian_centuries) - Self::solar_longitude(julian_centuries))
1577 .rem_euclid(360.0);
1578 let b = 360.0 * ((moment - Self::nth_new_moon(n)) / MEAN_SYNODIC_MONTH).rem_euclid(1.0);
1579 if (a - b).abs() > 180.0 {
1580 b
1581 } else {
1582 a
1583 }
1584 }
1585
1586 pub fn lunar_phase_at_or_before(phase: f64, moment: Moment) -> Moment {
1592 let julian_centuries = Self::julian_centuries(moment);
1593 let tau = moment.inner()
1594 - (MEAN_SYNODIC_MONTH / 360.0)
1595 * ((Self::lunar_phase(moment, julian_centuries) - phase) % 360.0);
1596 let a = tau - 2.0;
1597 let b = moment.inner().min(tau + 2.0);
1598
1599 let lunar_phase_f64 = |x: f64| -> f64 {
1600 Self::lunar_phase(Moment::new(x), Self::julian_centuries(Moment::new(x)))
1601 };
1602
1603 Moment::new(invert_angular(lunar_phase_f64, phase, (a, b)))
1604 }
1605
1606 pub fn solar_longitude(julian_centuries: f64) -> f64 {
1613 let c: f64 = julian_centuries;
1614 let mut lt = (
1615 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1616 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1617 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1618 );
1619 let [x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, x30, x31, x32, x33, x34, x35, x36, x37, x38, x39, x40, x41, x42, x43, x44, x45, x46, x47, x48] = [
1620 403406.0, 195207.0, 119433.0, 112392.0, 3891.0, 2819.0, 1721.0, 660.0, 350.0, 334.0,
1621 314.0, 268.0, 242.0, 234.0, 158.0, 132.0, 129.0, 114.0, 99.0, 93.0, 86.0, 78.0, 72.0,
1622 68.0, 64.0, 46.0, 38.0, 37.0, 32.0, 29.0, 28.0, 27.0, 27.0, 25.0, 24.0, 21.0, 21.0,
1623 20.0, 18.0, 17.0, 14.0, 13.0, 13.0, 13.0, 12.0, 10.0, 10.0, 10.0, 10.0,
1624 ];
1625 let [y0, y1, y2, y3, y4, y5, y6, y7, y8, y9, y10, y11, y12, y13, y14, y15, y16, y17, y18, y19, y20, y21, y22, y23, y24, y25, y26, y27, y28, y29, y30, y31, y32, y33, y34, y35, y36, y37, y38, y39, y40, y41, y42, y43, y44, y45, y46, y47, y48] = [
1626 270.54861, 340.19128, 63.91854, 331.26220, 317.843, 86.631, 240.052, 310.26, 247.23,
1627 260.87, 297.82, 343.14, 166.79, 81.53, 3.50, 132.75, 182.95, 162.03, 29.8, 266.4,
1628 249.2, 157.6, 257.8, 185.1, 69.9, 8.0, 197.1, 250.4, 65.3, 162.7, 341.5, 291.6, 98.5,
1629 146.7, 110.0, 5.2, 342.6, 230.9, 256.1, 45.3, 242.9, 115.2, 151.8, 285.3, 53.3, 126.6,
1630 205.7, 85.9, 146.1,
1631 ];
1632 let [z0, z1, z2, z3, z4, z5, z6, z7, z8, z9, z10, z11, z12, z13, z14, z15, z16, z17, z18, z19, z20, z21, z22, z23, z24, z25, z26, z27, z28, z29, z30, z31, z32, z33, z34, z35, z36, z37, z38, z39, z40, z41, z42, z43, z44, z45, z46, z47, z48] = [
1633 0.9287892,
1634 35999.1376958,
1635 35999.4089666,
1636 35998.7287385,
1637 71998.20261,
1638 71998.4403,
1639 36000.35726,
1640 71997.4812,
1641 32964.4678,
1642 -19.4410,
1643 445267.1117,
1644 45036.8840,
1645 3.1008,
1646 22518.4434,
1647 -19.9739,
1648 65928.9345,
1649 9038.0293,
1650 3034.7684,
1651 33718.148,
1652 3034.448,
1653 -2280.773,
1654 29929.992,
1655 31556.493,
1656 149.588,
1657 9037.750,
1658 107997.405,
1659 -4444.176,
1660 151.771,
1661 67555.316,
1662 31556.080,
1663 -4561.540,
1664 107996.706,
1665 1221.655,
1666 62894.167,
1667 31437.369,
1668 14578.298,
1669 -31931.757,
1670 34777.243,
1671 1221.999,
1672 62894.511,
1673 -4442.039,
1674 107997.909,
1675 119.066,
1676 16859.071,
1677 -4.578,
1678 26895.292,
1679 -39.127,
1680 12297.536,
1681 90073.778,
1682 ];
1683
1684 lt.0 = x0 * (y0 + z0 * c).to_radians().sin();
1686 lt.1 = x1 * (y1 + z1 * c).to_radians().sin();
1687 lt.2 = x2 * (y2 + z2 * c).to_radians().sin();
1688 lt.3 = x3 * (y3 + z3 * c).to_radians().sin();
1689 lt.4 = x4 * (y4 + z4 * c).to_radians().sin();
1690 lt.5 = x5 * (y5 + z5 * c).to_radians().sin();
1691 lt.6 = x6 * (y6 + z6 * c).to_radians().sin();
1692 lt.7 = x7 * (y7 + z7 * c).to_radians().sin();
1693 lt.8 = x8 * (y8 + z8 * c).to_radians().sin();
1694 lt.9 = x9 * (y9 + z9 * c).to_radians().sin();
1695 lt.10 = x10 * (y10 + z10 * c).to_radians().sin();
1696 lt.11 = x11 * (y11 + z11 * c).to_radians().sin();
1697 lt.12 = x12 * (y12 + z12 * c).to_radians().sin();
1698 lt.13 = x13 * (y13 + z13 * c).to_radians().sin();
1699 lt.14 = x14 * (y14 + z14 * c).to_radians().sin();
1700 lt.15 = x15 * (y15 + z15 * c).to_radians().sin();
1701 lt.16 = x16 * (y16 + z16 * c).to_radians().sin();
1702 lt.17 = x17 * (y17 + z17 * c).to_radians().sin();
1703 lt.18 = x18 * (y18 + z18 * c).to_radians().sin();
1704 lt.19 = x19 * (y19 + z19 * c).to_radians().sin();
1705 lt.20 = x20 * (y20 + z20 * c).to_radians().sin();
1706 lt.21 = x21 * (y21 + z21 * c).to_radians().sin();
1707 lt.22 = x22 * (y22 + z22 * c).to_radians().sin();
1708 lt.23 = x23 * (y23 + z23 * c).to_radians().sin();
1709 lt.24 = x24 * (y24 + z24 * c).to_radians().sin();
1710 lt.25 = x25 * (y25 + z25 * c).to_radians().sin();
1711 lt.26 = x26 * (y26 + z26 * c).to_radians().sin();
1712 lt.27 = x27 * (y27 + z27 * c).to_radians().sin();
1713 lt.28 = x28 * (y28 + z28 * c).to_radians().sin();
1714 lt.29 = x29 * (y29 + z29 * c).to_radians().sin();
1715 lt.30 = x30 * (y30 + z30 * c).to_radians().sin();
1716 lt.31 = x31 * (y31 + z31 * c).to_radians().sin();
1717 lt.32 = x32 * (y32 + z32 * c).to_radians().sin();
1718 lt.33 = x33 * (y33 + z33 * c).to_radians().sin();
1719 lt.34 = x34 * (y34 + z34 * c).to_radians().sin();
1720 lt.35 = x35 * (y35 + z35 * c).to_radians().sin();
1721 lt.36 = x36 * (y36 + z36 * c).to_radians().sin();
1722 lt.37 = x37 * (y37 + z37 * c).to_radians().sin();
1723 lt.38 = x38 * (y38 + z38 * c).to_radians().sin();
1724 lt.39 = x39 * (y39 + z39 * c).to_radians().sin();
1725 lt.40 = x40 * (y40 + z40 * c).to_radians().sin();
1726 lt.41 = x41 * (y41 + z41 * c).to_radians().sin();
1727 lt.42 = x42 * (y42 + z42 * c).to_radians().sin();
1728 lt.43 = x43 * (y43 + z43 * c).to_radians().sin();
1729 lt.44 = x44 * (y44 + z44 * c).to_radians().sin();
1730 lt.45 = x45 * (y45 + z45 * c).to_radians().sin();
1731 lt.46 = x46 * (y46 + z46 * c).to_radians().sin();
1732 lt.47 = x47 * (y47 + z47 * c).to_radians().sin();
1733 lt.48 = x48 * (y48 + z48 * c).to_radians().sin();
1734
1735 let mut lambda = lt.0
1736 + lt.1
1737 + lt.2
1738 + lt.3
1739 + lt.4
1740 + lt.5
1741 + lt.6
1742 + lt.7
1743 + lt.8
1744 + lt.9
1745 + lt.10
1746 + lt.11
1747 + lt.12
1748 + lt.13
1749 + lt.14
1750 + lt.15
1751 + lt.16
1752 + lt.17
1753 + lt.18
1754 + lt.19
1755 + lt.20
1756 + lt.21
1757 + lt.22
1758 + lt.23
1759 + lt.24
1760 + lt.25
1761 + lt.26
1762 + lt.27
1763 + lt.28
1764 + lt.29
1765 + lt.30
1766 + lt.31
1767 + lt.32
1768 + lt.33
1769 + lt.34
1770 + lt.35
1771 + lt.36
1772 + lt.37
1773 + lt.38
1774 + lt.39
1775 + lt.40
1776 + lt.41
1777 + lt.42
1778 + lt.43
1779 + lt.44
1780 + lt.45
1781 + lt.46
1782 + lt.47
1783 + lt.48;
1784 lambda *= 0.000005729577951308232;
1785 lambda += 282.7771834 + 36000.76953744 * c;
1786 (lambda + Self::aberration(c) + Self::nutation(julian_centuries)).rem_euclid(360.0)
1787 }
1788
1789 fn simple_best_view(date: RataDie, location: Location) -> Moment {
1795 let dark = Self::dusk(date.to_f64_date(), location, 4.5);
1796 let best = dark.unwrap_or((date + 1).as_moment());
1797
1798 Location::universal_from_standard(best, location)
1799 }
1800
1801 fn arc_of_light(moment: Moment) -> f64 {
1807 let julian_centuries = Self::julian_centuries(moment);
1808 (Self::lunar_latitude(julian_centuries).to_radians().cos()
1809 * Self::lunar_phase(moment, julian_centuries)
1810 .to_radians()
1811 .cos())
1812 .acos()
1813 .to_degrees()
1814 }
1815
1816 pub fn shaukat_criterion(date: Moment, location: Location) -> bool {
1822 let tee = Self::simple_best_view((date - 1.0).as_rata_die(), location);
1823 let phase = Self::lunar_phase(tee, Self::julian_centuries(tee));
1824 let h = Self::lunar_altitude(tee, location);
1825 let cap_arcl = Self::arc_of_light(tee);
1826
1827 let new = 0.0;
1828 let first_quarter = 90.0;
1829 let deg_10_6 = 10.6;
1830 let deg_90 = 90.0;
1831 let deg_4_1 = 4.1;
1832
1833 if phase > new
1834 && phase < first_quarter
1835 && cap_arcl >= deg_10_6
1836 && cap_arcl <= deg_90
1837 && h > deg_4_1
1838 {
1839 return true;
1840 }
1841
1842 false
1843 }
1844
1845 pub fn visible_crescent(date: Moment, location: Location) -> bool {
1848 Self::shaukat_criterion(date, location)
1849 }
1850
1851 pub fn estimate_prior_solar_longitude(angle: f64, moment: Moment) -> Moment {
1857 let rate = MEAN_TROPICAL_YEAR / 360.0;
1858 let julian_centuries = Self::julian_centuries(moment);
1859 let tau =
1860 moment - rate * (Self::solar_longitude(julian_centuries) - angle).rem_euclid(360.0);
1861 let delta = (Self::solar_longitude(Self::julian_centuries(tau)) - angle + 180.0)
1862 .rem_euclid(360.0)
1863 - 180.0;
1864 let result_rhs = tau - rate * delta;
1865 if moment < result_rhs {
1866 moment
1867 } else {
1868 result_rhs
1869 }
1870 }
1871
1872 fn aberration(c: f64) -> f64 {
1878 0.0000974 * (177.63 + 35999.01848 * c).to_radians().cos() - 0.005575
1883 }
1884
1885 pub fn new_moon_before(moment: Moment) -> Moment {
1891 Self::nth_new_moon(Self::num_of_new_moon_at_or_after(moment) - 1)
1892 }
1893
1894 pub fn new_moon_at_or_after(moment: Moment) -> Moment {
1900 Self::nth_new_moon(Self::num_of_new_moon_at_or_after(moment))
1901 }
1902
1903 pub fn num_of_new_moon_at_or_after(moment: Moment) -> i32 {
1911 let t0: Moment = NEW_MOON_ZERO;
1912 let phi = Self::lunar_phase(moment, Self::julian_centuries(moment));
1913 let maybe_n = i64_to_i32(
1914 (div_euclid_f64(moment - t0, MEAN_SYNODIC_MONTH) - phi / 360.0).round() as i64,
1915 );
1916 debug_assert!(maybe_n.is_ok(), "Num of new moon should be in range of i32");
1917 let n = maybe_n.unwrap_or_else(|e| e.saturate());
1918 let mut result = n;
1919 let mut iters = 0;
1920 let max_iters = 31;
1921 while iters < max_iters && Self::nth_new_moon(result) < moment {
1922 iters += 1;
1923 result += 1;
1924 }
1925 result
1926 }
1927
1928 pub fn sine_offset(moment: Moment, location: Location, alpha: f64) -> f64 {
1934 let phi = location.latitude;
1935 let tee_prime = Location::universal_from_local(moment, location);
1936 let delta = Self::declination(
1937 tee_prime,
1938 0.0,
1939 Self::solar_longitude(Self::julian_centuries(tee_prime)),
1940 );
1941
1942 phi.to_radians().tan() * delta.to_radians().tan()
1943 + alpha.to_radians().sin() / (delta.to_radians().cos() * phi.to_radians().cos())
1944 }
1945}
1946
1947#[cfg(test)]
1948mod tests {
1949
1950 use super::*;
1951
1952 const TEST_LOWER_BOUND_FACTOR: f64 = 0.9999999;
1954 const TEST_UPPER_BOUND_FACTOR: f64 = 1.0000001;
1955
1956 macro_rules! assert_eq_f64 {
1957 ($expected_value:expr, $value:expr, $moment:expr) => {
1958 if $expected_value > 0.0 {
1959 assert!($value > $expected_value * TEST_LOWER_BOUND_FACTOR,
1960 "calculation failed for the test case:\n\n\tMoment: {:?} with expected: {} and calculated: {}\n\n",
1961 $moment, $expected_value, $value);
1962 assert!($value < $expected_value * TEST_UPPER_BOUND_FACTOR,
1963 "calculation failed for the test case:\n\n\tMoment: {:?} with expected: {} and calculated: {}\n\n",
1964 $moment, $expected_value, $value);
1965 } else {
1966 assert!($value > $expected_value * TEST_UPPER_BOUND_FACTOR,
1967 "calculation failed for the test case:\n\n\tMoment: {:?} with expected: {} and calculated: {}\n\n",
1968 $moment, $expected_value, $value);
1969 assert!($value < $expected_value * TEST_LOWER_BOUND_FACTOR,
1970 "calculation failed for the test case:\n\n\tMoment: {:?} with expected: {} and calculated: {}\n\n",
1971 $moment, $expected_value, $value);
1972 }
1973 }
1974 }
1975
1976 #[test]
1977 fn check_ephemeris_correction() {
1980 let rd_vals = [
1981 -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
1982 470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
1983 664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
1984 ];
1985 let expected_ephemeris = [
1986 0.2141698518518519,
1987 0.14363257367091617,
1988 0.11444429141515931,
1989 0.10718320232694657,
1990 0.06949806372337948,
1991 0.05750681225096574,
1992 0.04475812294339828,
1993 0.017397257248984357,
1994 0.012796798891589713,
1995 0.008869421568656596,
1996 0.007262628304956149,
1997 0.005979700330107665,
1998 0.005740181544555194,
1999 0.0038756713829057486,
2000 0.0031575183970409424,
2001 0.0023931271439193596,
2002 0.0017316532690131062,
2003 0.0016698814624679225,
2004 6.150149905066665E-4,
2005 1.7716816592592584E-4,
2006 1.016458530046296E-4,
2007 1.7152348357870364E-4,
2008 1.3696411598154996E-4,
2009 6.153868613872005E-5,
2010 1.4168812498149138E-5,
2011 2.767107192307865E-4,
2012 2.9636802723679223E-4,
2013 3.028239003387824E-4,
2014 3.028239003387824E-4,
2015 6.75088347496296E-4,
2016 7.128242445629627E-4,
2017 9.633446296296293E-4,
2018 0.0029138888888888877,
2019 ];
2020 for (rd, expected_ephemeris) in rd_vals.iter().zip(expected_ephemeris.iter()) {
2021 let moment: Moment = Moment::new(*rd as f64);
2022 let ephemeris = Astronomical::ephemeris_correction(moment);
2023 let expected_ephemeris_value = expected_ephemeris;
2024 assert!(ephemeris > expected_ephemeris_value * TEST_LOWER_BOUND_FACTOR, "Ephemeris correction calculation failed for the test case:\n\n\tMoment: {moment:?} with expected: {expected_ephemeris_value} and calculated: {ephemeris}\n\n");
2025 assert!(ephemeris < expected_ephemeris_value * TEST_UPPER_BOUND_FACTOR, "Ephemeris correction calculation failed for the test case:\n\n\tMoment: {moment:?} with expected: {expected_ephemeris_value} and calculated: {ephemeris}\n\n");
2026 }
2027 }
2028
2029 #[test]
2030 fn check_solar_longitude() {
2033 let rd_vals = [
2034 -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
2035 470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
2036 664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
2037 ];
2038 let expected_solar_long = [
2039 119.47343190503307,
2040 254.2489611345809,
2041 181.43599673954304,
2042 188.66392267483752,
2043 289.0915666249348,
2044 59.11974154849304,
2045 228.31455470912624,
2046 34.46076992887538,
2047 63.18799596698955,
2048 2.4575913259759545,
2049 350.475934906397,
2050 13.498220866371412,
2051 37.403920329437824,
2052 81.02813003520714,
2053 313.86049865107634,
2054 19.95443016415811,
2055 176.05943166351062,
2056 344.92295174632454,
2057 79.96492181924987,
2058 99.30231774304411,
2059 121.53530416596914,
2060 88.56742889029556,
2061 129.289884101192,
2062 6.146910693067184,
2063 28.25199345351575,
2064 151.7806330331332,
2065 185.94586701843946,
2066 28.55560762159439,
2067 193.3478921554779,
2068 357.15125499424175,
2069 336.1706924761211,
2070 228.18487947607719,
2071 116.43935225951282,
2072 ];
2073 for (rd, expected_solar_long) in rd_vals.iter().zip(expected_solar_long.iter()) {
2074 let moment: Moment = Moment::new(*rd as f64);
2075 let solar_long =
2076 Astronomical::solar_longitude(Astronomical::julian_centuries(moment + 0.5));
2077 let expected_solar_long_value = expected_solar_long;
2078 assert!(solar_long > expected_solar_long_value * TEST_LOWER_BOUND_FACTOR, "Solar longitude calculation failed for the test case:\n\n\tMoment: {moment:?} with expected: {expected_solar_long_value} and calculated: {solar_long}\n\n");
2079 assert!(solar_long < expected_solar_long_value * TEST_UPPER_BOUND_FACTOR, "Solar longitude calculation failed for the test case:\n\n\tMoment: {moment:?} with expected: {expected_solar_long_value} and calculated: {solar_long}\n\n");
2080 }
2081 }
2082
2083 #[test]
2084 fn check_lunar_latitude() {
2088 let rd_vals = [
2089 -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
2090 470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
2091 664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
2092 ];
2093
2094 let expected_lunar_lat = [
2095 2.4527590208461576,
2096 -4.90223034654341,
2097 -2.9394693592610484,
2098 5.001904508580623,
2099 -3.208909826304433,
2100 0.894361559890105,
2101 -3.8633355687979827,
2102 -2.5224444701068927,
2103 1.0320696124422062,
2104 3.005689926794408,
2105 1.613842956502888,
2106 4.766740664556875,
2107 4.899202930916035,
2108 4.838473946607273,
2109 2.301475724501815,
2110 -0.8905637199828537,
2111 4.7657836433468495,
2112 -2.737358003826797,
2113 -4.035652608005429,
2114 -3.157214517184652,
2115 -1.8796147336498752,
2116 -3.379519408995276,
2117 -4.398341468078228,
2118 2.099198567294447,
2119 5.268746128633113,
2120 -1.6722994521634027,
2121 4.6820126551666865,
2122 3.705518210116447,
2123 2.493964063649065,
2124 -4.167774638752936,
2125 -2.873757531859998,
2126 -4.667251128743298,
2127 5.138562328560728,
2128 ];
2129
2130 for (rd, expected_lunar_lat) in rd_vals.iter().zip(expected_lunar_lat.iter()) {
2131 let moment: Moment = Moment::new(*rd as f64);
2132 let lunar_lat = Astronomical::lunar_latitude(Astronomical::julian_centuries(moment));
2133 let expected_lunar_lat_value = *expected_lunar_lat;
2134
2135 assert_eq_f64!(expected_lunar_lat_value, lunar_lat, moment)
2136 }
2137 }
2138
2139 #[test]
2140 fn check_lunar_longitude() {
2143 let rd_vals = [
2144 -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
2145 470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
2146 664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
2147 ];
2148 let expected_lunar_long = [
2149 244.85390528515035,
2150 208.85673853696503,
2151 213.74684265158967,
2152 292.04624333935743,
2153 156.81901407583166,
2154 108.0556329349528,
2155 39.35609790324581,
2156 98.56585102192106,
2157 332.95829627335894,
2158 92.25965175091615,
2159 78.13202909213766,
2160 274.9469953879383,
2161 128.3628442664409,
2162 89.51845094326185,
2163 24.607322526832988,
2164 53.4859568448797,
2165 187.89852001941696,
2166 320.1723620959754,
2167 314.0425667275923,
2168 145.47406514043587,
2169 185.03050779751646,
2170 142.18913274552065,
2171 253.74337531953228,
2172 151.64868501335397,
2173 287.9877436469169,
2174 25.626707154435444,
2175 290.28830064619893,
2176 189.91314245171338,
2177 284.93173002623826,
2178 152.3390442635215,
2179 51.66226507971774,
2180 26.68206023138705,
2181 175.5008226195208,
2182 ];
2183 for (rd, expected_lunar_long) in rd_vals.iter().zip(expected_lunar_long.iter()) {
2184 let moment: Moment = Moment::new(*rd as f64);
2185 let lunar_long = Astronomical::lunar_longitude(Astronomical::julian_centuries(moment));
2186 let expected_lunar_long_value = *expected_lunar_long;
2187
2188 assert_eq_f64!(expected_lunar_long_value, lunar_long, moment)
2189 }
2190 }
2191
2192 #[test]
2193 fn check_lunar_altitude() {
2194 let rd_vals = [
2195 -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
2196 470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
2197 664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
2198 ];
2199
2200 let expected_altitude_deg: [f64; 33] = [
2201 -13.163184128188277,
2202 -7.281425833096932,
2203 -77.1499009115812,
2204 -30.401178593900795,
2205 71.84857827681589,
2206 -43.79857984753659,
2207 40.65320421851649,
2208 -40.2787255279427,
2209 29.611156512065406,
2210 -19.973178784428228,
2211 -23.740743779700097,
2212 30.956688013173505,
2213 -18.88869091014726,
2214 -32.16116202243495,
2215 -45.68091943596022,
2216 -50.292110029959986,
2217 -54.3453056090807,
2218 -34.56600009726776,
2219 44.13198955291821,
2220 -57.539862986917285,
2221 -62.08243959461623,
2222 -54.07209109276471,
2223 -16.120452006695814,
2224 23.864594681196934,
2225 32.95014668614863,
2226 72.69165128891194,
2227 -29.849481790038908,
2228 31.610644151367637,
2229 -42.21968940776054,
2230 28.6478092363985,
2231 -38.95055354031621,
2232 27.601977078963245,
2233 -54.85468160086816,
2234 ];
2235
2236 for (rd, expected_alt) in rd_vals.iter().zip(expected_altitude_deg.iter()) {
2237 let moment: Moment = Moment::new(*rd as f64);
2238 let lunar_alt = Astronomical::lunar_altitude(moment, MECCA);
2239 let expected_alt_value = *expected_alt;
2240
2241 assert_eq_f64!(expected_alt_value, lunar_alt, moment)
2242 }
2243 }
2244
2245 #[test]
2246 fn check_lunar_distance() {
2247 let rd_vals = [
2248 -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
2249 470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
2250 664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
2251 ];
2252
2253 let expected_distances = [
2254 387624532.22874624,
2255 393677431.9167689,
2256 402232943.80299366,
2257 392558548.8426357,
2258 366799795.8707107,
2259 365107305.3822873,
2260 401995197.0122423,
2261 404025417.6150537,
2262 377671971.8515077,
2263 403160628.6150732,
2264 375160036.9057225,
2265 369934038.34809774,
2266 402543074.28064245,
2267 374847147.6967837,
2268 403469151.42100906,
2269 386211365.4436033,
2270 385336015.6086019,
2271 400371744.7464432,
2272 395970218.00750065,
2273 383858113.5538787,
2274 389634540.7722341,
2275 390868707.6609328,
2276 368015493.693663,
2277 399800095.77937233,
2278 404273360.3039046,
2279 382777325.7053601,
2280 378047375.3350678,
2281 385774023.9948239,
2282 371763698.0990588,
2283 362461692.8996066,
2284 394214466.3812425,
2285 405787977.04490376,
2286 404202826.42484397,
2287 ];
2288
2289 for (rd, expected_distance) in rd_vals.iter().zip(expected_distances.iter()) {
2290 let moment: Moment = Moment::new(*rd as f64);
2291 let distance = Astronomical::lunar_distance(moment);
2292 let expected_distance_val = *expected_distance;
2293
2294 assert_eq_f64!(expected_distance_val, distance, moment)
2295 }
2296 }
2297
2298 #[test]
2299 fn check_lunar_parallax() {
2300 let rd_vals = [
2301 -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
2302 470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
2303 664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
2304 ];
2305
2306 let expected_parallax = [
2307 0.9180377088277034,
2308 0.9208275970231943,
2309 0.20205836298974478,
2310 0.8029475944705559,
2311 0.3103764190238057,
2312 0.7224552232666479,
2313 0.6896953754669151,
2314 0.6900664438899986,
2315 0.8412721901635796,
2316 0.8519504336914271,
2317 0.8916972264563727,
2318 0.8471706468502866,
2319 0.8589744596828851,
2320 0.8253387743371953,
2321 0.6328154405175959,
2322 0.60452566100182,
2323 0.5528114670829496,
2324 0.7516491660573382,
2325 0.6624140811593374,
2326 0.5109678575066725,
2327 0.4391324179474404,
2328 0.5486027633624313,
2329 0.9540023420545446,
2330 0.835939538308717,
2331 0.7585615249134946,
2332 0.284040095327141,
2333 0.8384425157447107,
2334 0.8067682261382678,
2335 0.7279971552035109,
2336 0.8848306274359499,
2337 0.720943806048675,
2338 0.7980998225232075,
2339 0.5204553405568378,
2340 ];
2341
2342 for (rd, parallax) in rd_vals.iter().zip(expected_parallax.iter()) {
2343 let moment: Moment = Moment::new(*rd as f64);
2344 let lunar_altitude_val = Astronomical::lunar_altitude(moment, MECCA);
2345 let parallax_val = Astronomical::lunar_parallax(lunar_altitude_val, moment);
2346 let expected_parallax_val = *parallax;
2347
2348 assert_eq_f64!(expected_parallax_val, parallax_val, moment);
2349 }
2350 }
2351
2352 #[test]
2353 fn check_moonset() {
2354 let rd_vals = [
2355 -214193.0, -61387.0, 25469.0, 49217.0, 171307.0, 210155.0, 253427.0, 369740.0,
2356 400085.0, 434355.0, 452605.0, 470160.0, 473837.0, 507850.0, 524156.0, 544676.0,
2357 567118.0, 569477.0, 601716.0, 613424.0, 626596.0, 645554.0, 664224.0, 671401.0,
2358 694799.0, 704424.0, 708842.0, 709409.0, 709580.0, 727274.0, 728714.0, 744313.0,
2359 764652.0,
2360 ];
2361
2362 let expected_values = [
2363 -214192.91577491348,
2364 -61386.372392431986,
2365 25469.842646633304,
2366 49217.03030766261,
2367 171307.41988615665,
2368 210155.96578468647,
2369 253427.2528524993,
2370 0.0,
2371 400085.5281194299,
2372 434355.0524936674,
2373 452605.0379962325,
2374 470160.4931771927,
2375 473837.06032208423,
2376 507850.8560177605,
2377 0.0,
2378 544676.908706548,
2379 567118.8180096536,
2380 569477.7141856537,
2381 601716.4168627897,
2382 613424.9325031227,
2383 626596.9563783304,
2384 645554.9526297608,
2385 664224.070965863,
2386 671401.2004198332,
2387 694799.4892001058,
2388 704424.4299627786,
2389 708842.0314145002,
2390 709409.2245215117,
2391 0.0,
2392 727274.2148254914,
2393 0.0,
2394 744313.2118589033,
2395 764652.9631741203,
2396 ];
2397
2398 for (rd, expected_val) in rd_vals.iter().zip(expected_values.iter()) {
2399 let moment: Moment = Moment::new(*rd);
2400 let moonset_val = Astronomical::moonset(moment, MECCA);
2401 let expected_moonset_val = *expected_val;
2402 #[allow(clippy::unnecessary_unwrap)]
2403 if moonset_val.is_none() {
2404 assert_eq!(expected_moonset_val, 0.0);
2405 } else {
2406 assert_eq_f64!(expected_moonset_val, moonset_val.unwrap().inner(), moment);
2407 }
2408 }
2409 }
2410
2411 #[test]
2412 fn check_sunset() {
2413 let rd_vals = [
2414 -214193.0, -61387.0, 25469.0, 49217.0, 171307.0, 210155.0, 253427.0, 369740.0,
2415 400085.0, 434355.0, 452605.0, 470160.0, 473837.0, 507850.0, 524156.0, 544676.0,
2416 567118.0, 569477.0, 601716.0, 613424.0, 626596.0, 645554.0, 664224.0, 671401.0,
2417 694799.0, 704424.0, 708842.0, 709409.0, 709580.0, 727274.0, 728714.0, 744313.0,
2418 764652.0,
2419 ];
2420
2421 let expected_values = [
2422 -214192.2194436165,
2423 -61386.30267524347,
2424 25469.734889564967,
2425 49217.72851448112,
2426 171307.70878832813,
2427 210155.77420199668,
2428 253427.70087725233,
2429 369740.7627365203,
2430 400085.77677703864,
2431 434355.74808897293,
2432 452605.7425360138,
2433 470160.75310216413,
2434 473837.76440251875,
2435 507850.7840412511,
2436 524156.7225351998,
2437 544676.7561346035,
2438 567118.7396585084,
2439 569477.7396636717,
2440 601716.784057734,
2441 613424.7870863203,
2442 626596.781969136,
2443 645554.7863087669,
2444 664224.778132625,
2445 671401.7496876866,
2446 694799.7602310368,
2447 704424.7619096127,
2448 708842.730647343,
2449 709409.7603906896,
2450 709580.7240122546,
2451 727274.745361792,
2452 728714.734750938,
2453 744313.699821144,
2454 764652.7844809336,
2455 ];
2456
2457 let jerusalem = Location {
2458 latitude: 31.78,
2459 longitude: 35.24,
2460 elevation: 740.0,
2461 zone: (1_f64 / 12_f64),
2462 };
2463
2464 for (rd, expected_sunset_value) in rd_vals.iter().zip(expected_values.iter()) {
2465 let moment = Moment::new(*rd);
2466 let sunset_value = Astronomical::sunset(moment, jerusalem).unwrap();
2467 let expected_sunset_val = *expected_sunset_value;
2468 assert_eq_f64!(expected_sunset_val, sunset_value.inner(), moment)
2469 }
2470 }
2471
2472 #[test]
2473 fn check_next_new_moon() {
2476 let rd_vals = [
2477 -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
2478 470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
2479 664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
2480 ];
2481 let expected_next_new_moon = [
2482 -214174.60582868298,
2483 -61382.99532831192,
2484 25495.80977675628,
2485 49238.50244808781,
2486 171318.43531326813,
2487 210180.69184966758,
2488 253442.85936730343,
2489 369763.74641362444,
2490 400091.5783431683,
2491 434376.5781067696,
2492 452627.1919724953,
2493 470167.57836052414,
2494 473858.8532764285,
2495 507878.6668429224,
2496 524179.2470620894,
2497 544702.7538732041,
2498 567146.5131819838,
2499 569479.2032589674,
2500 601727.0335578924,
2501 613449.7621296605,
2502 626620.3698017383,
2503 645579.0767485882,
2504 664242.8867184789,
2505 671418.970538101,
2506 694807.5633711396,
2507 704433.4911827276,
2508 708863.5970001582,
2509 709424.4049294397,
2510 709602.0826867367,
2511 727291.2094001573,
2512 728737.4476913146,
2513 744329.5739998783,
2514 764676.1912733881,
2515 ];
2516 for (rd, expected_next_new_moon) in rd_vals.iter().zip(expected_next_new_moon.iter()) {
2517 let moment: Moment = Moment::new(*rd as f64);
2518 let next_new_moon = Astronomical::new_moon_at_or_after(moment);
2519 let expected_next_new_moon_moment = Moment::new(*expected_next_new_moon);
2520 if *expected_next_new_moon > 0.0 {
2521 assert!(expected_next_new_moon_moment.inner() > next_new_moon.inner() * TEST_LOWER_BOUND_FACTOR, "New moon calculation failed for the test case:\n\n\tMoment: {moment:?} with expected: {expected_next_new_moon_moment:?} and calculated: {next_new_moon:?}\n\n");
2522 assert!(expected_next_new_moon_moment.inner() < next_new_moon.inner() * TEST_UPPER_BOUND_FACTOR, "New moon calculation failed for the test case:\n\n\tMoment: {moment:?} with expected: {expected_next_new_moon_moment:?} and calculated: {next_new_moon:?}\n\n");
2523 } else {
2524 assert!(expected_next_new_moon_moment.inner() > next_new_moon.inner() * TEST_UPPER_BOUND_FACTOR, "New moon calculation failed for the test case:\n\n\tMoment: {moment:?} with expected: {expected_next_new_moon_moment:?} and calculated: {next_new_moon:?}\n\n");
2525 assert!(expected_next_new_moon_moment.inner() < next_new_moon.inner() * TEST_LOWER_BOUND_FACTOR, "New moon calculation failed for the test case:\n\n\tMoment: {moment:?} with expected: {expected_next_new_moon_moment:?} and calculated: {next_new_moon:?}\n\n");
2526 }
2527 }
2528 }
2529
2530 #[test]
2531 fn check_astronomy_0th_new_moon() {
2532 let zeroth_new_moon = Astronomical::nth_new_moon(0);
2534 assert_eq!(
2535 zeroth_new_moon.inner() as i32,
2536 11,
2537 "0th new moon check failed with nth_new_moon(0) = {zeroth_new_moon:?}"
2538 );
2539 }
2540
2541 #[test]
2542 fn check_num_of_new_moon_0() {
2543 assert_eq!(
2545 Astronomical::num_of_new_moon_at_or_after(Moment::new(0.0)),
2546 0
2547 );
2548 }
2549
2550 #[test]
2551 fn check_new_moon_directionality() {
2552 let mut moment: Moment = Moment::new(-15500.0);
2554 let max_moment = Moment::new(15501.0);
2555 let mut iters: i32 = 0;
2556 let max_iters = 10000;
2557 while iters < max_iters && moment < max_moment {
2558 let before = Astronomical::new_moon_before(moment);
2559 let at_or_after = Astronomical::new_moon_at_or_after(moment);
2560 assert!(before < at_or_after, "Directionality of fns new_moon_before and new_moon_at_or_after failed for Moment: {moment:?}");
2561 iters += 1;
2562 moment += 31.0;
2563 }
2564 assert!(
2565 iters > 500,
2566 "Testing failed: less than the expected number of testing iterations"
2567 );
2568 assert!(
2569 iters < max_iters,
2570 "Testing failed: more than the expected number of testing iterations"
2571 );
2572 }
2573
2574 #[test]
2575 fn check_location_valid_case() {
2576 let mut long = -180.0;
2578 let mut lat = -90.0;
2579 let zone = 0.0;
2580 while long <= 180.0 {
2581 while lat <= 90.0 {
2582 let location: Location = Location::try_new(lat, long, 1000.0, zone).unwrap();
2583 assert_eq!(lat, location.latitude());
2584 assert_eq!(long, location.longitude());
2585
2586 lat += 1.0;
2587 }
2588 long += 1.0;
2589 }
2590 }
2591
2592 #[test]
2593 fn check_location_errors() {
2594 let lat_too_small = Location::try_new(-90.1, 15.0, 1000.0, 0.0).unwrap_err();
2595 assert_eq!(lat_too_small, LocationOutOfBoundsError::Latitude(-90.1));
2596 let lat_too_large = Location::try_new(90.1, -15.0, 1000.0, 0.0).unwrap_err();
2597 assert_eq!(lat_too_large, LocationOutOfBoundsError::Latitude(90.1));
2598 let long_too_small = Location::try_new(15.0, 180.1, 1000.0, 0.0).unwrap_err();
2599 assert_eq!(long_too_small, LocationOutOfBoundsError::Longitude(180.1));
2600 let long_too_large = Location::try_new(-15.0, -180.1, 1000.0, 0.0).unwrap_err();
2601 assert_eq!(long_too_large, LocationOutOfBoundsError::Longitude(-180.1));
2602 }
2603
2604 #[test]
2605 fn check_obliquity() {
2606 let rd_vals = [
2607 -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
2608 470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
2609 664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
2610 ];
2611
2612 let expected_obliquity_val = [
2613 23.766686762858193,
2614 23.715893268155952,
2615 23.68649428364133,
2616 23.678396646319815,
2617 23.636406172247575,
2618 23.622930685681105,
2619 23.607863050353394,
2620 23.567099369895143,
2621 23.556410268115442,
2622 23.544315732982724,
2623 23.5378658942414,
2624 23.531656189162007,
2625 23.53035487913322,
2626 23.518307553466993,
2627 23.512526100422757,
2628 23.50524564635773,
2629 23.49727762748816,
2630 23.49643975090472,
2631 23.48498365949255,
2632 23.48082101433542,
2633 23.476136639530452,
2634 23.469392588649566,
2635 23.46274905945532,
2636 23.460194773340504,
2637 23.451866181318085,
2638 23.44843969966849,
2639 23.44686683973517,
2640 23.446664978744177,
2641 23.44660409993624,
2642 23.440304562352033,
2643 23.43979187336218,
2644 23.434238093381342,
2645 23.426996977623215,
2646 ];
2647
2648 for (rd, expected_obl_val) in rd_vals.iter().zip(expected_obliquity_val.iter()) {
2649 let moment = Moment::new(*rd as f64);
2650 let obl_val = Astronomical::obliquity(moment);
2651 let expected_val = *expected_obl_val;
2652
2653 assert_eq_f64!(expected_val, obl_val, moment)
2654 }
2655 }
2656}