1use statime_base::{
2 ClockId, DirectedLinkId, Direction, Duration, LeapStatus, LinkId, TAI, TimeSnapshot, Timestamp,
3};
4
5use crate::{
6 AlgoError,
7 estimator::{EstimatorState, UncertainValue},
8 link_noise::{LinkDelayNoiseEstimate, LinkNoiseEstimator},
9 ringbuffer::UnorderedRingBuffer,
10 storage::{FilterLinkStorage, KalmanStorageBase},
11};
12
13#[cfg(not(feature = "std"))]
14use crate::float_polyfill::FloatPolyfill;
15
16#[derive(Debug, Clone, Copy, PartialEq)]
18pub struct LinkConfig {
19 pub desired_error_bound: Duration,
21 pub period: Option<Duration>,
25}
26
27impl Default for LinkConfig {
28 fn default() -> Self {
29 Self {
30 desired_error_bound: Duration::from_seconds_nanos(0, 10_000_000),
32 period: None,
33 }
34 }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq)]
39pub struct TrackedLinkConfig {
40 pub decay_rate: f64,
43 pub longest_interval_for_delay_estimation: Duration,
46}
47
48impl Default for TrackedLinkConfig {
49 fn default() -> Self {
50 Self {
51 decay_rate: 0.01,
52 longest_interval_for_delay_estimation: Duration::from_seconds_nanos(0, 500_000_000),
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
58enum LinkState {
59 Tracked {
60 link_noise_estimator: LinkNoiseEstimator,
61 tracked_config: TrackedLinkConfig,
62 },
63 Untracked,
64}
65
66impl LinkState {
67 fn delay_and_noise_estimate(&self) -> Result<LinkDelayNoiseEstimate, AlgoError> {
72 match self {
73 LinkState::Tracked {
74 link_noise_estimator,
75 ..
76 } => Ok(link_noise_estimator.delay_and_noise_estimate()?),
77 LinkState::Untracked => Ok(LinkDelayNoiseEstimate {
78 delay: 0.0,
79 noise: 0.0,
80 }),
81 }
82 }
83
84 fn decay_rate(&self) -> f64 {
86 match self {
87 LinkState::Tracked { tracked_config, .. } => tracked_config.decay_rate,
88 LinkState::Untracked => 0.0,
89 }
90 }
91
92 fn measurement(&mut self, direction: Direction, offset: UncertainValue, time: Timestamp<TAI>) {
94 if let LinkState::Tracked {
95 link_noise_estimator,
96 tracked_config,
97 } = self
98 {
99 *link_noise_estimator = link_noise_estimator.clone().measurement(
100 direction,
101 offset.value,
102 time,
103 tracked_config.longest_interval_for_delay_estimation,
104 );
105 }
106 }
107
108 fn is_tracked(&self) -> bool {
110 matches!(self, LinkState::Tracked { .. })
111 }
112}
113
114#[derive(Debug, Clone)]
115struct ExternalLinkState {
116 last_offsets: UnorderedRingBuffer,
117 last_offset_uncertainty: f64,
118 importance: UnorderedRingBuffer,
119 root_delay: f64,
120 leap_status: Option<LeapStatus>,
121 usable: bool,
122}
123
124#[derive(Debug, Clone, Copy, PartialEq)]
125struct OffsetWindow {
126 low: f64,
127 high: f64,
128}
129
130impl OffsetWindow {
131 pub fn overlaps(self, other: OffsetWindow) -> bool {
133 self.low <= other.high && self.high >= other.low
134 }
135}
136
137#[derive(Debug, Clone)]
138pub struct LinkInfo {
139 id: LinkId,
140 active: bool,
141 link_state: LinkState,
142 desired_poll_interval: Duration,
143 external_link_state: Option<ExternalLinkState>,
144 config: LinkConfig,
145}
146
147impl LinkInfo {
148 fn offset_window<Storage: KalmanStorageBase>(
152 &self,
153 config: &ControllerConfig,
154 state: &EstimatorState<Storage>,
155 ) -> Option<OffsetWindow> {
156 let external_link_state = self.external_link_state.as_ref()?;
157
158 if external_link_state.last_offsets.as_ref().is_empty() || !external_link_state.usable {
159 return None;
160 }
161
162 let internal_clock = if state.is_internal_clock(self.id.first_clock()) {
163 self.id.first_clock()
164 } else {
165 self.id.second_clock()
166 };
167
168 let internal_offset = state.clock_offset(internal_clock).ok()?.value;
169
170 #[expect(
171 clippy::cast_precision_loss,
172 reason = "self.last_offsets length is always exactly representable as f64"
173 )]
174 let avg_offset = external_link_state
175 .last_offsets
176 .as_ref()
177 .iter()
178 .sum::<f64>()
179 / (external_link_state.last_offsets.as_ref().len() as f64);
180
181 let LinkDelayNoiseEstimate { delay, noise } =
182 self.link_state.delay_and_noise_estimate().ok()?;
183 let half_window_size = noise * config.select_link_uncertainty_window
184 + external_link_state.last_offset_uncertainty * config.select_offset_uncertainty_window
185 + (delay + external_link_state.root_delay) * config.select_delay_uncertainty_window;
186
187 if half_window_size < config.select_max_window_size.as_seconds() / 2.0 {
188 Some(OffsetWindow {
189 low: avg_offset - internal_offset - half_window_size,
190 high: avg_offset - internal_offset + half_window_size,
191 })
192 } else {
193 None
194 }
195 }
196
197 fn update_link_offsets_from_steer(&mut self, steered_clock: ClockId, offset_change: f64) {
198 if let Some(external_state) = &mut self.external_link_state
199 && self.id.contains_clock(steered_clock)
200 {
201 for offset in external_state.last_offsets.as_mut() {
202 *offset += offset_change;
203 }
204 }
205 }
206}
207
208#[derive(Debug, Clone)]
211struct LinkInfoList<L>(L);
212
213impl<L: FilterLinkStorage> LinkInfoList<L> {
214 fn new() -> Self {
216 LinkInfoList(L::new())
217 }
218
219 fn find_by_id(&self, id: LinkId) -> Result<&LinkInfo, AlgoError> {
224 self.0
225 .iter()
226 .find(|info| info.id == id)
227 .ok_or(AlgoError::UnknownLink(id))
228 }
229
230 fn find_by_id_mut(&mut self, id: LinkId) -> Result<&mut LinkInfo, AlgoError> {
235 self.0
236 .iter_mut()
237 .find(|info| info.id == id)
238 .ok_or(AlgoError::UnknownLink(id))
239 }
240
241 fn add_link(&mut self, link: LinkInfo) {
243 self.0.push(link);
244 }
245
246 fn remove_link(&mut self, id: LinkId) -> Result<LinkInfo, AlgoError> {
248 let index = self
249 .0
250 .iter()
251 .position(|info| info.id == id)
252 .ok_or(AlgoError::UnknownLink(id))?;
253 Ok(self.0.remove(index))
254 }
255
256 fn iter(&self) -> impl Iterator<Item = &LinkInfo> {
258 self.into_iter()
259 }
260}
261
262impl<'a, L: FilterLinkStorage> IntoIterator for &'a LinkInfoList<L> {
263 type Item = &'a LinkInfo;
264 type IntoIter = core::slice::Iter<'a, LinkInfo>;
265
266 fn into_iter(self) -> Self::IntoIter {
267 self.0.iter()
268 }
269}
270
271impl<'a, L: FilterLinkStorage> IntoIterator for &'a mut LinkInfoList<L> {
272 type Item = &'a mut LinkInfo;
273 type IntoIter = core::slice::IterMut<'a, LinkInfo>;
274
275 fn into_iter(self) -> Self::IntoIter {
276 self.0.iter_mut()
277 }
278}
279
280#[derive(Debug, Clone)]
282pub struct LinkFilter<Storage: KalmanStorageBase> {
283 links: LinkInfoList<Storage::FilterLinkStorage>,
284 estimation_state: EstimatorState<Storage>,
285}
286
287#[derive(Debug, Clone)]
291pub struct ControllerConfig {
292 pub select_offset_uncertainty_window: f64,
296 pub select_link_uncertainty_window: f64,
300 pub select_delay_uncertainty_window: f64,
304 pub select_max_window_size: Duration,
307 pub minimum_agreeing_sources: usize,
310}
311
312impl<Storage: KalmanStorageBase> LinkFilter<Storage> {
313 #[must_use]
315 pub fn empty(time: Timestamp<TAI>) -> Self {
316 LinkFilter {
317 links: LinkInfoList::new(),
318 estimation_state: EstimatorState::empty(time),
319 }
320 }
321
322 pub fn progress_time(mut self, new_time: Timestamp<TAI>) -> Result<Self, AlgoError> {
327 self.estimation_state = self.estimation_state.progress_time(new_time)?;
328 Ok(self)
329 }
330
331 pub fn absorb_frequency_steer(
336 mut self,
337 steered_clock: ClockId,
338 frequency_change: f64,
339 ) -> Result<Self, AlgoError> {
340 self.estimation_state = self
341 .estimation_state
342 .absorb_frequency_steer(steered_clock, frequency_change)?;
343 Ok(self)
344 }
345
346 pub fn absorb_offset_change(
353 mut self,
354 steered_clock: ClockId,
355 offset_change: f64,
356 ) -> Result<Self, AlgoError> {
357 self.estimation_state = self
358 .estimation_state
359 .absorb_offset_change(steered_clock, offset_change)?;
360 for link in &mut self.links {
361 if let LinkState::Tracked {
362 link_noise_estimator,
363 ..
364 } = &mut link.link_state
365 {
366 *link_noise_estimator = link_noise_estimator
367 .clone()
368 .absorb_offset_change(steered_clock);
369 }
370
371 link.update_link_offsets_from_steer(steered_clock, offset_change);
372 }
373 Ok(self)
374 }
375
376 pub fn absorb_system_clock_offset_change(
383 mut self,
384 steered_clock: ClockId,
385 offset_change: Duration,
386 ) -> Result<Self, AlgoError> {
387 self.estimation_state = self
388 .estimation_state
389 .absorb_system_clock_offset_change(steered_clock, offset_change)?;
390 for link in &mut self.links {
391 if let LinkState::Tracked {
392 link_noise_estimator,
393 ..
394 } = &mut link.link_state
395 {
396 *link_noise_estimator = link_noise_estimator
397 .clone()
398 .absorb_system_clock_offset_change(steered_clock, offset_change);
399 }
400
401 link.update_link_offsets_from_steer(steered_clock, offset_change.as_seconds());
402 }
403
404 Ok(self)
405 }
406
407 pub fn measurement(
412 mut self,
413 config: &ControllerConfig,
414 direction: DirectedLinkId,
415 mut offset: UncertainValue,
416 system_clock: ClockId,
417 ) -> Result<Self, AlgoError> {
418 let link = self.links.find_by_id_mut(direction.link_id())?;
419
420 link.link_state.measurement(
421 direction.direction(),
422 offset,
423 self.estimation_state.current_time(),
424 );
425 let Ok(estimates) = link.link_state.delay_and_noise_estimate() else {
426 return Ok(self);
428 };
429 let is_tracked_link = link.link_state.is_tracked();
430
431 if let Some(period) = link.config.period {
440 let period = period.as_seconds();
441 let prediction = self
442 .estimation_state
443 .prediction(direction, is_tracked_link)?;
444 if 4.0 * prediction.uncertainty() < period {
445 let delta = (offset.value - prediction.value).rem_euclid(period);
447 if delta > period / 2.0 {
448 offset.value = delta + prediction.value - period;
449 } else {
450 offset.value = delta + prediction.value;
451 }
452 } else {
453 self.estimation_state = Self::deactivate_link(link, self.estimation_state)?;
454 if let Some(external_link_state) = &mut link.external_link_state {
456 external_link_state.last_offsets = UnorderedRingBuffer::default();
457 }
458 return Ok(self);
459 }
460 }
461
462 if let Some(external_link_state) = &mut link.external_link_state {
463 let offset_to_external = if self
466 .estimation_state
467 .is_external_clock(direction.from_clock())
468 {
469 offset.value - estimates.delay
470 } else {
471 -(offset.value - estimates.delay)
472 };
473
474 external_link_state.last_offsets.insert(offset_to_external);
475 external_link_state.last_offset_uncertainty = offset.uncertainty();
476
477 let our_window = link.offset_window(config, &self.estimation_state);
478
479 let Some(consensus_window) = self.find_external_consensus_window(config) else {
480 self.links
487 .find_by_id_mut(direction.link_id())?
488 .desired_poll_interval = Duration::ZERO;
489 return Ok(self);
490 };
491
492 let link = self.links.find_by_id_mut(direction.link_id())?;
493
494 if let Some(our_window) = our_window
496 && our_window.overlaps(consensus_window)
497 {
498 self.estimation_state =
499 Self::activate_link(link, self.estimation_state, estimates)?;
500 } else {
501 self.estimation_state = Self::deactivate_link(link, self.estimation_state)?;
502 return Ok(self);
503 }
504 } else {
505 self.estimation_state = Self::activate_link(link, self.estimation_state, estimates)?;
506 }
507
508 let (new_estimation_state, importance) = self.estimation_state.measurement(
509 direction,
510 offset.add_uncertainty(estimates.noise),
511 is_tracked_link,
512 system_clock,
513 )?;
514
515 self.estimation_state = new_estimation_state;
516
517 let link = self.links.find_by_id_mut(direction.link_id())?;
518 if let Some(external_state) = &mut link.external_link_state {
519 external_state.importance.insert(importance);
520 }
521
522 self.update_desired_poll(direction.link_id())
523 }
524
525 fn activate_link(
528 link: &mut LinkInfo,
529 mut estimation_state: EstimatorState<Storage>,
530 estimates: LinkDelayNoiseEstimate,
531 ) -> Result<EstimatorState<Storage>, AlgoError> {
532 if !link.active {
533 link.active = true;
534 if link.link_state.is_tracked() {
535 estimation_state = estimation_state.add_link(
536 link.id,
537 (estimates.delay, estimates.noise).into(),
538 link.link_state.decay_rate(),
539 )?;
540 }
541 }
542 Ok(estimation_state)
543 }
544
545 fn deactivate_link(
548 link: &mut LinkInfo,
549 mut estimation_state: EstimatorState<Storage>,
550 ) -> Result<EstimatorState<Storage>, AlgoError> {
551 if link.active {
552 link.active = false;
553 link.desired_poll_interval = Duration::ZERO;
554 if link.link_state.is_tracked() {
555 estimation_state = estimation_state.remove_link(link.id)?;
556 }
557 if let Some(external_state) = &mut link.external_link_state {
558 external_state.importance = UnorderedRingBuffer::default();
559 }
560 }
561 Ok(estimation_state)
562 }
563
564 fn update_desired_poll(mut self, link_id: LinkId) -> Result<Self, AlgoError> {
565 let link = self.links.find_by_id_mut(link_id)?;
566
567 let desire_first_clock = if self
568 .estimation_state
569 .is_internal_clock(link_id.first_clock())
570 {
571 Some(self.estimation_state.time_to_error_bound_exceedance(
572 link_id.first_clock(),
573 link.config.desired_error_bound,
574 )?)
575 } else {
576 None
577 };
578
579 let desire_second_clock = if self
580 .estimation_state
581 .is_internal_clock(link_id.second_clock())
582 {
583 Some(self.estimation_state.time_to_error_bound_exceedance(
584 link_id.second_clock(),
585 link.config.desired_error_bound,
586 )?)
587 } else {
588 None
589 };
590
591 link.desired_poll_interval = match (desire_first_clock, desire_second_clock) {
592 (Some(first), None) => first,
593 (None, Some(second)) => second,
594 (Some(first), Some(second)) => first.min(second),
595 _ => {
596 return Err(AlgoError::BothClocksExternal(
597 link_id.first_clock(),
598 link_id.second_clock(),
599 ));
600 }
601 };
602
603 Ok(self)
604 }
605
606 pub fn external_data_update(
607 mut self,
608 link_id: LinkId,
609 root_delay: f64,
610 leap_status: Option<LeapStatus>,
611 usable: bool,
612 ) -> Result<Self, AlgoError> {
613 let link = self.links.find_by_id_mut(link_id)?;
614 if let Some(external_state) = &mut link.external_link_state {
615 external_state.root_delay = root_delay;
616 external_state.leap_status = leap_status;
617 external_state.usable = usable;
618 Ok(self)
619 } else {
620 Err(AlgoError::LinkNotExternal(link_id))
621 }
622 }
623
624 #[must_use]
625 pub fn leap_vote(&self, config: &ControllerConfig) -> Option<LeapStatus> {
626 let consensus_window = self.find_external_consensus_window(config)?;
627
628 let mut count_none = 0;
629 let mut count_59 = 0;
630 let mut count_61 = 0;
631
632 for link in self.links.iter() {
633 if let Some(window) = link.offset_window(config, &self.estimation_state)
634 && window.overlaps(consensus_window)
635 && let Some(state) = &link.external_link_state
636 {
637 match state.leap_status {
638 Some(LeapStatus::None) => count_none += 1,
639 Some(LeapStatus::Leap59) => count_59 += 1,
640 Some(LeapStatus::Leap61) => count_61 += 1,
641 None => { }
642 }
643 }
644 }
645
646 let total = count_none + count_59 + count_61;
647
648 if count_none * 2 > total {
649 Some(LeapStatus::None)
650 } else if count_59 * 2 > total {
651 Some(LeapStatus::Leap59)
652 } else if count_61 * 2 > total {
653 Some(LeapStatus::Leap61)
654 } else {
655 None
656 }
657 }
658
659 #[must_use]
660 pub fn local_root_delay(&self, config: &ControllerConfig) -> Option<f64> {
661 let consensus_window = self.find_external_consensus_window(config)?;
662
663 let mut best_delay = f64::MAX;
664 for link in self.links.iter() {
665 if let Some(window) = link.offset_window(config, &self.estimation_state)
666 && window.overlaps(consensus_window)
667 && let Some(state) = &link.external_link_state
668 && let Ok(estimate) = link.link_state.delay_and_noise_estimate()
669 {
670 best_delay = best_delay.min(state.root_delay + estimate.delay);
671 }
672 }
673
674 Some(best_delay)
675 }
676
677 pub fn add_clock(
682 mut self,
683 initial_offset: UncertainValue,
684 initial_frequency: UncertainValue,
685 initial_wander: f64,
686 ) -> Result<(Self, ClockId), AlgoError> {
687 let id = ClockId::new();
688 self.estimation_state = self.estimation_state.add_clock(
689 id,
690 initial_offset,
691 initial_frequency,
692 initial_wander,
693 )?;
694 Ok((self, id))
695 }
696
697 pub fn remove_clock(mut self, id: ClockId) -> Result<Self, AlgoError> {
703 if let Some(link) = self.links.iter().find(|l| l.id.contains_clock(id)) {
704 return Err(AlgoError::ClockInUse(id, link.id));
705 }
706
707 self.estimation_state = self.estimation_state.remove_clock(id)?;
708 Ok(self)
709 }
710
711 pub fn add_tracked_link(
718 mut self,
719 first_clock: ClockId,
720 second_clock: Option<ClockId>,
721 config: LinkConfig,
722 tracked_config: TrackedLinkConfig,
723 ) -> Result<(Self, LinkId), AlgoError> {
724 if self.estimation_state.is_external_clock(first_clock) {
725 return Err(AlgoError::UnexpectedExternalClock(first_clock));
726 }
727 if !self.estimation_state.is_internal_clock(first_clock) {
728 return Err(AlgoError::UnknownClock(first_clock));
729 }
730
731 let (second_clock, is_internal) = if let Some(second_clock) = second_clock {
732 if self.estimation_state.is_external_clock(second_clock) {
733 return Err(AlgoError::UnexpectedExternalClock(second_clock));
734 }
735 if !self.estimation_state.is_internal_clock(second_clock) {
736 return Err(AlgoError::UnknownClock(second_clock));
737 }
738 (second_clock, true)
739 } else {
740 let second_clock = ClockId::new();
741 self.estimation_state = self.estimation_state.add_external_clock(second_clock)?;
742 (second_clock, false)
743 };
744
745 let id =
746 LinkId::new(first_clock, second_clock).ok_or(AlgoError::ClocksEqual(first_clock))?;
747 self.links.add_link(LinkInfo {
748 id,
749 active: false,
750 link_state: LinkState::Tracked {
751 link_noise_estimator: LinkNoiseEstimator::new(id),
752 tracked_config,
753 },
754 desired_poll_interval: Duration::ZERO,
755 external_link_state: if is_internal {
756 None
757 } else {
758 Some(ExternalLinkState {
759 last_offsets: UnorderedRingBuffer::default(),
760 last_offset_uncertainty: 0.0,
761 importance: UnorderedRingBuffer::default(),
762 root_delay: 0.0,
763 leap_status: None,
764 usable: false,
765 })
766 },
767 config,
768 });
769
770 Ok((self, id))
771 }
772
773 pub fn add_untracked_link(
778 mut self,
779 first_clock: ClockId,
780 second_clock: Option<ClockId>,
781 config: LinkConfig,
782 ) -> Result<(Self, LinkId), AlgoError> {
783 if self.estimation_state.is_external_clock(first_clock) {
784 return Err(AlgoError::UnexpectedExternalClock(first_clock));
785 }
786 if !self.estimation_state.is_internal_clock(first_clock) {
787 return Err(AlgoError::UnknownClock(first_clock));
788 }
789
790 let (second_clock, is_internal) = if let Some(second_clock) = second_clock {
791 if self.estimation_state.is_external_clock(second_clock) {
792 return Err(AlgoError::UnexpectedExternalClock(second_clock));
793 }
794 if !self.estimation_state.is_internal_clock(second_clock) {
795 return Err(AlgoError::UnknownClock(second_clock));
796 }
797 (second_clock, true)
798 } else {
799 let second_clock = ClockId::new();
800 self.estimation_state = self.estimation_state.add_external_clock(second_clock)?;
801 (second_clock, false)
802 };
803
804 let id =
805 LinkId::new(first_clock, second_clock).ok_or(AlgoError::ClocksEqual(first_clock))?;
806 self.links.add_link(LinkInfo {
807 id,
808 active: is_internal,
809 link_state: LinkState::Untracked,
810 desired_poll_interval: Duration::ZERO,
811 external_link_state: if is_internal {
812 None
813 } else {
814 Some(ExternalLinkState {
815 last_offsets: UnorderedRingBuffer::default(),
816 last_offset_uncertainty: 0.0,
817 importance: UnorderedRingBuffer::default(),
818 root_delay: 0.0,
819 leap_status: None,
820 usable: false,
821 })
822 },
823 config,
824 });
825
826 Ok((self, id))
827 }
828
829 pub fn remove_link(mut self, id: LinkId) -> Result<Self, AlgoError> {
834 let link = self.links.remove_link(id)?;
835 if link.active && matches!(link.link_state, LinkState::Tracked { .. }) {
836 self.estimation_state = self.estimation_state.remove_link(id)?;
837 }
838
839 if self.estimation_state.is_external_clock(id.second_clock()) {
840 self.estimation_state = self
841 .estimation_state
842 .remove_external_clock(id.second_clock())?;
843 }
844
845 Ok(self)
846 }
847
848 pub fn link_active(&self, id: LinkId) -> Result<bool, AlgoError> {
855 let link = self.links.find_by_id(id)?;
856 Ok(link.active)
857 }
858
859 pub fn link_desired_poll_interval(&self, id: LinkId) -> Result<Duration, AlgoError> {
860 let link = self.links.find_by_id(id)?;
861 Ok(link.desired_poll_interval)
862 }
863
864 fn find_external_consensus_window(&self, config: &ControllerConfig) -> Option<OffsetWindow> {
865 let mut bounds: Storage::BoundStorage = self
866 .links
867 .iter()
868 .filter_map(|info| {
869 info.offset_window(config, &self.estimation_state)
870 .map(|window| {
871 [
872 (window.low, BoundType::Start),
873 (window.high, BoundType::End),
874 ]
875 })
876 })
877 .flatten()
878 .collect();
879
880 bounds.sort_unstable_by(|a, b| a.0.total_cmp(&b.0).then_with(|| a.1.cmp(&b.1)));
881
882 let mut maxlow: usize = 0;
886 let mut maxhigh: usize = 0;
887 let mut max_offset_low: f64 = 0.0;
888 let mut max_offset_high: f64 = 0.0;
889 let mut cur: usize = 0;
890
891 for (offset, boundtype) in &*bounds {
892 match boundtype {
893 BoundType::Start => {
894 cur += 1;
895 if cur > maxlow {
896 maxlow = cur;
897 max_offset_low = *offset;
898 }
899 }
900 BoundType::End => {
901 if cur > maxhigh {
902 maxhigh = cur;
903 max_offset_high = *offset;
904 }
905 cur -= 1;
906 }
907 }
908 }
909
910 assert_eq!(maxlow, maxhigh);
914 let max = maxlow;
915
916 if max >= config.minimum_agreeing_sources && max * 4 > bounds.len() {
917 Some(OffsetWindow {
918 low: max_offset_low,
919 high: max_offset_high,
920 })
921 } else {
922 None
923 }
924 }
925
926 pub fn clock_offset(&self, clock_id: ClockId) -> Result<UncertainValue, AlgoError> {
931 self.estimation_state.clock_offset(clock_id)
932 }
933
934 pub fn clock_frequency(&self, clock_id: ClockId) -> Result<UncertainValue, AlgoError> {
939 self.estimation_state.clock_frequency(clock_id)
940 }
941
942 pub fn clock_snapshot(&self, id: ClockId) -> Result<TimeSnapshot, AlgoError> {
943 self.estimation_state.clock_snapshot(id)
944 }
945
946 #[expect(
947 clippy::cast_precision_loss,
948 reason = "Buffer length always fits in f64 without precision loss."
949 )]
950 pub fn link_importance(&self, id: LinkId) -> Result<Option<f64>, AlgoError> {
951 let link = self.links.find_by_id(id)?;
952 if let Some(external_state) = &link.external_link_state {
953 let buf = external_state.importance.as_ref();
954 if !link.active || buf.is_empty() {
955 Ok(None)
956 } else {
957 Ok(Some(buf.iter().sum::<f64>() / (buf.len() as f64)))
958 }
959 } else {
960 Err(AlgoError::LinkNotExternal(id))
961 }
962 }
963
964 #[cfg(feature = "std")]
965 pub fn active_links(&self) -> std::vec::Vec<statime_base::ActiveLinkData> {
966 self.links
967 .iter()
968 .filter_map(|link| {
969 self.link_importance(link.id)
970 .ok()
971 .flatten()
972 .map(|importance| statime_base::ActiveLinkData {
973 id: link.id,
974 importance,
975 })
976 })
977 .collect()
978 }
979}
980
981#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
982pub enum BoundType {
983 Start,
984 End,
985}
986
987#[cfg(all(test, feature = "std"))]
988#[allow(clippy::too_many_lines, reason = "Test code")]
989mod tests {
990 use statime_base::{ClockId, Duration, LeapStatus, Timestamp};
991
992 use crate::{
993 AlgoError, LinkConfig, StdKalmanStorage, TrackedLinkConfig,
994 estimator::UncertainValue,
995 filter::{ControllerConfig, LinkFilter},
996 };
997
998 #[test]
999 fn untracked_internal_link_is_always_active() {
1000 let config = ControllerConfig {
1001 select_offset_uncertainty_window: 2.0,
1002 select_link_uncertainty_window: 2.0,
1003 select_delay_uncertainty_window: 0.7,
1004 select_max_window_size: Duration::from_seconds_nanos(1, 0),
1005 minimum_agreeing_sources: 3,
1006 };
1007
1008 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1009 let (filter, clock_a) = filter
1010 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1011 .unwrap();
1012 let (filter, clock_b) = filter
1013 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1014 .unwrap();
1015 let (filter, link_id) = filter
1016 .add_untracked_link(clock_a, Some(clock_b), LinkConfig::default())
1017 .unwrap();
1018 assert!(filter.link_active(link_id).unwrap());
1019 assert_eq!(
1020 filter.link_desired_poll_interval(link_id).unwrap(),
1021 Duration::ZERO
1022 );
1023 let filter = filter
1024 .measurement(&config, link_id.forward(), (0.0, 0.01).into(), clock_a)
1025 .unwrap();
1026 assert_ne!(
1027 filter.link_desired_poll_interval(link_id).unwrap(),
1028 Duration::ZERO
1029 );
1030 }
1031
1032 #[test]
1033 fn tracked_internal_link_becomes_active() {
1034 let config = ControllerConfig {
1035 select_offset_uncertainty_window: 2.0,
1036 select_link_uncertainty_window: 2.0,
1037 select_delay_uncertainty_window: 0.7,
1038 select_max_window_size: Duration::from_seconds_nanos(1, 0),
1039 minimum_agreeing_sources: 3,
1040 };
1041
1042 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1043 let (filter, clock_a) = filter
1044 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1045 .unwrap();
1046 let (filter, clock_b) = filter
1047 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1048 .unwrap();
1049 let (filter, link_id) = filter
1050 .add_tracked_link(
1051 clock_a,
1052 Some(clock_b),
1053 LinkConfig::default(),
1054 TrackedLinkConfig::default(),
1055 )
1056 .unwrap();
1057
1058 assert!(!filter.link_active(link_id).unwrap());
1059 assert_eq!(
1060 filter.link_desired_poll_interval(link_id).unwrap(),
1061 Duration::ZERO
1062 );
1063
1064 let filter = filter
1065 .measurement(&config, link_id.forward(), (0.0, 0.001).into(), clock_a)
1066 .unwrap()
1067 .measurement(&config, link_id.reverse(), (0.0, 0.001).into(), clock_a)
1068 .unwrap()
1069 .measurement(&config, link_id.forward(), (0.0, 0.001).into(), clock_a)
1070 .unwrap()
1071 .measurement(&config, link_id.reverse(), (0.0, 0.001).into(), clock_a)
1072 .unwrap()
1073 .measurement(&config, link_id.forward(), (0.0, 0.001).into(), clock_a)
1074 .unwrap()
1075 .measurement(&config, link_id.reverse(), (0.0, 0.001).into(), clock_a)
1076 .unwrap()
1077 .measurement(&config, link_id.forward(), (0.0, 0.001).into(), clock_a)
1078 .unwrap()
1079 .measurement(&config, link_id.reverse(), (0.0, 0.001).into(), clock_a)
1080 .unwrap();
1081
1082 assert!(filter.link_active(link_id).unwrap());
1083 assert_ne!(
1084 filter.link_desired_poll_interval(link_id).unwrap(),
1085 Duration::ZERO
1086 );
1087 }
1088
1089 #[test]
1090 fn external_links_need_consensus() {
1091 let config = ControllerConfig {
1092 select_offset_uncertainty_window: 2.0,
1093 select_link_uncertainty_window: 2.0,
1094 select_delay_uncertainty_window: 0.7,
1095 select_max_window_size: Duration::from_seconds_nanos(1, 0),
1096 minimum_agreeing_sources: 1,
1097 };
1098
1099 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1100 let (filter, clock_int) = filter
1101 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1102 .unwrap();
1103 let (filter, link_1) = filter
1104 .add_tracked_link(
1105 clock_int,
1106 None,
1107 LinkConfig::default(),
1108 TrackedLinkConfig::default(),
1109 )
1110 .unwrap();
1111 let (filter, link_2) = filter
1112 .add_tracked_link(
1113 clock_int,
1114 None,
1115 LinkConfig::default(),
1116 TrackedLinkConfig::default(),
1117 )
1118 .unwrap();
1119 let (filter, link_3) = filter
1120 .add_tracked_link(
1121 clock_int,
1122 None,
1123 LinkConfig::default(),
1124 TrackedLinkConfig::default(),
1125 )
1126 .unwrap();
1127
1128 let filter = filter
1129 .external_data_update(link_1, 0.1, None, true)
1131 .unwrap()
1132 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1133 .unwrap()
1134 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1135 .unwrap()
1136 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1137 .unwrap()
1138 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1139 .unwrap()
1140 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1141 .unwrap()
1142 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1143 .unwrap()
1144 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1145 .unwrap()
1146 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1147 .unwrap()
1148 .external_data_update(link_2, 0.1, None, true)
1150 .unwrap()
1151 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int)
1152 .unwrap()
1153 .measurement(&config, link_2.reverse(), (0.0, 0.001).into(), clock_int)
1154 .unwrap()
1155 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int)
1156 .unwrap()
1157 .measurement(&config, link_2.reverse(), (0.0, 0.001).into(), clock_int)
1158 .unwrap()
1159 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int)
1160 .unwrap()
1161 .measurement(&config, link_2.reverse(), (0.0, 0.001).into(), clock_int)
1162 .unwrap()
1163 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int)
1164 .unwrap()
1165 .measurement(&config, link_2.reverse(), (0.0, 0.001).into(), clock_int)
1166 .unwrap()
1167 .external_data_update(link_3, 0.1, None, true)
1169 .unwrap()
1170 .measurement(&config, link_3.forward(), (0.0, 0.001).into(), clock_int)
1171 .unwrap()
1172 .measurement(&config, link_3.reverse(), (0.0, 0.001).into(), clock_int)
1173 .unwrap()
1174 .measurement(&config, link_3.forward(), (0.0, 0.001).into(), clock_int)
1175 .unwrap()
1176 .measurement(&config, link_3.reverse(), (0.0, 0.001).into(), clock_int)
1177 .unwrap()
1178 .measurement(&config, link_3.forward(), (0.0, 0.001).into(), clock_int)
1179 .unwrap()
1180 .measurement(&config, link_3.reverse(), (0.0, 0.001).into(), clock_int)
1181 .unwrap()
1182 .measurement(&config, link_3.forward(), (0.0, 0.001).into(), clock_int)
1183 .unwrap()
1184 .measurement(&config, link_3.reverse(), (0.0, 0.001).into(), clock_int)
1185 .unwrap();
1186
1187 assert!(filter.link_active(link_1).unwrap());
1188 assert!(filter.link_active(link_2).unwrap());
1189 assert!(filter.link_active(link_3).unwrap());
1190
1191 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1192 let (filter, clock_int) = filter
1193 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1194 .unwrap();
1195 let (filter, link_1) = filter
1196 .add_tracked_link(
1197 clock_int,
1198 None,
1199 LinkConfig::default(),
1200 TrackedLinkConfig::default(),
1201 )
1202 .unwrap();
1203 let (filter, link_2) = filter
1204 .add_tracked_link(
1205 clock_int,
1206 None,
1207 LinkConfig::default(),
1208 TrackedLinkConfig::default(),
1209 )
1210 .unwrap();
1211 let (filter, link_3) = filter
1212 .add_tracked_link(
1213 clock_int,
1214 None,
1215 LinkConfig::default(),
1216 TrackedLinkConfig::default(),
1217 )
1218 .unwrap();
1219
1220 let filter = filter
1221 .external_data_update(link_1, 0.1, None, true)
1223 .unwrap()
1224 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1225 .unwrap()
1226 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1227 .unwrap()
1228 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1229 .unwrap()
1230 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1231 .unwrap()
1232 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1233 .unwrap()
1234 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1235 .unwrap()
1236 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1237 .unwrap()
1238 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1239 .unwrap()
1240 .external_data_update(link_2, 0.1, None, true)
1242 .unwrap()
1243 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int)
1244 .unwrap()
1245 .measurement(&config, link_2.reverse(), (0.0, 0.001).into(), clock_int)
1246 .unwrap()
1247 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int)
1248 .unwrap()
1249 .measurement(&config, link_2.reverse(), (0.0, 0.001).into(), clock_int)
1250 .unwrap()
1251 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int)
1252 .unwrap()
1253 .measurement(&config, link_2.reverse(), (0.0, 0.001).into(), clock_int)
1254 .unwrap()
1255 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int)
1256 .unwrap()
1257 .measurement(&config, link_2.reverse(), (0.0, 0.001).into(), clock_int)
1258 .unwrap()
1259 .external_data_update(link_3, 0.1, None, true)
1261 .unwrap()
1262 .measurement(&config, link_3.forward(), (1.0, 0.001).into(), clock_int)
1263 .unwrap()
1264 .measurement(&config, link_3.reverse(), (-1.0, 0.001).into(), clock_int)
1265 .unwrap()
1266 .measurement(&config, link_3.forward(), (1.0, 0.001).into(), clock_int)
1267 .unwrap()
1268 .measurement(&config, link_3.reverse(), (-1.0, 0.001).into(), clock_int)
1269 .unwrap()
1270 .measurement(&config, link_3.forward(), (1.0, 0.001).into(), clock_int)
1271 .unwrap()
1272 .measurement(&config, link_3.reverse(), (-1.0, 0.001).into(), clock_int)
1273 .unwrap()
1274 .measurement(&config, link_3.forward(), (1.0, 0.001).into(), clock_int)
1275 .unwrap()
1276 .measurement(&config, link_3.reverse(), (-1.0, 0.001).into(), clock_int)
1277 .unwrap();
1278
1279 assert!(filter.link_active(link_1).unwrap());
1280 assert!(filter.link_active(link_2).unwrap());
1281 assert!(!filter.link_active(link_3).unwrap());
1282
1283 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1284 let (filter, clock_int) = filter
1285 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1286 .unwrap();
1287 let (filter, link_1) = filter
1288 .add_tracked_link(
1289 clock_int,
1290 None,
1291 LinkConfig::default(),
1292 TrackedLinkConfig::default(),
1293 )
1294 .unwrap();
1295 let (filter, link_2) = filter
1296 .add_tracked_link(
1297 clock_int,
1298 None,
1299 LinkConfig::default(),
1300 TrackedLinkConfig::default(),
1301 )
1302 .unwrap();
1303 let (filter, link_3) = filter
1304 .add_tracked_link(
1305 clock_int,
1306 None,
1307 LinkConfig::default(),
1308 TrackedLinkConfig::default(),
1309 )
1310 .unwrap();
1311
1312 let filter = filter
1313 .external_data_update(link_1, 0.1, None, true)
1315 .unwrap()
1316 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1317 .unwrap()
1318 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1319 .unwrap()
1320 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1321 .unwrap()
1322 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1323 .unwrap()
1324 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1325 .unwrap()
1326 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1327 .unwrap()
1328 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1329 .unwrap()
1330 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1331 .unwrap()
1332 .external_data_update(link_2, 0.1, None, true)
1334 .unwrap()
1335 .measurement(&config, link_2.forward(), (1.0, 0.001).into(), clock_int)
1336 .unwrap()
1337 .measurement(&config, link_2.reverse(), (-1.0, 0.001).into(), clock_int)
1338 .unwrap()
1339 .measurement(&config, link_2.forward(), (1.0, 0.001).into(), clock_int)
1340 .unwrap()
1341 .measurement(&config, link_2.reverse(), (-1.0, 0.001).into(), clock_int)
1342 .unwrap()
1343 .measurement(&config, link_2.forward(), (1.0, 0.001).into(), clock_int)
1344 .unwrap()
1345 .measurement(&config, link_2.reverse(), (-1.0, 0.001).into(), clock_int)
1346 .unwrap()
1347 .measurement(&config, link_2.forward(), (1.0, 0.001).into(), clock_int)
1348 .unwrap()
1349 .measurement(&config, link_2.reverse(), (-1.0, 0.001).into(), clock_int)
1350 .unwrap()
1351 .external_data_update(link_3, 0.1, None, true)
1353 .unwrap()
1354 .measurement(&config, link_3.forward(), (1.0, 0.001).into(), clock_int)
1355 .unwrap()
1356 .measurement(&config, link_3.reverse(), (-1.0, 0.001).into(), clock_int)
1357 .unwrap()
1358 .measurement(&config, link_3.forward(), (1.0, 0.001).into(), clock_int)
1359 .unwrap()
1360 .measurement(&config, link_3.reverse(), (-1.0, 0.001).into(), clock_int)
1361 .unwrap()
1362 .measurement(&config, link_3.forward(), (1.0, 0.001).into(), clock_int)
1363 .unwrap()
1364 .measurement(&config, link_3.reverse(), (-1.0, 0.001).into(), clock_int)
1365 .unwrap()
1366 .measurement(&config, link_3.forward(), (1.0, 0.001).into(), clock_int)
1367 .unwrap()
1368 .measurement(&config, link_3.reverse(), (-1.0, 0.001).into(), clock_int)
1369 .unwrap();
1370
1371 assert!(filter.link_active(link_1).unwrap());
1373 assert!(!filter.link_active(link_2).unwrap());
1376 assert!(filter.link_active(link_3).unwrap());
1378
1379 let filter = filter
1380 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1381 .unwrap()
1382 .measurement(&config, link_2.forward(), (1.0, 0.001).into(), clock_int)
1383 .unwrap();
1384 assert!(!filter.link_active(link_1).unwrap());
1386 assert!(filter.link_active(link_2).unwrap());
1387 }
1388
1389 #[test]
1390 fn external_links_inactive_when_unusable() {
1391 let config = ControllerConfig {
1392 select_offset_uncertainty_window: 2.0,
1393 select_link_uncertainty_window: 2.0,
1394 select_delay_uncertainty_window: 0.7,
1395 select_max_window_size: Duration::from_seconds_nanos(1, 0),
1396 minimum_agreeing_sources: 1,
1397 };
1398
1399 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1400 let (filter, clock_int) = filter
1401 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1402 .unwrap();
1403 let (filter, link_1) = filter
1404 .add_untracked_link(clock_int, None, LinkConfig::default())
1405 .unwrap();
1406 let filter = filter
1407 .external_data_update(link_1, 0.1, None, true)
1408 .unwrap()
1409 .measurement(&config, link_1.forward(), (0.0, 1e-4).into(), clock_int)
1410 .unwrap();
1411
1412 assert!(filter.link_active(link_1).unwrap());
1413
1414 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1415 let (filter, clock_int) = filter
1416 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1417 .unwrap();
1418 let (filter, link_1) = filter
1419 .add_untracked_link(clock_int, None, LinkConfig::default())
1420 .unwrap();
1421 let filter = filter
1422 .external_data_update(link_1, 0.1, None, false)
1423 .unwrap()
1424 .measurement(&config, link_1.forward(), (0.0, 1e-4).into(), clock_int)
1425 .unwrap();
1426
1427 assert!(!filter.link_active(link_1).unwrap());
1428 }
1429
1430 #[test]
1431 fn internal_link_inactive_on_unusable_measurements() {
1432 let config = ControllerConfig {
1433 select_offset_uncertainty_window: 2.0,
1434 select_link_uncertainty_window: 2.0,
1435 select_delay_uncertainty_window: 0.7,
1436 select_max_window_size: Duration::from_seconds_nanos(1, 0),
1437 minimum_agreeing_sources: 3,
1438 };
1439
1440 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1441 let (filter, clock_a) = filter
1442 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1443 .unwrap();
1444 let (filter, clock_b) = filter
1445 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1446 .unwrap();
1447 let (filter, link_id) = filter
1448 .add_tracked_link(
1449 clock_a,
1450 Some(clock_b),
1451 LinkConfig::default(),
1452 TrackedLinkConfig::default(),
1453 )
1454 .unwrap();
1455
1456 assert!(!filter.link_active(link_id).unwrap());
1457
1458 let filter = filter
1459 .measurement(&config, link_id.forward(), (0.0, 0.0).into(), clock_a)
1460 .unwrap()
1461 .progress_time(Timestamp::UNIX_EPOCH + Duration::from_seconds_nanos(0, 600_000_000))
1462 .unwrap()
1463 .measurement(&config, link_id.reverse(), (0.0, 0.0).into(), clock_a)
1464 .unwrap()
1465 .progress_time(Timestamp::UNIX_EPOCH + Duration::from_seconds_nanos(10, 0))
1466 .unwrap()
1467 .measurement(&config, link_id.forward(), (0.0, 0.0).into(), clock_a)
1468 .unwrap()
1469 .progress_time(Timestamp::UNIX_EPOCH + Duration::from_seconds_nanos(10, 600_000_000))
1470 .unwrap()
1471 .measurement(&config, link_id.reverse(), (0.0, 0.0).into(), clock_a)
1472 .unwrap()
1473 .progress_time(Timestamp::UNIX_EPOCH + Duration::from_seconds_nanos(20, 0))
1474 .unwrap()
1475 .measurement(&config, link_id.forward(), (0.0, 0.0).into(), clock_a)
1476 .unwrap()
1477 .progress_time(Timestamp::UNIX_EPOCH + Duration::from_seconds_nanos(20, 600_000_000))
1478 .unwrap()
1479 .measurement(&config, link_id.reverse(), (0.0, 0.0).into(), clock_a)
1480 .unwrap()
1481 .progress_time(Timestamp::UNIX_EPOCH + Duration::from_seconds_nanos(30, 0))
1482 .unwrap()
1483 .measurement(&config, link_id.forward(), (0.0, 0.0).into(), clock_a)
1484 .unwrap()
1485 .progress_time(Timestamp::UNIX_EPOCH + Duration::from_seconds_nanos(30, 600_000_000))
1486 .unwrap()
1487 .measurement(&config, link_id.reverse(), (0.0, 0.0).into(), clock_a)
1488 .unwrap();
1489
1490 assert!(!filter.link_active(link_id).unwrap());
1491 }
1492
1493 #[test]
1494 fn external_links_activity_and_steering_works() {
1495 let config = ControllerConfig {
1496 select_offset_uncertainty_window: 2.0,
1497 select_link_uncertainty_window: 2.0,
1498 select_delay_uncertainty_window: 0.7,
1499 select_max_window_size: Duration::from_seconds_nanos(1, 0),
1500 minimum_agreeing_sources: 1,
1501 };
1502
1503 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1504 let (filter, clock_int_1) = filter
1505 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1506 .unwrap();
1507 let (filter, clock_int_2) = filter
1508 .add_clock((10.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1509 .unwrap();
1510 let (filter, link_1) = filter
1511 .add_untracked_link(clock_int_1, None, LinkConfig::default())
1512 .unwrap();
1513 let (filter, link_2) = filter
1514 .add_untracked_link(clock_int_1, None, LinkConfig::default())
1515 .unwrap();
1516 let (filter, link_3) = filter
1517 .add_untracked_link(clock_int_2, None, LinkConfig::default())
1518 .unwrap();
1519
1520 let filter = filter
1521 .external_data_update(link_1, 0.1, None, true)
1522 .unwrap()
1523 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int_1)
1524 .unwrap()
1525 .external_data_update(link_2, 0.1, None, true)
1526 .unwrap()
1527 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int_1)
1528 .unwrap()
1529 .external_data_update(link_3, 0.1, None, true)
1530 .unwrap()
1531 .measurement(
1532 &config,
1533 link_3.forward(),
1534 (-10.0, 0.001).into(),
1535 clock_int_1,
1536 )
1537 .unwrap();
1538
1539 assert!(filter.link_active(link_1).unwrap());
1540 assert!(filter.link_active(link_2).unwrap());
1541 assert!(filter.link_active(link_3).unwrap());
1542
1543 let filter = filter
1544 .absorb_offset_change(clock_int_2, -10.0)
1545 .unwrap()
1546 .measurement(&config, link_3.forward(), (0.0, 0.001).into(), clock_int_1)
1547 .unwrap();
1548
1549 assert!(filter.link_active(link_3).unwrap());
1550
1551 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(
1552 Timestamp::UNIX_EPOCH + Duration::from_seconds_nanos(10, 0),
1553 );
1554 let (filter, clock_int_1) = filter
1555 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1556 .unwrap();
1557 let (filter, clock_int_2) = filter
1558 .add_clock((-10.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1559 .unwrap();
1560 let (filter, link_1) = filter
1561 .add_untracked_link(clock_int_1, None, LinkConfig::default())
1562 .unwrap();
1563 let (filter, link_2) = filter
1564 .add_untracked_link(clock_int_1, None, LinkConfig::default())
1565 .unwrap();
1566 let (filter, link_3) = filter
1567 .add_untracked_link(clock_int_2, None, LinkConfig::default())
1568 .unwrap();
1569
1570 let filter = filter
1571 .external_data_update(link_1, 0.1, None, true)
1572 .unwrap()
1573 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int_1)
1574 .unwrap()
1575 .external_data_update(link_2, 0.1, None, true)
1576 .unwrap()
1577 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int_1)
1578 .unwrap()
1579 .external_data_update(link_3, 0.1, None, true)
1580 .unwrap()
1581 .measurement(&config, link_3.forward(), (10.0, 0.001).into(), clock_int_1)
1582 .unwrap();
1583
1584 assert!(filter.link_active(link_1).unwrap());
1585 assert!(filter.link_active(link_2).unwrap());
1586 assert!(filter.link_active(link_3).unwrap());
1587
1588 let filter = filter
1589 .absorb_system_clock_offset_change(clock_int_2, Duration::from_seconds_nanos(10, 0))
1590 .unwrap()
1591 .measurement(&config, link_3.forward(), (0.0, 0.001).into(), clock_int_1)
1592 .unwrap();
1593
1594 assert!(filter.link_active(link_3).unwrap());
1595 }
1596
1597 #[test]
1598 fn too_uncertain_external_links_inactive() {
1599 let config = ControllerConfig {
1600 select_offset_uncertainty_window: 2.0,
1601 select_link_uncertainty_window: 2.0,
1602 select_delay_uncertainty_window: 0.7,
1603 select_max_window_size: Duration::from_seconds_nanos(1, 0),
1604 minimum_agreeing_sources: 1,
1605 };
1606
1607 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1608 let (filter, clock_int) = filter
1609 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1610 .unwrap();
1611 let (filter, link_1) = filter
1612 .add_untracked_link(clock_int, None, LinkConfig::default())
1613 .unwrap();
1614 let filter = filter
1615 .external_data_update(link_1, 10.0, None, true)
1616 .unwrap()
1617 .measurement(&config, link_1.forward(), (0.0, 0.0).into(), clock_int)
1618 .unwrap();
1619
1620 assert!(!filter.link_active(link_1).unwrap());
1621
1622 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1623 let (filter, clock_int) = filter
1624 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1625 .unwrap();
1626 let (filter, link_1) = filter
1627 .add_untracked_link(clock_int, None, LinkConfig::default())
1628 .unwrap();
1629 let filter = filter
1630 .external_data_update(link_1, 0.0, None, true)
1631 .unwrap()
1632 .measurement(&config, link_1.forward(), (0.0, 1.0).into(), clock_int)
1633 .unwrap();
1634
1635 assert!(!filter.link_active(link_1).unwrap());
1636
1637 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1638 let (filter, clock_int) = filter
1639 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1640 .unwrap();
1641 let (filter, link_1) = filter
1642 .add_tracked_link(
1643 clock_int,
1644 None,
1645 LinkConfig::default(),
1646 TrackedLinkConfig {
1647 decay_rate: 0.0,
1648 ..TrackedLinkConfig::default()
1649 },
1650 )
1651 .unwrap();
1652 let filter = filter
1653 .external_data_update(link_1, 10.0, None, true)
1654 .unwrap()
1655 .measurement(&config, link_1.forward(), (0.0, 0.0).into(), clock_int)
1656 .unwrap()
1657 .measurement(&config, link_1.reverse(), (0.0, 0.0).into(), clock_int)
1658 .unwrap()
1659 .measurement(&config, link_1.forward(), (1.0, 0.0).into(), clock_int)
1660 .unwrap()
1661 .measurement(&config, link_1.reverse(), (1.0, 0.0).into(), clock_int)
1662 .unwrap()
1663 .measurement(&config, link_1.forward(), (1.0, 0.0).into(), clock_int)
1664 .unwrap()
1665 .measurement(&config, link_1.reverse(), (1.0, 0.0).into(), clock_int)
1666 .unwrap()
1667 .measurement(&config, link_1.forward(), (-1.0, 0.0).into(), clock_int)
1668 .unwrap()
1669 .measurement(&config, link_1.reverse(), (-1.0, 0.0).into(), clock_int)
1670 .unwrap()
1671 .measurement(&config, link_1.forward(), (-1.0, 0.0).into(), clock_int)
1672 .unwrap()
1673 .measurement(&config, link_1.reverse(), (-1.0, 0.0).into(), clock_int)
1674 .unwrap();
1675
1676 assert!(!filter.link_active(link_1).unwrap());
1677 }
1678
1679 #[test]
1680 fn periodic_internal_link() {
1681 let config = ControllerConfig {
1682 select_offset_uncertainty_window: 2.0,
1683 select_link_uncertainty_window: 2.0,
1684 select_delay_uncertainty_window: 0.7,
1685 select_max_window_size: Duration::from_seconds_nanos(1, 0),
1686 minimum_agreeing_sources: 1,
1687 };
1688
1689 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1690 let (filter, clock_1) = filter
1691 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1692 .unwrap();
1693 let (filter, clock_2) = filter
1694 .add_clock((0.0, 1e18).into(), (0.0, 0.0).into(), 1e-8)
1695 .unwrap();
1696 let (filter, link_1) = filter
1697 .add_untracked_link(clock_1, Some(clock_2), LinkConfig::default())
1698 .unwrap();
1699 let (filter, link_2) = filter
1700 .add_untracked_link(
1701 clock_1,
1702 Some(clock_2),
1703 LinkConfig {
1704 period: Some(Duration::from_seconds_nanos(1, 0)),
1705 ..LinkConfig::default()
1706 },
1707 )
1708 .unwrap();
1709
1710 let filter = filter
1711 .measurement(&config, link_2.forward(), (3.2, 0.0001).into(), clock_1)
1712 .unwrap();
1713 assert_eq!(filter.clock_offset(clock_2).unwrap(), (0.0, 1e18).into());
1714 assert!(!filter.link_active(link_2).unwrap());
1715 let filter = filter
1716 .measurement(&config, link_1.forward(), (5.0, 0.15).into(), clock_1)
1717 .unwrap();
1718 assert!(filter.link_active(link_1).unwrap());
1719 assert_uv_almost_eq!(
1720 filter.clock_offset(clock_2).unwrap(),
1721 UncertainValue::from((5.0, 0.15))
1722 );
1723 let filter = filter
1724 .measurement(&config, link_2.forward(), (3.2, 0.0001).into(), clock_1)
1725 .unwrap();
1726 assert!(filter.link_active(link_2).unwrap());
1727 assert_uv_almost_eq!(
1728 filter.clock_offset(clock_2).unwrap(),
1729 UncertainValue::from((5.2, 0.0001))
1730 );
1731 }
1732
1733 #[test]
1734 fn periodic_external_link() {
1735 let config = ControllerConfig {
1736 select_offset_uncertainty_window: 2.0,
1737 select_link_uncertainty_window: 2.0,
1738 select_delay_uncertainty_window: 0.7,
1739 select_max_window_size: Duration::from_seconds_nanos(1, 0),
1740 minimum_agreeing_sources: 1,
1741 };
1742
1743 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1744 let (filter, clock_1) = filter
1745 .add_clock((0.0, 1e18).into(), (0.0, 0.0).into(), 1e-8)
1746 .unwrap();
1747 let (filter, link_1) = filter
1748 .add_untracked_link(clock_1, None, LinkConfig::default())
1749 .unwrap();
1750 let (filter, link_2) = filter
1751 .add_untracked_link(
1752 clock_1,
1753 None,
1754 LinkConfig {
1755 period: Some(Duration::from_seconds_nanos(1, 0)),
1756 ..LinkConfig::default()
1757 },
1758 )
1759 .unwrap();
1760
1761 let filter = filter
1762 .external_data_update(link_1, 0.0, None, true)
1763 .unwrap()
1764 .external_data_update(link_2, 0.0, None, true)
1765 .unwrap()
1766 .measurement(&config, link_2.reverse(), (3.2, 0.0001).into(), clock_1)
1767 .unwrap();
1768 assert_eq!(filter.clock_offset(clock_1).unwrap(), (0.0, 1e18).into());
1769 assert!(!filter.link_active(link_2).unwrap());
1770 let filter = filter
1771 .measurement(&config, link_1.reverse(), (5.0, 0.15).into(), clock_1)
1772 .unwrap();
1773 assert!(filter.link_active(link_1).unwrap());
1774 assert_uv_almost_eq!(
1775 filter.clock_offset(clock_1).unwrap(),
1776 UncertainValue::from((5.0, 0.15))
1777 );
1778 let filter = filter
1779 .measurement(&config, link_2.reverse(), (3.2, 0.0001).into(), clock_1)
1780 .unwrap();
1781 assert!(filter.link_active(link_2).unwrap());
1782 assert_uv_almost_eq!(
1783 filter.clock_offset(clock_1).unwrap(),
1784 UncertainValue::from((5.2, 0.0001))
1785 );
1786 }
1787
1788 #[test]
1789 fn periodic_link_too_uncertain() {
1790 let config = ControllerConfig {
1791 select_offset_uncertainty_window: 2.0,
1792 select_link_uncertainty_window: 2.0,
1793 select_delay_uncertainty_window: 0.7,
1794 select_max_window_size: Duration::from_seconds_nanos(1, 0),
1795 minimum_agreeing_sources: 1,
1796 };
1797
1798 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1799 let (filter, clock_1) = filter
1800 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1801 .unwrap();
1802 let (filter, clock_2) = filter
1803 .add_clock((0.0, 1e18).into(), (0.0, 0.0).into(), 1e-8)
1804 .unwrap();
1805 let (filter, link_1) = filter
1806 .add_untracked_link(clock_1, Some(clock_2), LinkConfig::default())
1807 .unwrap();
1808 let (filter, link_2) = filter
1809 .add_untracked_link(
1810 clock_1,
1811 Some(clock_2),
1812 LinkConfig {
1813 period: Some(Duration::from_seconds_nanos(1, 0)),
1814 ..LinkConfig::default()
1815 },
1816 )
1817 .unwrap();
1818
1819 let filter = filter
1820 .measurement(&config, link_2.forward(), (3.2, 0.0001).into(), clock_1)
1821 .unwrap();
1822 assert_eq!(filter.clock_offset(clock_2).unwrap(), (0.0, 1e18).into());
1823 assert!(!filter.link_active(link_2).unwrap());
1824 let filter = filter
1825 .measurement(&config, link_1.forward(), (5.0, 0.3).into(), clock_1)
1826 .unwrap();
1827 assert!(filter.link_active(link_1).unwrap());
1828 assert_uv_almost_eq!(
1829 filter.clock_offset(clock_2).unwrap(),
1830 UncertainValue::from((5.0, 0.3))
1831 );
1832 let filter = filter
1833 .measurement(&config, link_2.forward(), (3.2, 0.0001).into(), clock_1)
1834 .unwrap();
1835 assert!(!filter.link_active(link_2).unwrap());
1836 assert_uv_almost_eq!(
1837 filter.clock_offset(clock_2).unwrap(),
1838 UncertainValue::from((5.0, 0.3))
1839 );
1840 }
1841
1842 #[test]
1843 fn leap_vote() {
1844 let config = ControllerConfig {
1845 select_offset_uncertainty_window: 2.0,
1846 select_link_uncertainty_window: 2.0,
1847 select_delay_uncertainty_window: 0.7,
1848 select_max_window_size: Duration::from_seconds_nanos(1, 0),
1849 minimum_agreeing_sources: 1,
1850 };
1851
1852 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
1853 let (filter, clock_int) = filter
1854 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
1855 .unwrap();
1856 let (filter, link_1) = filter
1857 .add_tracked_link(
1858 clock_int,
1859 None,
1860 LinkConfig::default(),
1861 TrackedLinkConfig::default(),
1862 )
1863 .unwrap();
1864 let (filter, link_2) = filter
1865 .add_tracked_link(
1866 clock_int,
1867 None,
1868 LinkConfig::default(),
1869 TrackedLinkConfig::default(),
1870 )
1871 .unwrap();
1872 let (filter, link_3) = filter
1873 .add_tracked_link(
1874 clock_int,
1875 None,
1876 LinkConfig::default(),
1877 TrackedLinkConfig::default(),
1878 )
1879 .unwrap();
1880
1881 let base_filter = filter
1882 .external_data_update(link_1, 0.1, None, true)
1884 .unwrap()
1885 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1886 .unwrap()
1887 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1888 .unwrap()
1889 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1890 .unwrap()
1891 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1892 .unwrap()
1893 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1894 .unwrap()
1895 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1896 .unwrap()
1897 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int)
1898 .unwrap()
1899 .measurement(&config, link_1.reverse(), (0.0, 0.001).into(), clock_int)
1900 .unwrap()
1901 .external_data_update(link_2, 0.1, None, true)
1903 .unwrap()
1904 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int)
1905 .unwrap()
1906 .measurement(&config, link_2.reverse(), (0.0, 0.001).into(), clock_int)
1907 .unwrap()
1908 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int)
1909 .unwrap()
1910 .measurement(&config, link_2.reverse(), (0.0, 0.001).into(), clock_int)
1911 .unwrap()
1912 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int)
1913 .unwrap()
1914 .measurement(&config, link_2.reverse(), (0.0, 0.001).into(), clock_int)
1915 .unwrap()
1916 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int)
1917 .unwrap()
1918 .measurement(&config, link_2.reverse(), (0.0, 0.001).into(), clock_int)
1919 .unwrap()
1920 .external_data_update(link_3, 0.1, None, true)
1922 .unwrap()
1923 .measurement(&config, link_3.forward(), (0.0, 0.001).into(), clock_int)
1924 .unwrap()
1925 .measurement(&config, link_3.reverse(), (0.0, 0.001).into(), clock_int)
1926 .unwrap()
1927 .measurement(&config, link_3.forward(), (0.0, 0.001).into(), clock_int)
1928 .unwrap()
1929 .measurement(&config, link_3.reverse(), (0.0, 0.001).into(), clock_int)
1930 .unwrap()
1931 .measurement(&config, link_3.forward(), (0.0, 0.001).into(), clock_int)
1932 .unwrap()
1933 .measurement(&config, link_3.reverse(), (0.0, 0.001).into(), clock_int)
1934 .unwrap()
1935 .measurement(&config, link_3.forward(), (0.0, 0.001).into(), clock_int)
1936 .unwrap()
1937 .measurement(&config, link_3.reverse(), (0.0, 0.001).into(), clock_int)
1938 .unwrap();
1939
1940 let filter = base_filter
1941 .clone()
1942 .external_data_update(link_1, 0.1, Some(LeapStatus::None), true)
1943 .unwrap()
1944 .external_data_update(link_2, 0.1, Some(LeapStatus::None), true)
1945 .unwrap()
1946 .external_data_update(link_3, 0.1, Some(LeapStatus::None), true)
1947 .unwrap();
1948 assert_eq!(filter.leap_vote(&config), Some(LeapStatus::None));
1949
1950 let filter = base_filter
1951 .clone()
1952 .external_data_update(link_1, 0.1, Some(LeapStatus::Leap59), true)
1953 .unwrap()
1954 .external_data_update(link_2, 0.1, Some(LeapStatus::Leap59), true)
1955 .unwrap()
1956 .external_data_update(link_3, 0.1, Some(LeapStatus::Leap59), true)
1957 .unwrap();
1958 assert_eq!(filter.leap_vote(&config), Some(LeapStatus::Leap59));
1959
1960 let filter = base_filter
1961 .clone()
1962 .external_data_update(link_1, 0.1, Some(LeapStatus::Leap61), true)
1963 .unwrap()
1964 .external_data_update(link_2, 0.1, Some(LeapStatus::Leap61), true)
1965 .unwrap()
1966 .external_data_update(link_3, 0.1, Some(LeapStatus::Leap61), true)
1967 .unwrap();
1968 assert_eq!(filter.leap_vote(&config), Some(LeapStatus::Leap61));
1969
1970 let filter = base_filter
1971 .clone()
1972 .external_data_update(link_1, 0.1, Some(LeapStatus::None), true)
1973 .unwrap()
1974 .external_data_update(link_2, 0.1, Some(LeapStatus::Leap59), true)
1975 .unwrap()
1976 .external_data_update(link_3, 0.1, Some(LeapStatus::Leap59), true)
1977 .unwrap();
1978 assert_eq!(filter.leap_vote(&config), Some(LeapStatus::Leap59));
1979
1980 let filter = base_filter
1981 .clone()
1982 .external_data_update(link_1, 0.1, Some(LeapStatus::None), true)
1983 .unwrap()
1984 .external_data_update(link_2, 0.1, None, true)
1985 .unwrap()
1986 .external_data_update(link_3, 0.1, None, true)
1987 .unwrap();
1988 assert_eq!(filter.leap_vote(&config), Some(LeapStatus::None));
1989
1990 let filter = base_filter
1991 .clone()
1992 .external_data_update(link_1, 0.1, Some(LeapStatus::None), true)
1993 .unwrap()
1994 .external_data_update(link_2, 0.1, Some(LeapStatus::Leap59), true)
1995 .unwrap()
1996 .external_data_update(link_3, 0.1, Some(LeapStatus::Leap61), true)
1997 .unwrap();
1998 assert_eq!(filter.leap_vote(&config), None);
1999 }
2000
2001 #[test]
2002 fn local_root_delay() {
2003 let config = ControllerConfig {
2004 select_offset_uncertainty_window: 2.0,
2005 select_link_uncertainty_window: 2.0,
2006 select_delay_uncertainty_window: 0.7,
2007 select_max_window_size: Duration::from_seconds_nanos(1, 0),
2008 minimum_agreeing_sources: 1,
2009 };
2010
2011 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
2012 let (filter, clock_int_1) = filter
2013 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
2014 .unwrap();
2015 let (filter, clock_int_2) = filter
2016 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
2017 .unwrap();
2018 let (filter, link_1) = filter
2019 .add_untracked_link(clock_int_1, None, LinkConfig::default())
2020 .unwrap();
2021 let (filter, link_2) = filter
2022 .add_untracked_link(clock_int_1, None, LinkConfig::default())
2023 .unwrap();
2024 let (filter, link_3) = filter
2025 .add_untracked_link(clock_int_2, None, LinkConfig::default())
2026 .unwrap();
2027
2028 let filter = filter
2029 .external_data_update(link_1, 0.3, None, true)
2030 .unwrap()
2031 .measurement(&config, link_1.forward(), (0.0, 0.001).into(), clock_int_1)
2032 .unwrap()
2033 .external_data_update(link_2, 0.2, None, true)
2034 .unwrap()
2035 .measurement(&config, link_2.forward(), (0.0, 0.001).into(), clock_int_1)
2036 .unwrap()
2037 .external_data_update(link_3, 0.1, None, true)
2038 .unwrap()
2039 .measurement(&config, link_3.forward(), (10.0, 0.001).into(), clock_int_1)
2040 .unwrap();
2041
2042 assert_eq!(filter.local_root_delay(&config), Some(0.2));
2043
2044 let filter = filter.remove_link(link_2).unwrap();
2045
2046 assert_eq!(filter.local_root_delay(&config), None);
2047 }
2048
2049 #[test]
2050 fn test_new_link_validation() {
2051 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
2052 let (filter, clock_1) = filter
2053 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 1e-8)
2054 .unwrap();
2055 let clock_2 = ClockId::new();
2056 let (filter, link_1) = filter
2057 .add_untracked_link(clock_1, None, LinkConfig::default())
2058 .unwrap();
2059 let clock_3 = link_1.second_clock();
2060
2061 assert_eq!(
2062 filter
2063 .clone()
2064 .add_untracked_link(clock_1, Some(clock_2), LinkConfig::default(),)
2065 .unwrap_err(),
2066 AlgoError::UnknownClock(clock_2)
2067 );
2068 assert_eq!(
2069 filter
2070 .clone()
2071 .add_untracked_link(clock_2, Some(clock_1), LinkConfig::default(),)
2072 .unwrap_err(),
2073 AlgoError::UnknownClock(clock_2)
2074 );
2075 assert_eq!(
2076 filter
2077 .clone()
2078 .add_untracked_link(clock_2, None, LinkConfig::default(),)
2079 .unwrap_err(),
2080 AlgoError::UnknownClock(clock_2)
2081 );
2082 assert_eq!(
2083 filter
2084 .clone()
2085 .add_untracked_link(clock_1, Some(clock_3), LinkConfig::default())
2086 .unwrap_err(),
2087 AlgoError::UnexpectedExternalClock(clock_3)
2088 );
2089 assert_eq!(
2090 filter
2091 .clone()
2092 .add_untracked_link(clock_3, Some(clock_1), LinkConfig::default())
2093 .unwrap_err(),
2094 AlgoError::UnexpectedExternalClock(clock_3)
2095 );
2096 assert_eq!(
2097 filter
2098 .clone()
2099 .add_untracked_link(clock_3, None, LinkConfig::default())
2100 .unwrap_err(),
2101 AlgoError::UnexpectedExternalClock(clock_3)
2102 );
2103
2104 assert_eq!(
2105 filter
2106 .clone()
2107 .add_tracked_link(
2108 clock_1,
2109 Some(clock_2),
2110 LinkConfig::default(),
2111 TrackedLinkConfig::default()
2112 )
2113 .unwrap_err(),
2114 AlgoError::UnknownClock(clock_2)
2115 );
2116 assert_eq!(
2117 filter
2118 .clone()
2119 .add_tracked_link(
2120 clock_2,
2121 Some(clock_1),
2122 LinkConfig::default(),
2123 TrackedLinkConfig::default()
2124 )
2125 .unwrap_err(),
2126 AlgoError::UnknownClock(clock_2)
2127 );
2128 assert_eq!(
2129 filter
2130 .clone()
2131 .add_tracked_link(
2132 clock_2,
2133 None,
2134 LinkConfig::default(),
2135 TrackedLinkConfig::default()
2136 )
2137 .unwrap_err(),
2138 AlgoError::UnknownClock(clock_2)
2139 );
2140 assert_eq!(
2141 filter
2142 .clone()
2143 .add_tracked_link(
2144 clock_1,
2145 Some(clock_3),
2146 LinkConfig::default(),
2147 TrackedLinkConfig::default()
2148 )
2149 .unwrap_err(),
2150 AlgoError::UnexpectedExternalClock(clock_3)
2151 );
2152 assert_eq!(
2153 filter
2154 .clone()
2155 .add_tracked_link(
2156 clock_3,
2157 Some(clock_1),
2158 LinkConfig::default(),
2159 TrackedLinkConfig::default()
2160 )
2161 .unwrap_err(),
2162 AlgoError::UnexpectedExternalClock(clock_3)
2163 );
2164 assert_eq!(
2165 filter
2166 .clone()
2167 .add_tracked_link(
2168 clock_3,
2169 None,
2170 LinkConfig::default(),
2171 TrackedLinkConfig::default()
2172 )
2173 .unwrap_err(),
2174 AlgoError::UnexpectedExternalClock(clock_3)
2175 );
2176 }
2177
2178 #[test]
2179 fn test_remove_clock_with_link_is_forbidden() {
2180 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
2181 let (filter, clock_1) = filter
2182 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 0.0)
2183 .unwrap();
2184 let (filter, link_1) = filter
2185 .add_untracked_link(clock_1, None, LinkConfig::default())
2186 .unwrap();
2187
2188 assert_eq!(
2189 filter.clone().remove_clock(clock_1).unwrap_err(),
2190 AlgoError::ClockInUse(clock_1, link_1)
2191 );
2192
2193 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
2194 let (filter, clock_1) = filter
2195 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 0.0)
2196 .unwrap();
2197 let (filter, link_1) = filter
2198 .add_tracked_link(
2199 clock_1,
2200 None,
2201 LinkConfig::default(),
2202 TrackedLinkConfig::default(),
2203 )
2204 .unwrap();
2205
2206 assert_eq!(
2207 filter.clone().remove_clock(clock_1).unwrap_err(),
2208 AlgoError::ClockInUse(clock_1, link_1)
2209 );
2210 }
2211
2212 #[test]
2213 fn test_remove_external_link_removes_external_clock() {
2214 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
2215 let (filter, clock_1) = filter
2216 .add_clock((0.0, 0.0).into(), (0.0, 0.0).into(), 0.0)
2217 .unwrap();
2218 let (filter, link_1) = filter
2219 .add_untracked_link(clock_1, None, LinkConfig::default())
2220 .unwrap();
2221 let clock_2 = link_1.second_clock();
2222
2223 assert!(filter.estimation_state.is_external_clock(clock_2));
2224 let filter = filter.remove_link(link_1).unwrap();
2225 assert!(!filter.estimation_state.is_external_clock(clock_2));
2226 }
2227
2228 #[test]
2229 fn test_importance() {
2230 let config = ControllerConfig {
2231 select_offset_uncertainty_window: 2.0,
2232 select_link_uncertainty_window: 2.0,
2233 select_delay_uncertainty_window: 0.7,
2234 select_max_window_size: Duration::from_seconds_nanos(1, 0),
2235 minimum_agreeing_sources: 1,
2236 };
2237
2238 let filter = LinkFilter::<StdKalmanStorage<()>>::empty(Timestamp::UNIX_EPOCH);
2239 let (filter, clock_1) = filter
2240 .add_clock((0.0, 1e-3).into(), (0.0, 0.0).into(), 7.5e-12f64.sqrt())
2241 .unwrap();
2242 let (filter, link_1) = filter
2243 .add_untracked_link(clock_1, None, LinkConfig::default())
2244 .unwrap();
2245 let (filter, _link_2) = filter
2246 .add_untracked_link(clock_1, None, LinkConfig::default())
2247 .unwrap();
2248 let filter = filter
2249 .external_data_update(link_1, 0.0, None, true)
2250 .unwrap()
2251 .measurement(&config, link_1.forward(), (0.0, 1e-3).into(), clock_1)
2252 .unwrap();
2253 assert!(filter.link_active(link_1).unwrap());
2254 let importance = filter.link_importance(link_1).unwrap().unwrap();
2255 assert_almost_eq!(importance, 0.5);
2256 let filter = filter
2257 .progress_time(Timestamp::UNIX_EPOCH + Duration::from_seconds_nanos(100, 0))
2258 .unwrap()
2259 .measurement(&config, link_1.forward(), (0.0, 1e-3).into(), clock_1)
2260 .unwrap();
2261 let importance = filter.link_importance(link_1).unwrap().unwrap();
2262 assert_almost_eq!(importance, 0.625);
2263
2264 let active_links = filter.active_links();
2265 assert_eq!(active_links.len(), 1);
2266 assert_eq!(active_links[0].id, link_1);
2267 assert_almost_eq!(active_links[0].importance, 0.625);
2268 }
2269}