use std::marker::PhantomData;
use crate::heuristic::Move;
use solverforge_core::domain::PlanningSolution;
#[derive(Debug, Clone)]
pub struct ExhaustiveSearchNode<S: PlanningSolution> {
depth: usize,
score: S::Score,
optimistic_bound: Option<S::Score>,
entity_index: Option<usize>,
value_index: Option<usize>,
parent_index: Option<usize>,
expanded: bool,
}
impl<S: PlanningSolution> ExhaustiveSearchNode<S> {
pub fn root(score: S::Score) -> Self {
Self {
depth: 0,
score,
optimistic_bound: None,
entity_index: None,
value_index: None,
parent_index: None,
expanded: false,
}
}
pub fn child(
parent_index: usize,
depth: usize,
score: S::Score,
entity_index: usize,
value_index: usize,
) -> Self {
Self {
depth,
score,
optimistic_bound: None,
entity_index: Some(entity_index),
value_index: Some(value_index),
parent_index: Some(parent_index),
expanded: false,
}
}
#[inline]
pub fn depth(&self) -> usize {
self.depth
}
#[inline]
pub fn score(&self) -> &S::Score {
&self.score
}
pub fn set_score(&mut self, score: S::Score) {
self.score = score;
}
#[inline]
pub fn optimistic_bound(&self) -> Option<&S::Score> {
self.optimistic_bound.as_ref()
}
pub fn set_optimistic_bound(&mut self, bound: S::Score) {
self.optimistic_bound = Some(bound);
}
#[inline]
pub fn entity_index(&self) -> Option<usize> {
self.entity_index
}
#[inline]
pub fn value_index(&self) -> Option<usize> {
self.value_index
}
#[inline]
pub fn parent_index(&self) -> Option<usize> {
self.parent_index
}
#[inline]
pub fn is_expanded(&self) -> bool {
self.expanded
}
pub fn mark_expanded(&mut self) {
self.expanded = true;
}
pub fn is_leaf(&self, total_entities: usize) -> bool {
self.depth >= total_entities
}
pub fn can_prune(&self, best_score: &S::Score) -> bool {
match &self.optimistic_bound {
Some(bound) => bound <= best_score,
None => false,
}
}
}
#[derive(Debug, Clone)]
pub struct MoveSequence<S, M>
where
S: PlanningSolution,
M: Move<S>,
{
moves: Vec<M>,
_phantom: PhantomData<fn() -> S>,
}
impl<S, M> MoveSequence<S, M>
where
S: PlanningSolution,
M: Move<S>,
{
pub fn new() -> Self {
Self {
moves: Vec::new(),
_phantom: PhantomData,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
moves: Vec::with_capacity(capacity),
_phantom: PhantomData,
}
}
pub fn push(&mut self, m: M) {
self.moves.push(m);
}
pub fn pop(&mut self) -> Option<M> {
self.moves.pop()
}
pub fn len(&self) -> usize {
self.moves.len()
}
pub fn is_empty(&self) -> bool {
self.moves.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &M> {
self.moves.iter()
}
pub fn clear(&mut self) {
self.moves.clear();
}
}
impl<S, M> Default for MoveSequence<S, M>
where
S: PlanningSolution,
M: Move<S>,
{
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::heuristic::r#move::ChangeMove;
use solverforge_core::score::SoftScore;
#[derive(Clone, Debug)]
struct TestSolution {
values: Vec<Option<i32>>,
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 get_value(s: &TestSolution, idx: usize) -> Option<i32> {
s.values.get(idx).copied().flatten()
}
fn set_value(s: &mut TestSolution, idx: usize, v: Option<i32>) {
if let Some(val) = s.values.get_mut(idx) {
*val = v;
}
}
#[test]
fn test_root_node() {
let node: ExhaustiveSearchNode<TestSolution> = ExhaustiveSearchNode::root(SoftScore::of(0));
assert_eq!(node.depth(), 0);
assert_eq!(node.score(), &SoftScore::of(0));
assert!(node.parent_index().is_none());
assert!(node.entity_index().is_none());
assert!(!node.is_expanded());
}
#[test]
fn test_child_node() {
let node: ExhaustiveSearchNode<TestSolution> =
ExhaustiveSearchNode::child(0, 1, SoftScore::of(-1), 0, 2);
assert_eq!(node.depth(), 1);
assert_eq!(node.score(), &SoftScore::of(-1));
assert_eq!(node.parent_index(), Some(0));
assert_eq!(node.entity_index(), Some(0));
assert_eq!(node.value_index(), Some(2));
}
#[test]
fn test_is_leaf() {
let node: ExhaustiveSearchNode<TestSolution> =
ExhaustiveSearchNode::child(0, 4, SoftScore::of(0), 3, 1);
assert!(node.is_leaf(4));
assert!(!node.is_leaf(5));
}
#[test]
fn test_optimistic_bound_pruning() {
let mut node: ExhaustiveSearchNode<TestSolution> =
ExhaustiveSearchNode::root(SoftScore::of(-5));
assert!(!node.can_prune(&SoftScore::of(0)));
node.set_optimistic_bound(SoftScore::of(-2));
assert!(node.can_prune(&SoftScore::of(0)));
node.set_optimistic_bound(SoftScore::of(5));
assert!(!node.can_prune(&SoftScore::of(0)));
}
type TestMove = ChangeMove<TestSolution, i32>;
#[test]
fn test_move_sequence() {
let mut seq: MoveSequence<TestSolution, TestMove> = MoveSequence::new();
assert!(seq.is_empty());
assert_eq!(seq.len(), 0);
seq.push(ChangeMove::new(
0,
Some(42),
get_value,
set_value,
"test",
0,
));
assert_eq!(seq.len(), 1);
let m = seq.pop();
assert!(m.is_some());
assert!(seq.is_empty());
}
}