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