use std::collections::BinaryHeap;
use std::fmt::Debug;
use solverforge_core::domain::PlanningSolution;
use solverforge_scoring::Director;
use crate::phase::Phase;
use crate::scope::ProgressCallback;
use crate::scope::{PhaseScope, SolverScope};
use super::config::ExhaustiveSearchConfig;
use super::decider::ExhaustiveSearchDecider;
use super::exploration_type::ExplorationType;
use super::node::ExhaustiveSearchNode;
use super::priority_node::PriorityNode;
pub struct ExhaustiveSearchPhase<Dec> {
decider: Dec,
config: ExhaustiveSearchConfig,
}
impl<Dec: Debug> Debug for ExhaustiveSearchPhase<Dec> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExhaustiveSearchPhase")
.field("decider", &self.decider)
.field("config", &self.config)
.finish()
}
}
impl<Dec> ExhaustiveSearchPhase<Dec> {
pub fn new(decider: Dec, config: ExhaustiveSearchConfig) -> Self {
Self { decider, config }
}
pub fn depth_first(decider: Dec) -> Self {
Self::new(
decider,
ExhaustiveSearchConfig {
exploration_type: ExplorationType::DepthFirst,
..Default::default()
},
)
}
pub fn breadth_first(decider: Dec) -> Self {
Self::new(
decider,
ExhaustiveSearchConfig {
exploration_type: ExplorationType::BreadthFirst,
..Default::default()
},
)
}
pub fn score_first(decider: Dec) -> Self {
Self::new(
decider,
ExhaustiveSearchConfig {
exploration_type: ExplorationType::ScoreFirst,
..Default::default()
},
)
}
pub fn phase_type_name(&self) -> &'static str {
"ExhaustiveSearch"
}
}
impl<S, D, BestCb, Dec> Phase<S, D, BestCb> for ExhaustiveSearchPhase<Dec>
where
S: PlanningSolution,
D: Director<S>,
BestCb: ProgressCallback<S>,
Dec: ExhaustiveSearchDecider<S, D>,
{
fn solve(&mut self, solver_scope: &mut SolverScope<S, D, BestCb>) {
let mut phase_scope = PhaseScope::new(solver_scope, 0);
let total_entities = self.decider.total_entities(phase_scope.score_director());
if total_entities == 0 {
return;
}
let initial_score = phase_scope.calculate_score();
let root = ExhaustiveSearchNode::root(initial_score);
let mut best_score: Option<S::Score> = None;
let mut nodes_explored: u64 = 0;
let mut frontier: BinaryHeap<PriorityNode<S>> = BinaryHeap::new();
frontier.push(PriorityNode::new(0, root, self.config.exploration_type));
let mut all_nodes: Vec<ExhaustiveSearchNode<S>> = Vec::new();
while let Some(priority_node) = frontier.pop() {
let node = priority_node.node;
let node_index = all_nodes.len();
if let Some(limit) = self.config.node_limit {
if nodes_explored >= limit {
break;
}
}
if let Some(limit) = self.config.depth_limit {
if node.depth() >= limit {
continue;
}
}
if self.config.enable_pruning {
if let Some(ref best) = best_score {
if node.can_prune(best) {
continue;
}
}
}
nodes_explored += 1;
if node.is_leaf(total_entities) {
let is_better = match &best_score {
None => true,
Some(best) => node.score() > best,
};
if is_better {
best_score = Some(*node.score());
phase_scope.update_best_solution();
}
all_nodes.push(node);
continue;
}
let children = self
.decider
.expand(node_index, &node, phase_scope.score_director_mut());
all_nodes.push(node);
for child in children {
if self.config.enable_pruning {
if let Some(ref best) = best_score {
if child.can_prune(best) {
continue;
}
}
}
let child_index = all_nodes.len() + frontier.len();
frontier.push(PriorityNode::new(
child_index,
child,
self.config.exploration_type,
));
}
}
}
fn phase_type_name(&self) -> &'static str {
"ExhaustiveSearch"
}
}
#[cfg(test)]
mod tests {
use solverforge_core::domain::PlanningSolution;
use solverforge_core::score::SoftScore;
use super::*;
use crate::phase::exhaustive::decider::SimpleDecider;
use crate::phase::exhaustive::exploration_type::ExplorationType;
#[derive(Clone, Debug)]
struct TestSolution {
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;
}
}
fn set_row(_s: &mut TestSolution, _idx: usize, _v: Option<i32>) {}
#[test]
fn test_exploration_type_display() {
assert_eq!(format!("{}", ExplorationType::DepthFirst), "DepthFirst");
assert_eq!(format!("{}", ExplorationType::BreadthFirst), "BreadthFirst");
assert_eq!(format!("{}", ExplorationType::ScoreFirst), "ScoreFirst");
assert_eq!(
format!("{}", ExplorationType::OptimisticBoundFirst),
"OptimisticBoundFirst"
);
}
#[test]
fn test_exploration_type_default() {
assert_eq!(ExplorationType::default(), ExplorationType::DepthFirst);
}
#[test]
fn test_config_default() {
let config = ExhaustiveSearchConfig::default();
assert_eq!(config.exploration_type, ExplorationType::DepthFirst);
assert_eq!(config.node_limit, Some(10_000));
assert!(config.depth_limit.is_none());
assert!(config.enable_pruning);
}
#[test]
fn test_phase_type_name() {
let decider: SimpleDecider<TestSolution, i32> =
SimpleDecider::new(0, "row", vec![0, 1, 2, 3], set_row);
let phase = ExhaustiveSearchPhase::depth_first(decider);
assert_eq!(phase.phase_type_name(), "ExhaustiveSearch");
}
#[test]
fn test_phase_debug() {
let decider: SimpleDecider<TestSolution, i32> =
SimpleDecider::new(0, "row", vec![0, 1, 2, 3], set_row);
let phase = ExhaustiveSearchPhase::depth_first(decider);
let debug = format!("{:?}", phase);
assert!(debug.contains("ExhaustiveSearchPhase"));
assert!(debug.contains("DepthFirst"));
}
}