blr-active 0.1.0

Active learning orchestration for Bayesian Linear Regression with Automatic Relevance Determination
Documentation
//! Multi-sensor calibration workflow example for `blr-active`.
//!
//! Shows how to calibrate two sensors concurrently using
//! `MultiSensorSession`, which manages separate `CalibrationSession`
//! instances and produces unified recommendations.
//!
//! Run from the repository root:
//!
//! ```bash
//! cargo run --example multi_sensor_workflow -p blr-active
//! ```

use blr_active::active_learning::multi_sensor::MultiSensorSession;
use blr_active::SessionConfig;

// ── Simulated sensors ──────────────────────────────────────────────────────

/// Hall-effect sensor: nonlinear magnetic response with noise σ=0.03
fn hall_sensor(b_field: f64, rng: &mut u64) -> f64 {
    let true_val = 0.4 * b_field + 0.05 * b_field * b_field * b_field;
    let noise = 0.03 * lcg_normal(rng);
    true_val + noise
}

/// RTD temperature sensor: near-linear response with noise σ=0.02
fn rtd_sensor(temp_c: f64, rng: &mut u64) -> f64 {
    let true_val = 100.0 + 0.385 * temp_c; // Pt100 simplified
    let noise = 0.02 * lcg_normal(rng);
    true_val + noise
}

fn lcg_normal(state: &mut u64) -> f64 {
    *state = state
        .wrapping_mul(6364136223846793005)
        .wrapping_add(1442695040888963407);
    (*state as i64 as f64) / (i64::MAX as f64)
}

fn main() {
    println!("=== Multi-Sensor Calibration Workflow ===\n");

    let mut rng: u64 = 0x1234_5678_abcd_ef00;

    // ── 1. Create a MultiSensorSession ─────────────────────────────────────
    //
    // Add each sensor with its own feature function and precision config.

    let mut multi = MultiSensorSession::new();

    // Hall sensor: [1, B, B², B³] polynomial features, input range ±2 T
    let hall_config = SessionConfig {
        target_precision: 0.08, // σ_noise × 3 ≈ Moderate tier
        max_iterations: 30,
        top_k: 1,
        input_range: (-2.0, 2.0),
        grid_resolution: 40,
        ..SessionConfig::default()
    };
    multi.add_sensor(
        "hall",
        hall_config,
        |b: f64| vec![1.0, b, b * b, b * b * b],
        4,
    );

    // RTD sensor: [1, T, T²] features, input range 0–150 °C
    let rtd_config = SessionConfig {
        target_precision: 0.06, // Moderate tier for RTD noise σ=0.02
        max_iterations: 30,
        top_k: 1,
        input_range: (0.0, 150.0),
        grid_resolution: 40,
        ..SessionConfig::default()
    };
    multi.add_sensor("rtd", rtd_config, |t: f64| vec![1.0, t, t * t], 3);

    // ── 2. Seed with initial measurements ──────────────────────────────────
    println!("Seeding each sensor with 6 initial measurements...");

    for i in 0..6 {
        let b = -2.0 + 4.0 * (i as f64) / 5.0;
        multi.add_measurement("hall", b, hall_sensor(b, &mut rng));
    }
    for i in 0..6 {
        let t = 0.0 + 150.0 * (i as f64) / 5.0;
        multi.add_measurement("rtd", t, rtd_sensor(t, &mut rng));
    }

    // ── 3. Active learning loop for all sensors ─────────────────────────────
    println!("\nRunning active learning (max 25 iterations each)...\n");
    println!(
        "{:<6}  {:<8}  {:<12}  {:<10}",
        "Iter", "Sensor", "Measure at", "y"
    );
    println!("{}", "-".repeat(45));

    let mut iteration = 0_usize;
    loop {
        if multi.all_goals_met() {
            println!("\n✓ All sensors have met their precision goals!");
            break;
        }

        let recommendations = multi.next_iteration_all();
        if recommendations.is_empty() {
            println!("No more recommendations — all sessions complete.");
            break;
        }

        for rec in &recommendations {
            if rec.recommendations.is_empty() {
                continue;
            }
            let input_val = rec.recommendations[0].input_value;
            let y = match rec.sensor_id.as_str() {
                "hall" => hall_sensor(input_val, &mut rng),
                "rtd" => rtd_sensor(input_val, &mut rng),
                _ => 0.0,
            };
            println!(
                "{:<6}  {:<8}  {:<12.3}  {:<10.4}",
                iteration, rec.sensor_id, input_val, y
            );
            multi.add_measurement(&rec.sensor_id, input_val, y);
        }

        iteration += 1;
        if iteration >= 25 {
            println!("\nMax demonstration iterations reached.");
            break;
        }
    }

    // ── 4. Final precision summary ──────────────────────────────────────────
    println!("\n── Final Precision Summary ──");
    for (sensor_id, precision_std) in multi.precision_summary() {
        println!("  {:<8}  std = {:.4}", sensor_id, precision_std);
    }

    println!("\nDone. Both sensors calibrated concurrently.");
}