Skip to main content

pleiades_data/coverage/
generation_spec.rs

1//! Per-body fitting cadence model for dense de440-backed artifact generation.
2//!
3//! Spans are accuracy-safe initial defaults (SP1); SP2 tunes them against the
4//! measured accuracy baseline. Within-span sampling oversamples the polynomial
5//! degree so each segment's least-squares fit is over-determined.
6
7use pleiades_backend::CelestialBody;
8
9/// Oversample factor: within-span sample count = (degree + 1) * this.
10pub const FITTING_OVERSAMPLE: usize = 3;
11
12/// Per-body segment span in days (initial SP1 defaults; tuned in SP2).
13pub fn fitting_segment_span_days(body: &CelestialBody) -> f64 {
14    match body {
15        CelestialBody::Moon => 4.0,
16        CelestialBody::Mercury => 8.0,
17        CelestialBody::Venus | CelestialBody::Sun => 16.0,
18        CelestialBody::Mars => 32.0,
19        CelestialBody::Jupiter => 128.0,
20        CelestialBody::Saturn => 256.0,
21        CelestialBody::Uranus | CelestialBody::Neptune | CelestialBody::Pluto => 512.0,
22        // Constrained asteroids (e.g. Eros) use a Mars-like span; only generated
23        // within their own corpus window by the caller.
24        _ => 16.0,
25    }
26}
27
28/// Per-body polynomial degree for the within-span fit (SP1 default).
29pub fn fitting_degree(_body: &CelestialBody) -> usize {
30    8
31}
32
33/// Number of de440 samples taken within each segment span.
34pub fn fitting_within_span_sample_count(body: &CelestialBody) -> usize {
35    (fitting_degree(body) + 1) * FITTING_OVERSAMPLE
36}
37
38/// Contiguous `[t0, t1]` spans tiling `[start_jd, end_jd]`, last clamped to `end_jd`.
39pub fn fitting_segment_boundaries(
40    body: &CelestialBody,
41    start_jd: f64,
42    end_jd: f64,
43) -> Vec<(f64, f64)> {
44    let span = fitting_segment_span_days(body);
45    let mut spans = Vec::new();
46    let mut t0 = start_jd;
47    while t0 < end_jd {
48        let t1 = (t0 + span).min(end_jd);
49        if t1 > t0 {
50            spans.push((t0, t1));
51        }
52        t0 = t1;
53    }
54    spans
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use pleiades_backend::CelestialBody;
61
62    #[test]
63    fn spans_match_documented_defaults() {
64        assert_eq!(fitting_segment_span_days(&CelestialBody::Moon), 4.0);
65        assert_eq!(fitting_segment_span_days(&CelestialBody::Mercury), 8.0);
66        assert_eq!(fitting_segment_span_days(&CelestialBody::Venus), 16.0);
67        assert_eq!(fitting_segment_span_days(&CelestialBody::Sun), 16.0);
68        assert_eq!(fitting_segment_span_days(&CelestialBody::Mars), 32.0);
69        assert_eq!(fitting_segment_span_days(&CelestialBody::Jupiter), 128.0);
70        assert_eq!(fitting_segment_span_days(&CelestialBody::Saturn), 256.0);
71        assert_eq!(fitting_segment_span_days(&CelestialBody::Uranus), 512.0);
72        assert_eq!(fitting_segment_span_days(&CelestialBody::Neptune), 512.0);
73        assert_eq!(fitting_segment_span_days(&CelestialBody::Pluto), 512.0);
74    }
75
76    #[test]
77    fn within_span_sample_count_oversamples_degree() {
78        let n = fitting_within_span_sample_count(&CelestialBody::Moon);
79        assert_eq!(
80            n,
81            (fitting_degree(&CelestialBody::Moon) + 1) * FITTING_OVERSAMPLE
82        );
83        assert!(
84            n > fitting_degree(&CelestialBody::Moon) + 1,
85            "must oversample"
86        );
87    }
88
89    #[test]
90    fn boundaries_tile_the_window_without_gaps_or_overlap() {
91        let spans = fitting_segment_boundaries(&CelestialBody::Jupiter, 1000.0, 1500.0);
92        assert_eq!(spans.first().unwrap().0, 1000.0);
93        assert_eq!(spans.last().unwrap().1, 1500.0);
94        for pair in spans.windows(2) {
95            assert_eq!(pair[0].1, pair[1].0, "spans must be contiguous");
96        }
97        for (t0, t1) in &spans {
98            assert!(t1 > t0, "each span is non-empty");
99        }
100    }
101}