1use crate::error::{CoreError, Result};
6
7#[aquamarine::aquamarine]
39pub fn softmax_into(logits: &[f64], out: &mut [f64], temperature: f64) -> Result<()> {
40 if logits.is_empty() {
41 return Err(CoreError::MathError {
42 message: "ロジット配列が空です。".to_string(),
43 });
44 }
45
46 if logits.len() != out.len() {
47 return Err(CoreError::MathError {
48 message: format!(
49 "出力バッファ長({out_len})がロジット長({logits_len})と一致しません。",
50 out_len = out.len(),
51 logits_len = logits.len()
52 ),
53 });
54 }
55
56 if !temperature.is_finite() || temperature <= 0.0 {
57 return Err(CoreError::MathError {
58 message: format!("温度パラメータは正の有限実数である必要があります: {temperature}。"),
59 });
60 }
61
62 if logits.len() == 1 {
64 if !logits[0].is_finite() {
65 return Err(CoreError::MathError {
66 message: "ロジットに非有限値(NaNまたは無限大)が含まれています。".to_string(),
67 });
68 }
69 out[0] = 1.0;
70 return Ok(());
71 }
72
73 let effective_tau = temperature.max(1e-4);
75
76 let mut max_logit = f64::NEG_INFINITY;
79 for &logit in logits {
80 if !logit.is_finite() {
81 return Err(CoreError::MathError {
82 message: "ロジットに非有限値(NaNまたは無限大)が含まれています。".to_string(),
83 });
84 }
85 if logit > max_logit {
86 max_logit = logit;
87 }
88 }
89
90 let mut sum_exp = 0.0;
94 for (i, &logit) in logits.iter().enumerate() {
95 let scaled = (logit - max_logit) / effective_tau;
96 let exp_val = scaled.exp();
97 out[i] = exp_val;
98 sum_exp += exp_val;
99 }
100
101 if sum_exp <= 0.0 || !sum_exp.is_finite() {
102 return Err(CoreError::MathError {
103 message: "ソフトマックスの分母が不正な値になりました。".to_string(),
104 });
105 }
106
107 for val in out.iter_mut() {
109 *val = (*val / sum_exp).clamp(0.0, 1.0);
110 }
111
112 Ok(())
113}
114
115pub fn softmax(logits: &[f64], temperature: f64) -> Result<Vec<f64>> {
128 let mut probs: Vec<f64> = vec![0.0; logits.len()];
129 softmax_into(logits, &mut probs, temperature)?;
130 Ok(probs)
131}
132
133pub fn normalized_entropy(probabilities: &[f64]) -> Result<f64> {
148 let k = probabilities.len();
149 if k == 0 {
150 return Err(CoreError::MathError {
151 message: "確率分布配列が空です。".to_string(),
152 });
153 }
154 if k == 1 {
155 if !probabilities[0].is_finite() {
156 return Err(CoreError::MathError {
157 message: "確率値に非有限値(NaNまたは無限大)が含まれています。".to_string(),
158 });
159 }
160 if probabilities[0] < 0.0 {
161 return Err(CoreError::MathError {
162 message: format!("負の確率値が含まれています: {}。", probabilities[0]),
163 });
164 }
165 return Ok(0.0);
166 }
167
168 let mut entropy = 0.0;
169 for &p in probabilities {
170 if !p.is_finite() {
171 return Err(CoreError::MathError {
172 message: "確率値に非有限値(NaNまたは無限大)が含まれています。".to_string(),
173 });
174 }
175 if p < 0.0 {
176 return Err(CoreError::MathError {
177 message: format!("負の確率値が含まれています: {p}。"),
178 });
179 }
180 if p > 0.0 {
181 entropy -= p * p.ln();
182 }
183 }
184
185 let max_entropy = (k as f64).ln();
186 if max_entropy <= 0.0 {
187 return Ok(0.0);
188 }
189
190 let h_norm = entropy / max_entropy;
191 Ok(h_norm.clamp(0.0, 1.0))
192}
193
194pub fn top_margin(probabilities: &[f64]) -> Result<f64> {
210 let k = probabilities.len();
211 if k == 0 {
212 return Err(CoreError::MathError {
213 message: "確率分布配列が空です。".to_string(),
214 });
215 }
216 if k == 1 {
217 if !probabilities[0].is_finite() {
218 return Err(CoreError::MathError {
219 message: "確率値に非有限値(NaNまたは無限大)が含まれています。".to_string(),
220 });
221 }
222 if probabilities[0] < 0.0 {
223 return Err(CoreError::MathError {
224 message: format!("負の確率値が含まれています: {}。", probabilities[0]),
225 });
226 }
227 return Ok(1.0);
228 }
229
230 let mut max1 = f64::NEG_INFINITY;
231 let mut max2 = f64::NEG_INFINITY;
232
233 for &p in probabilities {
234 if !p.is_finite() {
235 return Err(CoreError::MathError {
236 message: "確率値に非有限値(NaNまたは無限大)が含まれています。".to_string(),
237 });
238 }
239 if p < 0.0 {
240 return Err(CoreError::MathError {
241 message: format!("負の確率値が含まれています: {p}。"),
242 });
243 }
244
245 if p > max1 {
246 max2 = max1;
247 max1 = p;
248 } else if p > max2 {
249 max2 = p;
250 }
251 }
252
253 let margin = max1 - max2;
254 Ok(margin.clamp(0.0, 1.0))
255}
256
257pub fn composite_confidence(probabilities: &[f64]) -> Result<f64> {
274 let k = probabilities.len();
275 if k == 0 {
276 return Err(CoreError::MathError {
277 message: "確率分布配列が空です。".to_string(),
278 });
279 }
280 if k == 1 {
281 if !probabilities[0].is_finite() {
282 return Err(CoreError::MathError {
283 message: "確率値に非有限値(NaNまたは無限大)が含まれています。".to_string(),
284 });
285 }
286 if probabilities[0] < 0.0 {
287 return Err(CoreError::MathError {
288 message: format!("負の確率値が含まれています: {}。", probabilities[0]),
289 });
290 }
291 return Ok(1.0);
292 }
293
294 let h_norm = normalized_entropy(probabilities)?;
295 let margin = top_margin(probabilities)?;
296
297 let score = (1.0 - h_norm) * margin;
298 Ok(score.clamp(0.0, 1.0))
299}
300
301pub fn normalized_entropy_confidence(probabilities: &[f64]) -> Result<f64> {
309 let h_norm = normalized_entropy(probabilities)?;
310 Ok((1.0 - h_norm).clamp(0.0, 1.0))
311}
312
313pub fn argmax(values: &[f64]) -> Result<usize> {
325 if values.is_empty() {
326 return Err(CoreError::MathError {
327 message: "探索対象のスライスが空です。".to_string(),
328 });
329 }
330
331 let mut best_index = 0;
332 let mut max_val = f64::NEG_INFINITY;
333
334 for (i, &val) in values.iter().enumerate() {
335 if !val.is_finite() {
336 return Err(CoreError::MathError {
337 message: "スライス内に非有限値(NaNまたは無限大)が含まれています。".to_string(),
338 });
339 }
340 if val > max_val {
342 max_val = val;
343 best_index = i;
344 }
345 }
346
347 Ok(best_index)
348}
349
350pub fn expected_score(probabilities: &[f64]) -> Result<f64> {
354 if probabilities.len() < 2 {
355 return Err(CoreError::MathError {
356 message: "Score の確率分布は 2 段階以上必要です。".to_string(),
357 });
358 }
359
360 let mut score = 0.0;
361 for (k, &p) in probabilities.iter().enumerate() {
362 if p < 0.0 {
363 return Err(CoreError::MathError {
364 message: format!("負の確率値が含まれています: {p}。"),
365 });
366 }
367 score += (k as f64) * p;
368 }
369
370 Ok(score)
371}
372
373pub fn score_variance(probabilities: &[f64]) -> Result<f64> {
378 let mean = expected_score(probabilities)?;
379
380 let mut variance = 0.0;
381 for (k, &p) in probabilities.iter().enumerate() {
382 let diff = (k as f64) - mean;
383 variance += diff * diff * p;
384 }
385
386 Ok(variance.max(0.0))
388}
389
390pub fn normalized_variance_confidence(probabilities: &[f64]) -> Result<f64> {
407 let m = probabilities.len();
408 if m < 2 {
409 return Err(CoreError::MathError {
410 message: "Score の確信度計算には 2 段階以上の確率分布が必要です。".to_string(),
411 });
412 }
413
414 let var = score_variance(probabilities)?;
415 let max_var = ((m - 1) as f64).powi(2) / 4.0;
416
417 if max_var <= 0.0 {
418 return Ok(1.0);
419 }
420
421 let confidence = 1.0 - (var / max_var);
422 Ok(confidence.clamp(0.0, 1.0))
423}
424
425pub fn score_variance_ratio(probabilities: &[f64]) -> Result<f64> {
437 let m = probabilities.len();
438 if m < 2 {
439 return Err(CoreError::MathError {
440 message: "Score の分散比率計算には 2 段階以上の確率分布が必要です。".to_string(),
441 });
442 }
443
444 let var = score_variance(probabilities)?;
445 let max_var = ((m - 1) as f64).powi(2) / 4.0;
446
447 if max_var <= 0.0 {
448 return Ok(0.0);
449 }
450
451 let ratio = var / max_var;
452 Ok(ratio.clamp(0.0, 1.0))
453}
454
455pub fn bimodality_coefficient(probabilities: &[f64]) -> Result<f64> {
476 let m = probabilities.len();
477 if m < 2 {
478 return Err(CoreError::MathError {
479 message: "双峰性係数の計算には 2 段階以上の確率分布が必要です。".to_string(),
480 });
481 }
482
483 let mean = expected_score(probabilities)?;
484
485 let mut variance = 0.0;
487 let mut mu3 = 0.0;
489 let mut mu4 = 0.0;
491
492 for (k, &p) in probabilities.iter().enumerate() {
493 if p < 0.0 {
494 return Err(CoreError::MathError {
495 message: format!("負の確率値が含まれています: {p}。"),
496 });
497 }
498 let diff = (k as f64) - mean;
499 let diff2 = diff * diff;
500 variance += diff2 * p;
501 mu3 += diff2 * diff * p;
502 mu4 += diff2 * diff2 * p;
503 }
504
505 if variance < 1e-6 {
507 return Ok(0.0);
508 }
509
510 let sigma = variance.sqrt();
511 let sigma3 = variance * sigma;
512 let sigma4 = variance * variance;
513
514 if sigma4 <= 0.0 {
515 return Ok(0.0);
516 }
517
518 let gamma = mu3 / sigma3;
519 let kurtosis = mu4 / sigma4;
520
521 if kurtosis <= 0.0 {
522 return Ok(0.0);
523 }
524
525 let bc = (gamma * gamma + 1.0) / kurtosis;
526 Ok(bc.clamp(0.0, 1.0))
527}
528
529pub fn noul_probability(logit_true: f64, logit_false: f64, temperature: f64) -> Result<f64> {
544 if !temperature.is_finite() || temperature <= 0.0 {
545 return Err(CoreError::MathError {
546 message: format!("温度パラメータは正の実数である必要があります: {temperature}。"),
547 });
548 }
549
550 if !logit_true.is_finite() || !logit_false.is_finite() {
551 return Err(CoreError::MathError {
552 message: "ロジットに非有限値(NaNまたは無限大)が含まれています。".to_string(),
553 });
554 }
555
556 let effective_tau = temperature.max(1e-4);
557 let diff = (logit_true - logit_false) / effective_tau;
558
559 let prob = if diff >= 0.0 {
560 1.0 / (1.0 + (-diff).exp())
561 } else {
562 let exp_diff = diff.exp();
563 exp_diff / (1.0 + exp_diff)
564 };
565
566 Ok(prob.clamp(0.0, 1.0))
567}
568
569pub fn noul_confidence(probability: f64) -> Result<f64> {
576 if !probability.is_finite() || !(0.0..=1.0).contains(&probability) {
577 return Err(CoreError::MathError {
578 message: format!("確率は [0.0, 1.0] の有限実数である必要があります: {probability}。"),
579 });
580 }
581
582 let conf = (2.0 * probability - 1.0).abs();
583 Ok(conf.clamp(0.0, 1.0))
584}
585
586pub fn free_energy(logits: &[f64], temperature: f64) -> Result<f64> {
604 if logits.is_empty() {
605 return Err(CoreError::MathError {
606 message: "ロジット配列が空です。".to_string(),
607 });
608 }
609
610 if !temperature.is_finite() || temperature <= 0.0 {
611 return Err(CoreError::MathError {
612 message: format!("温度パラメータは正の有限実数である必要があります: {temperature}。"),
613 });
614 }
615
616 if logits.len() == 1 {
618 if !logits[0].is_finite() {
619 return Err(CoreError::MathError {
620 message: "ロジットに非有限値(NaNまたは無限大)が含まれています。".to_string(),
621 });
622 }
623 return Ok(-logits[0]);
624 }
625
626 let effective_tau = temperature.max(1e-4);
627
628 let mut max_logit = f64::NEG_INFINITY;
630 for &logit in logits {
631 if !logit.is_finite() {
632 return Err(CoreError::MathError {
633 message: "ロジットに非有限値(NaNまたは無限大)が含まれています。".to_string(),
634 });
635 }
636 if logit > max_logit {
637 max_logit = logit;
638 }
639 }
640
641 let mut sum_exp = 0.0;
645 for &logit in logits {
646 let scaled = (logit - max_logit) / effective_tau;
647 sum_exp += scaled.exp();
648 }
649
650 let energy = -max_logit - effective_tau * sum_exp.ln();
651 Ok(energy)
652}
653
654pub fn normalized_free_energy(logits: &[f64], temperature: f64) -> Result<f64> {
672 let k = logits.len();
673 let raw_energy = free_energy(logits, temperature)?;
674 if k <= 1 {
675 return Ok(raw_energy);
676 }
677
678 let effective_tau = temperature.max(1e-4);
679 let shift = effective_tau * (k as f64).ln();
680 Ok(raw_energy + shift)
681}
682
683#[cfg(test)]
684mod tests {
685 use super::*;
686
687 #[test]
688 fn test_softmax_basic() {
689 let logits = vec![2.0, 1.0, 0.1];
690 let probs = softmax(&logits, 1.0).unwrap();
691 assert_eq!(probs.len(), 3);
692 let sum: f64 = probs.iter().sum();
693 assert!((sum - 1.0).abs() < 1e-6);
694 assert!(probs[0] > probs[1]);
695 assert!(probs[1] > probs[2]);
696 }
697
698 #[test]
699 fn test_softmax_into_basic() {
700 let logits = vec![2.0, 1.0, 0.1];
701 let mut probs = vec![0.0; 3];
702 softmax_into(&logits, &mut probs, 1.0).unwrap();
703 let sum: f64 = probs.iter().sum();
704 assert!((sum - 1.0).abs() < 1e-6);
705 assert!(probs[0] > probs[1]);
706 assert!(probs[1] > probs[2]);
707 }
708
709 #[test]
710 fn test_softmax_extreme_logits() {
711 let logits = vec![10000.0, 0.0, -10000.0];
713 let probs = softmax(&logits, 1.0).unwrap();
714 assert_eq!(probs.len(), 3);
715 assert!((probs[0] - 1.0).abs() < 1e-6);
716 assert_eq!(probs[1], 0.0);
717 assert_eq!(probs[2], 0.0);
718 let sum: f64 = probs.iter().sum();
719 assert!((sum - 1.0).abs() < 1e-6);
720 }
721
722 #[test]
723 fn test_softmax_identical_logits() {
724 let logits = vec![5.0, 5.0, 5.0, 5.0];
726 let probs = softmax(&logits, 1.0).unwrap();
727 for &p in &probs {
728 assert!((p - 0.25).abs() < 1e-6);
729 }
730 let sum: f64 = probs.iter().sum();
731 assert!((sum - 1.0).abs() < 1e-6);
732 }
733
734 #[test]
735 fn test_softmax_single_candidate() {
736 let logits = vec![std::f64::consts::PI];
738 let probs = softmax(&logits, 0.5).unwrap();
739 assert_eq!(probs.len(), 1);
740 assert_eq!(probs[0], 1.0);
741
742 let mut out = vec![0.0];
743 softmax_into(&logits, &mut out, 2.0).unwrap();
744 assert_eq!(out[0], 1.0);
745 }
746
747 #[test]
748 fn test_softmax_temperature_limits() {
749 let logits = vec![2.0, 1.0, 0.0];
750
751 let probs_high = softmax(&logits, 1000.0).unwrap();
753 for &p in &probs_high {
754 assert!((p - 1.0 / 3.0).abs() < 1e-2);
755 }
756
757 let probs_low = softmax(&logits, 1e-4).unwrap();
759 assert!((probs_low[0] - 1.0).abs() < 1e-6);
760 assert!(probs_low[1] < 1e-6);
761 assert!(probs_low[2] < 1e-6);
762
763 let probs_micro = softmax(&logits, 1e-9).unwrap();
765 assert!((probs_micro[0] - 1.0).abs() < 1e-6);
766 }
767
768 #[test]
769 fn test_softmax_errors() {
770 let empty_logits: Vec<f64> = vec![];
772 assert!(softmax(&empty_logits, 1.0).is_err());
773
774 let logits = vec![1.0, 2.0];
776 let mut short_out = vec![0.0];
777 assert!(softmax_into(&logits, &mut short_out, 1.0).is_err());
778
779 assert!(softmax(&logits, 0.0).is_err());
781 assert!(softmax(&logits, -1.0).is_err());
782 assert!(softmax(&logits, f64::NAN).is_err());
783 assert!(softmax(&logits, f64::INFINITY).is_err());
784
785 let nan_logits = vec![1.0, f64::NAN, 3.0];
787 assert!(softmax(&nan_logits, 1.0).is_err());
788 let inf_logits = vec![1.0, f64::INFINITY, 3.0];
789 assert!(softmax(&inf_logits, 1.0).is_err());
790 let neg_inf_logits = vec![f64::NEG_INFINITY, 1.0];
791 assert!(softmax(&neg_inf_logits, 1.0).is_err());
792 }
793
794 #[test]
795 fn test_normalized_entropy_confidence() {
796 let uniform = vec![0.25, 0.25, 0.25, 0.25];
798 let conf_uniform = normalized_entropy_confidence(&uniform).unwrap();
799 assert!(conf_uniform < 1e-6);
800
801 let peaked = vec![1.0, 0.0, 0.0, 0.0];
803 let conf_peaked = normalized_entropy_confidence(&peaked).unwrap();
804 assert!((conf_peaked - 1.0).abs() < 1e-6);
805 }
806
807 #[test]
808 fn test_expected_score() {
809 let probs = vec![0.1, 0.2, 0.7]; let score = expected_score(&probs).unwrap();
811 let expected = 0.0 * 0.1 + 1.0 * 0.2 + 2.0 * 0.7;
812 assert!((score - expected).abs() < 1e-6);
813 }
814
815 #[test]
816 fn test_noul_probability() {
817 let prob_equal = noul_probability(1.0, 1.0, 1.0).unwrap();
818 assert!((prob_equal - 0.5).abs() < 1e-6);
819
820 let prob_high = noul_probability(10.0, 0.0, 1.0).unwrap();
821 assert!(prob_high > 0.99);
822
823 assert!(noul_probability(1.0, 0.0, 0.0).is_err());
825 assert!(noul_probability(1.0, 0.0, -1.0).is_err());
826 assert!(noul_probability(1.0, 0.0, f64::NAN).is_err());
827 }
828
829 #[test]
830 fn test_argmax() {
831 let values = vec![0.1, 0.8, 0.4];
833 assert_eq!(argmax(&values).unwrap(), 1);
834
835 let tied = vec![0.5, 0.9, 0.9, 0.2];
837 assert_eq!(argmax(&tied).unwrap(), 1);
838
839 let first_max = vec![10.0, 5.0, 2.0];
841 assert_eq!(argmax(&first_max).unwrap(), 0);
842
843 let last_max = vec![1.0, 2.0, 3.0];
845 assert_eq!(argmax(&last_max).unwrap(), 2);
846
847 let empty: Vec<f64> = vec![];
849 assert!(argmax(&empty).is_err());
850
851 let nan_vals = vec![1.0, f64::NAN, 2.0];
853 assert!(argmax(&nan_vals).is_err());
854 let inf_vals = vec![1.0, f64::INFINITY];
855 assert!(argmax(&inf_vals).is_err());
856 }
857
858 #[test]
859 fn test_score_variance_and_confidence() {
860 let one_hot = vec![0.0, 0.0, 1.0, 0.0];
862 let var_one_hot = score_variance(&one_hot).unwrap();
863 let conf_one_hot = normalized_variance_confidence(&one_hot).unwrap();
864 assert!(var_one_hot < 1e-6);
865 assert!((conf_one_hot - 1.0).abs() < 1e-6);
866
867 let bimodal = vec![0.5, 0.0, 0.0, 0.5];
873 let var_bimodal = score_variance(&bimodal).unwrap();
874 let conf_bimodal = normalized_variance_confidence(&bimodal).unwrap();
875 assert!((var_bimodal - 2.25).abs() < 1e-6);
876 assert!(conf_bimodal < 1e-6);
877
878 let adjacent = vec![0.0, 0.5, 0.5, 0.0];
883 let var_adjacent = score_variance(&adjacent).unwrap();
884 let conf_adjacent = normalized_variance_confidence(&adjacent).unwrap();
885 assert!((var_adjacent - 0.25).abs() < 1e-6);
886 assert!((conf_adjacent - (8.0 / 9.0)).abs() < 1e-6);
887
888 let ent_bimodal = normalized_entropy_confidence(&bimodal).unwrap();
892 let ent_adjacent = normalized_entropy_confidence(&adjacent).unwrap();
893 assert!((ent_bimodal - ent_adjacent).abs() < 1e-6);
894 assert!(conf_adjacent > conf_bimodal);
895
896 let single_level = vec![1.0];
898 assert!(score_variance(&single_level).is_err());
899 assert!(normalized_variance_confidence(&single_level).is_err());
900 }
901
902 #[test]
903 fn test_noul_stability_and_confidence() {
904 let prob_huge_pos = noul_probability(10000.0, 0.0, 1.0).unwrap();
906 assert!((prob_huge_pos - 1.0).abs() < 1e-6);
907
908 let prob_huge_neg = noul_probability(-10000.0, 0.0, 1.0).unwrap();
909 assert!(prob_huge_neg < 1e-6);
910
911 assert!((noul_confidence(0.5).unwrap() - 0.0).abs() < 1e-6);
914 assert!((noul_confidence(1.0).unwrap() - 1.0).abs() < 1e-6);
916 assert!((noul_confidence(0.0).unwrap() - 1.0).abs() < 1e-6);
918 assert!((noul_confidence(0.8).unwrap() - 0.6).abs() < 1e-6);
920
921 assert!(noul_confidence(-0.1).is_err());
923 assert!(noul_confidence(1.1).is_err());
924 assert!(noul_confidence(f64::NAN).is_err());
925 }
926
927 #[test]
928 fn test_softmax_non_finite_positions() {
929 let test_cases = vec![
931 vec![f64::NAN, 1.0, 2.0],
932 vec![1.0, f64::NAN, 2.0],
933 vec![1.0, 2.0, f64::NAN],
934 vec![f64::INFINITY, 1.0, 2.0],
935 vec![1.0, f64::INFINITY, 2.0],
936 vec![1.0, 2.0, f64::INFINITY],
937 vec![f64::NEG_INFINITY, 1.0, 2.0],
938 vec![1.0, f64::NEG_INFINITY, 2.0],
939 vec![1.0, 2.0, f64::NEG_INFINITY],
940 ];
941
942 for (i, case) in test_cases.iter().enumerate() {
943 let res = softmax(case, 1.0);
944 assert!(
945 res.is_err(),
946 "ケース {i} で非有限値がすり抜けました: {case:?}。"
947 );
948 }
949
950 assert!(softmax(&[f64::NAN], 1.0).is_err());
952 assert!(softmax(&[f64::INFINITY], 1.0).is_err());
953 assert!(softmax(&[f64::NEG_INFINITY], 1.0).is_err());
954
955 let all_neg_inf = vec![f64::NEG_INFINITY, f64::NEG_INFINITY];
957 assert!(softmax(&all_neg_inf, 1.0).is_err());
958 }
959
960 #[test]
961 fn test_softmax_mask_value_convergence() {
962 let logits = vec![3.0, 1.0, -10000.0];
966 let probs = softmax(&logits, 1.0).unwrap();
967 assert_eq!(probs.len(), 3);
968
969 assert_eq!(probs[2], 0.0);
971
972 let valid_sum = probs[0] + probs[1];
974 assert!((valid_sum - 1.0).abs() < 1e-6);
975
976 let expected_ratio = (2.0_f64).exp(); let actual_ratio = probs[0] / probs[1];
979 assert!((actual_ratio - expected_ratio).abs() < 1e-4);
980 }
981
982 #[test]
983 fn test_argmax_non_finite_positions() {
984 let test_cases = vec![
986 vec![f64::NAN, 0.5, 0.2],
987 vec![0.5, f64::NAN, 0.2],
988 vec![0.5, 0.2, f64::NAN],
989 vec![f64::INFINITY, 0.5, 0.2],
990 vec![0.5, f64::INFINITY, 0.2],
991 vec![0.5, 0.2, f64::INFINITY],
992 vec![f64::NEG_INFINITY, 0.5, 0.2],
993 vec![0.5, f64::NEG_INFINITY, 0.2],
994 vec![0.5, 0.2, f64::NEG_INFINITY],
995 ];
996
997 for (i, case) in test_cases.iter().enumerate() {
998 assert!(
999 argmax(case).is_err(),
1000 "argmax ケース {i} で非有限値がすり抜けました: {case:?}。"
1001 );
1002 }
1003 }
1004
1005 #[test]
1006 fn test_noul_non_finite_inputs() {
1007 assert!(noul_probability(f64::NAN, 0.0, 1.0).is_err());
1009 assert!(noul_probability(0.0, f64::NAN, 1.0).is_err());
1010 assert!(noul_probability(f64::INFINITY, 0.0, 1.0).is_err());
1011 assert!(noul_probability(0.0, f64::INFINITY, 1.0).is_err());
1012 assert!(noul_probability(f64::NEG_INFINITY, 0.0, 1.0).is_err());
1013 assert!(noul_probability(0.0, f64::NEG_INFINITY, 1.0).is_err());
1014 }
1015
1016 #[test]
1017 fn test_normalized_entropy() {
1018 assert_eq!(normalized_entropy(&[1.0]).unwrap(), 0.0);
1020
1021 let uniform2 = vec![0.5, 0.5];
1023 assert!((normalized_entropy(&uniform2).unwrap() - 1.0).abs() < 1e-6);
1024
1025 let uniform4 = vec![0.25, 0.25, 0.25, 0.25];
1026 assert!((normalized_entropy(&uniform4).unwrap() - 1.0).abs() < 1e-6);
1027
1028 let peaked = vec![1.0, 0.0, 0.0, 0.0];
1030 assert!(normalized_entropy(&peaked).unwrap() < 1e-6);
1031
1032 let p3 = vec![0.7, 0.2, 0.1];
1037 let h_norm3 = normalized_entropy(&p3).unwrap();
1038 assert!((h_norm3 - 0.7298466).abs() < 1e-5);
1039
1040 assert!(normalized_entropy(&[]).is_err());
1042 assert!(normalized_entropy(&[-0.1, 1.1]).is_err());
1043 assert!(normalized_entropy(&[0.5, f64::NAN]).is_err());
1044 assert!(normalized_entropy(&[f64::INFINITY, 0.5]).is_err());
1045 assert!(normalized_entropy(&[f64::NAN]).is_err());
1046 }
1047
1048 #[test]
1049 fn test_top_margin() {
1050 assert_eq!(top_margin(&[1.0]).unwrap(), 1.0);
1052
1053 let p_clear = vec![0.7, 0.2, 0.1];
1055 assert!((top_margin(&p_clear).unwrap() - 0.5).abs() < 1e-6);
1056
1057 let p_tied = vec![0.4, 0.4, 0.2];
1059 assert!(top_margin(&p_tied).unwrap() < 1e-6);
1060
1061 let p_all_equal = vec![0.25, 0.25, 0.25, 0.25];
1062 assert!(top_margin(&p_all_equal).unwrap() < 1e-6);
1063
1064 let p_onehot = vec![1.0, 0.0, 0.0];
1066 assert!((top_margin(&p_onehot).unwrap() - 1.0).abs() < 1e-6);
1067
1068 let p_last = vec![0.1, 0.2, 0.7];
1070 assert!((top_margin(&p_last).unwrap() - 0.5).abs() < 1e-6);
1071
1072 assert!(top_margin(&[]).is_err());
1074 assert!(top_margin(&[-0.5, 1.5]).is_err());
1075 assert!(top_margin(&[0.5, f64::NAN]).is_err());
1076 assert!(top_margin(&[f64::INFINITY, 0.0]).is_err());
1077 assert!(top_margin(&[f64::NAN]).is_err());
1078 }
1079
1080 #[test]
1081 fn test_composite_confidence() {
1082 assert_eq!(composite_confidence(&[1.0]).unwrap(), 1.0);
1084
1085 let p_onehot = vec![1.0, 0.0, 0.0];
1087 assert!((composite_confidence(&p_onehot).unwrap() - 1.0).abs() < 1e-6);
1088
1089 let p_uniform = vec![0.25, 0.25, 0.25, 0.25];
1091 assert!(composite_confidence(&p_uniform).unwrap() < 1e-6);
1092
1093 let p_collapse = vec![0.49, 0.49, 0.02];
1097 assert!(composite_confidence(&p_collapse).unwrap() < 1e-6);
1098
1099 let p_high = vec![0.85, 0.10, 0.05];
1106 let s_conf = composite_confidence(&p_high).unwrap();
1107 assert!(s_conf > 0.35 && s_conf < 0.45);
1108
1109 let p_extreme = vec![0.98, 0.01, 0.01];
1115 let s_extreme = composite_confidence(&p_extreme).unwrap();
1116 assert!(s_extreme > 0.80);
1117
1118 assert!(composite_confidence(&[]).is_err());
1120 assert!(composite_confidence(&[-0.1, 1.1]).is_err());
1121 assert!(composite_confidence(&[0.5, f64::NAN]).is_err());
1122 }
1123
1124 #[test]
1125 fn test_bimodality_coefficient() {
1126 let p_onehot = vec![1.0, 0.0, 0.0, 0.0];
1128 assert_eq!(bimodality_coefficient(&p_onehot).unwrap(), 0.0);
1129
1130 let p_bimodal = vec![0.5, 0.0, 0.0, 0.5];
1133 let bc_bimodal = bimodality_coefficient(&p_bimodal).unwrap();
1134 assert!((bc_bimodal - 1.0).abs() < 1e-6);
1135
1136 let p_bimodal_5 = vec![0.5, 0.0, 0.0, 0.0, 0.5];
1138 let bc_bimodal_5 = bimodality_coefficient(&p_bimodal_5).unwrap();
1139 assert!((bc_bimodal_5 - 1.0).abs() < 1e-6);
1140
1141 let p_uniform_4 = vec![0.25, 0.25, 0.25, 0.25];
1143 let bc_uniform_4 = bimodality_coefficient(&p_uniform_4).unwrap();
1144 assert!(bc_uniform_4 > 0.55 && bc_uniform_4 < 0.65);
1145
1146 let p_uniform_10 = vec![0.1; 10];
1147 let bc_uniform_10 = bimodality_coefficient(&p_uniform_10).unwrap();
1148 assert!(bc_uniform_10 > 0.55 && bc_uniform_10 < 0.60);
1149
1150 let p_unimodal = vec![0.05, 0.2, 0.5, 0.2, 0.05];
1152 let bc_unimodal = bimodality_coefficient(&p_unimodal).unwrap();
1153 assert!(bc_unimodal < 0.555);
1154
1155 let p_adjacent = vec![0.0, 0.5, 0.5, 0.0];
1158 let var_ratio = score_variance_ratio(&p_adjacent).unwrap();
1159 assert!(var_ratio < 0.20);
1160
1161 assert!(bimodality_coefficient(&[]).is_err());
1163 assert!(bimodality_coefficient(&[1.0]).is_err());
1164 assert!(bimodality_coefficient(&[-0.1, 1.1]).is_err());
1165 }
1166
1167 #[test]
1168 fn test_score_variance_ratio() {
1169 let p_bimodal = vec![0.5, 0.0, 0.0, 0.5];
1171 let ratio = score_variance_ratio(&p_bimodal).unwrap();
1172 assert!((ratio - 1.0).abs() < 1e-6);
1173
1174 let p_onehot = vec![0.0, 1.0, 0.0, 0.0];
1176 let ratio_onehot = score_variance_ratio(&p_onehot).unwrap();
1177 assert!(ratio_onehot < 1e-6);
1178
1179 assert!(score_variance_ratio(&[]).is_err());
1181 assert!(score_variance_ratio(&[1.0]).is_err());
1182 }
1183
1184 #[test]
1185 fn test_free_energy_basic() {
1186 let logits = vec![2.0, 1.0, 0.0];
1187 let t = 1.0;
1188 let fe = free_energy(&logits, t).unwrap();
1189 let expected = -((2.0f64).exp() + (1.0f64).exp() + (0.0f64).exp()).ln();
1191 assert!((fe - expected).abs() < 1e-9);
1192
1193 let fe_single = free_energy(&[3.5], 1.0).unwrap();
1195 assert!((fe_single - (-3.5)).abs() < 1e-9);
1196 }
1197
1198 #[test]
1199 fn test_normalized_free_energy_uniform() {
1200 for &k in &[2, 3, 5, 10, 50] {
1202 let logits_zero = vec![0.0; k];
1203 let nfe_zero = normalized_free_energy(&logits_zero, 1.0).unwrap();
1204 assert!(
1205 nfe_zero.abs() < 1e-9,
1206 "k={k} でロジットが 0 のとき E_norm は 0.0 であるべき"
1207 );
1208
1209 let c = 2.5;
1210 let logits_const = vec![c; k];
1211 let nfe_const = normalized_free_energy(&logits_const, 1.2).unwrap();
1212 assert!(
1213 (nfe_const - (-c)).abs() < 1e-9,
1214 "k={k} でロジットが c のとき E_norm は -c であるべき"
1215 );
1216 }
1217 }
1218
1219 #[test]
1220 fn test_free_energy_extreme_logits() {
1221 let logits = vec![1000.0, 999.0, 998.0];
1223 let fe = free_energy(&logits, 1.0).unwrap();
1224 assert!(fe.is_finite());
1225 assert!(fe < -999.0);
1227
1228 let nfe = normalized_free_energy(&logits, 1.0).unwrap();
1229 assert!(nfe.is_finite());
1230 assert!(nfe < -998.0);
1232 }
1233
1234 #[test]
1235 fn test_free_energy_ood_vs_id() {
1236 let t = 1.0;
1237 let id_logits = vec![5.0, 0.2, -0.5];
1239 let id_energy = normalized_free_energy(&id_logits, t).unwrap();
1240 assert!(id_energy < -3.5);
1241
1242 let ood_logits = vec![-4.0, -4.5, -5.0];
1244 let ood_energy = normalized_free_energy(&ood_logits, t).unwrap();
1245 assert!(ood_energy > 3.0);
1246
1247 assert!(ood_energy > id_energy + 6.0);
1249 }
1250
1251 #[test]
1252 fn test_free_energy_errors_and_edge_cases() {
1253 assert!(free_energy(&[], 1.0).is_err());
1255 assert!(normalized_free_energy(&[], 1.0).is_err());
1256
1257 assert!(free_energy(&[1.0, 2.0], 0.0).is_err());
1259 assert!(free_energy(&[1.0, 2.0], -1.0).is_err());
1260 assert!(free_energy(&[1.0, 2.0], f64::NAN).is_err());
1261
1262 assert!(free_energy(&[1.0, f64::NAN], 1.0).is_err());
1264 assert!(free_energy(&[1.0, f64::INFINITY], 1.0).is_err());
1265 }
1266}