solverforge-solver 0.8.5

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

use solverforge_core::score::SoftScore;
use solverforge_core::PlanningSolution;
use solverforge_scoring::ScoreDirector;

use super::super::*;
use super::common::NoOpPhase;

type TestDirector = ScoreDirector<TestSolution, ()>;
type EntityTestDirector = ScoreDirector<EntityTestSolution, ()>;

#[derive(Clone, Debug)]
struct TestSolution {
    value: i64,
    score: Option<SoftScore>,
}

impl PlanningSolution for TestSolution {
    type Score = SoftScore;

    fn score(&self) -> Option<Self::Score> {
        self.score
    }

    fn set_score(&mut self, score: Option<Self::Score>) {
        self.score = score;
    }
}

#[derive(Clone, Debug)]
struct TestEntity {
    #[allow(dead_code)]
    id: i64,
    value: Option<i64>,
}

#[derive(Clone, Debug)]
struct EntityTestSolution {
    entities: Vec<TestEntity>,
    target_sum: i64,
    score: Option<SoftScore>,
}

impl PlanningSolution for EntityTestSolution {
    type Score = SoftScore;

    fn score(&self) -> Option<Self::Score> {
        self.score
    }

    fn set_score(&mut self, score: Option<Self::Score>) {
        self.score = score;
    }
}

fn calculate_entity_score(solution: &EntityTestSolution) -> SoftScore {
    let sum: i64 = solution.entities.iter().filter_map(|e| e.value).sum();
    let diff = (sum - solution.target_sum).abs();
    SoftScore::of(-diff)
}

#[test]
fn test_solver_with_time_limit_termination() {
    let solution = EntityTestSolution {
        entities: vec![
            TestEntity {
                id: 0,
                value: Some(1),
            },
            TestEntity {
                id: 1,
                value: Some(2),
            },
            TestEntity {
                id: 2,
                value: Some(2),
            },
        ],
        target_sum: 5,
        score: None,
    };

    let factory =
        solver_factory_builder::<EntityTestSolution, EntityTestDirector, _>(calculate_entity_score)
            .with_time_limit(Duration::from_millis(100))
            .build()
            .expect("Failed to build factory");

    let score = factory.calculate_score(&solution);
    assert_eq!(score, SoftScore::of(0));
}

#[test]
fn test_solver_with_step_limit_termination() {
    let solution = EntityTestSolution {
        entities: vec![
            TestEntity {
                id: 0,
                value: Some(0),
            },
            TestEntity {
                id: 1,
                value: Some(0),
            },
        ],
        target_sum: 6,
        score: None,
    };

    let factory =
        solver_factory_builder::<EntityTestSolution, EntityTestDirector, _>(calculate_entity_score)
            .with_step_limit(5)
            .build()
            .expect("Failed to build factory");

    let score = factory.calculate_score(&solution);
    assert_eq!(score, SoftScore::of(-6));
}

#[test]
fn test_solver_factory_with_entity_solution() {
    let solution = EntityTestSolution {
        entities: vec![
            TestEntity {
                id: 0,
                value: Some(2),
            },
            TestEntity {
                id: 1,
                value: Some(3),
            },
        ],
        target_sum: 5,
        score: None,
    };

    let factory =
        solver_factory_builder::<EntityTestSolution, EntityTestDirector, _>(calculate_entity_score)
            .build()
            .expect("Failed to build factory");

    let score = factory.calculate_score(&solution);
    assert_eq!(score, SoftScore::of(0));
}

#[test]
fn test_solver_factory_with_phases() {
    let factory = solver_factory_builder::<TestSolution, TestDirector, _>(|s: &TestSolution| {
        SoftScore::of(-s.value)
    })
    .with_phase(NoOpPhase)
    .with_step_limit(10)
    .build()
    .expect("Failed to build factory");

    let solution = TestSolution {
        value: 5,
        score: None,
    };
    let score = factory.calculate_score(&solution);
    assert_eq!(score, SoftScore::of(-5));
}

#[test]
fn test_solver_factory_with_multiple_phases() {
    let factory = solver_factory_builder::<TestSolution, TestDirector, _>(|s: &TestSolution| {
        SoftScore::of(-s.value)
    })
    .with_phase(NoOpPhase)
    .with_phase(NoOpPhase)
    .with_time_limit(Duration::from_secs(1))
    .build()
    .expect("Failed to build factory");

    let solution = TestSolution {
        value: 7,
        score: None,
    };
    let score = factory.calculate_score(&solution);
    assert_eq!(score, SoftScore::of(-7));
}

#[test]
fn test_construction_and_local_search_types_exist() {
    assert_eq!(ConstructionType::default(), ConstructionType::FirstFit);
    assert_eq!(LocalSearchType::default(), LocalSearchType::HillClimbing);

    let _tabu = LocalSearchType::TabuSearch { tabu_size: 10 };
    let _sa = LocalSearchType::SimulatedAnnealing {
        starting_temp: 1.0,
        decay_rate: 0.99,
    };
    let _la = LocalSearchType::LateAcceptance { size: 100 };
    let _bf = ConstructionType::BestFit;
}