solverforge-solver 0.11.1

Solver engine for SolverForge
Documentation
use std::time::Duration;

use super::*;

#[test]
fn solver_snapshot_preserves_exact_counts_and_durations() {
    let mut stats = SolverStats::default();
    stats.start();
    stats.record_step();
    stats.record_generated_batch(3, Duration::from_millis(4));
    stats.record_evaluated_move(Duration::from_millis(5));
    stats.record_move_accepted();
    stats.record_score_calculation();

    let snapshot = stats.snapshot();

    assert_eq!(snapshot.step_count, 1);
    assert_eq!(snapshot.moves_generated, 3);
    assert_eq!(snapshot.moves_evaluated, 1);
    assert_eq!(snapshot.moves_accepted, 1);
    assert_eq!(snapshot.score_calculations, 1);
    assert_eq!(snapshot.generation_time, Duration::from_millis(4));
    assert_eq!(snapshot.evaluation_time, Duration::from_millis(5));
}

#[test]
fn phase_stats_track_generation_and_evaluation_separately() {
    let mut stats = PhaseStats::new(2, "LocalSearch");
    stats.record_step();
    stats.record_generated_batch(7, Duration::from_millis(8));
    stats.record_evaluated_move(Duration::from_millis(9));
    stats.record_move_accepted();
    stats.record_score_calculation();

    assert_eq!(stats.phase_index, 2);
    assert_eq!(stats.phase_type, "LocalSearch");
    assert_eq!(stats.step_count, 1);
    assert_eq!(stats.moves_generated, 7);
    assert_eq!(stats.moves_evaluated, 1);
    assert_eq!(stats.moves_accepted, 1);
    assert_eq!(stats.score_calculations, 1);
    assert_eq!(stats.generation_time(), Duration::from_millis(8));
    assert_eq!(stats.evaluation_time(), Duration::from_millis(9));
}

#[test]
fn solver_snapshot_includes_selector_level_telemetry() {
    let mut stats = SolverStats::default();
    stats.start();
    stats.record_selector_generated(2, 3, Duration::from_millis(4));
    stats.record_selector_evaluated(2, Duration::from_millis(5));
    stats.record_selector_accepted(2);
    stats.record_selector_applied(2);

    let snapshot = stats.snapshot();

    assert_eq!(snapshot.moves_generated, 3);
    assert_eq!(snapshot.moves_evaluated, 1);
    assert_eq!(snapshot.moves_accepted, 1);
    assert_eq!(snapshot.moves_applied, 1);
    assert_eq!(snapshot.selector_telemetry.len(), 1);
    assert_eq!(snapshot.selector_telemetry[0].selector_index, 2);
    assert_eq!(snapshot.selector_telemetry[0].selector_label, "selector-2");
    assert_eq!(snapshot.selector_telemetry[0].moves_generated, 3);
    assert_eq!(snapshot.selector_telemetry[0].moves_evaluated, 1);
    assert_eq!(snapshot.selector_telemetry[0].moves_accepted, 1);
    assert_eq!(snapshot.selector_telemetry[0].moves_applied, 1);
}

#[test]
fn solver_snapshot_prefers_observed_selector_label() {
    let mut stats = SolverStats::default();
    stats.record_selector_generated(2, 1, Duration::from_millis(1));
    stats.record_selector_generated_with_label(2, "conflict_repair", 1, Duration::from_millis(2));

    let snapshot = stats.snapshot();

    assert_eq!(
        snapshot.selector_telemetry[0].selector_label,
        "conflict_repair"
    );
    assert_eq!(snapshot.selector_telemetry[0].moves_generated, 2);
}

#[test]
fn unattributed_applied_moves_do_not_create_selector_zero_telemetry() {
    let mut stats = SolverStats::default();
    stats.record_generated_move(Duration::from_millis(1));
    stats.record_evaluated_move(Duration::from_millis(2));
    stats.record_move_accepted();
    stats.record_move_applied();

    let snapshot = stats.snapshot();

    assert_eq!(snapshot.moves_generated, 1);
    assert_eq!(snapshot.moves_evaluated, 1);
    assert_eq!(snapshot.moves_accepted, 1);
    assert_eq!(snapshot.moves_applied, 1);
    assert!(snapshot.selector_telemetry.is_empty());
}

#[test]
fn throughput_helpers_use_stage_specific_durations() {
    let mut solver_stats = SolverStats::default();
    solver_stats.start();
    solver_stats.record_generated_batch(5, Duration::from_millis(7));
    solver_stats.record_evaluated_move(Duration::from_millis(11));

    let mut phase_stats = PhaseStats::new(1, "LocalSearch");
    phase_stats.record_generated_batch(3, Duration::from_millis(13));
    phase_stats.record_evaluated_move(Duration::from_millis(17));

    assert_eq!(
        solver_stats.generated_throughput(),
        Throughput {
            count: 5,
            elapsed: Duration::from_millis(7),
        }
    );
    assert_eq!(
        solver_stats.evaluated_throughput(),
        Throughput {
            count: 1,
            elapsed: Duration::from_millis(11),
        }
    );
    assert_eq!(
        phase_stats.generated_throughput(),
        Throughput {
            count: 3,
            elapsed: Duration::from_millis(13),
        }
    );
    assert_eq!(
        phase_stats.evaluated_throughput(),
        Throughput {
            count: 1,
            elapsed: Duration::from_millis(17),
        }
    );
}

#[test]
fn whole_units_per_second_uses_integer_rate_math() {
    assert_eq!(whole_units_per_second(3, Duration::from_millis(2_000)), 1);
    assert_eq!(whole_units_per_second(9, Duration::from_secs(2)), 4);
    assert_eq!(whole_units_per_second(5, Duration::ZERO), 0);
}

#[test]
fn format_duration_uses_exact_integer_units() {
    assert_eq!(format_duration(Duration::from_millis(750)), "750ms");
    assert_eq!(format_duration(Duration::from_millis(2_500)), "2s 500ms");
    assert_eq!(format_duration(Duration::from_secs(125)), "2m 5s");
    assert_eq!(format_duration(Duration::from_micros(42)), "42us");
}