1use crate::ThresholdError;
2use strum_macros::{Display, EnumString};
3use validator::{Validate, ValidationError};
4
5#[derive(PartialEq, Eq, Clone, Copy, Debug, Display, EnumString)]
14#[strum(serialize_all = "snake_case")]
15pub enum Outcome {
16 Trusted,
17 Accepted,
18 Suspected,
19 Restricted,
20}
21
22#[derive(Debug, Validate, Copy, Clone, PartialEq)]
38#[validate(schema(function = "validate_sum", skip_on_field_errors = false))]
39pub struct Decision {
40 #[validate(range(min = 0.0, max = 1.0))]
41 pub accept: f64,
42 #[validate(range(min = 0.0, max = 1.0))]
43 pub restrict: f64,
44 #[validate(range(min = 0.0, max = 1.0))]
45 pub unknown: f64,
46}
47
48impl Default for Decision {
49 fn default() -> Self {
52 UNKNOWN
53 }
54}
55
56pub const ACCEPT: Decision = Decision {
58 accept: 1.0,
59 restrict: 0.0,
60 unknown: 0.0,
61};
62
63pub const RESTRICT: Decision = Decision {
65 accept: 0.0,
66 restrict: 1.0,
67 unknown: 0.0,
68};
69
70pub const UNKNOWN: Decision = Decision {
72 accept: 0.0,
73 restrict: 0.0,
74 unknown: 1.0,
75};
76
77fn validate_sum(decision: &Decision) -> Result<(), ValidationError> {
79 let sum = decision.accept + decision.restrict + decision.unknown;
80 if sum < 0.0 - 2.0 * f64::EPSILON {
81 return Err(ValidationError::new("sum cannot be negative"));
82 } else if sum > 1.0 + 2.0 * f64::EPSILON {
83 return Err(ValidationError::new("sum cannot be greater than one"));
84 } else if !(sum > 1.0 - 2.0 * f64::EPSILON && sum < 1.0 + 2.0 * f64::EPSILON) {
85 return Err(ValidationError::new("sum should be equal to one"));
86 }
87
88 Ok(())
89}
90
91impl Decision {
92 pub fn accepted(accept: f64) -> Self {
111 Self {
112 accept,
113 restrict: 0.0,
114 unknown: 0.0,
115 }
116 .scale()
117 }
118
119 pub fn restricted(restrict: f64) -> Self {
138 Self {
139 accept: 0.0,
140 restrict,
141 unknown: 0.0,
142 }
143 .scale()
144 }
145
146 pub fn pignistic(&self) -> Self {
150 Self {
151 accept: self.accept + self.unknown / 2.0,
152 restrict: self.restrict + self.unknown / 2.0,
153 unknown: 0.0,
154 }
155 }
156
157 pub fn is_accepted(&self, threshold: f64) -> bool {
164 let p = self.pignistic();
165 p.accept >= threshold
166 }
167
168 pub fn is_unknown(&self) -> bool {
171 self.unknown >= f64::EPSILON
172 && self.accept >= 0.0
173 && self.accept <= f64::EPSILON
174 && self.restrict >= 0.0
175 && self.restrict <= f64::EPSILON
176 }
177
178 pub fn outcome(
197 &self,
198 trust: f64,
199 suspicious: f64,
200 restrict: f64,
201 ) -> Result<Outcome, ThresholdError> {
202 let p = self.pignistic();
203 if trust > suspicious || suspicious > restrict {
204 return Err(ThresholdError::ThresholdOutOfOrder);
205 }
206 if !(0.0..=1.0).contains(&trust) {
207 return Err(ThresholdError::ThresholdOutOfRange(trust));
208 }
209 if !(0.0..=1.0).contains(&suspicious) {
210 return Err(ThresholdError::ThresholdOutOfRange(suspicious));
211 }
212 if !(0.0..=1.0).contains(&restrict) {
213 return Err(ThresholdError::ThresholdOutOfRange(restrict));
214 }
215 match p.restrict {
216 x if x <= trust => Ok(Outcome::Trusted),
217 x if x < suspicious => Ok(Outcome::Accepted),
218 x if x >= restrict => Ok(Outcome::Restricted),
219 _ => Ok(Outcome::Suspected),
221 }
222 }
223
224 pub fn clamp(&self) -> Self {
228 self.clamp_min_unknown(0.0)
229 }
230
231 pub fn clamp_min_unknown(&self, min: f64) -> Self {
239 let accept: f64 = self.accept.clamp(0.0, 1.0);
240 let restrict: f64 = self.restrict.clamp(0.0, 1.0);
241 let unknown: f64 = self.unknown.clamp(min.max(0.0), 1.0);
242
243 Self {
244 accept,
245 restrict,
246 unknown,
247 }
248 }
249
250 pub fn fill_unknown(&self) -> Self {
253 let unknown = if self.unknown + f64::EPSILON >= 0.0 {
255 self.unknown
256 } else {
257 0.0
258 };
259 let sum = self.accept + self.restrict + unknown;
260 Self {
261 accept: self.accept,
262 restrict: self.restrict,
263 unknown: if sum - f64::EPSILON <= 1.0 {
264 1.0 - self.accept - self.restrict
265 } else {
266 unknown
267 },
268 }
269 }
270
271 pub fn scale(&self) -> Self {
276 self.scale_min_unknown(0.0)
277 }
278
279 pub fn scale_min_unknown(&self, min: f64) -> Self {
289 let d = self.fill_unknown().clamp();
290 let mut sum = d.accept + d.restrict + d.unknown;
291 let mut accept = d.accept;
292 let mut restrict = d.restrict;
293 let mut unknown = d.unknown;
294
295 if sum > 0.0 {
296 accept /= sum;
297 restrict /= sum;
298 unknown /= sum
299 }
300 if unknown < min {
301 unknown = min
302 }
303 sum = 1.0 - unknown;
304 if sum > 0.0 {
305 let denominator = accept + restrict;
306 accept = sum * (accept / denominator);
307 restrict = sum * (restrict / denominator)
308 }
309 Self {
310 accept,
311 restrict,
312 unknown,
313 }
314 }
315
316 pub fn weight(&self, factor: f64) -> Self {
327 Self {
328 accept: self.accept * factor,
329 restrict: self.restrict * factor,
330 unknown: 0.0,
331 }
332 .scale()
333 }
334
335 fn pairwise_combine(left: &Self, right: &Self, normalize: bool) -> Self {
344 let nullh = if normalize {
346 left.accept * right.restrict + left.restrict * right.accept
347 } else {
348 0.0
350 };
351
352 Self {
353 accept: (left.accept * right.accept
359 + left.accept * right.unknown
360 + left.unknown * right.accept)
361 / (1.0 - nullh),
362 restrict: (left.restrict * right.restrict
363 + left.restrict * right.unknown
364 + left.unknown * right.restrict)
365 / (1.0 - nullh),
366 unknown: (left.unknown * right.unknown) / (1.0 - nullh),
367 }
368 }
369
370 pub fn combine_conjunctive<'a, I>(decisions: I) -> Self
379 where
380 Self: 'a,
381 I: IntoIterator<Item = &'a Self>,
382 {
383 let mut d = Self {
384 accept: 0.0,
385 restrict: 0.0,
386 unknown: 1.0,
387 };
388 for m in decisions {
389 d = Self::pairwise_combine(&d, m, true);
390 }
391 d
392 }
393
394 pub fn combine_murphy<'a, I>(decisions: I) -> Self
408 where
409 Self: 'a,
410 I: IntoIterator<Item = &'a Self>,
411 {
412 let mut sum_a = 0.0;
413 let mut sum_d = 0.0;
414 let mut sum_u = 0.0;
415 let mut length: usize = 0;
416 for m in decisions {
417 sum_a += m.accept;
418 sum_d += m.restrict;
419 sum_u += m.unknown;
420 length += 1;
421 }
422 let avg_d = Self {
423 accept: sum_a / length as f64,
424 restrict: sum_d / length as f64,
425 unknown: sum_u / length as f64,
426 };
427 let mut d = Self {
428 accept: 0.0,
429 restrict: 0.0,
430 unknown: 1.0,
431 };
432 for _ in 0..length {
433 d = Self::pairwise_combine(&d, &avg_d, true);
434 }
435 d
436 }
437
438 pub fn conflict<'a, I>(decisions: I) -> f64
444 where
445 Self: 'a,
446 I: IntoIterator<Item = &'a Self>,
447 {
448 let mut d = Self {
449 accept: 0.0,
450 restrict: 0.0,
451 unknown: 1.0,
452 };
453 for m in decisions {
454 d = Self::pairwise_combine(&d, m, false);
455 }
456 let diff = d.accept + d.restrict + d.unknown;
457 if diff > 0.0 {
458 -diff.ln()
459 } else {
460 f64::INFINITY
461 }
462 }
463}
464
465impl approx::AbsDiffEq for Decision {
466 type Epsilon = <f64 as approx::AbsDiffEq>::Epsilon;
467
468 fn default_epsilon() -> Self::Epsilon {
469 <f64 as approx::AbsDiffEq>::default_epsilon()
470 }
471
472 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
473 f64::abs_diff_eq(&self.accept, &other.accept, epsilon)
474 && f64::abs_diff_eq(&self.restrict, &other.restrict, epsilon)
475 && f64::abs_diff_eq(&self.unknown, &other.unknown, epsilon)
476 }
477}
478
479impl approx::RelativeEq for Decision {
480 fn default_max_relative() -> Self::Epsilon {
481 <f64 as approx::RelativeEq>::default_max_relative()
482 }
483
484 fn relative_eq(
485 &self,
486 other: &Self,
487 epsilon: Self::Epsilon,
488 max_relative: Self::Epsilon,
489 ) -> bool {
490 f64::relative_eq(&self.accept, &other.accept, epsilon, max_relative)
491 && f64::relative_eq(&self.restrict, &other.restrict, epsilon, max_relative)
492 && f64::relative_eq(&self.unknown, &other.unknown, epsilon, max_relative)
493 }
494}
495
496impl approx::UlpsEq for Decision {
497 fn default_max_ulps() -> u32 {
498 <f64 as approx::UlpsEq>::default_max_ulps()
499 }
500
501 fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
502 f64::ulps_eq(&self.accept, &other.accept, epsilon, max_ulps)
503 && f64::ulps_eq(&self.restrict, &other.restrict, epsilon, max_ulps)
504 && f64::ulps_eq(&self.unknown, &other.unknown, epsilon, max_ulps)
505 }
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511
512 macro_rules! test_decision {
513 ($name:ident, $dec:expr, $v:expr $(, $attr:ident = $val:expr)*) => {
514 #[test]
515 fn $name() {
516 $(assert_relative_eq!($dec.$attr, $val, epsilon = 2.0 * f64::EPSILON);)*
517 if $v {
519 match $dec.validate() {
520 Ok(_) => assert!(true),
521 Err(e) => assert!(false, "decision should have validated: {}", e),
522 }
523 } else {
524 match $dec.validate() {
525 Ok(_) => assert!(false, "decision should not validate"),
526 Err(_) => assert!(true),
527 }
528 }
529 }
530 }
531 }
532
533 test_decision!(
534 validate_zero,
535 Decision {
536 accept: 0.0,
537 restrict: 0.0,
538 unknown: 0.0,
539 },
540 false,
541 accept = 0.0,
542 restrict = 0.0,
543 unknown = 0.0
544 );
545
546 test_decision!(
547 validate_simple,
548 Decision {
549 accept: 0.25,
550 restrict: 0.25,
551 unknown: 0.50,
552 },
553 true,
554 accept = 0.25,
555 restrict = 0.25,
556 unknown = 0.50
557 );
558
559 test_decision!(
560 validate_negative,
561 Decision {
562 accept: -0.25,
563 restrict: 0.75,
564 unknown: 0.50,
565 },
566 false,
567 accept = -0.25,
568 restrict = 0.75,
569 unknown = 0.50
570 );
571
572 test_decision!(
573 pignistic_simple,
574 Decision {
575 accept: 0.25,
576 restrict: 0.25,
577 unknown: 0.50,
578 }
579 .pignistic(),
580 true,
581 accept = 0.5,
582 restrict = 0.5,
583 unknown = 0.0
584 );
585
586 test_decision!(
587 clamp_zero,
588 Decision {
589 accept: 0.0,
590 restrict: 0.0,
591 unknown: 0.0,
592 }
593 .clamp(),
594 false,
595 accept = 0.0,
596 restrict = 0.0,
597 unknown = 0.0
598 );
599
600 test_decision!(
601 clamp_three_halves,
602 Decision {
603 accept: 0.50,
604 restrict: 0.50,
605 unknown: 0.50,
606 }
607 .clamp(),
608 false,
609 accept = 0.50,
610 restrict = 0.50,
611 unknown = 0.50
612 );
613
614 test_decision!(
615 clamp_three_whole,
616 Decision {
617 accept: 1.0,
618 restrict: 1.0,
619 unknown: 1.0,
620 }
621 .clamp(),
622 false,
623 accept = 1.0,
624 restrict = 1.0,
625 unknown = 1.0
626 );
627
628 test_decision!(
629 clamp_negative,
630 Decision {
631 accept: -1.0,
632 restrict: -1.0,
633 unknown: -1.0,
634 }
635 .clamp(),
636 false,
637 accept = 0.0,
638 restrict = 0.0,
639 unknown = 0.0
640 );
641
642 test_decision!(
643 clamp_triple_double,
644 Decision {
645 accept: 2.0,
646 restrict: 2.0,
647 unknown: 2.0,
648 }
649 .clamp(),
650 false,
651 accept = 1.0,
652 restrict = 1.0,
653 unknown = 1.0
654 );
655
656 test_decision!(
657 clamp_min_unknown,
658 Decision {
659 accept: 2.0,
660 restrict: 2.0,
661 unknown: -1.0,
662 }
663 .clamp_min_unknown(0.3),
664 false,
665 accept = 1.0,
666 restrict = 1.0,
667 unknown = 0.3
668 );
669
670 test_decision!(
671 fill_unknown_zero,
672 Decision {
673 accept: 0.0,
674 restrict: 0.0,
675 unknown: 0.0,
676 }
677 .fill_unknown(),
678 true,
679 accept = 0.0,
680 restrict = 0.0,
681 unknown = 1.0
682 );
683
684 test_decision!(
685 fill_unknown_normal,
686 Decision {
687 accept: 0.25,
688 restrict: 0.25,
689 unknown: 0.1,
690 }
691 .fill_unknown(),
692 true,
693 accept = 0.25,
694 restrict = 0.25,
695 unknown = 0.5
696 );
697
698 test_decision!(
699 fill_unknown_over_one,
700 Decision {
701 accept: 0.5,
702 restrict: 0.5,
703 unknown: 0.5,
704 }
705 .fill_unknown(),
706 false,
707 accept = 0.5,
708 restrict = 0.5,
709 unknown = 0.5
710 );
711
712 test_decision!(
713 fill_unknown_mixed,
714 Decision {
715 accept: 2.0,
716 restrict: 2.0,
717 unknown: -3.5,
718 }
719 .fill_unknown(),
720 false,
721 accept = 2.0,
722 restrict = 2.0,
723 unknown = 0.0
724 );
725
726 test_decision!(
727 scale_zero,
728 Decision {
729 accept: 0.0,
730 restrict: 0.0,
731 unknown: 0.0,
732 }
733 .scale(),
734 true,
735 accept = 0.0,
736 restrict = 0.0,
737 unknown = 1.0
738 );
739
740 test_decision!(
741 scale_unknown,
742 Decision {
743 accept: 0.0,
744 restrict: 0.0,
745 unknown: 1.0,
746 }
747 .scale(),
748 true,
749 accept = 0.0,
750 restrict = 0.0,
751 unknown = 1.0
752 );
753
754 test_decision!(
755 scale_negative,
756 Decision {
757 accept: -1.0,
758 restrict: -1.0,
759 unknown: -1.0,
760 }
761 .scale(),
762 true,
763 accept = 0.0,
764 restrict = 0.0,
765 unknown = 1.0
766 );
767
768 test_decision!(
769 scale_double,
770 Decision {
771 accept: 2.0,
772 restrict: 2.0,
773 unknown: 2.0,
774 }
775 .scale(),
776 true,
777 accept = 0.3333333333333333,
778 restrict = 0.3333333333333333,
779 unknown = 0.3333333333333333
780 );
781
782 test_decision!(
783 scale_mixed,
784 Decision {
785 accept: 2.0,
786 restrict: 2.0,
787 unknown: -3.5,
788 }
789 .scale(),
790 true,
791 accept = 0.5,
792 restrict = 0.5,
793 unknown = 0.0
794 );
795
796 test_decision!(
797 weight_zero_by_zero,
798 Decision {
799 accept: 0.0,
800 restrict: 0.0,
801 unknown: 0.0,
802 }
803 .weight(0.0),
804 true,
805 accept = 0.0,
806 restrict = 0.0,
807 unknown = 1.0
808 );
809
810 test_decision!(
811 weight_zero_by_one,
812 Decision {
813 accept: 0.0,
814 restrict: 0.0,
815 unknown: 0.0,
816 }
817 .weight(1.0),
818 true,
819 accept = 0.0,
820 restrict = 0.0,
821 unknown = 1.0
822 );
823
824 test_decision!(
825 weight_one_by_zero,
826 Decision {
827 accept: 0.0,
828 restrict: 0.0,
829 unknown: 1.0,
830 }
831 .weight(0.0),
832 true,
833 accept = 0.0,
834 restrict = 0.0,
835 unknown = 1.0
836 );
837
838 test_decision!(
839 weight_one_by_one,
840 Decision {
841 accept: 0.0,
842 restrict: 0.0,
843 unknown: 1.0,
844 }
845 .weight(1.0),
846 true,
847 accept = 0.0,
848 restrict = 0.0,
849 unknown = 1.0
850 );
851
852 test_decision!(
853 weight_negative,
854 Decision {
855 accept: -1.0,
856 restrict: -1.0,
857 unknown: -1.0,
858 }
859 .weight(1.0),
860 true,
861 accept = 0.0,
862 restrict = 0.0,
863 unknown = 1.0
864 );
865
866 test_decision!(
867 weight_two_by_one,
868 Decision {
869 accept: 2.0,
870 restrict: 2.0,
871 unknown: 2.0,
872 }
873 .weight(1.0),
874 true,
875 accept = 0.50,
876 restrict = 0.50,
877 unknown = 0.0
878 );
879
880 test_decision!(
881 weight_two_by_two,
882 Decision {
883 accept: 2.0,
884 restrict: 2.0,
885 unknown: 2.0,
886 }
887 .weight(2.0),
888 true,
889 accept = 0.50,
890 restrict = 0.50,
891 unknown = 0.0
892 );
893
894 test_decision!(
895 weight_two_by_one_eighth,
896 Decision {
897 accept: 2.0,
898 restrict: 2.0,
899 unknown: 2.0,
900 }
901 .weight(0.125),
902 true,
903 accept = 0.25,
904 restrict = 0.25,
905 unknown = 0.5
906 );
907
908 test_decision!(
909 weight_one_eighth_by_two,
910 Decision {
911 accept: 0.125,
912 restrict: 0.125,
913 unknown: 0.75,
914 }
915 .weight(2.0),
916 true,
917 accept = 0.25,
918 restrict = 0.25,
919 unknown = 0.50
920 );
921
922 test_decision!(
923 pairwise_combine_simple,
924 Decision::pairwise_combine(
925 &Decision {
926 accept: 0.25,
927 restrict: 0.5,
928 unknown: 0.25,
929 },
930 &Decision {
931 accept: 0.25,
932 restrict: 0.1,
933 unknown: 0.65,
934 },
935 true,
936 ),
937 true,
938 accept = 0.338235294117647,
939 restrict = 0.4705882352941177,
940 unknown = 0.1911764705882353
941 );
942
943 test_decision!(
944 pairwise_combine_factored_out,
945 Decision::pairwise_combine(
946 &Decision {
947 accept: 0.25,
948 restrict: 0.5,
949 unknown: 0.25,
950 },
951 &Decision {
952 accept: 0.0,
953 restrict: 0.0,
954 unknown: 1.0,
955 },
956 true,
957 ),
958 true,
959 accept = 0.25,
960 restrict = 0.5,
961 unknown = 0.25
962 );
963
964 test_decision!(
965 pairwise_combine_certainty,
966 Decision::pairwise_combine(
967 &Decision {
968 accept: 0.25,
969 restrict: 0.5,
970 unknown: 0.25,
971 },
972 &Decision {
973 accept: 1.0,
974 restrict: 0.0,
975 unknown: 0.0,
976 },
977 true,
978 ),
979 true,
980 accept = 1.0,
981 restrict = 0.0,
982 unknown = 0.0
983 );
984
985 test_decision!(
986 combine_conjunctive_simple_with_unknown,
987 Decision::combine_conjunctive(&[
988 Decision {
989 accept: 0.35,
990 restrict: 0.20,
991 unknown: 0.45,
992 },
993 Decision {
994 accept: 0.0,
995 restrict: 0.0,
996 unknown: 1.0,
997 }
998 ]),
999 true,
1000 accept = 0.35,
1001 restrict = 0.2,
1002 unknown = 0.45
1003 );
1004
1005 test_decision!(
1006 combine_murphy_simple_with_unknown,
1007 Decision::combine_murphy(&[
1008 Decision {
1009 accept: 0.35,
1010 restrict: 0.20,
1011 unknown: 0.45,
1012 },
1013 Decision {
1014 accept: 0.0,
1015 restrict: 0.0,
1016 unknown: 1.0,
1017 }
1018 ]),
1019 true,
1020 accept = 0.2946891191709844,
1021 restrict = 0.16062176165803108,
1022 unknown = 0.5446891191709845
1023 );
1024
1025 #[test]
1026 fn test_combine_conjunctive_high_conflict() {
1027 let d = Decision::combine_conjunctive(&[
1028 Decision {
1029 accept: 1.0,
1030 restrict: 0.0,
1031 unknown: 0.0,
1032 },
1033 Decision {
1034 accept: 0.0,
1035 restrict: 1.0,
1036 unknown: 0.0,
1037 },
1038 ]);
1039 assert!(d.accept.is_nan());
1040 assert!(d.restrict.is_nan());
1041 assert!(d.unknown.is_nan());
1042 }
1043
1044 test_decision!(
1045 combine_murphy_high_conflict,
1046 Decision::combine_murphy(&[
1047 Decision {
1048 accept: 1.0,
1049 restrict: 0.0,
1050 unknown: 0.0,
1051 },
1052 Decision {
1053 accept: 0.0,
1054 restrict: 1.0,
1055 unknown: 0.0,
1056 }
1057 ]),
1058 true,
1059 accept = 0.5,
1060 restrict = 0.5,
1061 unknown = 0.0
1062 );
1063
1064 #[test]
1065 fn decision_is_accepted() {
1066 let d = Decision {
1067 accept: 0.25,
1068 restrict: 0.2,
1069 unknown: 0.55,
1070 };
1071 assert!(d.is_accepted(0.5));
1072
1073 let d = Decision {
1074 accept: 0.25,
1075 restrict: 0.25,
1076 unknown: 0.50,
1077 };
1078 assert!(d.is_accepted(0.5));
1079
1080 let d = Decision {
1081 accept: 0.2,
1082 restrict: 0.25,
1083 unknown: 0.55,
1084 };
1085 assert!(!d.is_accepted(0.5));
1086 }
1087
1088 #[test]
1089 fn decision_is_unknown() {
1090 let d = Decision {
1091 accept: 0.0,
1092 restrict: 0.2,
1093 unknown: 0.8,
1094 };
1095 assert!(!d.is_unknown());
1096
1097 let d = Decision {
1098 accept: 0.2,
1099 restrict: 0.0,
1100 unknown: 0.8,
1101 };
1102 assert!(!d.is_unknown());
1103
1104 let d = Decision {
1105 accept: 0.0,
1106 restrict: 0.0,
1107 unknown: 1.0,
1108 };
1109 assert!(d.is_unknown());
1110
1111 let d = Decision {
1112 accept: 0.0,
1113 restrict: 0.0,
1114 unknown: 0.1,
1115 };
1116 assert!(d.is_unknown());
1117 }
1118
1119 #[test]
1120 fn decision_outcome() -> Result<(), Box<dyn std::error::Error>> {
1121 let d = Decision {
1122 accept: 0.65,
1123 restrict: 0.0,
1124 unknown: 0.35,
1125 };
1126 let outcome = d.outcome(0.2, 0.4, 0.8)?;
1127 assert_eq!(outcome, Outcome::Trusted);
1128
1129 let d = Decision {
1130 accept: 0.45,
1131 restrict: 0.05,
1132 unknown: 0.5,
1133 };
1134 let outcome = d.outcome(0.2, 0.4, 0.8)?;
1135 assert_eq!(outcome, Outcome::Accepted);
1136
1137 let d = Decision {
1138 accept: 0.25,
1139 restrict: 0.2,
1140 unknown: 0.55,
1141 };
1142 let outcome = d.outcome(0.2, 0.4, 0.8)?;
1143 assert_eq!(outcome, Outcome::Suspected);
1144
1145 let d = Decision {
1146 accept: 0.05,
1147 restrict: 0.65,
1148 unknown: 0.3,
1149 };
1150 let outcome = d.outcome(0.2, 0.4, 0.8)?;
1151 assert_eq!(outcome, Outcome::Restricted);
1152
1153 Ok(())
1154 }
1155
1156 #[test]
1157 fn decision_conflict() -> Result<(), Box<dyn std::error::Error>> {
1158 let decisions = &[
1160 Decision {
1161 accept: 1.0,
1162 restrict: 0.0,
1163 unknown: 0.0,
1164 },
1165 Decision {
1166 accept: 0.0,
1167 restrict: 1.0,
1168 unknown: 0.0,
1169 },
1170 ];
1171 let conflict = Decision::conflict(decisions);
1172 assert_eq!(conflict, f64::INFINITY);
1173
1174 let decisions = &[
1176 Decision {
1177 accept: 1.0,
1178 restrict: 0.0,
1179 unknown: 0.0,
1180 },
1181 Decision {
1182 accept: 0.25,
1183 restrict: 0.0,
1184 unknown: 0.75,
1185 },
1186 Decision {
1187 accept: 0.5,
1188 restrict: 0.0,
1189 unknown: 0.5,
1190 },
1191 Decision {
1192 accept: 0.75,
1193 restrict: 0.0,
1194 unknown: 0.25,
1195 },
1196 Decision {
1197 accept: 0.0,
1198 restrict: 0.0,
1199 unknown: 1.0,
1200 },
1201 ];
1202 let conflict = Decision::conflict(decisions);
1203 assert_relative_eq!(conflict, 0.0, epsilon = 2.0 * f64::EPSILON);
1204
1205 let decisions = &[
1207 Decision {
1208 accept: 0.25,
1209 restrict: 0.0,
1210 unknown: 0.75,
1211 },
1212 Decision {
1213 accept: 0.0,
1214 restrict: 0.5,
1215 unknown: 0.5,
1216 },
1217 ];
1218 let conflict = Decision::conflict(decisions);
1219 assert_relative_eq!(conflict, 0.13353139262452263, epsilon = 2.0 * f64::EPSILON);
1221
1222 let decisions = &[
1224 Decision {
1225 accept: 0.25,
1226 restrict: 0.25,
1227 unknown: 0.50,
1228 },
1229 Decision {
1230 accept: 0.25,
1231 restrict: 0.0,
1232 unknown: 0.75,
1233 },
1234 Decision {
1235 accept: 0.0,
1236 restrict: 0.5,
1237 unknown: 0.5,
1238 },
1239 Decision {
1240 accept: 0.35,
1241 restrict: 0.20,
1242 unknown: 0.45,
1243 },
1244 ];
1245 let conflict = Decision::conflict(decisions);
1246 assert_relative_eq!(conflict, 0.5425743220805709, epsilon = 2.0 * f64::EPSILON);
1248
1249 let decisions = &[
1251 Decision {
1252 accept: 0.35,
1253 restrict: 0.20,
1254 unknown: 0.45,
1255 },
1256 Decision {
1257 accept: 0.35,
1258 restrict: 0.20,
1259 unknown: 0.45,
1260 },
1261 Decision {
1262 accept: 0.35,
1263 restrict: 0.20,
1264 unknown: 0.45,
1265 },
1266 Decision {
1267 accept: 0.35,
1268 restrict: 0.20,
1269 unknown: 0.45,
1270 },
1271 ];
1272 let conflict = Decision::conflict(decisions);
1273 assert_relative_eq!(conflict, 0.6031236779123568, epsilon = 2.0 * f64::EPSILON);
1275
1276 Ok(())
1277 }
1278}