deep_time/sidereal/mod.rs
1//! Sidereal rotation and time calculations for celestial bodies.
2//!
3//! [`Sidereal`] struct with ready-to-use `EARTH`, `MARS`, `MOON` constants.
4//! Computes rotation angle, LMST/LAST, GMST/GAST.
5//!
6//! With the `"sidereal-earth"` feature enabled a rust implementation of the
7//! ERFA Earth Equation of the Origins / Equinoxes are both available as well.
8
9#[cfg(feature = "sidereal-earth")]
10pub mod earth_eo_ee;
11
12use crate::Real;
13use core::f64::consts::TAU;
14
15#[cfg(feature = "sidereal-earth")]
16use earth_eo_ee::*;
17
18/// Represents the rotational state of a celestial body and provides
19/// methods to compute the orientation of its prime meridian at any
20/// given time.
21///
22/// The rotation angle of the prime meridian is the basis for
23/// calculating local sidereal time. Local sidereal time is required
24/// to compute the hour angle of a celestial object (HA = LST − RA),
25/// to determine when an object will cross the local meridian,
26/// to convert between horizon coordinates (altitude/azimuth) and
27/// equatorial coordinates, and to calculate accurate pointing
28/// directions for telescopes and spacecraft antennas.
29///
30/// The struct implements the modern CIO-based rotation model and
31/// works for any rotating body (Earth, Mars, the Moon, etc.) by
32/// supplying the appropriate rotation rate and reference values.
33///
34/// ## Fields
35///
36/// * `rate_rad_per_sec` — Mean sidereal rotation rate in radians per SI second.
37/// * `ref_epoch` — Reference epoch (MJD) at which `ref_angle_rad` is defined.
38/// * `ref_angle_rad` — Rotation angle of the prime meridian at `ref_epoch`.
39/// * `longitude_rad` — Observer longitude on the body (radians, east positive).
40/// `0.0` corresponds to the body's prime meridian.
41/// * `correction_rad` — General-purpose additive correction in radians.
42///
43/// ## Examples
44///
45/// Basic usage with Earth constants:
46///
47/// ```rust
48/// use deep_time::Sidereal;
49///
50/// let mut earth = Sidereal::EARTH;
51/// earth.longitude_rad = 0.0; // Greenwich
52///
53/// let mjd = 60000.0;
54/// let era = earth.rotation_angle(mjd);
55///
56/// // Local Mean Sidereal Time using the mean Equation of the Origins
57/// // (requires the "sidereal-earth" feature)
58/// # #[cfg(feature = "sidereal-earth")] {
59/// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
60/// let lmst = earth.local_sidereal_time_mean(mjd, eo_mean);
61/// # }
62/// ```
63///
64/// Realistic usage with DUT1 correction (UT1 time scale):
65///
66/// ```rust
67/// // This advanced example requires the "eop" feature for EopData
68/// // and "sidereal-earth" for the EO calculations.
69/// # #[cfg(all(feature = "eop", feature = "sidereal-earth"))] {
70/// use deep_time::{Dt, Sidereal};
71/// use deep_time::eop::{EopData, EopFormat, Separator};
72///
73/// let eop = EopData::from_text_file(
74/// "finals.all.iau2000.txt",
75/// EopFormat::Finals2000A,
76/// Separator::Whitespace,
77/// ).unwrap();
78///
79/// let mjd_utc = 56879.0;
80/// let dut1 = Dt::mjd_to_eop_offset_f(mjd_utc, &eop).unwrap();
81/// let mjd_ut1 = mjd_utc + dut1 / 86400.0;
82///
83/// let earth = Sidereal::EARTH;
84///
85/// let era = earth.rotation_angle(mjd_ut1);
86///
87/// let eo_mean = earth.earth_eo_mean(mjd_ut1 + 32.184 / 86400.0);
88/// let gmst = earth.sidereal_angle_mean(mjd_ut1, eo_mean);
89///
90/// // Local Mean Sidereal Time
91/// let lmst = earth.local_sidereal_time_mean(mjd_ut1, eo_mean);
92/// # }
93/// ```
94#[derive(Debug, Clone, Copy)]
95pub struct Sidereal {
96 /// Mean sidereal rotation rate in **radians per SI second**.
97 pub rate_rad_per_sec: Real,
98 /// Reference epoch.
99 pub ref_epoch: Real,
100 /// Rotation angle of the prime meridian (radians) at `ref_epoch`.
101 pub ref_angle_rad: Real,
102 /// Longitude of the observer on the body (radians, east positive).
103 /// `0.0` = body's prime meridian.
104 pub longitude_rad: Real,
105 /// General scalar correction in radians.
106 pub correction_rad: Real,
107}
108
109impl Sidereal {
110 /// Pre-configured `Sidereal` for Earth using IAU 2000/2006 conventions.
111 ///
112 /// This uses:
113 /// - The conventional mean sidereal rotation rate of Earth.
114 /// - J2000.0 as the reference epoch (`ref_epoch = 51544.5`).
115 /// - The Earth Rotation Angle (ERA) at J2000.0 as `ref_angle_rad`.
116 ///
117 /// You can still customize fields after construction (e.g. `longitude_rad`
118 /// or `correction_rad`).
119 pub const EARTH: Self = Self {
120 rate_rad_per_sec: (1.00273781191135448 * core::f64::consts::TAU) / 86400.0,
121 ref_epoch: 51544.5,
122 ref_angle_rad: 0.7790572732640 * core::f64::consts::TAU,
123 longitude_rad: 0.0,
124 correction_rad: 0.0,
125 };
126
127 /// Pre-configured `Sidereal` for Mars.
128 ///
129 /// Uses a simplified mean sidereal rotation rate and J2000.0 as the
130 /// reference epoch. `ref_angle_rad` is set to zero (no specific
131 /// reference angle is defined).
132 ///
133 /// You can customize fields (especially `longitude_rad`) after construction.
134 pub const MARS: Self = Self {
135 rate_rad_per_sec: core::f64::consts::TAU / 88642.663,
136 ref_epoch: 51544.5,
137 ref_angle_rad: 0.0,
138 longitude_rad: 0.0,
139 correction_rad: 0.0,
140 };
141
142 /// Pre-configured `Sidereal` for the Moon.
143 ///
144 /// Uses a simplified mean sidereal rotation rate and J2000.0 as the
145 /// reference epoch. `ref_angle_rad` is set to zero (no specific
146 /// reference angle is defined).
147 ///
148 /// You can customize fields (especially `longitude_rad`) after construction.
149 pub const MOON: Self = Self {
150 rate_rad_per_sec: core::f64::consts::TAU / 2_360_591.424,
151 ref_epoch: 51544.5,
152 ref_angle_rad: 0.0,
153 longitude_rad: 0.0,
154 correction_rad: 0.0,
155 };
156
157 // Normalize to [0, 2π)
158 #[inline]
159 const fn normalize_angle(angle: Real) -> Real {
160 ((angle % TAU) + TAU) % TAU
161 }
162
163 /// Returns the instantaneous rotation angle of the body's prime meridian
164 /// (in radians) at the given instant, normalized to `[0, 2π)`.
165 ///
166 /// For Earth this is the pure Earth Rotation Angle (ERA) in the
167 /// Celestial Intermediate Origin (CIO) frame. It does **not** include
168 /// observer longitude or the Equation of the Origins.
169 ///
170 /// Matches Astropy's `Time.earth_rotation_angle(longitude=None)`
171 /// (or with `longitude=0`).
172 ///
173 /// ## Examples
174 ///
175 /// ```rust
176 /// use deep_time::Sidereal;
177 ///
178 /// let era = Sidereal::EARTH.rotation_angle(57753.5);
179 /// ```
180 pub const fn rotation_angle(&self, mjd: Real) -> Real {
181 // elapsed time in seconds between ref_epoch (MJD) and the given mjd
182 let elapsed_days = mjd - self.ref_epoch;
183 let elapsed_sec = elapsed_days * 86400.0;
184
185 let angle = self.ref_angle_rad + self.rate_rad_per_sec * elapsed_sec + self.correction_rad;
186
187 Self::normalize_angle(angle)
188 }
189
190 /// Returns the rotation angle of the prime meridian at the observer's
191 /// longitude, normalized to `[0, 2π)`.
192 ///
193 /// This is equivalent to `rotation_angle(mjd) + self.longitude_rad`.
194 /// It gives the angle between the Celestial Intermediate Origin (CIO)
195 /// and the observer’s local meridian.
196 ///
197 /// This value is commonly used when computing the local hour angle
198 /// of a celestial object:
199 ///
200 /// ```text
201 /// HA = local_rotation_angle(mjd) - RA
202 /// ```
203 ///
204 /// ## Examples
205 ///
206 /// ```rust
207 /// use deep_time::Sidereal;
208 ///
209 /// let mut earth = Sidereal::EARTH;
210 /// earth.longitude_rad = 0.0; // Greenwich
211 ///
212 /// let mjd = 60000.0;
213 /// let local_era = earth.local_rotation_angle(mjd);
214 /// ```
215 #[inline]
216 pub const fn local_rotation_angle(&self, mjd: Real) -> Real {
217 Self::normalize_angle(self.rotation_angle(mjd) + self.longitude_rad)
218 }
219
220 /// Returns the sidereal angle of the body's prime meridian in radians,
221 /// normalized to `[0, 2π)`.
222 ///
223 /// This computes Greenwich Mean Sidereal Time (GMST) when an appropriate
224 /// Equation of the Origins value is supplied.
225 ///
226 /// ## Parameters
227 ///
228 /// - `eo_rad`: The Equation of the Origins value to subtract from the
229 /// Earth Rotation Angle (ERA).
230 /// - Pass `0.0` to get the pure CIO-based rotation angle (ERA).
231 /// - Pass the **mean** Equation of the Origins (e.g. from
232 /// [`earth_eo_mean`](Self::earth_eo_mean)) to obtain GMST.
233 ///
234 /// ## Details
235 ///
236 /// - When `eo_rad = 0.0`, the result is the modern Earth Rotation Angle (ERA)
237 /// relative to the Celestial Intermediate Origin (CIO).
238 ///
239 /// - When `eo_rad` is the mean Equation of the Origins (i.e. the value that
240 /// satisfies `GMST = ERA − eo_rad`), the result is Greenwich Mean Sidereal
241 /// Time (GMST) referred to the mean equinox. This is the traditional
242 /// equinox-based mean sidereal time.
243 ///
244 /// ## Examples
245 ///
246 /// ```rust
247 /// use deep_time::Sidereal;
248 ///
249 /// let earth = Sidereal::EARTH;
250 /// let mjd = 60000.0;
251 ///
252 /// // Pure CIO-based rotation angle (Earth Rotation Angle)
253 /// let era = earth.sidereal_angle_mean(mjd, 0.0);
254 ///
255 /// // Traditional mean sidereal time using the mean Equation of the Origins
256 /// // (requires "sidereal-earth" feature)
257 /// # #[cfg(feature = "sidereal-earth")] {
258 /// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
259 /// let gmst = earth.sidereal_angle_mean(mjd, eo_mean);
260 /// # }
261 /// ```
262 #[inline]
263 pub const fn sidereal_angle_mean(&self, mjd: Real, eo_rad: Real) -> Real {
264 let angle = self.rotation_angle(mjd) - eo_rad;
265 Self::normalize_angle(angle)
266 }
267
268 /// Returns the local sidereal angle at the observer's longitude in radians,
269 /// normalized to `[0, 2π)`.
270 ///
271 /// This computes **Local Mean Sidereal Time (LMST)** when an appropriate
272 /// Equation of the Origins value is supplied.
273 ///
274 /// ## Parameters
275 ///
276 /// - `eo_rad`: The Equation of the Origins value to subtract from the
277 /// Earth Rotation Angle (ERA).
278 /// - Pass `0.0` to get the pure local Earth Rotation Angle (CIO-based).
279 /// - Pass the **mean** Equation of the Origins (e.g. from
280 /// [`earth_eo_mean`](Self::earth_eo_mean)) to obtain Local Mean
281 /// Sidereal Time (LMST).
282 ///
283 /// ## Details
284 ///
285 /// - When `eo_rad = 0.0`, the result is the local Earth Rotation Angle
286 /// relative to the Celestial Intermediate Origin (CIO) at the observer’s
287 /// longitude.
288 ///
289 /// - When `eo_rad` is the mean Equation of the Origins, the result is
290 /// **Local Mean Sidereal Time (LMST)** referred to the mean equinox.
291 ///
292 /// This value is commonly used when calculating the local hour angle of a
293 /// celestial object:
294 ///
295 /// ```text
296 /// HA = local_sidereal_angle_mean(mjd, eo) − RA
297 /// ```
298 ///
299 /// ## Examples
300 ///
301 /// ```rust
302 /// use deep_time::Sidereal;
303 ///
304 /// let mut earth = Sidereal::EARTH;
305 /// earth.longitude_rad = 0.0; // Greenwich
306 ///
307 /// let mjd = 60000.0;
308 ///
309 /// // Pure local Earth Rotation Angle (CIO-based)
310 /// let local_era = earth.local_sidereal_angle_mean(mjd, 0.0);
311 ///
312 /// // Local Mean Sidereal Time using the mean Equation of the Origins
313 /// // (requires "sidereal-earth" feature)
314 /// # #[cfg(feature = "sidereal-earth")] {
315 /// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
316 /// let lmst = earth.local_sidereal_angle_mean(mjd, eo_mean);
317 /// # }
318 /// ```
319 #[inline]
320 pub const fn local_sidereal_angle_mean(&self, mjd: Real, eo_rad: Real) -> Real {
321 let angle = self.rotation_angle(mjd) + self.longitude_rad - eo_rad;
322 Self::normalize_angle(angle)
323 }
324
325 /// Returns sidereal time at the body's prime meridian as seconds since
326 /// sidereal midnight, wrapped to the range `[0, 86400)`.
327 ///
328 /// This is the time equivalent of [`sidereal_angle_mean`].
329 ///
330 /// ## Parameters
331 ///
332 /// - `eo_rad`: The Equation of the Origins value to use.
333 /// - Pass `0.0` to get the time equivalent of the pure Earth Rotation Angle (ERA).
334 /// - Pass the **mean** Equation of the Origins (e.g. from
335 /// [`earth_eo_mean`](Self::earth_eo_mean)) to obtain Greenwich Mean
336 /// Sidereal Time (GMST).
337 ///
338 /// ## Details
339 ///
340 /// - When `eo_rad = 0.0`, the result is the time equivalent of the modern
341 /// Earth Rotation Angle (ERA).
342 ///
343 /// - When `eo_rad` is the mean Equation of the Origins, the result is
344 /// **Greenwich Mean Sidereal Time (GMST)** referred to the mean equinox.
345 ///
346 /// As of Astropy 7.x, this is consistent with
347 /// `Time.sidereal_time("mean").to_value("sec")` (when no longitude is
348 /// specified) when using matching UT1 time and the mean Equation of the Origins.
349 ///
350 /// ## Examples
351 ///
352 /// ```rust
353 /// use deep_time::Sidereal;
354 ///
355 /// let earth = Sidereal::EARTH;
356 /// let mjd = 60000.0;
357 ///
358 /// // Time equivalent of pure Earth Rotation Angle
359 /// let era_seconds = earth.sidereal_time_mean(mjd, 0.0);
360 ///
361 /// // Greenwich Mean Sidereal Time in seconds
362 /// // (requires "sidereal-earth" feature)
363 /// # #[cfg(feature = "sidereal-earth")] {
364 /// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
365 /// let gmst_seconds = earth.sidereal_time_mean(mjd, eo_mean);
366 /// # }
367 /// ```
368 pub const fn sidereal_time_mean(&self, mjd: Real, eo_rad: Real) -> Real {
369 let angle = self.sidereal_angle_mean(mjd, eo_rad);
370 let fraction = ((angle / TAU) % 1.0 + 1.0) % 1.0;
371 fraction * 86400.0
372 }
373
374 /// Returns local sidereal time at the observer's longitude as seconds since
375 /// sidereal midnight, wrapped to the range `[0, 86400)`.
376 ///
377 /// This is the time equivalent of [`local_sidereal_angle_mean`].
378 ///
379 /// ## Parameters
380 ///
381 /// - `eo_rad`: The Equation of the Origins value to use.
382 /// - Pass `0.0` to get the time equivalent of the local Earth Rotation Angle (CIO-based).
383 /// - Pass the **mean** Equation of the Origins (e.g. from
384 /// [`earth_eo_mean`](Self::earth_eo_mean)) to obtain **Local Mean Sidereal Time (LMST)**.
385 ///
386 /// ## Details
387 ///
388 /// - When `eo_rad = 0.0`, the result is the time equivalent of the local
389 /// Earth Rotation Angle relative to the Celestial Intermediate Origin (CIO)
390 /// at the observer’s longitude.
391 ///
392 /// - When `eo_rad` is the mean Equation of the Origins, the result is
393 /// **Local Mean Sidereal Time (LMST)** referred to the mean equinox.
394 ///
395 /// As of Astropy 7.x, this is consistent with
396 /// `Time.sidereal_time("mean", longitude=...).to_value("sec")` when using
397 /// matching UT1 time and the mean Equation of the Origins.
398 ///
399 /// ## Examples
400 ///
401 /// ```rust
402 /// use deep_time::Sidereal;
403 ///
404 /// let mut earth = Sidereal::EARTH;
405 /// earth.longitude_rad = 0.0; // Greenwich
406 ///
407 /// let mjd = 60000.0;
408 ///
409 /// // Time equivalent of local Earth Rotation Angle
410 /// let local_era_seconds = earth.local_sidereal_time_mean(mjd, 0.0);
411 ///
412 /// // Local Mean Sidereal Time in seconds
413 /// // (requires "sidereal-earth" feature)
414 /// # #[cfg(feature = "sidereal-earth")] {
415 /// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
416 /// let lmst_seconds = earth.local_sidereal_time_mean(mjd, eo_mean);
417 /// # }
418 /// ```
419 pub const fn local_sidereal_time_mean(&self, mjd: Real, eo_rad: Real) -> Real {
420 let angle = self.local_sidereal_angle_mean(mjd, eo_rad);
421 let fraction = ((angle / TAU) % 1.0 + 1.0) % 1.0;
422 fraction * 86400.0
423 }
424
425 /// Returns the apparent sidereal angle of the body's prime meridian
426 /// in radians, normalized to `[0, 2π)`.
427 ///
428 /// This computes **Greenwich Apparent Sidereal Time (GAST)** when the
429 /// apparent Equation of the Origins is supplied.
430 ///
431 /// ## Parameters
432 ///
433 /// - `eo_rad`: The **apparent** Equation of the Origins
434 /// (e.g. from [`earth_eo_apparent`](Self::earth_eo_apparent)).
435 /// When supplied, the result is Greenwich Apparent Sidereal Time (GAST)
436 /// referred to the true equinox.
437 ///
438 /// ## Details
439 ///
440 /// This function implements the direct relationship:
441 ///
442 /// ```text
443 /// GAST = ERA − EO_apparent
444 /// ```
445 ///
446 /// As of Astropy 7.x, this is consistent with
447 /// `Time.sidereal_time("apparent").rad` (when no longitude is specified)
448 /// when using matching UT1 time and the apparent Equation of the Origins.
449 ///
450 /// ## Examples
451 ///
452 /// ```rust
453 /// use deep_time::Sidereal;
454 ///
455 /// let earth = Sidereal::EARTH;
456 /// let mjd = 60000.0;
457 ///
458 /// // Greenwich Apparent Sidereal Time
459 /// // (requires "sidereal-earth" feature)
460 /// # #[cfg(feature = "sidereal-earth")] {
461 /// let eo_app = earth.earth_eo_apparent(mjd + 32.184 / 86400.0);
462 /// let gast = earth.sidereal_angle_apparent(mjd, eo_app);
463 /// # }
464 /// ```
465 pub const fn sidereal_angle_apparent(&self, mjd: Real, eo_rad: Real) -> Real {
466 let angle = self.rotation_angle(mjd) - eo_rad;
467 Self::normalize_angle(angle)
468 }
469
470 /// Returns the local apparent sidereal angle at the observer's longitude
471 /// in radians, normalized to `[0, 2π)`.
472 ///
473 /// This computes **Local Apparent Sidereal Time (LAST)** when the
474 /// apparent Equation of the Origins is supplied.
475 ///
476 /// ## Parameters
477 ///
478 /// - `eo_rad`: The **apparent** Equation of the Origins
479 /// (e.g. from [`earth_eo_apparent`](Self::earth_eo_apparent)).
480 /// When supplied, the result is Local Apparent Sidereal Time (LAST)
481 /// at the observer’s longitude, referred to the true equinox.
482 ///
483 /// ## Details
484 ///
485 /// This function implements the direct relationship:
486 ///
487 /// ```text
488 /// LAST = ERA + longitude − EO_apparent
489 /// ```
490 ///
491 /// As of Astropy 7.x, this is consistent with
492 /// `Time.sidereal_time("apparent", longitude=...).rad` when using
493 /// matching UT1 time and the apparent Equation of the Origins.
494 ///
495 /// ## Examples
496 ///
497 /// ```rust
498 /// use deep_time::Sidereal;
499 ///
500 /// let mut earth = Sidereal::EARTH;
501 /// earth.longitude_rad = 0.0; // Greenwich
502 ///
503 /// let mjd = 60000.0;
504 ///
505 /// // Local Apparent Sidereal Time
506 /// // (requires "sidereal-earth" feature)
507 /// # #[cfg(feature = "sidereal-earth")] {
508 /// let eo_app = earth.earth_eo_apparent(mjd + 32.184 / 86400.0);
509 /// let last = earth.local_sidereal_angle_apparent(mjd, eo_app);
510 /// # }
511 /// ```
512 pub const fn local_sidereal_angle_apparent(&self, mjd: Real, eo_rad: Real) -> Real {
513 let angle = self.rotation_angle(mjd) + self.longitude_rad - eo_rad;
514 Self::normalize_angle(angle)
515 }
516
517 /// Returns apparent sidereal time at the body's prime meridian as seconds
518 /// since sidereal midnight, wrapped to the range `[0, 86400)`.
519 ///
520 /// This is the time equivalent of [`sidereal_angle_apparent`].
521 ///
522 /// When the **apparent** Equation of the Origins is supplied, this function
523 /// returns **Greenwich Apparent Sidereal Time (GAST)**.
524 ///
525 /// ## Parameters
526 ///
527 /// - `eo_rad`: The **apparent** Equation of the Origins
528 /// (e.g. from [`earth_eo_apparent`](Self::earth_eo_apparent)).
529 /// When supplied, the result is Greenwich Apparent Sidereal Time (GAST)
530 /// in seconds since sidereal midnight.
531 ///
532 /// ## Details
533 ///
534 /// This function computes:
535 ///
536 /// ```text
537 /// GAST (seconds) = (ERA − EO_apparent) in fractional days × 86400
538 /// ```
539 ///
540 /// As of Astropy 7.x, this is consistent with
541 /// `Time.sidereal_time("apparent").to_value("sec")` (Greenwich) when using
542 /// matching UT1 time and the apparent Equation of the Origins.
543 ///
544 /// ## Examples
545 ///
546 /// ```rust
547 /// use deep_time::Sidereal;
548 ///
549 /// let earth = Sidereal::EARTH;
550 /// let mjd = 60000.0;
551 ///
552 /// // Greenwich Apparent Sidereal Time in seconds
553 /// // (requires "sidereal-earth" feature)
554 /// # #[cfg(feature = "sidereal-earth")] {
555 /// let eo_app = earth.earth_eo_apparent(mjd + 32.184 / 86400.0);
556 /// let gast_seconds = earth.sidereal_time_apparent(mjd, eo_app);
557 /// # }
558 /// ```
559 pub const fn sidereal_time_apparent(&self, mjd: Real, eo_rad: Real) -> Real {
560 let angle = self.sidereal_angle_apparent(mjd, eo_rad);
561 let fraction = ((angle / TAU) % 1.0 + 1.0) % 1.0;
562 fraction * 86400.0
563 }
564
565 /// Returns local apparent sidereal time at the observer's longitude as
566 /// seconds since sidereal midnight, wrapped to the range `[0, 86400)`.
567 ///
568 /// This is the time equivalent of [`local_sidereal_angle_apparent`].
569 ///
570 /// When the **apparent** Equation of the Origins is supplied, this function
571 /// returns **Local Apparent Sidereal Time (LAST)**.
572 ///
573 /// ## Parameters
574 ///
575 /// - `eo_rad`: The **apparent** Equation of the Origins
576 /// (e.g. from [`earth_eo_apparent`](Self::earth_eo_apparent)).
577 /// When supplied, the result is Local Apparent Sidereal Time (LAST)
578 /// at the observer’s longitude, in seconds since sidereal midnight.
579 ///
580 /// ## Details
581 ///
582 /// This function computes:
583 ///
584 /// ```text
585 /// LAST (seconds) = (ERA + longitude − EO_apparent) in fractional days × 86400
586 /// ```
587 ///
588 /// As of Astropy 7.x, this is consistent with
589 /// `Time.sidereal_time("apparent", longitude=...).to_value("sec")` when using
590 /// matching UT1 time and the apparent Equation of the Origins.
591 ///
592 /// ## Examples
593 ///
594 /// ```rust
595 /// use deep_time::Sidereal;
596 ///
597 /// let mut earth = Sidereal::EARTH;
598 /// earth.longitude_rad = 0.0; // Greenwich
599 ///
600 /// let mjd = 60000.0;
601 ///
602 /// // Local Apparent Sidereal Time in seconds
603 /// // (requires "sidereal-earth" feature)
604 /// # #[cfg(feature = "sidereal-earth")] {
605 /// let eo_app = earth.earth_eo_apparent(mjd + 32.184 / 86400.0);
606 /// let last_seconds = earth.local_sidereal_time_apparent(mjd, eo_app);
607 /// # }
608 /// ```
609 pub const fn local_sidereal_time_apparent(&self, mjd: Real, eo_rad: Real) -> Real {
610 let angle = self.local_sidereal_angle_apparent(mjd, eo_rad);
611 let fraction = ((angle / TAU) % 1.0 + 1.0) % 1.0;
612 fraction * 86400.0
613 }
614
615 /// Returns the apparent Equation of the Origins (radians) at the given MJD.
616 ///
617 /// This returns the value computed by ERFA’s `eo06a`. It is the modern
618 /// CIO-based quantity used to derive **Greenwich Apparent Sidereal Time (GAST)**
619 /// from the Earth Rotation Angle (ERA).
620 ///
621 /// When you subtract this value from the ERA, you get GAST:
622 ///
623 /// ```text
624 /// GAST = ERA − earth_eo_apparent(...)
625 /// ```
626 ///
627 /// This method is equivalent to calling `erfa.eo06a(tt.jd1, tt.jd2)` in Astropy.
628 ///
629 /// You should pass the value returned by this function to the apparent
630 /// sidereal time functions (`sidereal_angle_apparent`, `local_sidereal_angle_apparent`,
631 /// `sidereal_time_apparent`, and `local_sidereal_time_apparent`).
632 ///
633 /// ## Examples
634 ///
635 /// ```rust
636 /// use deep_time::Sidereal;
637 ///
638 /// let earth = Sidereal::EARTH;
639 /// let mjd_tt = 60000.0 + 32.184 / 86400.0;
640 ///
641 /// let eo_app = earth.earth_eo_apparent(mjd_tt);
642 /// let gast = earth.sidereal_angle_apparent(mjd_tt, eo_app);
643 /// ```
644 #[cfg(feature = "sidereal-earth")]
645 #[inline]
646 pub const fn earth_eo_apparent(&self, tt_mjd: Real) -> Real {
647 // Convert MJD → two-part Julian Date
648 let date1 = 2400000.5 + tt_mjd;
649 earth_eo(date1, 0.0)
650 }
651
652 /// Returns the mean Equation of the Origins (radians) at the given MJD.
653 ///
654 /// This returns the value that should be subtracted from the Earth Rotation
655 /// Angle (ERA) to obtain **Greenwich Mean Sidereal Time (GMST)**:
656 ///
657 /// ```text
658 /// GMST = ERA − earth_eo_mean(...)
659 /// ```
660 ///
661 /// Internally, this is computed as:
662 ///
663 /// ```text
664 /// earth_eo_mean = earth_eo_apparent() + earth_ee()
665 /// ```
666 ///
667 /// This is equivalent to computing `era - gmst` in Astropy:
668 ///
669 /// ```python
670 /// era = ut1.earth_rotation_angle(...).rad
671 /// gmst = ut1.sidereal_time("mean", ...).rad
672 /// eo_mean = era - gmst
673 /// ```
674 ///
675 /// You should pass the value returned by this function to the mean
676 /// sidereal time functions (`sidereal_angle_mean`, `local_sidereal_angle_mean`,
677 /// `sidereal_time_mean`, and `local_sidereal_time_mean`).
678 ///
679 /// ## Examples
680 ///
681 /// ```rust
682 /// use deep_time::Sidereal;
683 ///
684 /// let earth = Sidereal::EARTH;
685 /// let mjd_tt = 60000.0 + 32.184 / 86400.0;
686 ///
687 /// let eo_mean = earth.earth_eo_mean(mjd_tt);
688 /// let gmst = earth.sidereal_angle_mean(mjd_tt, eo_mean);
689 /// ```
690 #[cfg(feature = "sidereal-earth")]
691 #[inline]
692 pub const fn earth_eo_mean(&self, tt_mjd: Real) -> Real {
693 // Convert MJD → two-part Julian Date
694 let date1 = 2400000.5 + tt_mjd;
695 earth_eo(date1, 0.0) + earth_ee(date1, 0.0)
696 }
697
698 /// Returns the Equation of the Equinoxes (radians) at the given MJD.
699 ///
700 /// This returns the value computed by ERFA’s `ee06a`. The Equation of the
701 /// Equinoxes represents the nutation contribution to sidereal time and is
702 /// defined as:
703 ///
704 /// ```text
705 /// EE = GAST − GMST
706 /// ```
707 ///
708 /// It is equivalent to computing `gast - gmst` in Astropy:
709 ///
710 /// ```python
711 /// gast = ut1.sidereal_time("apparent", ...).rad
712 /// gmst = ut1.sidereal_time("mean", ...).rad
713 /// ee = gast - gmst
714 /// ```
715 ///
716 /// This value is used internally when converting between mean and apparent
717 /// sidereal time (for example, when the mean functions are given the apparent
718 /// EO + EE).
719 ///
720 /// ## Examples
721 ///
722 /// ```rust
723 /// use deep_time::Sidereal;
724 ///
725 /// let earth = Sidereal::EARTH;
726 /// let mjd_tt = 60000.0 + 32.184 / 86400.0;
727 ///
728 /// let ee = earth.earth_ee(mjd_tt);
729 /// ```
730 #[cfg(feature = "sidereal-earth")]
731 #[inline]
732 pub const fn earth_ee(&self, tt_mjd: Real) -> Real {
733 // Convert MJD → two-part Julian Date
734 let date1 = 2400000.5 + tt_mjd;
735 earth_ee(date1, 0.0)
736 }
737}