1use crate::ml_features::{InferredOwnership, OwnershipFeatures, OwnershipPrediction};
37use serde::{Deserialize, Serialize};
38use std::collections::VecDeque;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42pub enum SelectionStrategy {
43 UncertaintySampling,
45 MarginSampling,
47 EntropySampling,
49 Random,
51}
52
53impl std::fmt::Display for SelectionStrategy {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match self {
56 SelectionStrategy::UncertaintySampling => write!(f, "uncertainty"),
57 SelectionStrategy::MarginSampling => write!(f, "margin"),
58 SelectionStrategy::EntropySampling => write!(f, "entropy"),
59 SelectionStrategy::Random => write!(f, "random"),
60 }
61 }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct UncertainSample {
67 pub id: u64,
69 pub variable: String,
71 pub source_file: Option<String>,
73 pub source_line: Option<u32>,
75 pub features: OwnershipFeatures,
77 pub prediction: OwnershipPrediction,
79 pub uncertainty_score: f64,
81 pub strategy: SelectionStrategy,
83 pub label: Option<InferredOwnership>,
85 pub queued_at: u64,
87 pub labeled_at: Option<u64>,
89}
90
91impl UncertainSample {
92 pub fn new(
94 id: u64,
95 variable: impl Into<String>,
96 features: OwnershipFeatures,
97 prediction: OwnershipPrediction,
98 uncertainty_score: f64,
99 strategy: SelectionStrategy,
100 ) -> Self {
101 let now = std::time::SystemTime::now()
102 .duration_since(std::time::UNIX_EPOCH)
103 .unwrap_or_default()
104 .as_millis() as u64;
105
106 Self {
107 id,
108 variable: variable.into(),
109 source_file: None,
110 source_line: None,
111 features,
112 prediction,
113 uncertainty_score: uncertainty_score.clamp(0.0, 1.0),
114 strategy,
115 label: None,
116 queued_at: now,
117 labeled_at: None,
118 }
119 }
120
121 pub fn with_source(mut self, file: impl Into<String>, line: u32) -> Self {
123 self.source_file = Some(file.into());
124 self.source_line = Some(line);
125 self
126 }
127
128 pub fn is_labeled(&self) -> bool {
130 self.label.is_some()
131 }
132
133 pub fn apply_label(&mut self, label: InferredOwnership) {
135 let now = std::time::SystemTime::now()
136 .duration_since(std::time::UNIX_EPOCH)
137 .unwrap_or_default()
138 .as_millis() as u64;
139
140 self.label = Some(label);
141 self.labeled_at = Some(now);
142 }
143
144 pub fn prediction_correct(&self) -> Option<bool> {
146 self.label.map(|l| l == self.prediction.kind)
147 }
148}
149
150#[derive(Debug, Clone)]
152pub struct UncertaintyCalculator {
153 pub(crate) confidence_threshold: f64,
155}
156
157impl Default for UncertaintyCalculator {
158 fn default() -> Self {
159 Self::new()
160 }
161}
162
163impl UncertaintyCalculator {
164 pub fn new() -> Self {
166 Self {
167 confidence_threshold: 0.65,
168 }
169 }
170
171 pub fn with_confidence_threshold(threshold: f64) -> Self {
173 Self {
174 confidence_threshold: threshold.clamp(0.0, 1.0),
175 }
176 }
177
178 pub fn calculate(&self, prediction: &OwnershipPrediction, strategy: SelectionStrategy) -> f64 {
180 match strategy {
181 SelectionStrategy::UncertaintySampling => self.uncertainty_sampling(prediction),
182 SelectionStrategy::MarginSampling => self.margin_sampling(prediction),
183 SelectionStrategy::EntropySampling => self.entropy_sampling(prediction),
184 SelectionStrategy::Random => rand_like_score(prediction),
185 }
186 }
187
188 fn uncertainty_sampling(&self, prediction: &OwnershipPrediction) -> f64 {
190 1.0 - (prediction.confidence as f64)
191 }
192
193 fn margin_sampling(&self, prediction: &OwnershipPrediction) -> f64 {
195 if prediction.fallback.is_some() {
197 let primary_conf = prediction.confidence as f64;
199 let secondary_conf = primary_conf * 0.8; let margin = primary_conf - secondary_conf;
201 1.0 - margin.min(1.0)
203 } else {
204 self.uncertainty_sampling(prediction)
205 }
206 }
207
208 fn entropy_sampling(&self, prediction: &OwnershipPrediction) -> f64 {
210 let p = prediction.confidence as f64;
211 if p <= 0.0 || p >= 1.0 {
212 return 0.0;
213 }
214
215 let entropy = -p * p.log2() - (1.0 - p) * (1.0 - p).log2();
217 entropy.min(1.0)
219 }
220
221 pub fn is_uncertain(&self, prediction: &OwnershipPrediction) -> bool {
223 (prediction.confidence as f64) < self.confidence_threshold
224 }
225}
226
227fn rand_like_score(prediction: &OwnershipPrediction) -> f64 {
229 let kind_hash = match prediction.kind {
231 InferredOwnership::Owned => 0.1,
232 InferredOwnership::Borrowed => 0.2,
233 InferredOwnership::BorrowedMut => 0.3,
234 InferredOwnership::Shared => 0.4,
235 InferredOwnership::RawPointer => 0.5,
236 InferredOwnership::Vec => 0.6,
237 InferredOwnership::Slice => 0.7,
238 InferredOwnership::SliceMut => 0.8,
239 };
240 let conf_part = prediction.confidence as f64 * 0.3;
241 ((kind_hash + conf_part) * 7.0) % 1.0
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct SampleQueue {
247 pending: VecDeque<UncertainSample>,
249 labeled: Vec<UncertainSample>,
251 strategy: SelectionStrategy,
253 max_pending: usize,
255 next_id: u64,
257 total_processed: u64,
259}
260
261impl Default for SampleQueue {
262 fn default() -> Self {
263 Self::new(SelectionStrategy::UncertaintySampling)
264 }
265}
266
267impl SampleQueue {
268 pub fn new(strategy: SelectionStrategy) -> Self {
270 Self {
271 pending: VecDeque::new(),
272 labeled: Vec::new(),
273 strategy,
274 max_pending: 1000,
275 next_id: 1,
276 total_processed: 0,
277 }
278 }
279
280 pub fn with_max_pending(mut self, max: usize) -> Self {
282 self.max_pending = max;
283 self
284 }
285
286 pub fn strategy(&self) -> SelectionStrategy {
288 self.strategy
289 }
290
291 pub fn pending_count(&self) -> usize {
293 self.pending.len()
294 }
295
296 pub fn labeled_count(&self) -> usize {
298 self.labeled.len()
299 }
300
301 pub fn total_processed(&self) -> u64 {
303 self.total_processed
304 }
305
306 pub fn enqueue(&mut self, mut sample: UncertainSample) -> u64 {
308 sample.id = self.next_id;
309 self.next_id += 1;
310 self.total_processed += 1;
311
312 let insert_pos = self
314 .pending
315 .iter()
316 .position(|s| s.uncertainty_score < sample.uncertainty_score)
317 .unwrap_or(self.pending.len());
318
319 if insert_pos < self.max_pending {
320 self.pending.insert(insert_pos, sample);
321
322 if self.pending.len() > self.max_pending {
324 self.pending.pop_back();
325 }
326 }
327
328 self.next_id - 1
329 }
330
331 pub fn next_for_labeling(&mut self) -> Option<UncertainSample> {
333 self.pending.pop_front()
334 }
335
336 pub fn peek_next(&self) -> Option<&UncertainSample> {
338 self.pending.front()
339 }
340
341 pub fn batch_for_labeling(&mut self, n: usize) -> Vec<UncertainSample> {
343 let mut batch = Vec::with_capacity(n);
344 for _ in 0..n {
345 if let Some(sample) = self.pending.pop_front() {
346 batch.push(sample);
347 } else {
348 break;
349 }
350 }
351 batch
352 }
353
354 pub fn submit_labeled(&mut self, sample: UncertainSample) {
356 if sample.is_labeled() {
357 self.labeled.push(sample);
358 }
359 }
360
361 pub fn get_labeled_samples(&self) -> &[UncertainSample] {
363 &self.labeled
364 }
365
366 pub fn take_labeled_samples(&mut self) -> Vec<UncertainSample> {
368 std::mem::take(&mut self.labeled)
369 }
370
371 pub fn clear_pending(&mut self) {
373 self.pending.clear();
374 }
375
376 pub fn stats(&self) -> QueueStats {
378 let labeled_correct = self
379 .labeled
380 .iter()
381 .filter_map(|s| s.prediction_correct())
382 .filter(|&c| c)
383 .count();
384 let labeled_total = self
385 .labeled
386 .iter()
387 .filter(|s| s.prediction_correct().is_some())
388 .count();
389
390 QueueStats {
391 pending: self.pending.len(),
392 labeled: self.labeled.len(),
393 total_processed: self.total_processed,
394 avg_uncertainty: if self.pending.is_empty() {
395 0.0
396 } else {
397 self.pending
398 .iter()
399 .map(|s| s.uncertainty_score)
400 .sum::<f64>()
401 / self.pending.len() as f64
402 },
403 prediction_accuracy: if labeled_total > 0 {
404 labeled_correct as f64 / labeled_total as f64
405 } else {
406 0.0
407 },
408 }
409 }
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
414pub struct QueueStats {
415 pub pending: usize,
417 pub labeled: usize,
419 pub total_processed: u64,
421 pub avg_uncertainty: f64,
423 pub prediction_accuracy: f64,
425}
426
427#[derive(Debug)]
429pub struct ActiveLearner {
430 calculator: UncertaintyCalculator,
432 queue: SampleQueue,
434 min_uncertainty: f64,
436}
437
438impl Default for ActiveLearner {
439 fn default() -> Self {
440 Self::new()
441 }
442}
443
444impl ActiveLearner {
445 pub fn new() -> Self {
447 Self {
448 calculator: UncertaintyCalculator::new(),
449 queue: SampleQueue::new(SelectionStrategy::UncertaintySampling),
450 min_uncertainty: 0.35,
451 }
452 }
453
454 pub fn with_strategy(strategy: SelectionStrategy) -> Self {
456 Self {
457 calculator: UncertaintyCalculator::new(),
458 queue: SampleQueue::new(strategy),
459 min_uncertainty: 0.35,
460 }
461 }
462
463 pub fn with_min_uncertainty(mut self, threshold: f64) -> Self {
465 self.min_uncertainty = threshold.clamp(0.0, 1.0);
466 self
467 }
468
469 pub fn with_confidence_threshold(mut self, threshold: f64) -> Self {
471 self.calculator = UncertaintyCalculator::with_confidence_threshold(threshold);
472 self
473 }
474
475 pub fn process_prediction(
479 &mut self,
480 variable: impl Into<String>,
481 features: OwnershipFeatures,
482 prediction: OwnershipPrediction,
483 ) -> (f64, bool) {
484 let strategy = self.queue.strategy();
485 let uncertainty = self.calculator.calculate(&prediction, strategy);
486
487 let queued = if uncertainty >= self.min_uncertainty {
488 let sample =
489 UncertainSample::new(0, variable, features, prediction, uncertainty, strategy);
490 self.queue.enqueue(sample);
491 true
492 } else {
493 false
494 };
495
496 (uncertainty, queued)
497 }
498
499 pub fn next_for_labeling(&mut self) -> Option<UncertainSample> {
501 self.queue.next_for_labeling()
502 }
503
504 pub fn batch_for_labeling(&mut self, n: usize) -> Vec<UncertainSample> {
506 self.queue.batch_for_labeling(n)
507 }
508
509 pub fn submit_labeled(&mut self, sample: UncertainSample) {
511 self.queue.submit_labeled(sample);
512 }
513
514 pub fn get_training_samples(&self) -> &[UncertainSample] {
516 self.queue.get_labeled_samples()
517 }
518
519 pub fn take_training_samples(&mut self) -> Vec<UncertainSample> {
521 self.queue.take_labeled_samples()
522 }
523
524 pub fn stats(&self) -> QueueStats {
526 self.queue.stats()
527 }
528
529 pub fn is_uncertain(&self, prediction: &OwnershipPrediction) -> bool {
531 self.calculator.is_uncertain(prediction)
532 }
533
534 pub fn to_markdown(&self) -> String {
536 let stats = self.stats();
537
538 format!(
539 r#"## Active Learning Report
540
541### Queue Status
542
543| Metric | Value |
544|--------|-------|
545| Strategy | {} |
546| Pending Samples | {} |
547| Labeled Samples | {} |
548| Total Processed | {} |
549| Avg Uncertainty | {:.2} |
550| Prediction Accuracy | {:.1}% |
551
552### Configuration
553
554| Parameter | Value |
555|-----------|-------|
556| Min Uncertainty | {:.2} |
557| Confidence Threshold | {:.2} |
558"#,
559 self.queue.strategy(),
560 stats.pending,
561 stats.labeled,
562 stats.total_processed,
563 stats.avg_uncertainty,
564 stats.prediction_accuracy * 100.0,
565 self.min_uncertainty,
566 self.calculator.confidence_threshold,
567 )
568 }
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574
575 fn make_prediction(kind: InferredOwnership, confidence: f32) -> OwnershipPrediction {
576 OwnershipPrediction {
577 kind,
578 confidence,
579 fallback: None,
580 }
581 }
582
583 #[test]
588 fn selection_strategy_display() {
589 assert_eq!(
590 SelectionStrategy::UncertaintySampling.to_string(),
591 "uncertainty"
592 );
593 assert_eq!(SelectionStrategy::MarginSampling.to_string(), "margin");
594 assert_eq!(SelectionStrategy::EntropySampling.to_string(), "entropy");
595 assert_eq!(SelectionStrategy::Random.to_string(), "random");
596 }
597
598 #[test]
603 fn uncertainty_calculator_default() {
604 let calc = UncertaintyCalculator::new();
605 assert!((calc.confidence_threshold - 0.65).abs() < 0.001);
606 }
607
608 #[test]
609 fn uncertainty_sampling_high_confidence() {
610 let calc = UncertaintyCalculator::new();
611 let pred = make_prediction(InferredOwnership::Owned, 0.95);
612
613 let score = calc.calculate(&pred, SelectionStrategy::UncertaintySampling);
614 assert!((score - 0.05).abs() < 0.001); }
616
617 #[test]
618 fn uncertainty_sampling_low_confidence() {
619 let calc = UncertaintyCalculator::new();
620 let pred = make_prediction(InferredOwnership::Owned, 0.3);
621
622 let score = calc.calculate(&pred, SelectionStrategy::UncertaintySampling);
623 assert!((score - 0.7).abs() < 0.001); }
625
626 #[test]
627 fn entropy_sampling_mid_confidence() {
628 let calc = UncertaintyCalculator::new();
629 let pred = make_prediction(InferredOwnership::Owned, 0.5);
630
631 let score = calc.calculate(&pred, SelectionStrategy::EntropySampling);
632 assert!((score - 1.0).abs() < 0.001);
634 }
635
636 #[test]
637 fn entropy_sampling_high_confidence() {
638 let calc = UncertaintyCalculator::new();
639 let pred = make_prediction(InferredOwnership::Owned, 0.95);
640
641 let score = calc.calculate(&pred, SelectionStrategy::EntropySampling);
642 assert!(score < 0.5);
644 }
645
646 #[test]
647 fn is_uncertain_below_threshold() {
648 let calc = UncertaintyCalculator::new(); let pred = make_prediction(InferredOwnership::Owned, 0.5);
650 assert!(calc.is_uncertain(&pred));
651 }
652
653 #[test]
654 fn is_uncertain_above_threshold() {
655 let calc = UncertaintyCalculator::new();
656 let pred = make_prediction(InferredOwnership::Owned, 0.8);
657 assert!(!calc.is_uncertain(&pred));
658 }
659
660 #[test]
665 fn uncertain_sample_new() {
666 let features = OwnershipFeatures::default();
667 let pred = make_prediction(InferredOwnership::Borrowed, 0.4);
668 let sample = UncertainSample::new(
669 1,
670 "ptr",
671 features,
672 pred,
673 0.6,
674 SelectionStrategy::UncertaintySampling,
675 );
676
677 assert_eq!(sample.id, 1);
678 assert_eq!(sample.variable, "ptr");
679 assert!((sample.uncertainty_score - 0.6).abs() < 0.001);
680 assert!(!sample.is_labeled());
681 }
682
683 #[test]
684 fn uncertain_sample_apply_label() {
685 let features = OwnershipFeatures::default();
686 let pred = make_prediction(InferredOwnership::Borrowed, 0.4);
687 let mut sample = UncertainSample::new(
688 1,
689 "ptr",
690 features,
691 pred,
692 0.6,
693 SelectionStrategy::UncertaintySampling,
694 );
695
696 sample.apply_label(InferredOwnership::Owned);
697
698 assert!(sample.is_labeled());
699 assert_eq!(sample.label, Some(InferredOwnership::Owned));
700 assert!(sample.labeled_at.is_some());
701 }
702
703 #[test]
704 fn uncertain_sample_prediction_correct() {
705 let features = OwnershipFeatures::default();
706 let pred = make_prediction(InferredOwnership::Borrowed, 0.4);
707 let mut sample = UncertainSample::new(
708 1,
709 "ptr",
710 features,
711 pred,
712 0.6,
713 SelectionStrategy::UncertaintySampling,
714 );
715
716 assert!(sample.prediction_correct().is_none());
718
719 sample.apply_label(InferredOwnership::Borrowed);
721 assert_eq!(sample.prediction_correct(), Some(true));
722 }
723
724 #[test]
725 fn uncertain_sample_prediction_incorrect() {
726 let features = OwnershipFeatures::default();
727 let pred = make_prediction(InferredOwnership::Borrowed, 0.4);
728 let mut sample = UncertainSample::new(
729 1,
730 "ptr",
731 features,
732 pred,
733 0.6,
734 SelectionStrategy::UncertaintySampling,
735 );
736
737 sample.apply_label(InferredOwnership::Owned);
739 assert_eq!(sample.prediction_correct(), Some(false));
740 }
741
742 #[test]
747 fn sample_queue_new() {
748 let queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
749 assert_eq!(queue.pending_count(), 0);
750 assert_eq!(queue.labeled_count(), 0);
751 }
752
753 #[test]
754 fn sample_queue_enqueue() {
755 let mut queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
756
757 let sample = UncertainSample::new(
758 0,
759 "ptr",
760 OwnershipFeatures::default(),
761 make_prediction(InferredOwnership::Borrowed, 0.4),
762 0.6,
763 SelectionStrategy::UncertaintySampling,
764 );
765
766 let id = queue.enqueue(sample);
767 assert_eq!(id, 1);
768 assert_eq!(queue.pending_count(), 1);
769 }
770
771 #[test]
772 fn sample_queue_priority_order() {
773 let mut queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
774
775 for uncertainty in [0.3, 0.9, 0.5] {
777 let sample = UncertainSample::new(
778 0,
779 "ptr",
780 OwnershipFeatures::default(),
781 make_prediction(InferredOwnership::Borrowed, 0.4),
782 uncertainty,
783 SelectionStrategy::UncertaintySampling,
784 );
785 queue.enqueue(sample);
786 }
787
788 let first = queue.next_for_labeling().unwrap();
790 assert!((first.uncertainty_score - 0.9).abs() < 0.001);
791
792 let second = queue.next_for_labeling().unwrap();
793 assert!((second.uncertainty_score - 0.5).abs() < 0.001);
794 }
795
796 #[test]
797 fn sample_queue_max_pending() {
798 let mut queue =
799 SampleQueue::new(SelectionStrategy::UncertaintySampling).with_max_pending(3);
800
801 for i in 0..5 {
803 let sample = UncertainSample::new(
804 0,
805 format!("ptr{}", i),
806 OwnershipFeatures::default(),
807 make_prediction(InferredOwnership::Borrowed, 0.4),
808 (i as f64 + 1.0) * 0.1,
809 SelectionStrategy::UncertaintySampling,
810 );
811 queue.enqueue(sample);
812 }
813
814 assert_eq!(queue.pending_count(), 3);
816 }
817
818 #[test]
819 fn sample_queue_batch_labeling() {
820 let mut queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
821
822 for i in 0..5 {
823 let sample = UncertainSample::new(
824 0,
825 format!("ptr{}", i),
826 OwnershipFeatures::default(),
827 make_prediction(InferredOwnership::Borrowed, 0.4),
828 0.5,
829 SelectionStrategy::UncertaintySampling,
830 );
831 queue.enqueue(sample);
832 }
833
834 let batch = queue.batch_for_labeling(3);
835 assert_eq!(batch.len(), 3);
836 assert_eq!(queue.pending_count(), 2);
837 }
838
839 #[test]
840 fn sample_queue_submit_labeled() {
841 let mut queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
842
843 let mut sample = UncertainSample::new(
844 1,
845 "ptr",
846 OwnershipFeatures::default(),
847 make_prediction(InferredOwnership::Borrowed, 0.4),
848 0.6,
849 SelectionStrategy::UncertaintySampling,
850 );
851 sample.apply_label(InferredOwnership::Owned);
852
853 queue.submit_labeled(sample);
854 assert_eq!(queue.labeled_count(), 1);
855 }
856
857 #[test]
858 fn sample_queue_stats() {
859 let mut queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
860
861 let sample1 = UncertainSample::new(
863 0,
864 "ptr1",
865 OwnershipFeatures::default(),
866 make_prediction(InferredOwnership::Borrowed, 0.4),
867 0.6,
868 SelectionStrategy::UncertaintySampling,
869 );
870 queue.enqueue(sample1);
871
872 let mut sample2 = UncertainSample::new(
874 0,
875 "ptr2",
876 OwnershipFeatures::default(),
877 make_prediction(InferredOwnership::Borrowed, 0.4),
878 0.5,
879 SelectionStrategy::UncertaintySampling,
880 );
881 sample2.apply_label(InferredOwnership::Borrowed);
882 queue.submit_labeled(sample2);
883
884 let stats = queue.stats();
885 assert_eq!(stats.pending, 1);
886 assert_eq!(stats.labeled, 1);
887 assert!((stats.prediction_accuracy - 1.0).abs() < 0.001); }
889
890 #[test]
895 fn active_learner_new() {
896 let learner = ActiveLearner::new();
897 let stats = learner.stats();
898 assert_eq!(stats.pending, 0);
899 assert_eq!(stats.labeled, 0);
900 }
901
902 #[test]
903 fn active_learner_process_uncertain() {
904 let mut learner = ActiveLearner::new().with_min_uncertainty(0.3);
905
906 let features = OwnershipFeatures::default();
907 let pred = make_prediction(InferredOwnership::Borrowed, 0.4); let (uncertainty, queued) = learner.process_prediction("ptr", features, pred);
910
911 assert!((uncertainty - 0.6).abs() < 0.001);
912 assert!(queued);
913 assert_eq!(learner.stats().pending, 1);
914 }
915
916 #[test]
917 fn active_learner_process_certain() {
918 let mut learner = ActiveLearner::new().with_min_uncertainty(0.3);
919
920 let features = OwnershipFeatures::default();
921 let pred = make_prediction(InferredOwnership::Owned, 0.95); let (uncertainty, queued) = learner.process_prediction("ptr", features, pred);
924
925 assert!((uncertainty - 0.05).abs() < 0.001);
926 assert!(!queued); assert_eq!(learner.stats().pending, 0);
928 }
929
930 #[test]
931 fn active_learner_labeling_workflow() {
932 let mut learner = ActiveLearner::new().with_min_uncertainty(0.3);
933
934 let features = OwnershipFeatures::default();
936 let pred = make_prediction(InferredOwnership::Borrowed, 0.4);
937 learner.process_prediction("ptr", features, pred);
938
939 let mut sample = learner.next_for_labeling().unwrap();
941 assert_eq!(sample.variable, "ptr");
942
943 sample.apply_label(InferredOwnership::Owned);
945 learner.submit_labeled(sample);
946
947 let training = learner.get_training_samples();
949 assert_eq!(training.len(), 1);
950 assert_eq!(training[0].label, Some(InferredOwnership::Owned));
951 }
952
953 #[test]
954 fn active_learner_to_markdown() {
955 let learner = ActiveLearner::new();
956 let md = learner.to_markdown();
957
958 assert!(md.contains("Active Learning Report"));
959 assert!(md.contains("Queue Status"));
960 assert!(md.contains("Strategy"));
961 }
962
963 #[test]
964 fn active_learner_with_strategy() {
965 let learner = ActiveLearner::with_strategy(SelectionStrategy::EntropySampling);
966 assert_eq!(learner.queue.strategy(), SelectionStrategy::EntropySampling);
967 }
968
969 #[test]
970 fn active_learner_batch_labeling() {
971 let mut learner = ActiveLearner::new().with_min_uncertainty(0.2);
972
973 for i in 0..5 {
975 let features = OwnershipFeatures::default();
976 let pred = make_prediction(InferredOwnership::Borrowed, 0.3 + (i as f32 * 0.05));
977 learner.process_prediction(format!("ptr{}", i), features, pred);
978 }
979
980 let batch = learner.batch_for_labeling(3);
981 assert_eq!(batch.len(), 3);
982 }
983
984 #[test]
989 fn margin_sampling_with_fallback() {
990 let calc = UncertaintyCalculator::new();
991 let pred = OwnershipPrediction {
992 kind: InferredOwnership::Borrowed,
993 confidence: 0.6,
994 fallback: Some(InferredOwnership::Owned),
995 };
996
997 let score = calc.calculate(&pred, SelectionStrategy::MarginSampling);
998 assert!(score > 0.5);
1000 }
1001
1002 #[test]
1003 fn margin_sampling_without_fallback() {
1004 let calc = UncertaintyCalculator::new();
1005 let pred = make_prediction(InferredOwnership::Borrowed, 0.6);
1006
1007 let score = calc.calculate(&pred, SelectionStrategy::MarginSampling);
1008 assert!((score - 0.4).abs() < 0.001);
1010 }
1011
1012 #[test]
1017 fn entropy_at_zero_confidence() {
1018 let calc = UncertaintyCalculator::new();
1019 let pred = make_prediction(InferredOwnership::Owned, 0.0);
1020
1021 let score = calc.calculate(&pred, SelectionStrategy::EntropySampling);
1022 assert!((score - 0.0).abs() < 0.001); }
1024
1025 #[test]
1026 fn entropy_at_one_confidence() {
1027 let calc = UncertaintyCalculator::new();
1028 let pred = make_prediction(InferredOwnership::Owned, 1.0);
1029
1030 let score = calc.calculate(&pred, SelectionStrategy::EntropySampling);
1031 assert!((score - 0.0).abs() < 0.001); }
1033
1034 #[test]
1039 fn random_sampling_all_ownership_kinds() {
1040 let calc = UncertaintyCalculator::new();
1041 let kinds = vec![
1042 InferredOwnership::Owned,
1043 InferredOwnership::Borrowed,
1044 InferredOwnership::BorrowedMut,
1045 InferredOwnership::Shared,
1046 InferredOwnership::RawPointer,
1047 InferredOwnership::Vec,
1048 InferredOwnership::Slice,
1049 InferredOwnership::SliceMut,
1050 ];
1051
1052 for kind in kinds {
1053 let pred = make_prediction(kind, 0.5);
1054 let score = calc.calculate(&pred, SelectionStrategy::Random);
1055 assert!(score >= 0.0 && score <= 1.0);
1056 }
1057 }
1058
1059 #[test]
1064 fn uncertain_sample_with_source() {
1065 let features = OwnershipFeatures::default();
1066 let pred = make_prediction(InferredOwnership::Borrowed, 0.4);
1067 let sample = UncertainSample::new(
1068 1,
1069 "ptr",
1070 features,
1071 pred,
1072 0.6,
1073 SelectionStrategy::UncertaintySampling,
1074 )
1075 .with_source("test.c", 42);
1076
1077 assert_eq!(sample.source_file, Some("test.c".to_string()));
1078 assert_eq!(sample.source_line, Some(42));
1079 }
1080
1081 #[test]
1086 fn sample_queue_peek_next() {
1087 let mut queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
1088
1089 assert!(queue.peek_next().is_none());
1090
1091 let sample = UncertainSample::new(
1092 0,
1093 "ptr",
1094 OwnershipFeatures::default(),
1095 make_prediction(InferredOwnership::Borrowed, 0.4),
1096 0.6,
1097 SelectionStrategy::UncertaintySampling,
1098 );
1099 queue.enqueue(sample);
1100
1101 assert!(queue.peek_next().is_some());
1102 assert_eq!(queue.pending_count(), 1); }
1104
1105 #[test]
1106 fn sample_queue_clear_pending() {
1107 let mut queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
1108
1109 for i in 0..3 {
1110 let sample = UncertainSample::new(
1111 0,
1112 format!("ptr{}", i),
1113 OwnershipFeatures::default(),
1114 make_prediction(InferredOwnership::Borrowed, 0.4),
1115 0.5,
1116 SelectionStrategy::UncertaintySampling,
1117 );
1118 queue.enqueue(sample);
1119 }
1120
1121 assert_eq!(queue.pending_count(), 3);
1122 queue.clear_pending();
1123 assert_eq!(queue.pending_count(), 0);
1124 }
1125
1126 #[test]
1127 fn sample_queue_take_labeled() {
1128 let mut queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
1129
1130 let mut sample = UncertainSample::new(
1131 1,
1132 "ptr",
1133 OwnershipFeatures::default(),
1134 make_prediction(InferredOwnership::Borrowed, 0.4),
1135 0.6,
1136 SelectionStrategy::UncertaintySampling,
1137 );
1138 sample.apply_label(InferredOwnership::Owned);
1139 queue.submit_labeled(sample);
1140
1141 assert_eq!(queue.labeled_count(), 1);
1142 let taken = queue.take_labeled_samples();
1143 assert_eq!(taken.len(), 1);
1144 assert_eq!(queue.labeled_count(), 0);
1145 }
1146
1147 #[test]
1148 fn sample_queue_submit_unlabeled_rejected() {
1149 let mut queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
1150
1151 let sample = UncertainSample::new(
1152 1,
1153 "ptr",
1154 OwnershipFeatures::default(),
1155 make_prediction(InferredOwnership::Borrowed, 0.4),
1156 0.6,
1157 SelectionStrategy::UncertaintySampling,
1158 );
1159 queue.submit_labeled(sample);
1161 assert_eq!(queue.labeled_count(), 0); }
1163
1164 #[test]
1165 fn sample_queue_batch_from_empty() {
1166 let mut queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
1167 let batch = queue.batch_for_labeling(5);
1168 assert!(batch.is_empty());
1169 }
1170
1171 #[test]
1172 fn sample_queue_batch_partial() {
1173 let mut queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
1174
1175 for i in 0..2 {
1176 let sample = UncertainSample::new(
1177 0,
1178 format!("ptr{}", i),
1179 OwnershipFeatures::default(),
1180 make_prediction(InferredOwnership::Borrowed, 0.4),
1181 0.5,
1182 SelectionStrategy::UncertaintySampling,
1183 );
1184 queue.enqueue(sample);
1185 }
1186
1187 let batch = queue.batch_for_labeling(5);
1188 assert_eq!(batch.len(), 2); }
1190
1191 #[test]
1192 fn sample_queue_total_processed() {
1193 let mut queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
1194 assert_eq!(queue.total_processed(), 0);
1195
1196 for i in 0..3 {
1197 let sample = UncertainSample::new(
1198 0,
1199 format!("ptr{}", i),
1200 OwnershipFeatures::default(),
1201 make_prediction(InferredOwnership::Borrowed, 0.4),
1202 0.5,
1203 SelectionStrategy::UncertaintySampling,
1204 );
1205 queue.enqueue(sample);
1206 }
1207 assert_eq!(queue.total_processed(), 3);
1208 }
1209
1210 #[test]
1211 fn sample_queue_get_labeled_samples() {
1212 let queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
1213 assert!(queue.get_labeled_samples().is_empty());
1214 }
1215
1216 #[test]
1221 fn sample_queue_overflow_low_priority_dropped() {
1222 let mut queue =
1223 SampleQueue::new(SelectionStrategy::UncertaintySampling).with_max_pending(2);
1224
1225 for u in [0.9, 0.8] {
1227 let sample = UncertainSample::new(
1228 0,
1229 "ptr",
1230 OwnershipFeatures::default(),
1231 make_prediction(InferredOwnership::Borrowed, 0.4),
1232 u,
1233 SelectionStrategy::UncertaintySampling,
1234 );
1235 queue.enqueue(sample);
1236 }
1237
1238 let low = UncertainSample::new(
1240 0,
1241 "low",
1242 OwnershipFeatures::default(),
1243 make_prediction(InferredOwnership::Borrowed, 0.4),
1244 0.01,
1245 SelectionStrategy::UncertaintySampling,
1246 );
1247 queue.enqueue(low);
1248
1249 assert_eq!(queue.pending_count(), 2);
1250 let first = queue.peek_next().unwrap();
1252 assert!((first.uncertainty_score - 0.9).abs() < 0.01);
1253 }
1254
1255 #[test]
1260 fn active_learner_with_confidence_threshold() {
1261 let learner = ActiveLearner::new().with_confidence_threshold(0.8);
1262 let high = make_prediction(InferredOwnership::Owned, 0.75);
1263 assert!(learner.is_uncertain(&high)); let very_high = make_prediction(InferredOwnership::Owned, 0.85);
1266 assert!(!learner.is_uncertain(&very_high)); }
1268
1269 #[test]
1270 fn active_learner_take_training_samples() {
1271 let mut learner = ActiveLearner::new().with_min_uncertainty(0.2);
1272
1273 let features = OwnershipFeatures::default();
1274 let pred = make_prediction(InferredOwnership::Borrowed, 0.4);
1275 learner.process_prediction("ptr", features, pred);
1276
1277 let mut sample = learner.next_for_labeling().unwrap();
1278 sample.apply_label(InferredOwnership::Owned);
1279 learner.submit_labeled(sample);
1280
1281 let training = learner.take_training_samples();
1282 assert_eq!(training.len(), 1);
1283 assert!(learner.get_training_samples().is_empty()); }
1285
1286 #[test]
1291 fn sample_queue_stats_empty() {
1292 let queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
1293 let stats = queue.stats();
1294 assert_eq!(stats.pending, 0);
1295 assert_eq!(stats.labeled, 0);
1296 assert_eq!(stats.total_processed, 0);
1297 assert!((stats.avg_uncertainty - 0.0).abs() < 0.001);
1298 assert!((stats.prediction_accuracy - 0.0).abs() < 0.001);
1299 }
1300
1301 #[test]
1302 fn sample_queue_stats_with_incorrect_prediction() {
1303 let mut queue = SampleQueue::new(SelectionStrategy::UncertaintySampling);
1304
1305 let mut sample = UncertainSample::new(
1306 0,
1307 "ptr",
1308 OwnershipFeatures::default(),
1309 make_prediction(InferredOwnership::Borrowed, 0.4),
1310 0.6,
1311 SelectionStrategy::UncertaintySampling,
1312 );
1313 sample.apply_label(InferredOwnership::Owned); queue.submit_labeled(sample);
1315
1316 let stats = queue.stats();
1317 assert!((stats.prediction_accuracy - 0.0).abs() < 0.001); }
1319
1320 #[test]
1325 fn uncertainty_calculator_custom_threshold_clamp() {
1326 let calc = UncertaintyCalculator::with_confidence_threshold(2.0);
1327 assert!((calc.confidence_threshold - 1.0).abs() < 0.001);
1328
1329 let calc2 = UncertaintyCalculator::with_confidence_threshold(-1.0);
1330 assert!((calc2.confidence_threshold - 0.0).abs() < 0.001);
1331 }
1332
1333 #[test]
1338 fn sample_queue_default() {
1339 let queue = SampleQueue::default();
1340 assert_eq!(queue.strategy(), SelectionStrategy::UncertaintySampling);
1341 }
1342
1343 #[test]
1344 fn active_learner_default() {
1345 let learner = ActiveLearner::default();
1346 let stats = learner.stats();
1347 assert_eq!(stats.pending, 0);
1348 }
1349
1350 #[test]
1355 fn uncertain_sample_clamp_high() {
1356 let sample = UncertainSample::new(
1357 1,
1358 "ptr",
1359 OwnershipFeatures::default(),
1360 make_prediction(InferredOwnership::Borrowed, 0.4),
1361 1.5, SelectionStrategy::UncertaintySampling,
1363 );
1364 assert!((sample.uncertainty_score - 1.0).abs() < 0.001);
1365 }
1366
1367 #[test]
1368 fn uncertain_sample_clamp_low() {
1369 let sample = UncertainSample::new(
1370 1,
1371 "ptr",
1372 OwnershipFeatures::default(),
1373 make_prediction(InferredOwnership::Borrowed, 0.4),
1374 -0.5, SelectionStrategy::UncertaintySampling,
1376 );
1377 assert!((sample.uncertainty_score - 0.0).abs() < 0.001);
1378 }
1379
1380 #[test]
1385 fn active_learner_min_uncertainty_clamp() {
1386 let learner = ActiveLearner::new().with_min_uncertainty(2.0);
1387 let features = OwnershipFeatures::default();
1389 let pred = make_prediction(InferredOwnership::Borrowed, 0.01);
1390 let mut learner2 = learner;
1391 let (_, queued) = learner2.process_prediction("ptr", features, pred);
1392 assert!(!queued);
1394 }
1395
1396 #[test]
1401 fn active_learner_process_with_margin_strategy() {
1402 let mut learner =
1403 ActiveLearner::with_strategy(SelectionStrategy::MarginSampling).with_min_uncertainty(0.2);
1404
1405 let features = OwnershipFeatures::default();
1406 let pred = OwnershipPrediction {
1407 kind: InferredOwnership::Borrowed,
1408 confidence: 0.4,
1409 fallback: Some(InferredOwnership::Owned),
1410 };
1411 let (_, queued) = learner.process_prediction("ptr", features, pred);
1412 assert!(queued);
1413 }
1414
1415 #[test]
1416 fn active_learner_process_with_entropy_strategy() {
1417 let mut learner =
1418 ActiveLearner::with_strategy(SelectionStrategy::EntropySampling).with_min_uncertainty(0.2);
1419
1420 let features = OwnershipFeatures::default();
1421 let pred = make_prediction(InferredOwnership::Borrowed, 0.5);
1422 let (uncertainty, queued) = learner.process_prediction("ptr", features, pred);
1423 assert!((uncertainty - 1.0).abs() < 0.001); assert!(queued);
1425 }
1426
1427 #[test]
1428 fn active_learner_process_with_random_strategy() {
1429 let mut learner =
1430 ActiveLearner::with_strategy(SelectionStrategy::Random).with_min_uncertainty(0.0);
1431
1432 let features = OwnershipFeatures::default();
1433 let pred = make_prediction(InferredOwnership::Borrowed, 0.5);
1434 let (uncertainty, queued) = learner.process_prediction("ptr", features, pred);
1435 assert!(uncertainty >= 0.0 && uncertainty <= 1.0);
1436 assert!(queued);
1437 }
1438}