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