calendrical_calculations/
astronomy.rs

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