solverforge_solver/phase/construction/grouped_scalar/
assignment_candidate.rs1#[cfg(test)]
2use solverforge_core::domain::PlanningSolution;
3
4#[cfg(test)]
5use super::assignment_stream::collect_assignment_moves;
6use crate::builder::ScalarAssignmentBinding;
7#[cfg(test)]
8use crate::heuristic::r#move::CompoundScalarMove;
9
10#[derive(Clone, Copy, Debug)]
11pub struct ScalarAssignmentMoveOptions {
12 pub(crate) value_candidate_limit: Option<usize>,
13 pub(crate) max_moves: usize,
14 pub(crate) max_depth: usize,
15 pub(crate) max_rematch_size: usize,
16 pub(crate) entity_offset: usize,
17 pub(crate) required_scarcity_ordering: bool,
18}
19
20impl ScalarAssignmentMoveOptions {
21 pub fn for_construction(limits: crate::builder::ScalarGroupLimits) -> Self {
22 Self {
23 value_candidate_limit: limits.value_candidate_limit,
24 max_moves: limits.group_candidate_limit.unwrap_or(usize::MAX),
25 max_depth: limits.max_augmenting_depth.unwrap_or(3),
26 max_rematch_size: limits.max_rematch_size.unwrap_or(4).max(2),
27 entity_offset: 0,
28 required_scarcity_ordering: true,
29 }
30 }
31
32 pub(crate) fn for_selector(
33 limits: crate::builder::ScalarGroupLimits,
34 value_candidate_limit: Option<usize>,
35 max_moves_per_step: usize,
36 entity_offset: usize,
37 ) -> Self {
38 Self {
39 value_candidate_limit: value_candidate_limit.or(limits.value_candidate_limit),
40 max_moves: max_moves_per_step,
41 max_depth: limits.max_augmenting_depth.unwrap_or(3),
42 max_rematch_size: limits.max_rematch_size.unwrap_or(4).max(2),
43 entity_offset,
44 required_scarcity_ordering: true,
45 }
46 }
47
48 pub fn with_max_moves(mut self, max_moves: usize) -> Self {
49 self.max_moves = max_moves;
50 self
51 }
52
53 pub fn with_entity_offset(mut self, entity_offset: usize) -> Self {
54 self.entity_offset = entity_offset;
55 self
56 }
57
58 pub fn with_required_scarcity_ordering(mut self, enabled: bool) -> Self {
59 self.required_scarcity_ordering = enabled;
60 self
61 }
62}
63
64#[derive(Clone, Copy)]
65pub(super) struct AssignmentMoveIntent {
66 pub(super) allow_optional_displacement: bool,
67 pub(super) reason: &'static str,
68}
69
70impl AssignmentMoveIntent {
71 pub(super) const fn required() -> Self {
72 Self {
73 allow_optional_displacement: true,
74 reason: "scalar_assignment_required",
75 }
76 }
77
78 pub(super) const fn optional() -> Self {
79 Self {
80 allow_optional_displacement: false,
81 reason: "scalar_assignment_optional",
82 }
83 }
84
85 pub(super) const fn capacity_repair() -> Self {
86 Self {
87 allow_optional_displacement: true,
88 reason: "scalar_assignment_capacity_repair",
89 }
90 }
91
92 pub(super) const fn reassignment() -> Self {
93 Self {
94 allow_optional_displacement: true,
95 reason: "scalar_assignment_reassignment",
96 }
97 }
98}
99
100pub(crate) fn remaining_required_count<S>(group: &ScalarAssignmentBinding<S>, solution: &S) -> u64 {
101 group.remaining_required_count(solution)
102}
103
104#[cfg(test)]
105pub(crate) fn selector_assignment_moves<S>(
106 group: &ScalarAssignmentBinding<S>,
107 solution: &S,
108 options: ScalarAssignmentMoveOptions,
109) -> Vec<CompoundScalarMove<S>>
110where
111 S: PlanningSolution,
112{
113 collect_assignment_moves(group, solution, options)
114}
115
116pub(super) fn ordered_entities<S, F>(
117 group: &ScalarAssignmentBinding<S>,
118 solution: &S,
119 mut predicate: F,
120) -> Vec<usize>
121where
122 F: FnMut(usize) -> bool,
123{
124 let mut entities = (0..group.entity_count(solution))
125 .filter(|entity_index| predicate(*entity_index))
126 .collect::<Vec<_>>();
127 entities.sort_by_key(|entity_index| {
128 (
129 group.entity_order_key(solution, *entity_index),
130 *entity_index,
131 )
132 });
133 entities
134}
135
136pub(super) fn rotate_entity_order(entities: &mut [usize], entity_offset: usize) {
137 if entities.is_empty() {
138 return;
139 }
140 let len = entities.len();
141 entities.rotate_left(entity_offset % len);
142}