Skip to main content

deep_time/sidereal/
mod.rs

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