solverforge-solver 0.12.0

Solver engine for SolverForge
Documentation
use super::*;

#[test]
fn descriptor_first_fit_ignores_value_order_hook() {
    fn prefer_worker_one(_solution: &dyn std::any::Any, _entity_index: usize, value: usize) -> i64 {
        if value == 1 {
            0
        } else {
            10
        }
    }

    let descriptor = queue_descriptor(None, Some(prefer_worker_one));
    let plan = QueuePlan {
        workers: vec![Worker, Worker],
        tasks: vec![QueueTask {
            worker_idx: None,
            preferred_worker: 0,
            allowed_workers: vec![0, 1],
        }],
        assignment_log: Vec::new(),
        score: None,
    };
    let director = QueueScoreDirector::new(plan, descriptor.clone());
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let config = ConstructionHeuristicConfig {
        value_candidate_limit: None,
        construction_heuristic_type: ConstructionHeuristicType::FirstFit,
        construction_obligation: Default::default(),
        target: VariableTargetConfig::default(),
        k: 1,
        group_name: None,
        group_candidate_limit: None,
        termination: None,
    };
    let mut phase = build_descriptor_construction::<QueuePlan>(Some(&config), &descriptor);
    phase.solve(&mut solver_scope);

    assert_eq!(solver_scope.working_solution().tasks[0].worker_idx, Some(0));
}

#[test]
fn descriptor_allocate_to_value_from_queue_consumes_value_order_hook() {
    fn prefer_worker_one(_solution: &dyn std::any::Any, _entity_index: usize, value: usize) -> i64 {
        if value == 1 {
            0
        } else {
            10
        }
    }

    let descriptor = queue_descriptor(None, Some(prefer_worker_one));
    let plan = QueuePlan {
        workers: vec![Worker, Worker],
        tasks: vec![QueueTask {
            worker_idx: None,
            preferred_worker: 0,
            allowed_workers: vec![0, 1],
        }],
        assignment_log: Vec::new(),
        score: None,
    };
    let director = QueueScoreDirector::new(plan, descriptor.clone());
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let config = ConstructionHeuristicConfig {
        value_candidate_limit: None,
        construction_heuristic_type: ConstructionHeuristicType::AllocateToValueFromQueue,
        construction_obligation: Default::default(),
        target: VariableTargetConfig::default(),
        k: 1,
        group_name: None,
        group_candidate_limit: None,
        termination: None,
    };
    let mut phase = build_descriptor_construction::<QueuePlan>(Some(&config), &descriptor);
    phase.solve(&mut solver_scope);

    assert_eq!(solver_scope.working_solution().tasks[0].worker_idx, Some(1));
}

#[test]
fn descriptor_weakest_fit_uses_live_value_order_key() {
    let descriptor = queue_descriptor(None, Some(queue_value_load_key));
    let plan = QueuePlan {
        workers: vec![Worker, Worker],
        tasks: vec![
            QueueTask {
                worker_idx: None,
                preferred_worker: 0,
                allowed_workers: vec![0, 1],
            },
            QueueTask {
                worker_idx: None,
                preferred_worker: 0,
                allowed_workers: vec![0, 1],
            },
        ],
        assignment_log: Vec::new(),
        score: None,
    };
    let director = QueueScoreDirector::new(plan, descriptor.clone());
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let config = ConstructionHeuristicConfig {
        value_candidate_limit: None,
        construction_heuristic_type: ConstructionHeuristicType::WeakestFit,
        construction_obligation: Default::default(),
        target: VariableTargetConfig::default(),
        k: 1,
        group_name: None,
        group_candidate_limit: None,
        termination: None,
    };
    let mut phase = build_descriptor_construction::<QueuePlan>(Some(&config), &descriptor);
    phase.solve(&mut solver_scope);

    assert_eq!(
        solver_scope
            .working_solution()
            .tasks
            .iter()
            .map(|task| task.worker_idx)
            .collect::<Vec<_>>(),
        vec![Some(0), Some(1)]
    );
}

#[test]
fn descriptor_live_refresh_generates_candidates_for_selected_slot_only() {
    let descriptor = queue_descriptor(None, Some(queue_value_load_key));
    let worker_count = 5;
    let task_count = 4;
    let plan = QueuePlan {
        workers: (0..worker_count).map(|_| Worker).collect(),
        tasks: (0..task_count)
            .map(|_| QueueTask {
                worker_idx: None,
                preferred_worker: 0,
                allowed_workers: (0..worker_count).collect(),
            })
            .collect(),
        assignment_log: Vec::new(),
        score: None,
    };
    let director = QueueScoreDirector::new(plan, descriptor.clone());
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let config = ConstructionHeuristicConfig {
        value_candidate_limit: None,
        construction_heuristic_type: ConstructionHeuristicType::WeakestFit,
        construction_obligation: Default::default(),
        target: VariableTargetConfig::default(),
        k: 1,
        group_name: None,
        group_candidate_limit: None,
        termination: None,
    };
    let mut phase = build_descriptor_construction::<QueuePlan>(Some(&config), &descriptor);
    phase.solve(&mut solver_scope);

    assert_eq!(solver_scope.stats().step_count, task_count as u64);
    assert_eq!(
        solver_scope.stats().moves_generated,
        (worker_count * task_count) as u64
    );
    assert_eq!(
        solver_scope
            .working_solution()
            .tasks
            .iter()
            .filter(|task| task.worker_idx.is_some())
            .count(),
        task_count
    );
}

#[test]
fn descriptor_strongest_fit_uses_live_value_order_key() {
    let descriptor = queue_descriptor(None, Some(queue_value_balance_key));
    let plan = QueuePlan {
        workers: vec![Worker, Worker],
        tasks: vec![
            QueueTask {
                worker_idx: None,
                preferred_worker: 0,
                allowed_workers: vec![0, 1],
            },
            QueueTask {
                worker_idx: None,
                preferred_worker: 0,
                allowed_workers: vec![0, 1],
            },
        ],
        assignment_log: Vec::new(),
        score: None,
    };
    let director = QueueScoreDirector::new(plan, descriptor.clone());
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let config = ConstructionHeuristicConfig {
        value_candidate_limit: None,
        construction_heuristic_type: ConstructionHeuristicType::StrongestFit,
        construction_obligation: Default::default(),
        target: VariableTargetConfig::default(),
        k: 1,
        group_name: None,
        group_candidate_limit: None,
        termination: None,
    };
    let mut phase = build_descriptor_construction::<QueuePlan>(Some(&config), &descriptor);
    phase.solve(&mut solver_scope);

    assert_eq!(
        solver_scope
            .working_solution()
            .tasks
            .iter()
            .map(|task| task.worker_idx)
            .collect::<Vec<_>>(),
        vec![Some(0), Some(1)]
    );
}