1#![forbid(unsafe_code)]
46
47use std::sync::OnceLock;
48
49use pleiades_backend::{CelestialBody, CustomBodyId};
50use pleiades_jpl::SnapshotEntry;
51
52mod accuracy_baseline;
53mod backend;
54mod coverage;
55mod data;
56mod lookup;
57mod regenerate;
58pub mod thresholds;
59
60pub use accuracy_baseline::{
61 accuracy_baseline_against, packaged_artifact_accuracy_baseline, BodyChannelError,
62};
63pub use backend::*;
64pub use coverage::*;
65pub use data::*;
66pub use lookup::*;
67pub use regenerate::*;
68
69#[cfg(test)]
72pub(crate) use coverage::{
73 channel_from_fit_samples_with_control_points, distance_channel_from_fit_samples,
74 distance_channel_from_samples, packaged_artifact_body_cadence,
75 packaged_artifact_fit_outlier_sample_fractions, packaged_artifact_fit_sample_fractions,
76 packaged_artifact_fit_sample_fractions_for_body, PackagedArtifactBodyCadence,
77};
78#[cfg(test)]
79pub(crate) use data::PACKAGED_ARTIFACT_FIXTURE;
80#[cfg(test)]
81pub(crate) use lookup::{
82 validate_packaged_artifact_access_summary_line, validate_packaged_artifact_storage_profile,
83 validate_packaged_artifact_storage_summary_line,
84 validate_packaged_frame_treatment_summary_line,
85};
86#[cfg(test)]
87pub(crate) use regenerate::{
88 best_residual_segment,
89 body_segment_span_limit,
91 chebyshev_lobatto_fractions,
92 coordinates,
93 evaluate_polynomial_channel,
94 packaged_artifact_fit_sample_counts_for_body,
95 packaged_artifact_residual_sample_fractions_for_channel,
96 packaged_artifact_segment_validation_fractions_for_body,
97 packaged_artifact_split_fraction_for_interval,
98 segment_channel_value,
99 segment_error_prefers_candidate,
100 segment_fit_candidate_is_better,
101 segment_from_pair,
102 segment_from_pair_fallback,
103 snapshot_entry_from_ecliptic_coordinates,
104 validate_packaged_artifact_phase1_source_inputs,
105 PackagedArtifactFitCandidateScore,
106 PackagedArtifactSegmentFitError,
107 PackagedArtifactSplitCurvature,
109 PACKAGED_ARTIFACT_DENSE_FIT_SAMPLE_COUNTS,
110 PACKAGED_ARTIFACT_DENSE_RESIDUAL_SAMPLE_FRACTIONS,
111 PACKAGED_ARTIFACT_DENSE_VALIDATION_SAMPLE_FRACTIONS,
112 PACKAGED_ARTIFACT_FOUR_FIFTHS_SPLIT_FRACTION,
113 PACKAGED_ARTIFACT_LEFT_BIASED_SPLIT_FRACTION,
115 PACKAGED_ARTIFACT_LEFT_EXTREME_SPLIT_FRACTION,
116 PACKAGED_ARTIFACT_MEDIUM_FIT_SAMPLE_COUNTS,
117 PACKAGED_ARTIFACT_MEDIUM_VALIDATION_SAMPLE_FRACTIONS,
118 PACKAGED_ARTIFACT_ONE_EIGHTH_SPLIT_FRACTION,
119 PACKAGED_ARTIFACT_ONE_FIFTH_SPLIT_FRACTION,
120 PACKAGED_ARTIFACT_ONE_NINTH_SPLIT_FRACTION,
121 PACKAGED_ARTIFACT_ONE_SEVENTH_SPLIT_FRACTION,
122 PACKAGED_ARTIFACT_ONE_THIRD_SPLIT_FRACTION,
123 PACKAGED_ARTIFACT_RESIDUAL_SAMPLE_FRACTIONS,
124 PACKAGED_ARTIFACT_RIGHT_BIASED_SPLIT_FRACTION,
125 PACKAGED_ARTIFACT_RIGHT_EXTREME_SPLIT_FRACTION,
126 PACKAGED_ARTIFACT_SEVEN_EIGHTHS_SPLIT_FRACTION,
127 PACKAGED_ARTIFACT_SIX_SEVENTHS_SPLIT_FRACTION,
128};
129#[cfg(test)]
131pub(crate) use pleiades_backend::{
132 Apparentness, BackendFamily, CoordinateFrame, EclipticCoordinates, EphemerisBackend,
133 EphemerisErrorKind, EphemerisRequest, Instant, JulianDay, QualityAnnotation, TimeRange,
134 TimeScale, ZodiacMode,
135};
136#[cfg(test)]
137pub(crate) use pleiades_compression::{
138 ArtifactOutput, ArtifactProfile, ChannelKind, CompressedArtifact, EndianPolicy,
139 PolynomialChannel, Segment, SpeedPolicy,
140};
141#[cfg(test)]
142pub(crate) use pleiades_jpl::{
143 production_generation_source_summary_for_report, production_holdout_corpus, reference_snapshot,
144};
145
146const PACKAGE_NAME: &str = "pleiades-data";
147const ARTIFACT_LABEL: &str = "stage-5 packaged-data draft";
148const ARTIFACT_PROFILE_ID: &str = "pleiades-packaged-artifact-profile/stage-5-draft";
149const PACKAGED_ARTIFACT_GENERATION_STRATEGY_TAIL: &str = "with 8-point and 10-point Chebyshev-Lobatto baseline candidates before the dense body-specific ladders and 12-point and 14-point candidates for inner and outer planets before fallback, with 10-point, 12-point, 14-point, 16-point, 18-point, and 20-point options for luminaries, lunar points, Pluto, selected asteroids, and custom bodies, and the best dense candidate wins before fallback, with equal-error, equal-sample-count ties preferring the simpler segment, residual correction channels on high-curvature spans when they improve the fit, residual-channel combinations and remaining channel-order permutations when composing those channels, preferring the smaller residual footprint on equal-error ties, higher-order reconstruction from fit samples when it quantizes cleanly, shared four-point control-point fallback across longitude, latitude, and distance channels when the higher-order fit does not quantize cleanly, quarter-biased splits on very long dense-body spans when quarter-point curvature is strongly asymmetric, a dense quarter-point control-point lattice before exact-third fallback on irregular spans, one-sixth and five-sixth probe fractions on very long dense-body spans when quarter-point curvature stays balanced, one-third and two-thirds probe fractions on long dense-body spans when quarter-point curvature stays balanced, a dense five-point fallback on the longest dense-body spans when one-fifth through four-fifth samples fit cleanly, a dense seven-point fallback on super-extreme dense-body spans when one-seventh through six-sevenths samples fit cleanly, one-ninth and eight-ninths probe fractions on super-extreme dense-body spans when the finer probes stay balanced, one-eighth and seven-eighths probe fractions on super-extreme dense-body spans when the ninth-point probes stay balanced, one-seventh and six-sevenths probe fractions on extreme dense-body spans when the super-extreme probes stay balanced, one-fifth and four-fifth probe fractions on the longest dense-body spans when the coarser probes stay balanced, and quadratic fallback otherwise";
150
151pub(crate) fn packaged_artifact_generation_policy_note_text() -> &'static str {
152 static NOTE: OnceLock<String> = OnceLock::new();
153 NOTE.get_or_init(|| {
154 format!(
155 "bodies with a single sampled epoch use point segments; bodies with two or more sampled epochs are recursively subdivided into quadratic windows using body-class span caps and measured-fit comparison against the fallback, {}",
156 PACKAGED_ARTIFACT_GENERATION_STRATEGY_TAIL
157 )
158 })
159 .as_str()
160}
161
162pub(crate) fn packaged_artifact_source_text() -> &'static str {
163 static SOURCE: OnceLock<String> = OnceLock::new();
164 SOURCE.get_or_init(|| {
165 format!(
166 "Quantized adjacent same-body quadratic windows with longitude-unwrapped planetary fits, with the comparison-body planetary set densely fit from the JPL de440 kernel over the default 1900-2100 coverage window and the constrained asteroid:433-Eros sourced from its committed reference corpus, with point segments only for single-epoch bodies and recursively subdivided quadratic spans for multi-epoch bodies using body-class span caps and measured-fit comparison against the fallback, {}.",
167 PACKAGED_ARTIFACT_GENERATION_STRATEGY_TAIL
168 )
169 })
170 .as_str()
171}
172
173const PACKAGED_BASE_BODIES: [CelestialBody; 10] = [
174 CelestialBody::Sun,
175 CelestialBody::Moon,
176 CelestialBody::Mercury,
177 CelestialBody::Venus,
178 CelestialBody::Mars,
179 CelestialBody::Jupiter,
180 CelestialBody::Saturn,
181 CelestialBody::Uranus,
182 CelestialBody::Neptune,
183 CelestialBody::Pluto,
184];
185
186const PACKAGED_REFERENCE_EPOCH_JD: f64 = 2_451_545.0;
187
188pub(crate) fn packaged_bodies() -> &'static [CelestialBody] {
189 static BODIES: OnceLock<Vec<CelestialBody>> = OnceLock::new();
190 BODIES.get_or_init(|| {
191 let mut bodies = PACKAGED_BASE_BODIES.to_vec();
192 bodies.push(CelestialBody::Custom(CustomBodyId::new(
193 "asteroid", "433-Eros",
194 )));
195 bodies
196 })
197}
198
199pub(crate) fn is_carried_but_unserved(body: &CelestialBody) -> bool {
207 matches!(
208 body,
209 CelestialBody::Custom(id) if id.catalog == "asteroid" && id.designation == "433-Eros"
210 )
211}
212
213pub fn packaged_body_claims() -> Vec<pleiades_backend::BodyClaim> {
218 use pleiades_backend::{AccuracyClass, BodyClaim, ClaimEvidence};
219 packaged_bodies()
220 .iter()
221 .filter(|body| !is_carried_but_unserved(body))
222 .cloned()
223 .map(|body| {
224 BodyClaim::release_grade(body, AccuracyClass::High, ClaimEvidence::ArtifactValidated)
225 })
226 .collect()
227}
228
229pub fn apsis_body_claims() -> Vec<pleiades_backend::BodyClaim> {
234 use pleiades_backend::{AccuracyClass, BodyClaim, ClaimEvidence};
235 let source = "Swiss Ephemeris 2.10.03 SE_OSCU_APOG (validate-lilith)".to_string();
236 vec![
237 BodyClaim::release_grade(
238 CelestialBody::TrueApogee,
239 AccuracyClass::High,
240 ClaimEvidence::CorpusValidated {
241 source: source.clone(),
242 },
243 ),
244 BodyClaim::release_grade(
245 CelestialBody::TruePerigee,
246 AccuracyClass::High,
247 ClaimEvidence::CorpusValidated { source },
248 ),
249 ]
250}
251
252pub fn true_node_body_claims() -> Vec<pleiades_backend::BodyClaim> {
259 use pleiades_backend::{AccuracyClass, BodyClaim, ClaimEvidence};
260 vec![BodyClaim::release_grade(
261 CelestialBody::TrueNode,
262 AccuracyClass::High,
263 ClaimEvidence::CorpusValidated {
264 source: "Swiss Ephemeris 2.10.03 SE_TRUE_NODE (validate-true-node)".to_string(),
265 },
266 )]
267}
268
269pub fn mean_lunar_point_body_claims() -> Vec<pleiades_backend::BodyClaim> {
277 use pleiades_backend::{AccuracyClass, BodyClaim, ClaimEvidence};
278 let source = "Swiss Ephemeris 2.10.03 SE_MEAN_NODE / SE_MEAN_APOG (validate-mean-lunar-points)";
279 [
280 CelestialBody::MeanNode,
281 CelestialBody::MeanApogee,
282 CelestialBody::MeanPerigee,
283 ]
284 .into_iter()
285 .map(|body| {
286 BodyClaim::release_grade(
287 body,
288 AccuracyClass::High,
289 ClaimEvidence::CorpusValidated {
290 source: source.to_string(),
291 },
292 )
293 })
294 .collect()
295}
296
297pub(crate) fn packaged_reference_entry_for_body(
298 snapshot: &[SnapshotEntry],
299 body: &CelestialBody,
300) -> Option<SnapshotEntry> {
301 snapshot
302 .iter()
303 .find(|entry| {
304 entry.body == *body
305 && (entry.epoch.julian_day.days() - PACKAGED_REFERENCE_EPOCH_JD).abs()
306 < f64::EPSILON
307 })
308 .cloned()
309 .or_else(|| snapshot.iter().find(|entry| entry.body == *body).cloned())
310}
311
312pub(crate) const AU_IN_KM: f64 = 149_597_870.7;
313
314pub const fn package_name() -> &'static str {
316 PACKAGE_NAME
317}
318
319#[cfg(test)]
320mod test_support;
321#[cfg(test)]
322mod tests;