1use crate::rinex::observations::RinexObs;
13
14const SQRT_3: f64 = 1.732_050_807_568_877_2;
15
16#[derive(Debug, Clone, Copy, PartialEq)]
18pub enum AllanSeries<'a> {
19 PhaseSeconds(&'a [f64]),
21 FractionalFrequency(&'a [f64]),
23 PhaseSecondsWithGaps(&'a [Option<f64>]),
25 FractionalFrequencyWithGaps(&'a [Option<f64>]),
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Default)]
31pub enum TauGrid {
32 #[default]
34 Octave,
35 All,
37 Explicit(Vec<usize>),
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
43pub enum GapPolicy {
44 #[default]
46 Reject,
47 OmitTerms,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct AllanEstimatorSet {
54 pub adev: bool,
56 pub overlapping_adev: bool,
58 pub mdev: bool,
60 pub hdev: bool,
62 pub tdev: bool,
64}
65
66impl AllanEstimatorSet {
67 pub const fn none() -> Self {
69 Self {
70 adev: false,
71 overlapping_adev: false,
72 mdev: false,
73 hdev: false,
74 tdev: false,
75 }
76 }
77
78 pub const fn standard() -> Self {
80 Self {
81 adev: false,
82 overlapping_adev: true,
83 mdev: true,
84 hdev: true,
85 tdev: true,
86 }
87 }
88
89 pub const fn all() -> Self {
91 Self {
92 adev: true,
93 overlapping_adev: true,
94 mdev: true,
95 hdev: true,
96 tdev: true,
97 }
98 }
99
100 fn is_empty(self) -> bool {
101 !self.adev && !self.overlapping_adev && !self.mdev && !self.hdev && !self.tdev
102 }
103}
104
105impl Default for AllanEstimatorSet {
106 fn default() -> Self {
107 Self::standard()
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Default)]
113pub struct AllanOptions {
114 pub estimators: AllanEstimatorSet,
116 pub tau_grid: TauGrid,
118 pub gap_policy: GapPolicy,
120}
121
122#[derive(Debug, Clone, PartialEq)]
124pub struct AllanInput<'a> {
125 pub series: AllanSeries<'a>,
127 pub tau0_s: f64,
129 pub options: AllanOptions,
131}
132
133#[derive(Debug, Clone, PartialEq)]
135pub struct AllanResult {
136 pub tau_s: Vec<f64>,
138 pub deviation: Vec<f64>,
140 pub n: Vec<usize>,
142}
143
144impl AllanResult {
145 fn new() -> Self {
146 Self {
147 tau_s: Vec::new(),
148 deviation: Vec::new(),
149 n: Vec::new(),
150 }
151 }
152
153 fn push(&mut self, tau_s: f64, deviation: f64, n: usize) {
154 self.tau_s.push(tau_s);
155 self.deviation.push(deviation);
156 self.n.push(n);
157 }
158
159 pub fn len(&self) -> usize {
161 self.tau_s.len()
162 }
163
164 pub fn is_empty(&self) -> bool {
166 self.tau_s.is_empty()
167 }
168}
169
170#[derive(Debug, Clone, PartialEq, Default)]
172pub struct AllanDeviationCurves {
173 pub adev: Option<AllanResult>,
175 pub overlapping_adev: Option<AllanResult>,
177 pub mdev: Option<AllanResult>,
179 pub hdev: Option<AllanResult>,
181 pub tdev: Option<AllanResult>,
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187pub enum AllanEstimator {
188 Adev,
190 OverlappingAdev,
192 Mdev,
194 Hdev,
196 Tdev,
198}
199
200impl AllanEstimator {
201 fn label(self) -> &'static str {
202 match self {
203 Self::Adev => "ADEV",
204 Self::OverlappingAdev => "OADEV",
205 Self::Mdev => "MDEV",
206 Self::Hdev => "HDEV",
207 Self::Tdev => "TDEV",
208 }
209 }
210}
211
212#[derive(Debug, Clone, PartialEq)]
214pub enum AllanError {
215 EmptySeries,
217 InvalidTau0 { tau0_s: f64 },
219 NoEstimators,
221 EmptyTauGrid,
223 InvalidAveragingFactor { averaging_factor: usize },
225 TooFewSamples {
227 estimator: AllanEstimator,
228 averaging_factor: usize,
229 available_phase_samples: usize,
230 },
231 NonFiniteSample { index: usize },
233 Gap { index: usize },
235 NonFiniteTau {
237 estimator: AllanEstimator,
238 averaging_factor: usize,
239 },
240 NonFiniteDeviation {
242 estimator: AllanEstimator,
243 averaging_factor: usize,
244 },
245}
246
247impl core::fmt::Display for AllanError {
248 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
249 match self {
250 Self::EmptySeries => write!(f, "Allan series is empty"),
251 Self::InvalidTau0 { tau0_s } => {
252 write!(f, "tau0_s must be finite and positive, got {tau0_s}")
253 }
254 Self::NoEstimators => write!(f, "no Allan estimators selected"),
255 Self::EmptyTauGrid => write!(f, "explicit tau grid is empty"),
256 Self::InvalidAveragingFactor { averaging_factor } => {
257 write!(
258 f,
259 "averaging factor must be positive, got {averaging_factor}"
260 )
261 }
262 Self::TooFewSamples {
263 estimator,
264 averaging_factor,
265 available_phase_samples,
266 } => write!(
267 f,
268 "{} has no valid terms for averaging factor {} with {} phase samples",
269 estimator.label(),
270 averaging_factor,
271 available_phase_samples
272 ),
273 Self::NonFiniteSample { index } => {
274 write!(f, "sample {index} is not finite")
275 }
276 Self::Gap { index } => {
277 write!(f, "sample {index} is missing")
278 }
279 Self::NonFiniteTau {
280 estimator,
281 averaging_factor,
282 } => write!(
283 f,
284 "{} tau is not finite for averaging factor {}",
285 estimator.label(),
286 averaging_factor
287 ),
288 Self::NonFiniteDeviation {
289 estimator,
290 averaging_factor,
291 } => write!(
292 f,
293 "{} deviation is not finite for averaging factor {}",
294 estimator.label(),
295 averaging_factor
296 ),
297 }
298 }
299}
300
301impl std::error::Error for AllanError {}
302
303pub fn receiver_clock_phase_deviations(obs: &RinexObs) -> Vec<Option<f64>> {
308 obs.epochs()
309 .iter()
310 .map(|epoch| {
311 if epoch.flag > 1 {
312 None
313 } else {
314 epoch.rcv_clock_offset_s
315 }
316 })
317 .collect()
318}
319
320pub fn compute_allan_deviations(
322 input: &AllanInput<'_>,
323) -> Result<AllanDeviationCurves, AllanError> {
324 if input.options.estimators.is_empty() {
325 return Err(AllanError::NoEstimators);
326 }
327
328 let phase = prepare_phase(input.series, input.tau0_s, input.options.gap_policy)?;
329 let mut curves = AllanDeviationCurves::default();
330
331 if input.options.estimators.adev {
332 curves.adev = Some(compute_curve(
333 &phase,
334 input.tau0_s,
335 &input.options.tau_grid,
336 AllanEstimator::Adev,
337 )?);
338 }
339 if input.options.estimators.overlapping_adev {
340 curves.overlapping_adev = Some(compute_curve(
341 &phase,
342 input.tau0_s,
343 &input.options.tau_grid,
344 AllanEstimator::OverlappingAdev,
345 )?);
346 }
347 if input.options.estimators.mdev {
348 curves.mdev = Some(compute_curve(
349 &phase,
350 input.tau0_s,
351 &input.options.tau_grid,
352 AllanEstimator::Mdev,
353 )?);
354 }
355 if input.options.estimators.hdev {
356 curves.hdev = Some(compute_curve(
357 &phase,
358 input.tau0_s,
359 &input.options.tau_grid,
360 AllanEstimator::Hdev,
361 )?);
362 }
363 if input.options.estimators.tdev {
364 curves.tdev = Some(compute_curve(
365 &phase,
366 input.tau0_s,
367 &input.options.tau_grid,
368 AllanEstimator::Tdev,
369 )?);
370 }
371
372 Ok(curves)
373}
374
375pub fn allan_deviation(
377 series: AllanSeries<'_>,
378 tau0_s: f64,
379 averaging_factors: &[usize],
380) -> Result<AllanResult, AllanError> {
381 compute_explicit(series, tau0_s, averaging_factors, AllanEstimator::Adev)
382}
383
384pub fn overlapping_adev(
386 series: AllanSeries<'_>,
387 tau0_s: f64,
388 averaging_factors: &[usize],
389) -> Result<AllanResult, AllanError> {
390 compute_explicit(
391 series,
392 tau0_s,
393 averaging_factors,
394 AllanEstimator::OverlappingAdev,
395 )
396}
397
398pub fn modified_adev(
400 series: AllanSeries<'_>,
401 tau0_s: f64,
402 averaging_factors: &[usize],
403) -> Result<AllanResult, AllanError> {
404 compute_explicit(series, tau0_s, averaging_factors, AllanEstimator::Mdev)
405}
406
407pub fn hadamard_deviation(
409 series: AllanSeries<'_>,
410 tau0_s: f64,
411 averaging_factors: &[usize],
412) -> Result<AllanResult, AllanError> {
413 compute_explicit(series, tau0_s, averaging_factors, AllanEstimator::Hdev)
414}
415
416pub fn time_deviation(
418 series: AllanSeries<'_>,
419 tau0_s: f64,
420 averaging_factors: &[usize],
421) -> Result<AllanResult, AllanError> {
422 compute_explicit(series, tau0_s, averaging_factors, AllanEstimator::Tdev)
423}
424
425fn compute_explicit(
426 series: AllanSeries<'_>,
427 tau0_s: f64,
428 averaging_factors: &[usize],
429 estimator: AllanEstimator,
430) -> Result<AllanResult, AllanError> {
431 let phase = prepare_phase(series, tau0_s, GapPolicy::Reject)?;
432 compute_curve(
433 &phase,
434 tau0_s,
435 &TauGrid::Explicit(averaging_factors.to_vec()),
436 estimator,
437 )
438}
439
440#[derive(Debug, Clone, Copy)]
441struct PhasePoint {
442 value_s: f64,
443 run_id: usize,
444}
445
446fn prepare_phase(
447 series: AllanSeries<'_>,
448 tau0_s: f64,
449 gap_policy: GapPolicy,
450) -> Result<Vec<Option<PhasePoint>>, AllanError> {
451 validate_tau0(tau0_s)?;
452 match series {
453 AllanSeries::PhaseSeconds(values) => phase_from_contiguous(values),
454 AllanSeries::FractionalFrequency(values) => phase_from_contiguous_frequency(values, tau0_s),
455 AllanSeries::PhaseSecondsWithGaps(values) => phase_from_gapped(values, gap_policy),
456 AllanSeries::FractionalFrequencyWithGaps(values) => {
457 phase_from_gapped_frequency(values, tau0_s, gap_policy)
458 }
459 }
460}
461
462fn validate_tau0(tau0_s: f64) -> Result<(), AllanError> {
463 if tau0_s.is_finite() && tau0_s > 0.0 {
464 Ok(())
465 } else {
466 Err(AllanError::InvalidTau0 { tau0_s })
467 }
468}
469
470fn phase_from_contiguous(values: &[f64]) -> Result<Vec<Option<PhasePoint>>, AllanError> {
471 if values.is_empty() {
472 return Err(AllanError::EmptySeries);
473 }
474 values
475 .iter()
476 .enumerate()
477 .map(|(index, &value_s)| {
478 validate_sample(index, value_s).map(|value_s| Some(PhasePoint { value_s, run_id: 0 }))
479 })
480 .collect()
481}
482
483fn phase_from_contiguous_frequency(
484 values: &[f64],
485 tau0_s: f64,
486) -> Result<Vec<Option<PhasePoint>>, AllanError> {
487 if values.is_empty() {
488 return Err(AllanError::EmptySeries);
489 }
490 let mut phase = Vec::with_capacity(values.len() + 1);
491 let mut value_s = 0.0;
492 phase.push(Some(PhasePoint { value_s, run_id: 0 }));
493 for (index, &frequency) in values.iter().enumerate() {
494 let frequency = validate_sample(index, frequency)?;
495 value_s += tau0_s * frequency;
496 if !value_s.is_finite() {
497 return Err(AllanError::NonFiniteSample { index });
498 }
499 phase.push(Some(PhasePoint { value_s, run_id: 0 }));
500 }
501 Ok(phase)
502}
503
504fn phase_from_gapped(
505 values: &[Option<f64>],
506 gap_policy: GapPolicy,
507) -> Result<Vec<Option<PhasePoint>>, AllanError> {
508 if values.is_empty() {
509 return Err(AllanError::EmptySeries);
510 }
511
512 let mut phase = Vec::with_capacity(values.len());
513 let mut run_id = 0usize;
514 let mut in_run = false;
515 for (index, value) in values.iter().enumerate() {
516 match value {
517 Some(value_s) => {
518 let value_s = validate_sample(index, *value_s)?;
519 if !in_run {
520 run_id += 1;
521 in_run = true;
522 }
523 phase.push(Some(PhasePoint { value_s, run_id }));
524 }
525 None => {
526 if gap_policy == GapPolicy::Reject {
527 return Err(AllanError::Gap { index });
528 }
529 in_run = false;
530 phase.push(None);
531 }
532 }
533 }
534 Ok(phase)
535}
536
537fn phase_from_gapped_frequency(
538 values: &[Option<f64>],
539 tau0_s: f64,
540 gap_policy: GapPolicy,
541) -> Result<Vec<Option<PhasePoint>>, AllanError> {
542 if values.is_empty() {
543 return Err(AllanError::EmptySeries);
544 }
545 if gap_policy == GapPolicy::Reject {
546 for (index, value) in values.iter().enumerate() {
547 if value.is_none() {
548 return Err(AllanError::Gap { index });
549 }
550 }
551 }
552
553 let mut phase = vec![None; values.len() + 1];
554 let mut run_id = 0usize;
555 let mut index = 0usize;
556 while index < values.len() {
557 if values[index].is_none() {
558 index += 1;
559 continue;
560 };
561
562 run_id += 1;
563 let mut value_s = 0.0;
564 phase[index] = Some(PhasePoint { value_s, run_id });
565 let mut sample_index = index;
566 while let Some(current) = values.get(sample_index).copied().flatten() {
567 let current = validate_sample(sample_index, current)?;
568 value_s += tau0_s * current;
569 if !value_s.is_finite() {
570 return Err(AllanError::NonFiniteSample {
571 index: sample_index,
572 });
573 }
574 phase[sample_index + 1] = Some(PhasePoint { value_s, run_id });
575 sample_index += 1;
576 }
577 index = sample_index + 1;
578 }
579
580 Ok(phase)
581}
582
583fn validate_sample(index: usize, value: f64) -> Result<f64, AllanError> {
584 if value.is_finite() {
585 Ok(value)
586 } else {
587 Err(AllanError::NonFiniteSample { index })
588 }
589}
590
591fn compute_curve(
592 phase: &[Option<PhasePoint>],
593 tau0_s: f64,
594 tau_grid: &TauGrid,
595 estimator: AllanEstimator,
596) -> Result<AllanResult, AllanError> {
597 let averaging_factors = averaging_factors(phase.len(), estimator, tau_grid)?;
598 let strict = matches!(tau_grid, TauGrid::Explicit(_));
599 let mut result = AllanResult::new();
600
601 for m in averaging_factors {
602 if m == 0 {
603 return Err(AllanError::InvalidAveragingFactor {
604 averaging_factor: m,
605 });
606 }
607 if candidate_count(phase.len(), estimator, m).is_none() {
608 if strict {
609 return Err(AllanError::TooFewSamples {
610 estimator,
611 averaging_factor: m,
612 available_phase_samples: phase.len(),
613 });
614 }
615 continue;
616 }
617
618 let tau_s = tau0_s * m as f64;
619 if !tau_s.is_finite() {
620 return Err(AllanError::NonFiniteTau {
621 estimator,
622 averaging_factor: m,
623 });
624 }
625
626 let (sum_sq, n) = estimator_sum_squares(phase, estimator, m);
627 if n == 0 {
628 if strict {
629 return Err(AllanError::TooFewSamples {
630 estimator,
631 averaging_factor: m,
632 available_phase_samples: phase.len(),
633 });
634 }
635 continue;
636 }
637
638 let deviation = deviation_from_sum(sum_sq, n, tau_s, m, estimator);
639 if !deviation.is_finite() {
640 return Err(AllanError::NonFiniteDeviation {
641 estimator,
642 averaging_factor: m,
643 });
644 }
645 result.push(tau_s, deviation, n);
646 }
647
648 if result.is_empty() {
649 return Err(AllanError::TooFewSamples {
650 estimator,
651 averaging_factor: 1,
652 available_phase_samples: phase.len(),
653 });
654 }
655 Ok(result)
656}
657
658fn averaging_factors(
659 phase_len: usize,
660 estimator: AllanEstimator,
661 tau_grid: &TauGrid,
662) -> Result<Vec<usize>, AllanError> {
663 match tau_grid {
664 TauGrid::Explicit(values) => {
665 if values.is_empty() {
666 Err(AllanError::EmptyTauGrid)
667 } else {
668 Ok(values.clone())
669 }
670 }
671 TauGrid::All => Ok((1..=max_averaging_factor(phase_len, estimator)).collect()),
672 TauGrid::Octave => {
673 let max_m = max_averaging_factor(phase_len, estimator);
674 let mut values = Vec::new();
675 let mut m = 1usize;
676 while m <= max_m {
677 values.push(m);
678 if m > max_m / 2 {
679 break;
680 }
681 m *= 2;
682 }
683 Ok(values)
684 }
685 }
686}
687
688fn max_averaging_factor(phase_len: usize, estimator: AllanEstimator) -> usize {
689 match estimator {
690 AllanEstimator::Adev | AllanEstimator::OverlappingAdev => phase_len.saturating_sub(1) / 2,
691 AllanEstimator::Mdev | AllanEstimator::Tdev => phase_len / 3,
692 AllanEstimator::Hdev => phase_len.saturating_sub(1) / 3,
693 }
694}
695
696fn candidate_count(phase_len: usize, estimator: AllanEstimator, m: usize) -> Option<usize> {
697 if m == 0 {
698 return None;
699 }
700 match estimator {
701 AllanEstimator::Adev => {
702 let frequency_len = phase_len.checked_sub(1)?;
703 (frequency_len / m).checked_sub(1)
704 }
705 AllanEstimator::OverlappingAdev => phase_len.checked_sub(m.checked_mul(2)?),
706 AllanEstimator::Mdev | AllanEstimator::Tdev => {
707 phase_len.checked_sub(m.checked_mul(3)?)?.checked_add(1)
708 }
709 AllanEstimator::Hdev => phase_len.checked_sub(m.checked_mul(3)?),
710 }
711}
712
713fn estimator_sum_squares(
714 phase: &[Option<PhasePoint>],
715 estimator: AllanEstimator,
716 m: usize,
717) -> (f64, usize) {
718 match estimator {
719 AllanEstimator::Adev => plain_adev_sum_squares(phase, m),
720 AllanEstimator::OverlappingAdev => overlapping_adev_sum_squares(phase, m),
721 AllanEstimator::Mdev | AllanEstimator::Tdev => modified_adev_sum_squares(phase, m),
722 AllanEstimator::Hdev => hadamard_sum_squares(phase, m),
723 }
724}
725
726fn deviation_from_sum(
727 sum_sq: f64,
728 n: usize,
729 tau_s: f64,
730 m: usize,
731 estimator: AllanEstimator,
732) -> f64 {
733 let n = n as f64;
734 match estimator {
735 AllanEstimator::Adev | AllanEstimator::OverlappingAdev => {
736 (sum_sq / (2.0 * n * tau_s * tau_s)).sqrt()
737 }
738 AllanEstimator::Mdev => {
739 let mf = m as f64;
740 (sum_sq / (2.0 * mf * mf * n * tau_s * tau_s)).sqrt()
741 }
742 AllanEstimator::Hdev => (sum_sq / (6.0 * n * tau_s * tau_s)).sqrt(),
743 AllanEstimator::Tdev => {
744 let mf = m as f64;
745 let mdev = (sum_sq / (2.0 * mf * mf * n * tau_s * tau_s)).sqrt();
746 tau_s * mdev / SQRT_3
747 }
748 }
749}
750
751fn plain_adev_sum_squares(phase: &[Option<PhasePoint>], m: usize) -> (f64, usize) {
752 let Some(count) = candidate_count(phase.len(), AllanEstimator::Adev, m) else {
753 return (0.0, 0);
754 };
755 let mut sum_sq = 0.0;
756 let mut n = 0usize;
757 for j in 0..count {
758 let i = j * m;
759 if let Some((diff, _)) = second_difference(phase, i, m) {
760 let square = diff * diff;
761 sum_sq += square;
762 n += 1;
763 }
764 }
765 (sum_sq, n)
766}
767
768fn overlapping_adev_sum_squares(phase: &[Option<PhasePoint>], m: usize) -> (f64, usize) {
769 let Some(count) = candidate_count(phase.len(), AllanEstimator::OverlappingAdev, m) else {
770 return (0.0, 0);
771 };
772 let mut sum_sq = 0.0;
773 let mut n = 0usize;
774 for i in 0..count {
775 if let Some((diff, _)) = second_difference(phase, i, m) {
776 let square = diff * diff;
777 sum_sq += square;
778 n += 1;
779 }
780 }
781 (sum_sq, n)
782}
783
784fn modified_adev_sum_squares(phase: &[Option<PhasePoint>], m: usize) -> (f64, usize) {
785 let Some(count) = candidate_count(phase.len(), AllanEstimator::Mdev, m) else {
786 return (0.0, 0);
787 };
788 let mut sum_sq = 0.0;
789 let mut n = 0usize;
790 for j in 0..count {
791 let mut inner = 0.0;
792 let mut run_id = None;
793 let mut valid = true;
794 for i in j..(j + m) {
795 let Some((diff, diff_run_id)) = second_difference(phase, i, m) else {
796 valid = false;
797 break;
798 };
799 if run_id.is_some_and(|current| current != diff_run_id) {
800 valid = false;
801 break;
802 }
803 run_id = Some(diff_run_id);
804 inner += diff;
805 }
806 if valid {
807 let square = inner * inner;
808 sum_sq += square;
809 n += 1;
810 }
811 }
812 (sum_sq, n)
813}
814
815fn hadamard_sum_squares(phase: &[Option<PhasePoint>], m: usize) -> (f64, usize) {
816 let Some(count) = candidate_count(phase.len(), AllanEstimator::Hdev, m) else {
817 return (0.0, 0);
818 };
819 let mut sum_sq = 0.0;
820 let mut n = 0usize;
821 for i in 0..count {
822 if let Some(diff) = third_difference(phase, i, m) {
823 let square = diff * diff;
824 sum_sq += square;
825 n += 1;
826 }
827 }
828 (sum_sq, n)
829}
830
831fn second_difference(phase: &[Option<PhasePoint>], i: usize, m: usize) -> Option<(f64, usize)> {
832 let x0 = phase.get(i).copied().flatten()?;
833 let x1 = phase.get(i + m).copied().flatten()?;
834 let x2 = phase.get(i + 2 * m).copied().flatten()?;
835 if x0.run_id != x1.run_id || x0.run_id != x2.run_id {
836 return None;
837 }
838 Some((x2.value_s - 2.0 * x1.value_s + x0.value_s, x0.run_id))
839}
840
841fn third_difference(phase: &[Option<PhasePoint>], i: usize, m: usize) -> Option<f64> {
842 let x0 = phase.get(i).copied().flatten()?;
843 let x1 = phase.get(i + m).copied().flatten()?;
844 let x2 = phase.get(i + 2 * m).copied().flatten()?;
845 let x3 = phase.get(i + 3 * m).copied().flatten()?;
846 if x0.run_id != x1.run_id || x0.run_id != x2.run_id || x0.run_id != x3.run_id {
847 return None;
848 }
849 Some(x3.value_s - 3.0 * x2.value_s + 3.0 * x1.value_s - x0.value_s)
850}