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