solverforge_scoring/constraint/projected/
uni.rs1use std::marker::PhantomData;
2
3use solverforge_core::score::Score;
4use solverforge_core::{ConstraintRef, ImpactType};
5
6use crate::api::constraint_set::IncrementalConstraint;
7use crate::stream::filter::UniFilter;
8use crate::stream::projected::{RowCoordinate, RowOwner, Source};
9use crate::stream::relational::hash::{FastMap, FastSet};
10
11pub struct Uni<S, Out, Src, F, W, Sc>
12where
13 Src: Source<S, Out>,
14 Sc: Score,
15{
16 constraint_ref: ConstraintRef,
17 impact_type: ImpactType,
18 source: Src,
19 filter: F,
20 weight: W,
21 is_hard: bool,
22 source_state: Option<Src::State>,
23 row_contributions: FastMap<RowCoordinate, Sc>,
24 rows_by_owner: FastMap<RowOwner, Vec<RowCoordinate>>,
25 _phantom: PhantomData<(fn() -> S, fn() -> Out)>,
26}
27
28impl<S, Out, Src, F, W, Sc> Uni<S, Out, Src, F, W, Sc>
29where
30 S: Send + Sync + 'static,
31 Out: Send + Sync + 'static,
32 Src: Source<S, Out>,
33 F: UniFilter<S, Out>,
34 W: Fn(&Out) -> Sc + Send + Sync,
35 Sc: Score + 'static,
36{
37 pub fn new(
38 constraint_ref: ConstraintRef,
39 impact_type: ImpactType,
40 source: Src,
41 filter: F,
42 weight: W,
43 is_hard: bool,
44 ) -> Self {
45 Self {
46 constraint_ref,
47 impact_type,
48 source,
49 filter,
50 weight,
51 is_hard,
52 source_state: None,
53 row_contributions: FastMap::default(),
54 rows_by_owner: FastMap::default(),
55 _phantom: PhantomData,
56 }
57 }
58
59 fn compute_score(&self, output: &Out) -> Sc {
60 let base = (self.weight)(output);
61 match self.impact_type {
62 ImpactType::Penalty => -base,
63 ImpactType::Reward => base,
64 }
65 }
66
67 fn ensure_source_state(&mut self, solution: &S) {
68 if self.source_state.is_none() {
69 self.source_state = Some(self.source.build_state(solution));
70 }
71 }
72
73 fn index_coordinate(&mut self, coordinate: RowCoordinate) {
74 coordinate.for_each_owner(|owner| {
75 self.rows_by_owner
76 .entry(owner)
77 .or_default()
78 .push(coordinate);
79 });
80 }
81
82 fn unindex_coordinate(&mut self, coordinate: RowCoordinate) {
83 coordinate.for_each_owner(|owner| {
84 let mut remove_bucket = false;
85 if let Some(rows) = self.rows_by_owner.get_mut(&owner) {
86 rows.retain(|candidate| *candidate != coordinate);
87 remove_bucket = rows.is_empty();
88 }
89 if remove_bucket {
90 self.rows_by_owner.remove(&owner);
91 }
92 });
93 }
94
95 fn insert_row(&mut self, solution: &S, coordinate: RowCoordinate, output: Out) -> Sc {
96 if self.row_contributions.contains_key(&coordinate) || !self.filter.test(solution, &output)
97 {
98 return Sc::zero();
99 }
100 let contribution = self.compute_score(&output);
101 self.row_contributions.insert(coordinate, contribution);
102 self.index_coordinate(coordinate);
103 contribution
104 }
105
106 fn retract_row(&mut self, coordinate: RowCoordinate) -> Sc {
107 let Some(contribution) = self.row_contributions.remove(&coordinate) else {
108 return Sc::zero();
109 };
110 self.unindex_coordinate(coordinate);
111 -contribution
112 }
113
114 fn localized_owners(&self, descriptor_index: usize, entity_index: usize) -> Vec<RowOwner> {
115 let mut owners = Vec::new();
116 for slot in 0..self.source.source_count() {
117 if self
118 .source
119 .change_source(slot)
120 .assert_localizes(descriptor_index, &self.constraint_ref.name)
121 {
122 owners.push(RowOwner {
123 source_slot: slot,
124 entity_index,
125 });
126 }
127 }
128 owners
129 }
130
131 fn coordinates_for_owners(&self, owners: &[RowOwner]) -> Vec<RowCoordinate> {
132 let mut seen = FastSet::default();
133 let mut coordinates = Vec::new();
134 for owner in owners {
135 let Some(rows) = self.rows_by_owner.get(owner) else {
136 continue;
137 };
138 for &coordinate in rows {
139 if seen.insert(coordinate) {
140 coordinates.push(coordinate);
141 }
142 }
143 }
144 coordinates
145 }
146}
147
148impl<S, Out, Src, F, W, Sc> IncrementalConstraint<S, Sc> for Uni<S, Out, Src, F, W, Sc>
149where
150 S: Send + Sync + 'static,
151 Out: Send + Sync + 'static,
152 Src: Source<S, Out>,
153 F: UniFilter<S, Out>,
154 W: Fn(&Out) -> Sc + Send + Sync,
155 Sc: Score + 'static,
156{
157 fn evaluate(&self, solution: &S) -> Sc {
158 let state = self.source.build_state(solution);
159 let mut total = Sc::zero();
160 self.source.collect_all(solution, &state, |_, output| {
161 if self.filter.test(solution, &output) {
162 total = total + self.compute_score(&output);
163 }
164 });
165 total
166 }
167
168 fn match_count(&self, solution: &S) -> usize {
169 let state = self.source.build_state(solution);
170 let mut count = 0;
171 self.source.collect_all(solution, &state, |_, output| {
172 if self.filter.test(solution, &output) {
173 count += 1;
174 }
175 });
176 count
177 }
178
179 fn initialize(&mut self, solution: &S) -> Sc {
180 self.reset();
181 let state = self.source.build_state(solution);
182 let mut total = Sc::zero();
183 let mut rows = Vec::new();
184 self.source
185 .collect_all(solution, &state, |coordinate, output| {
186 rows.push((coordinate, output));
187 });
188 self.source_state = Some(state);
189 for (coordinate, output) in rows {
190 total = total + self.insert_row(solution, coordinate, output);
191 }
192 total
193 }
194
195 fn on_insert(&mut self, solution: &S, entity_index: usize, descriptor_index: usize) -> Sc {
196 let owners = self.localized_owners(descriptor_index, entity_index);
197 self.ensure_source_state(solution);
198 {
199 let state = self.source_state.as_mut().expect("projected source state");
200 for owner in &owners {
201 self.source.insert_entity_state(
202 solution,
203 state,
204 owner.source_slot,
205 owner.entity_index,
206 );
207 }
208 }
209 let mut rows = Vec::new();
210 let state = self.source_state.as_ref().expect("projected source state");
211 for owner in &owners {
212 self.source.collect_entity(
213 solution,
214 state,
215 owner.source_slot,
216 owner.entity_index,
217 |coordinate, output| rows.push((coordinate, output)),
218 );
219 }
220 let mut total = Sc::zero();
221 for (coordinate, output) in rows {
222 total = total + self.insert_row(solution, coordinate, output);
223 }
224 total
225 }
226
227 fn on_retract(&mut self, solution: &S, entity_index: usize, descriptor_index: usize) -> Sc {
228 let owners = self.localized_owners(descriptor_index, entity_index);
229 let mut total = Sc::zero();
230 for coordinate in self.coordinates_for_owners(&owners) {
231 total = total + self.retract_row(coordinate);
232 }
233 if let Some(state) = self.source_state.as_mut() {
234 for owner in &owners {
235 self.source.retract_entity_state(
236 solution,
237 state,
238 owner.source_slot,
239 owner.entity_index,
240 );
241 }
242 }
243 total
244 }
245
246 fn reset(&mut self) {
247 self.source_state = None;
248 self.row_contributions.clear();
249 self.rows_by_owner.clear();
250 }
251
252 fn name(&self) -> &str {
253 &self.constraint_ref.name
254 }
255
256 fn constraint_ref(&self) -> &ConstraintRef {
257 &self.constraint_ref
258 }
259
260 fn is_hard(&self) -> bool {
261 self.is_hard
262 }
263}