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