aprender-core 0.31.2

Next-generation machine learning library in pure Rust
//! FALSIFY contract tests for active learning strategies.
//!
//! Verifies mathematical invariants of uncertainty, margin, entropy,
//! and query-by-committee scoring functions.

// CONTRACT: active-learning-v1.yaml
// HASH: sha256:a1b2c3d4e5f67890
// Generated by: pv probar --binding
// DO NOT EDIT — regenerate with `pv probar --binding`

use aprender::active_learning::{
    EntropySampling, MarginSampling, QueryByCommittee, QueryStrategy, UncertaintySampling,
};
use aprender::primitives::Vector;
use proptest::prelude::*;

/// Generate a normalized probability vector of given length using LCG.
fn make_prob_vec(rng: &mut u64, n_classes: usize) -> Vector<f32> {
    let mut raw = Vec::with_capacity(n_classes);
    for _ in 0..n_classes {
        *rng = rng.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
        raw.push((*rng >> 33) as f32 / u32::MAX as f32 + 0.001); // avoid zero
    }
    let sum: f32 = raw.iter().sum();
    let normalized: Vec<f32> = raw.iter().map(|x| x / sum).collect();
    Vector::from_vec(normalized)
}

proptest! {
    #![proptest_config(ProptestConfig::with_cases(256))]

    // FALSIFY-AL-001: Uncertainty score in [0, 1]
    #[test]
    fn prop_uncertainty_score_bounded(
        n_samples in 1usize..10,
        n_classes in 2usize..6,
        seed in 0u64..10000,
    ) {
        let mut rng = seed;
        let predictions: Vec<Vector<f32>> = (0..n_samples)
            .map(|_| make_prob_vec(&mut rng, n_classes))
            .collect();

        let strategy = UncertaintySampling::new();
        let scores = strategy.score(&predictions);

        prop_assert_eq!(scores.len(), predictions.len());
        for (i, &s) in scores.iter().enumerate() {
            prop_assert!(
                s >= 0.0 && s <= 1.0,
                "FALSIFY-AL-001: uncertainty[{}] = {}, expected in [0, 1]",
                i, s
            );
        }
    }

    // FALSIFY-AL-002: Margin score in [0, 1]
    #[test]
    fn prop_margin_score_bounded(
        n_samples in 1usize..10,
        n_classes in 2usize..6,
        seed in 0u64..10000,
    ) {
        let mut rng = seed;
        let predictions: Vec<Vector<f32>> = (0..n_samples)
            .map(|_| make_prob_vec(&mut rng, n_classes))
            .collect();

        let strategy = MarginSampling::new();
        let scores = strategy.score(&predictions);

        prop_assert_eq!(scores.len(), predictions.len());
        for (i, &s) in scores.iter().enumerate() {
            prop_assert!(
                s >= 0.0 && s <= 1.0,
                "FALSIFY-AL-002: margin[{}] = {}, expected in [0, 1]",
                i, s
            );
        }
    }

    // FALSIFY-AL-003: Entropy score non-negative
    #[test]
    fn prop_entropy_score_non_negative(
        n_samples in 1usize..10,
        n_classes in 2usize..6,
        seed in 0u64..10000,
    ) {
        let mut rng = seed;
        let predictions: Vec<Vector<f32>> = (0..n_samples)
            .map(|_| make_prob_vec(&mut rng, n_classes))
            .collect();

        let strategy = EntropySampling::new();
        let scores = strategy.score(&predictions);

        prop_assert_eq!(scores.len(), predictions.len());
        for (i, &s) in scores.iter().enumerate() {
            prop_assert!(
                s >= 0.0,
                "FALSIFY-AL-003: entropy[{}] = {}, expected >= 0",
                i, s
            );
            prop_assert!(
                s.is_finite(),
                "FALSIFY-AL-003: entropy[{}] = {}, expected finite",
                i, s
            );
        }
    }

    // FALSIFY-AL-004: QBC vote entropy non-negative
    #[test]
    fn prop_qbc_score_non_negative(
        n_members in 2usize..5,
        n_samples in 2usize..8,
        n_classes in 2usize..5,
        seed in 0u64..10000,
    ) {
        let mut rng = seed;
        let committee_preds: Vec<Vec<Vector<f32>>> = (0..n_members)
            .map(|_| {
                (0..n_samples)
                    .map(|_| make_prob_vec(&mut rng, n_classes))
                    .collect()
            })
            .collect();

        let qbc = QueryByCommittee::new(n_members);
        let scores = qbc.score_committee(&committee_preds);

        prop_assert_eq!(scores.len(), n_samples);
        for (i, &s) in scores.iter().enumerate() {
            prop_assert!(
                s >= 0.0,
                "FALSIFY-AL-004: qbc[{}] = {}, expected >= 0",
                i, s
            );
            prop_assert!(
                s.is_finite(),
                "FALSIFY-AL-004: qbc[{}] = {}, expected finite",
                i, s
            );
        }
    }
}