use std::fmt::Debug;
use std::marker::PhantomData;
use solverforge_core::domain::PlanningSolution;
use solverforge_scoring::Director;
use tracing::info;
use crate::heuristic::r#move::Move;
use crate::phase::construction::{ConstructionForager, EntityPlacer};
use crate::phase::Phase;
use crate::scope::ProgressCallback;
use crate::scope::{PhaseScope, SolverScope, StepScope};
pub struct ConstructionHeuristicPhase<S, M, P, Fo>
where
S: PlanningSolution,
M: Move<S>,
P: EntityPlacer<S, M>,
Fo: ConstructionForager<S, M>,
{
placer: P,
forager: Fo,
_phantom: PhantomData<fn() -> (S, M)>,
}
impl<S, M, P, Fo> ConstructionHeuristicPhase<S, M, P, Fo>
where
S: PlanningSolution,
M: Move<S>,
P: EntityPlacer<S, M>,
Fo: ConstructionForager<S, M>,
{
pub fn new(placer: P, forager: Fo) -> Self {
Self {
placer,
forager,
_phantom: PhantomData,
}
}
}
impl<S, M, P, Fo> Debug for ConstructionHeuristicPhase<S, M, P, Fo>
where
S: PlanningSolution,
M: Move<S>,
P: EntityPlacer<S, M> + Debug,
Fo: ConstructionForager<S, M> + Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConstructionHeuristicPhase")
.field("placer", &self.placer)
.field("forager", &self.forager)
.finish()
}
}
impl<S, D, BestCb, M, P, Fo> Phase<S, D, BestCb> for ConstructionHeuristicPhase<S, M, P, Fo>
where
S: PlanningSolution,
D: Director<S>,
BestCb: ProgressCallback<S>,
M: Move<S>,
P: EntityPlacer<S, M>,
Fo: ConstructionForager<S, M>,
{
fn solve(&mut self, solver_scope: &mut SolverScope<S, D, BestCb>) {
let mut phase_scope = PhaseScope::new(solver_scope, 0);
let phase_index = phase_scope.phase_index();
info!(
event = "phase_start",
phase = "Construction Heuristic",
phase_index = phase_index,
);
let placements = self.placer.get_placements(phase_scope.score_director());
for mut placement in placements {
if phase_scope.solver_scope().should_terminate_construction() {
break;
}
let moves_in_placement = placement.moves.len() as u64;
let mut step_scope = StepScope::new(&mut phase_scope);
let selected_idx = self
.forager
.pick_move_index(&placement, step_scope.score_director_mut());
for i in 0..moves_in_placement {
let accepted = selected_idx == Some(i as usize);
step_scope
.phase_scope_mut()
.solver_scope_mut()
.stats_mut()
.record_move(accepted);
}
if let Some(idx) = selected_idx {
let m = placement.take_move(idx);
m.do_move(step_scope.score_director_mut());
let step_score = step_scope.calculate_score();
step_scope.set_step_score(step_score);
}
step_scope.complete();
}
phase_scope.update_best_solution();
let best_score = phase_scope
.solver_scope()
.best_score()
.map(|s| format!("{}", s))
.unwrap_or_else(|| "none".to_string());
let duration = phase_scope.elapsed();
let steps = phase_scope.step_count();
let speed = if duration.as_secs_f64() > 0.0 {
(steps as f64 / duration.as_secs_f64()) as u64
} else {
0
};
let stats = phase_scope.solver_scope().stats();
info!(
event = "phase_end",
phase = "Construction Heuristic",
phase_index = phase_index,
duration_ms = duration.as_millis() as u64,
steps = steps,
moves_evaluated = stats.moves_evaluated,
moves_accepted = stats.moves_accepted,
score_calculations = stats.score_calculations,
speed = speed,
score = best_score,
);
}
fn phase_type_name(&self) -> &'static str {
"ConstructionHeuristic"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::heuristic::selector::{FromSolutionEntitySelector, StaticValueSelector};
use crate::phase::construction::{BestFitForager, FirstFitForager, QueuedEntityPlacer};
use crate::test_utils::{
create_simple_nqueens_director, get_queen_row, set_queen_row, NQueensSolution,
};
fn create_placer(
values: Vec<i64>,
) -> QueuedEntityPlacer<
NQueensSolution,
i64,
FromSolutionEntitySelector,
StaticValueSelector<NQueensSolution, i64>,
> {
let es = FromSolutionEntitySelector::new(0);
let vs = StaticValueSelector::new(values);
QueuedEntityPlacer::new(es, vs, get_queen_row, set_queen_row, 0, "row")
}
#[test]
fn test_construction_first_fit() {
let director = create_simple_nqueens_director(4);
let mut solver_scope = SolverScope::new(director);
solver_scope.start_solving();
let values: Vec<i64> = (0..4).collect();
let placer = create_placer(values);
let forager = FirstFitForager::new();
let mut phase = ConstructionHeuristicPhase::new(placer, forager);
phase.solve(&mut solver_scope);
let solution = solver_scope.working_solution();
assert_eq!(solution.queens.len(), 4);
for queen in &solution.queens {
assert!(queen.row.is_some(), "Queen should have a row assigned");
}
assert!(solver_scope.best_solution().is_some());
assert!(solver_scope.stats().moves_evaluated > 0);
}
#[test]
fn test_construction_best_fit() {
let director = create_simple_nqueens_director(4);
let mut solver_scope = SolverScope::new(director);
solver_scope.start_solving();
let values: Vec<i64> = (0..4).collect();
let placer = create_placer(values);
let forager = BestFitForager::new();
let mut phase = ConstructionHeuristicPhase::new(placer, forager);
phase.solve(&mut solver_scope);
let solution = solver_scope.working_solution();
for queen in &solution.queens {
assert!(queen.row.is_some(), "Queen should have a row assigned");
}
assert!(solver_scope.best_solution().is_some());
assert!(solver_scope.best_score().is_some());
assert_eq!(solver_scope.stats().moves_evaluated, 16);
}
#[test]
fn test_construction_empty_solution() {
let director = create_simple_nqueens_director(0);
let mut solver_scope = SolverScope::new(director);
solver_scope.start_solving();
let values: Vec<i64> = vec![];
let placer = create_placer(values);
let forager = FirstFitForager::new();
let mut phase = ConstructionHeuristicPhase::new(placer, forager);
phase.solve(&mut solver_scope);
assert_eq!(solver_scope.stats().moves_evaluated, 0);
}
}