solverforge-solver 0.12.0

Solver engine for SolverForge
Documentation

#[test]
fn scalar_runtime_frontier_marks_kept_optional_none_as_complete() {
    let descriptor = scalar_runtime_descriptor();
    let plan = ScalarRuntimePlan {
        score: None,
        workers: vec![ScalarRuntimeWorker],
        tasks: vec![ScalarRuntimeTask { worker_idx: None }],
    };
    let director = ScalarRuntimeDirector::new(plan, descriptor.clone());
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let mut targeted_phase = Construction::new(
        Some(config(
            ConstructionHeuristicType::CheapestInsertion,
            Some("Task"),
            Some("worker_idx"),
        )),
        descriptor.clone(),
        scalar_runtime_model(),
    );
    targeted_phase.solve(&mut solver_scope);

    assert_eq!(solver_scope.working_solution().tasks[0].worker_idx, None);
    assert_eq!(solver_scope.stats().step_count, 1);
    assert!(
        !scalar_work_remaining_with_frontier(
            &descriptor,
            solver_scope.construction_frontier(),
            solver_scope.solution_revision(),
            None,
            None,
            solver_scope.working_solution(),
        ),
        "completed optional None should not be treated as remaining scalar work",
    );

    let mut untargeted_phase = Construction::new(None, descriptor, scalar_runtime_model());
    untargeted_phase.solve(&mut solver_scope);

    assert_eq!(solver_scope.working_solution().tasks[0].worker_idx, None);
    assert_eq!(solver_scope.stats().step_count, 1);
    assert_eq!(solver_scope.stats().moves_accepted, 0);
}

#[test]
fn no_op_runtime_construction_still_seeds_score_and_best_solution() {
    let descriptor = scalar_runtime_descriptor();
    let plan = ScalarRuntimePlan {
        score: None,
        workers: vec![ScalarRuntimeWorker],
        tasks: vec![ScalarRuntimeTask {
            worker_idx: Some(0),
        }],
    };
    let director = ScalarRuntimeDirector::new(plan, descriptor.clone());
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let mut phase = Construction::new(None, descriptor, scalar_runtime_model());
    phase.solve(&mut solver_scope);

    assert_eq!(
        solver_scope.current_score().copied(),
        Some(SoftScore::of(-1))
    );
    assert_eq!(solver_scope.best_score().copied(), Some(SoftScore::of(-1)));
}

#[test]
fn scalar_runtime_first_fit_keeps_none_when_optional_baseline_is_not_beaten() {
    let descriptor = scalar_runtime_descriptor();
    let plan = ScalarRuntimePlan {
        score: None,
        workers: vec![
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
        ],
        tasks: vec![ScalarRuntimeTask { worker_idx: None }],
    };
    let director = ScalarRuntimeDirector::with_score_mode(
        plan,
        descriptor.clone(),
        ScalarRuntimeScoreMode::ByWorker {
            unassigned_score: 0,
            assigned_scores: [-5, -1, -2],
        },
    );
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let mut phase = Construction::new(None, descriptor, scalar_runtime_model());
    phase.solve(&mut solver_scope);

    assert_eq!(solver_scope.working_solution().tasks[0].worker_idx, None);
    assert_eq!(
        solver_scope.current_score().copied(),
        Some(SoftScore::of(0))
    );
    assert_eq!(solver_scope.stats().moves_accepted, 0);
    assert_eq!(solver_scope.stats().step_count, 1);
}

#[test]
fn scalar_runtime_first_fit_skips_worse_candidate_for_later_improvement() {
    let descriptor = scalar_runtime_descriptor();
    let plan = ScalarRuntimePlan {
        score: None,
        workers: vec![
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
        ],
        tasks: vec![ScalarRuntimeTask { worker_idx: None }],
    };
    let director = ScalarRuntimeDirector::with_score_mode(
        plan,
        descriptor.clone(),
        ScalarRuntimeScoreMode::ByWorker {
            unassigned_score: 0,
            assigned_scores: [-5, 7, -1],
        },
    );
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let mut phase = Construction::new(None, descriptor, scalar_runtime_model());
    phase.solve(&mut solver_scope);

    assert_eq!(solver_scope.working_solution().tasks[0].worker_idx, Some(1));
    assert_eq!(
        solver_scope.current_score().copied(),
        Some(SoftScore::of(7))
    );
    assert_eq!(solver_scope.stats().moves_accepted, 1);
}

#[test]
fn scalar_runtime_first_fit_takes_first_improving_candidate() {
    let descriptor = scalar_runtime_descriptor();
    let plan = ScalarRuntimePlan {
        score: None,
        workers: vec![
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
        ],
        tasks: vec![ScalarRuntimeTask { worker_idx: None }],
    };
    let director = ScalarRuntimeDirector::with_score_mode(
        plan,
        descriptor.clone(),
        ScalarRuntimeScoreMode::ByWorker {
            unassigned_score: 0,
            assigned_scores: [7, -5, 3],
        },
    );
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let mut phase = Construction::new(None, descriptor, scalar_runtime_model());
    phase.solve(&mut solver_scope);

    assert_eq!(solver_scope.working_solution().tasks[0].worker_idx, Some(0));
    assert_eq!(
        solver_scope.current_score().copied(),
        Some(SoftScore::of(7))
    );
    assert_eq!(solver_scope.stats().moves_accepted, 1);
}

#[test]
fn scalar_runtime_first_fit_required_slot_still_assigns_first_doable() {
    let descriptor = scalar_runtime_descriptor_with_allows_unassigned(false);
    let plan = ScalarRuntimePlan {
        score: None,
        workers: vec![
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
        ],
        tasks: vec![ScalarRuntimeTask { worker_idx: None }],
    };
    let director = ScalarRuntimeDirector::with_score_mode(
        plan,
        descriptor.clone(),
        ScalarRuntimeScoreMode::ByWorker {
            unassigned_score: 0,
            assigned_scores: [-5, -1, -2],
        },
    );
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let mut phase = Construction::new(
        None,
        descriptor,
        scalar_runtime_model_with_allows_unassigned(false),
    );
    phase.solve(&mut solver_scope);

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

#[test]
fn scalar_runtime_weakest_fit_keeps_none_from_runtime_value_order_hook() {
    let descriptor = scalar_runtime_descriptor();
    let plan = ScalarRuntimePlan {
        score: None,
        workers: vec![
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
        ],
        tasks: vec![ScalarRuntimeTask { worker_idx: None }],
    };
    let director = ScalarRuntimeDirector::with_score_mode(
        plan,
        descriptor.clone(),
        ScalarRuntimeScoreMode::ByWorker {
            unassigned_score: 0,
            assigned_scores: [0, 7, 3],
        },
    );
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let mut phase = Construction::new(
        Some(config(
            ConstructionHeuristicType::WeakestFit,
            Some("Task"),
            Some("worker_idx"),
        )),
        descriptor,
        scalar_runtime_model_with_hooks(true, Some(scalar_runtime_value_order_key)),
    );
    phase.solve(&mut solver_scope);

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

#[test]
fn scalar_runtime_weakest_fit_assigns_from_runtime_value_order_hook() {
    let descriptor = scalar_runtime_descriptor();
    let plan = ScalarRuntimePlan {
        score: None,
        workers: vec![
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
        ],
        tasks: vec![ScalarRuntimeTask { worker_idx: None }],
    };
    let director = ScalarRuntimeDirector::with_score_mode(
        plan,
        descriptor.clone(),
        ScalarRuntimeScoreMode::ByWorker {
            unassigned_score: 0,
            assigned_scores: [1, 7, 3],
        },
    );
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let mut phase = Construction::new(
        Some(config(
            ConstructionHeuristicType::WeakestFit,
            Some("Task"),
            Some("worker_idx"),
        )),
        descriptor,
        scalar_runtime_model_with_hooks(true, Some(scalar_runtime_value_order_key)),
    );
    phase.solve(&mut solver_scope);

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

#[test]
fn scalar_runtime_strongest_fit_keeps_none_from_runtime_value_order_hook() {
    let descriptor = scalar_runtime_descriptor();
    let plan = ScalarRuntimePlan {
        score: None,
        workers: vec![
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
        ],
        tasks: vec![ScalarRuntimeTask { worker_idx: None }],
    };
    let director = ScalarRuntimeDirector::with_score_mode(
        plan,
        descriptor.clone(),
        ScalarRuntimeScoreMode::ByWorker {
            unassigned_score: 0,
            assigned_scores: [7, 3, 0],
        },
    );
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let mut phase = Construction::new(
        Some(config(
            ConstructionHeuristicType::StrongestFit,
            Some("Task"),
            Some("worker_idx"),
        )),
        descriptor,
        scalar_runtime_model_with_hooks(true, Some(scalar_runtime_value_order_key)),
    );
    phase.solve(&mut solver_scope);

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

#[test]
fn scalar_runtime_strongest_fit_assigns_from_runtime_value_order_hook() {
    let descriptor = scalar_runtime_descriptor();
    let plan = ScalarRuntimePlan {
        score: None,
        workers: vec![
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
            ScalarRuntimeWorker,
        ],
        tasks: vec![ScalarRuntimeTask { worker_idx: None }],
    };
    let director = ScalarRuntimeDirector::with_score_mode(
        plan,
        descriptor.clone(),
        ScalarRuntimeScoreMode::ByWorker {
            unassigned_score: 0,
            assigned_scores: [-5, 3, 7],
        },
    );
    let mut solver_scope = SolverScope::new(director);
    solver_scope.start_solving();

    let mut phase = Construction::new(
        Some(config(
            ConstructionHeuristicType::StrongestFit,
            Some("Task"),
            Some("worker_idx"),
        )),
        descriptor,
        scalar_runtime_model_with_hooks(true, Some(scalar_runtime_value_order_key)),
    );
    phase.solve(&mut solver_scope);

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