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