use crate::phase::construction::grouped_scalar::ScalarAssignmentState;
pub struct GroupedScalarSelector<S> {
group: crate::builder::context::ScalarGroupBinding<S>,
value_candidate_limit: Option<usize>,
max_moves_per_step: Option<usize>,
require_hard_improvement: bool,
}
impl<S> GroupedScalarSelector<S> {
pub(crate) fn new(
group: crate::builder::context::ScalarGroupBinding<S>,
value_candidate_limit: Option<usize>,
max_moves_per_step: Option<usize>,
require_hard_improvement: bool,
) -> Self {
let effective_value_candidate_limit =
value_candidate_limit.or(group.limits.value_candidate_limit);
let effective_max_moves_per_step = max_moves_per_step.or(group.limits.max_moves_per_step);
Self {
group,
value_candidate_limit: effective_value_candidate_limit,
max_moves_per_step: effective_max_moves_per_step,
require_hard_improvement,
}
}
fn effective_max_moves_per_step(&self, solution: &S) -> usize {
if let Some(max_moves_per_step) = self.max_moves_per_step {
return max_moves_per_step;
}
match &self.group.kind {
crate::builder::ScalarGroupBindingKind::Assignment(assignment) => {
let max_rematch_size = self.group.limits.max_rematch_size.unwrap_or(4).max(2);
assignment
.entity_count(solution)
.saturating_mul(max_rematch_size)
.clamp(256, 4096)
}
crate::builder::ScalarGroupBindingKind::Candidates { .. } => 256,
}
}
fn limits(&self, max_moves_per_step: usize) -> crate::builder::context::ScalarGroupLimits {
crate::builder::context::ScalarGroupLimits {
value_candidate_limit: self.value_candidate_limit,
group_candidate_limit: Some(max_moves_per_step),
max_moves_per_step: Some(max_moves_per_step),
max_augmenting_depth: self.group.limits.max_augmenting_depth,
max_rematch_size: self.group.limits.max_rematch_size,
}
}
}
impl<S> std::fmt::Debug for GroupedScalarSelector<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GroupedScalarSelector")
.field("group_name", &self.group.group_name)
.field("value_candidate_limit", &self.value_candidate_limit)
.field("max_moves_per_step", &self.max_moves_per_step)
.field("require_hard_improvement", &self.require_hard_improvement)
.finish()
}
}
pub struct GroupedScalarCursor<'a, S>
where
S: PlanningSolution + 'static,
{
store: CandidateStore<S, ScalarMoveUnion<S, usize>>,
next_index: usize,
group: &'a crate::builder::context::ScalarGroupBinding<S>,
activation: Option<GroupedScalarActivation<'a, S>>,
assignment_cursor:
Option<crate::phase::construction::grouped_scalar::ScalarAssignmentMoveCursor<S>>,
pooled_assignment: Option<crate::phase::construction::grouped_scalar::ScalarAssignmentState>,
require_hard_improvement: bool,
}
struct GroupedScalarActivation<'a, S> {
group: &'a crate::builder::context::ScalarGroupBinding<S>,
limits: crate::builder::context::ScalarGroupLimits,
max_moves_per_step: usize,
context: MoveStreamContext,
require_hard_improvement: bool,
}
impl<'a, S> GroupedScalarCursor<'a, S>
where
S: PlanningSolution + 'static,
{
fn new(
group: &'a crate::builder::context::ScalarGroupBinding<S>,
store: CandidateStore<S, ScalarMoveUnion<S, usize>>,
) -> Self {
Self {
store,
group,
next_index: 0,
activation: None,
assignment_cursor: None,
pooled_assignment: None,
require_hard_improvement: false,
}
}
fn deferred(activation: GroupedScalarActivation<'a, S>) -> Self {
Self {
store: CandidateStore::with_capacity(activation.max_moves_per_step),
group: activation.group,
next_index: 0,
activation: Some(activation),
assignment_cursor: None,
pooled_assignment: None,
require_hard_improvement: false,
}
}
fn activate(&mut self, solution: &S) {
let Some(activation) = self.activation.take() else {
return;
};
let GroupedScalarActivation {
group,
limits,
max_moves_per_step,
context,
require_hard_improvement,
} = activation;
match &group.kind {
crate::builder::ScalarGroupBindingKind::Assignment(assignment) => {
let selection_salt =
0xC0A1_E5CE_AAA0_0002 ^ group.group_name.len() as u64;
let entity_offset = if context.is_canonical() {
0
} else {
context
.offset_seed(selection_salt)
.wrapping_add(context.step_index() as usize)
};
let options = crate::phase::construction::grouped_scalar::ScalarAssignmentMoveOptions::for_selector(
group.limits,
limits.value_candidate_limit,
max_moves_per_step,
entity_offset,
);
self.assignment_cursor = Some(
match self.pooled_assignment.take() {
Some(mut pooled)
if pooled.row_count() == assignment.entity_count(solution) =>
{
let changed = (0..pooled.row_count())
.filter(|&entity| {
pooled.current_value(entity)
!= assignment.current_value(solution, entity)
})
.collect::<Vec<_>>();
if !changed.is_empty() {
let rows = changed.into_iter().map(|entity| {
(entity, assignment.current_value(solution, entity))
});
pooled.refresh_rows(assignment, solution, rows);
}
crate::phase::construction::grouped_scalar::ScalarAssignmentMoveCursor::with_pooled_state(
assignment, solution, options, pooled,
)
}
_ => crate::phase::construction::grouped_scalar::ScalarAssignmentMoveCursor::new(
assignment, solution, options,
),
},
);
self.require_hard_improvement = require_hard_improvement;
}
crate::builder::ScalarGroupBindingKind::Candidates { candidate_provider } => {
let mut candidates = candidate_provider(solution, limits);
context.apply_selection_order(
&mut candidates,
0xC0A1_E5CE_AAA0_0001 ^ group.group_name.len() as u64,
);
let mut seen_candidates = Vec::new();
let mut targets = std::collections::HashSet::new();
for candidate in candidates {
if self.store.len() >= max_moves_per_step {
break;
}
if candidate.edits().is_empty()
|| seen_candidates
.iter()
.any(|seen_candidate| seen_candidate == &candidate)
{
continue;
}
targets.clear();
if candidate.edits().iter().any(|edit| {
!targets.insert((
edit.descriptor_index(),
edit.entity_index(),
edit.variable_name(),
))
}) {
continue;
}
let Some(mov) = crate::phase::construction::grouped_scalar::compound_move_for_group_candidate(
group,
solution,
&candidate,
) else {
continue;
};
let mov = mov.with_require_hard_improvement(require_hard_improvement);
if mov.is_doable_on(solution) {
self.store.push(ScalarMoveUnion::CompoundScalar(mov));
seen_candidates.push(candidate);
}
}
}
}
}
}
impl<S: PlanningSolution + 'static> GroupedScalarCursor<'_, S> {
pub(crate) fn into_pooled_assignment(mut self) -> Option<ScalarAssignmentState> {
let returned = self
.assignment_cursor
.take()
.map(crate::phase::construction::grouped_scalar::ScalarAssignmentMoveCursor::into_state);
let pooled = returned.or_else(|| self.pooled_assignment.take());
drop(self);
pooled
}
}
impl<S> MoveCursor<S, ScalarMoveUnion<S, usize>> for GroupedScalarCursor<'_, S>
where
S: PlanningSolution + 'static,
{
fn next_candidate(&mut self, solution: &S) -> Option<CandidateId> {
self.next_candidate_with_control(solution, &mut || false)
}
fn next_candidate_with_control<ShouldStop>(
&mut self,
solution: &S,
should_stop: &mut ShouldStop,
) -> Option<CandidateId>
where
ShouldStop: FnMut() -> bool,
{
if should_stop() {
return None;
}
self.activate(solution);
if self.next_index < self.store.len() {
let id = CandidateId::new(self.next_index);
self.next_index += 1;
return Some(id);
}
let crate::builder::ScalarGroupBindingKind::Assignment(assignment) = &self.group.kind else {
return None;
};
let assignment_cursor = self.assignment_cursor.as_mut()?;
let mov = assignment_cursor
.next_move_with_control(assignment, solution, should_stop)?
.with_require_hard_improvement(self.require_hard_improvement);
let id = self.store.push(ScalarMoveUnion::CompoundScalar(mov));
self.next_index = id.index() + 1;
Some(id)
}
fn candidate(
&self,
id: CandidateId,
) -> Option<MoveCandidateRef<'_, S, ScalarMoveUnion<S, usize>>> {
self.store.candidate(id)
}
fn take_candidate(&mut self, id: CandidateId) -> ScalarMoveUnion<S, usize> {
self.store.take_candidate(id)
}
fn release_candidate(&mut self, id: CandidateId) -> bool {
self.store.release_candidate(id)
}
}
impl<S> MoveSelector<S, ScalarMoveUnion<S, usize>> for GroupedScalarSelector<S>
where
S: PlanningSolution + 'static,
{
type Cursor<'a>
= GroupedScalarCursor<'a, S>
where
Self: 'a;
fn open_cursor<'a, D: solverforge_scoring::Director<S>>(
&'a self,
score_director: &D,
) -> Self::Cursor<'a> {
self.open_cursor_with_context(score_director, MoveStreamContext::default())
}
fn open_cursor_with_context<'a, D: solverforge_scoring::Director<S>>(
&'a self,
score_director: &D,
context: MoveStreamContext,
) -> Self::Cursor<'a> {
let solution = score_director.working_solution();
let max_moves_per_step = self.effective_max_moves_per_step(solution);
if max_moves_per_step == 0 {
return GroupedScalarCursor::new(&self.group, CandidateStore::new());
}
GroupedScalarCursor::deferred(GroupedScalarActivation {
group: &self.group,
limits: self.limits(max_moves_per_step),
max_moves_per_step,
context,
require_hard_improvement: self.require_hard_improvement,
})
}
fn size<D: solverforge_scoring::Director<S>>(&self, score_director: &D) -> usize {
self.effective_max_moves_per_step(score_director.working_solution())
}
}
impl<S: PlanningSolution + 'static> GroupedScalarSelector<S> {
pub(crate) fn open_cursor_with_retained_state<'a, D: solverforge_scoring::Director<S>>(
&'a self,
state: &mut Option<Box<ScalarAssignmentState>>,
score_director: &D,
context: MoveStreamContext,
) -> GroupedScalarCursor<'a, S> {
let mut cursor = self.open_cursor_with_context(score_director, context);
cursor.pooled_assignment = state.take().map(|boxed| *boxed);
cursor
}
}