use std::fmt::Debug;
use std::marker::PhantomData;
use smallvec::{smallvec, SmallVec};
use solverforge_core::domain::PlanningSolution;
use solverforge_scoring::Director;
use super::k_opt_reconnection::KOptReconnection;
use super::metadata::{
encode_option_debug, encode_usize, hash_str, MoveTabuScope, ScopedValueTabuToken,
};
use super::{Move, MoveTabuSignature};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct CutPoint {
entity_index: usize,
position: usize,
}
impl CutPoint {
#[inline]
pub const fn new(entity_index: usize, position: usize) -> Self {
Self {
entity_index,
position,
}
}
#[inline]
pub const fn entity_index(&self) -> usize {
self.entity_index
}
#[inline]
pub const fn position(&self) -> usize {
self.position
}
}
pub struct KOptMove<S, V> {
cuts: [CutPoint; 5],
cut_count: u8,
reconnection: KOptReconnection,
list_len: fn(&S, usize) -> usize,
list_get: fn(&S, usize, usize) -> Option<V>,
sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
sublist_insert: fn(&mut S, usize, usize, Vec<V>),
variable_name: &'static str,
descriptor_index: usize,
entity_index: usize,
_phantom: PhantomData<fn() -> V>,
}
impl<S, V> Clone for KOptMove<S, V> {
fn clone(&self) -> Self {
Self {
cuts: self.cuts,
cut_count: self.cut_count,
reconnection: self.reconnection,
list_len: self.list_len,
list_get: self.list_get,
sublist_remove: self.sublist_remove,
sublist_insert: self.sublist_insert,
variable_name: self.variable_name,
descriptor_index: self.descriptor_index,
entity_index: self.entity_index,
_phantom: PhantomData,
}
}
}
impl<S, V: Debug> Debug for KOptMove<S, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let cuts: Vec<_> = self.cuts[..self.cut_count as usize]
.iter()
.map(|c| c.position)
.collect();
f.debug_struct("KOptMove")
.field("k", &self.cut_count)
.field("entity", &self.entity_index)
.field("cuts", &cuts)
.field("reconnection", &self.reconnection)
.field("variable_name", &self.variable_name)
.finish()
}
}
impl<S, V> KOptMove<S, V> {
#[allow(clippy::too_many_arguments)]
pub fn new(
cuts: &[CutPoint],
reconnection: &KOptReconnection,
list_len: fn(&S, usize) -> usize,
list_get: fn(&S, usize, usize) -> Option<V>,
sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
sublist_insert: fn(&mut S, usize, usize, Vec<V>),
variable_name: &'static str,
descriptor_index: usize,
) -> Self {
assert!(!cuts.is_empty() && cuts.len() <= 5, "k must be 1-5");
let mut cut_array = [CutPoint::default(); 5];
for (i, cut) in cuts.iter().enumerate() {
cut_array[i] = *cut;
}
let entity_index = cuts[0].entity_index;
Self {
cuts: cut_array,
cut_count: cuts.len() as u8,
reconnection: *reconnection,
list_len,
list_get,
sublist_remove,
sublist_insert,
variable_name,
descriptor_index,
entity_index,
_phantom: PhantomData,
}
}
#[inline]
pub fn k(&self) -> usize {
self.cut_count as usize
}
#[inline]
pub fn cuts(&self) -> &[CutPoint] {
&self.cuts[..self.cut_count as usize]
}
pub fn is_intra_route(&self) -> bool {
let first = self.cuts[0].entity_index;
self.cuts[..self.cut_count as usize]
.iter()
.all(|c| c.entity_index == first)
}
}
impl<S, V> Move<S> for KOptMove<S, V>
where
S: PlanningSolution,
V: Clone + Send + Sync + Debug + 'static,
{
fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
let solution = score_director.working_solution();
let k = self.cut_count as usize;
if k < 2 {
return false;
}
if self.reconnection.k() != k {
return false;
}
let len = (self.list_len)(solution, self.entity_index);
for cut in &self.cuts[..k] {
if cut.position > len {
return false;
}
}
if self.is_intra_route() {
for i in 1..k {
if self.cuts[i].position <= self.cuts[i - 1].position {
return false;
}
}
}
true
}
fn do_move<D: Director<S>>(&self, score_director: &mut D) {
let k = self.cut_count as usize;
let entity = self.entity_index;
score_director.before_variable_changed(self.descriptor_index, entity);
let solution = score_director.working_solution_mut();
let len = (self.list_len)(solution, entity);
let all_elements = (self.sublist_remove)(solution, entity, 0, len);
let mut boundaries = Vec::with_capacity(k + 2);
boundaries.push(0);
for i in 0..k {
boundaries.push(self.cuts[i].position);
}
boundaries.push(len);
let mut segments: Vec<Vec<V>> = Vec::with_capacity(k + 1);
for i in 0..=k {
let start = boundaries[i];
let end = boundaries[i + 1];
segments.push(all_elements[start..end].to_vec());
}
let mut new_elements = Vec::with_capacity(len);
for pos in 0..self.reconnection.segment_count() {
let seg_idx = self.reconnection.segment_at(pos);
let mut seg = std::mem::take(&mut segments[seg_idx]);
if self.reconnection.should_reverse(seg_idx) {
seg.reverse();
}
new_elements.extend(seg);
}
let new_len = new_elements.len();
(self.sublist_insert)(
score_director.working_solution_mut(),
entity,
0,
new_elements,
);
score_director.after_variable_changed(self.descriptor_index, entity);
let sublist_remove = self.sublist_remove;
let sublist_insert = self.sublist_insert;
score_director.register_undo(Box::new(move |s: &mut S| {
let _ = sublist_remove(s, entity, 0, new_len);
sublist_insert(s, entity, 0, all_elements);
}));
}
fn descriptor_index(&self) -> usize {
self.descriptor_index
}
fn entity_indices(&self) -> &[usize] {
std::slice::from_ref(&self.entity_index)
}
fn variable_name(&self) -> &str {
self.variable_name
}
fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
let mut touched_value_ids: SmallVec<[u64; 2]> = SmallVec::new();
let len = (self.list_len)(score_director.working_solution(), self.entity_index);
let k = self.cut_count as usize;
let first_pos = self.cuts[0].position;
let last_pos = self.cuts[k - 1].position;
for pos in first_pos..len.min(last_pos.max(first_pos)) {
let value = (self.list_get)(score_director.working_solution(), self.entity_index, pos);
touched_value_ids.push(encode_option_debug(value.as_ref()));
}
let entity_id = encode_usize(self.entity_index);
let variable_id = hash_str(self.variable_name);
let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
let destination_value_tokens: SmallVec<[ScopedValueTabuToken; 2]> = touched_value_ids
.iter()
.copied()
.map(|value_id| scope.value_token(value_id))
.collect();
let mut move_id = smallvec![
encode_usize(self.descriptor_index),
variable_id,
entity_id,
encode_usize(k)
];
for cut in &self.cuts[..k] {
move_id.push(encode_usize(cut.position));
}
for segment in self.reconnection.segment_order() {
move_id.push(u64::from(*segment));
}
for idx in 0..self.reconnection.segment_count() {
move_id.push(if self.reconnection.should_reverse(idx) {
1
} else {
0
});
}
move_id.extend(touched_value_ids.iter().copied());
let mut inverse_order = vec![0u8; self.reconnection.segment_count()];
for (pos, segment) in self
.reconnection
.segment_order()
.iter()
.copied()
.enumerate()
{
inverse_order[segment as usize] = pos as u8;
}
let mut undo_move_id = smallvec![
encode_usize(self.descriptor_index),
variable_id,
entity_id,
encode_usize(k)
];
for cut in &self.cuts[..k] {
undo_move_id.push(encode_usize(cut.position));
}
for segment in inverse_order {
undo_move_id.push(u64::from(segment));
}
for idx in 0..self.reconnection.segment_count() {
undo_move_id.push(if self.reconnection.should_reverse(idx) {
1
} else {
0
});
}
undo_move_id.extend(touched_value_ids.iter().copied());
MoveTabuSignature::new(scope, move_id, undo_move_id)
.with_entity_tokens([scope.entity_token(entity_id)])
.with_destination_value_tokens(destination_value_tokens)
}
}