1use std::hash::Hash;
8use std::marker::PhantomData;
9
10use solverforge_core::score::Score;
11use solverforge_core::{ConstraintRef, ImpactType};
12
13use super::collection_extract::CollectionExtract;
14use super::collector::{Accumulator, Collector};
15use super::relational::leaf_collector::{leaf_key, LeafCollector, OptionalEntityKey};
16use super::relational::operator::{
17 CollectionNode, ComplementNode, ComplementView, GroupNode, GroupView,
18};
19use super::relational::view_plan::{GroupKey, ViewEqualPlan};
20use super::relational::Leaf;
21use super::weighting_support::ConstraintWeight;
22use crate::constraint::relational::OperatorTerminal;
23
24pub struct ComplementedConstraintStream<S, A, B, K, EA, EB, KA, KB, C, V, R, Acc, D, Sc>
93where
94 Sc: Score,
95{
96 extractor_a: EA,
97 extractor_b: EB,
98 key_a: KA,
99 key_b: KB,
100 collector: C,
101 default_fn: D,
102 _phantom: PhantomData<(
103 fn() -> S,
104 fn() -> A,
105 fn() -> B,
106 fn() -> K,
107 fn() -> V,
108 fn() -> R,
109 fn() -> Acc,
110 fn() -> Sc,
111 )>,
112}
113
114impl<S, A, B, K, EA, EB, KA, KB, C, V, R, Acc, D, Sc>
115 ComplementedConstraintStream<S, A, B, K, EA, EB, KA, KB, C, V, R, Acc, D, Sc>
116where
117 S: Send + Sync + 'static,
118 A: Clone + Send + Sync + 'static,
119 B: Clone + Send + Sync + 'static,
120 K: Clone + Eq + Hash + Send + Sync + 'static,
121 EA: CollectionExtract<S, Item = A>,
122 EB: CollectionExtract<S, Item = B>,
123 KA: Fn(&A) -> Option<K> + Send + Sync,
124 KB: Fn(&B) -> K + Send + Sync,
125 C: for<'i> Collector<&'i A, Value = V, Result = R, Accumulator = Acc> + Send + Sync + 'static,
126 V: Send + Sync + 'static,
127 R: Send + Sync + 'static,
128 Acc: Accumulator<V, R> + Send + Sync + 'static,
129 D: Fn(&B) -> R + Send + Sync,
130 Sc: Score + 'static,
131{
132 fn into_weighted_builder<W>(
133 self,
134 impact_type: ImpactType,
135 weight_fn: W,
136 is_hard: bool,
137 ) -> ComplementedConstraintBuilder<S, A, B, K, EA, EB, KA, KB, C, V, R, Acc, D, W, Sc>
138 where
139 W: Fn(&K, &R) -> Sc + Send + Sync,
140 {
141 ComplementedConstraintBuilder {
142 extractor_a: self.extractor_a,
143 extractor_b: self.extractor_b,
144 key_a: self.key_a,
145 key_b: self.key_b,
146 collector: self.collector,
147 default_fn: self.default_fn,
148 impact_type,
149 weight_fn,
150 is_hard,
151 _phantom: PhantomData,
152 }
153 }
154
155 pub(crate) fn new(
157 extractor_a: EA,
158 extractor_b: EB,
159 key_a: KA,
160 key_b: KB,
161 collector: C,
162 default_fn: D,
163 ) -> Self {
164 Self {
165 extractor_a,
166 extractor_b,
167 key_a,
168 key_b,
169 collector,
170 default_fn,
171 _phantom: PhantomData,
172 }
173 }
174
175 pub fn penalize<W>(
176 self,
177 weight: W,
178 ) -> ComplementedConstraintBuilder<
179 S,
180 A,
181 B,
182 K,
183 EA,
184 EB,
185 KA,
186 KB,
187 C,
188 V,
189 R,
190 Acc,
191 D,
192 impl Fn(&K, &R) -> Sc + Send + Sync,
193 Sc,
194 >
195 where
196 W: for<'w> ConstraintWeight<(&'w K, &'w R), Sc> + Send + Sync,
197 {
198 let is_hard = weight.is_hard();
199 self.into_weighted_builder(
200 ImpactType::Penalty,
201 move |key: &K, result: &R| weight.score((key, result)),
202 is_hard,
203 )
204 }
205
206 pub fn reward<W>(
207 self,
208 weight: W,
209 ) -> ComplementedConstraintBuilder<
210 S,
211 A,
212 B,
213 K,
214 EA,
215 EB,
216 KA,
217 KB,
218 C,
219 V,
220 R,
221 Acc,
222 D,
223 impl Fn(&K, &R) -> Sc + Send + Sync,
224 Sc,
225 >
226 where
227 W: for<'w> ConstraintWeight<(&'w K, &'w R), Sc> + Send + Sync,
228 {
229 let is_hard = weight.is_hard();
230 self.into_weighted_builder(
231 ImpactType::Reward,
232 move |key: &K, result: &R| weight.score((key, result)),
233 is_hard,
234 )
235 }
236}
237
238impl<S, A, B, K, EA, EB, KA, KB, C, V, R, Acc, D, Sc: Score> std::fmt::Debug
239 for ComplementedConstraintStream<S, A, B, K, EA, EB, KA, KB, C, V, R, Acc, D, Sc>
240{
241 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242 f.debug_struct("ComplementedConstraintStream").finish()
243 }
244}
245
246pub struct ComplementedConstraintBuilder<S, A, B, K, EA, EB, KA, KB, C, V, R, Acc, D, W, Sc>
248where
249 Sc: Score,
250{
251 extractor_a: EA,
252 extractor_b: EB,
253 key_a: KA,
254 key_b: KB,
255 collector: C,
256 default_fn: D,
257 impact_type: ImpactType,
258 weight_fn: W,
259 is_hard: bool,
260 _phantom: PhantomData<(
261 fn() -> S,
262 fn() -> A,
263 fn() -> B,
264 fn() -> K,
265 fn() -> V,
266 fn() -> R,
267 fn() -> Acc,
268 fn() -> Sc,
269 )>,
270}
271
272impl<S, A, B, K, EA, EB, KA, KB, C, V, R, Acc, D, W, Sc>
273 ComplementedConstraintBuilder<S, A, B, K, EA, EB, KA, KB, C, V, R, Acc, D, W, Sc>
274where
275 S: Send + Sync + 'static,
276 A: Clone + Send + Sync + 'static,
277 B: Clone + Send + Sync + 'static,
278 K: Clone + Eq + Hash + Send + Sync + 'static,
279 EA: CollectionExtract<S, Item = A> + 'static,
280 EB: CollectionExtract<S, Item = B> + 'static,
281 KA: Fn(&A) -> Option<K> + Send + Sync + 'static,
282 KB: Fn(&B) -> K + Send + Sync + Clone + 'static,
283 C: for<'i> Collector<&'i A, Value = V, Result = R, Accumulator = Acc> + Send + Sync + 'static,
284 V: Send + Sync + 'static,
285 R: Send + Sync + 'static,
286 Acc: Accumulator<V, R> + Send + Sync + 'static,
287 D: Fn(&B) -> R + Send + Sync,
288 W: Fn(&K, &R) -> Sc + Send + Sync,
289 Sc: Score + 'static,
290{
291 pub fn named(
292 self,
293 name: &str,
294 ) -> OperatorTerminal<
295 S,
296 ComplementNode<
297 S,
298 CollectionNode<S, EB>,
299 GroupNode<
300 CollectionNode<S, EA>,
301 impl for<'l> Fn(&Leaf<'l, A>) -> Option<K> + Send + Sync,
302 LeafCollector<C, V, R, Acc>,
303 Option<K>,
304 Acc,
305 V,
306 R,
307 >,
308 ViewEqualPlan<Option<K>, OptionalEntityKey<KB>, GroupKey>,
309 impl for<'l> Fn(&S, &Leaf<'l, B>) -> R + Send + Sync,
310 R,
311 >,
312 impl for<'l> Fn(
313 &S,
314 &ComplementView<
315 'l,
316 Leaf<'l, B>,
317 GroupView<'l, S, CollectionNode<S, EA>, Option<K>, Acc, V, R>,
318 R,
319 >,
320 ) -> Sc
321 + Send
322 + Sync,
323 Sc,
324 > {
325 let key_a = self.key_a;
326 let key_b = self.key_b;
327 let key_b_weight = key_b.clone();
328 let default_fn = self.default_fn;
329 let weight_fn = self.weight_fn;
330 let group = GroupNode::new(
331 CollectionNode::new(self.extractor_a, 0),
332 leaf_key(key_a),
333 LeafCollector::new(self.collector),
334 );
335 let targets = CollectionNode::new(self.extractor_b, 1);
336 let tree = ComplementNode::new(
337 targets,
338 group,
339 ViewEqualPlan::new(OptionalEntityKey::new(key_b.clone()), GroupKey),
340 move |_s: &S, row: &Leaf<'_, B>| default_fn(row.entity),
341 );
342 let weight = move |_s: &S,
343 row: &ComplementView<
344 '_,
345 Leaf<'_, B>,
346 GroupView<'_, S, CollectionNode<S, EA>, Option<K>, Acc, V, R>,
347 R,
348 >| match row {
349 ComplementView::Real(pair) => {
350 let key = key_b_weight(pair.left.entity);
351 pair.right.with_result(|r| weight_fn(&key, r))
352 }
353 ComplementView::Default(projected) => {
354 let key = key_b_weight(projected.input.entity);
355 weight_fn(&key, projected.value)
356 }
357 };
358 OperatorTerminal::new(
359 ConstraintRef::new("", name),
360 self.impact_type,
361 tree,
362 weight,
363 self.is_hard,
364 )
365 }
366
367 }
369
370impl<S, A, B, K, EA, EB, KA, KB, C, V, R, Acc, D, W, Sc: Score> std::fmt::Debug
371 for ComplementedConstraintBuilder<S, A, B, K, EA, EB, KA, KB, C, V, R, Acc, D, W, Sc>
372{
373 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374 f.debug_struct("ComplementedConstraintBuilder")
375 .field("impact_type", &self.impact_type)
376 .finish()
377 }
378}