Skip to main content

solverforge_scoring/constraint/projected/
uni.rs

1use 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}