1use std::hash::Hash;
2use std::marker::PhantomData;
3
4use solverforge_core::score::Score;
5use solverforge_core::{ConstraintRef, ImpactType};
6
7use crate::constraint::exists::{IncrementalExistsConstraint, SelfFlatten};
8use crate::stream::collection_extract::{FlattenExtract, TrackedCollectionExtract};
9use crate::stream::filter::UniFilter;
10use crate::stream::weighting_support::fixed_weight_is_hard;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ExistenceMode {
14 Exists,
15 NotExists,
16}
17
18pub struct ExistsConstraintStream<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, Sc>
19where
20 Sc: Score,
21{
22 pub(super) mode: ExistenceMode,
23 pub(super) extractor_a: EA,
24 pub(super) extractor_parent: EP,
25 pub(super) key_a: KA,
26 pub(super) key_b: KB,
27 pub(super) filter_a: FA,
28 pub(super) filter_parent: FP,
29 pub(super) flatten: Flatten,
30 pub(super) _phantom: PhantomData<(
31 fn() -> S,
32 fn() -> A,
33 fn() -> P,
34 fn() -> B,
35 fn() -> K,
36 fn() -> Sc,
37 )>,
38}
39
40impl<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, Sc>
41 ExistsConstraintStream<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, Sc>
42where
43 S: Send + Sync + 'static,
44 A: Clone + Send + Sync + 'static,
45 P: Clone + Send + Sync + 'static,
46 B: Clone + Send + Sync + 'static,
47 K: Eq + Hash + Clone + Send + Sync,
48 EA: TrackedCollectionExtract<S, Item = A>,
49 EP: TrackedCollectionExtract<S, Item = P>,
50 KA: Fn(&A) -> K + Send + Sync,
51 KB: Fn(&B) -> K + Send + Sync,
52 FA: UniFilter<S, A>,
53 FP: UniFilter<S, P>,
54 Flatten: FlattenExtract<P, Item = B>,
55 Sc: Score + 'static,
56{
57 fn into_weighted_builder<W>(
58 self,
59 impact_type: ImpactType,
60 weight: W,
61 is_hard: bool,
62 ) -> ExistsConstraintBuilder<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc>
63 where
64 W: Fn(&A) -> Sc + Send + Sync,
65 {
66 ExistsConstraintBuilder {
67 mode: self.mode,
68 extractor_a: self.extractor_a,
69 extractor_parent: self.extractor_parent,
70 key_a: self.key_a,
71 key_b: self.key_b,
72 filter_a: self.filter_a,
73 filter_parent: self.filter_parent,
74 flatten: self.flatten,
75 impact_type,
76 weight,
77 is_hard,
78 _phantom: PhantomData,
79 }
80 }
81
82 pub fn new(
83 mode: ExistenceMode,
84 extractor_a: EA,
85 extractor_parent: EP,
86 keys: (KA, KB),
87 filter_a: FA,
88 filter_parent: FP,
89 flatten: Flatten,
90 ) -> Self {
91 let (key_a, key_b) = keys;
92 Self {
93 mode,
94 extractor_a,
95 extractor_parent,
96 key_a,
97 key_b,
98 filter_a,
99 filter_parent,
100 flatten,
101 _phantom: PhantomData,
102 }
103 }
104
105 pub fn penalize(
106 self,
107 weight: Sc,
108 ) -> ExistsConstraintBuilder<
109 S,
110 A,
111 P,
112 B,
113 K,
114 EA,
115 EP,
116 KA,
117 KB,
118 FA,
119 FP,
120 Flatten,
121 impl Fn(&A) -> Sc + Send + Sync,
122 Sc,
123 >
124 where
125 Sc: Copy,
126 {
127 self.into_weighted_builder(
128 ImpactType::Penalty,
129 move |_: &A| weight,
130 fixed_weight_is_hard(weight),
131 )
132 }
133
134 pub fn penalize_with<W>(
135 self,
136 weight_fn: W,
137 ) -> ExistsConstraintBuilder<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc>
138 where
139 W: Fn(&A) -> Sc + Send + Sync,
140 {
141 self.into_weighted_builder(ImpactType::Penalty, weight_fn, false)
142 }
143
144 pub fn penalize_hard_with<W>(
145 self,
146 weight_fn: W,
147 ) -> ExistsConstraintBuilder<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc>
148 where
149 W: Fn(&A) -> Sc + Send + Sync,
150 {
151 self.into_weighted_builder(ImpactType::Penalty, weight_fn, true)
152 }
153
154 pub fn penalize_hard(
155 self,
156 ) -> ExistsConstraintBuilder<
157 S,
158 A,
159 P,
160 B,
161 K,
162 EA,
163 EP,
164 KA,
165 KB,
166 FA,
167 FP,
168 Flatten,
169 impl Fn(&A) -> Sc + Send + Sync,
170 Sc,
171 >
172 where
173 Sc: Copy,
174 {
175 self.penalize(Sc::one_hard())
176 }
177
178 pub fn penalize_soft(
179 self,
180 ) -> ExistsConstraintBuilder<
181 S,
182 A,
183 P,
184 B,
185 K,
186 EA,
187 EP,
188 KA,
189 KB,
190 FA,
191 FP,
192 Flatten,
193 impl Fn(&A) -> Sc + Send + Sync,
194 Sc,
195 >
196 where
197 Sc: Copy,
198 {
199 self.penalize(Sc::one_soft())
200 }
201
202 pub fn reward(
203 self,
204 weight: Sc,
205 ) -> ExistsConstraintBuilder<
206 S,
207 A,
208 P,
209 B,
210 K,
211 EA,
212 EP,
213 KA,
214 KB,
215 FA,
216 FP,
217 Flatten,
218 impl Fn(&A) -> Sc + Send + Sync,
219 Sc,
220 >
221 where
222 Sc: Copy,
223 {
224 self.into_weighted_builder(
225 ImpactType::Reward,
226 move |_: &A| weight,
227 fixed_weight_is_hard(weight),
228 )
229 }
230
231 pub fn reward_with<W>(
232 self,
233 weight_fn: W,
234 ) -> ExistsConstraintBuilder<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc>
235 where
236 W: Fn(&A) -> Sc + Send + Sync,
237 {
238 self.into_weighted_builder(ImpactType::Reward, weight_fn, false)
239 }
240
241 pub fn reward_hard_with<W>(
242 self,
243 weight_fn: W,
244 ) -> ExistsConstraintBuilder<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc>
245 where
246 W: Fn(&A) -> Sc + Send + Sync,
247 {
248 self.into_weighted_builder(ImpactType::Reward, weight_fn, true)
249 }
250
251 pub fn reward_hard(
252 self,
253 ) -> ExistsConstraintBuilder<
254 S,
255 A,
256 P,
257 B,
258 K,
259 EA,
260 EP,
261 KA,
262 KB,
263 FA,
264 FP,
265 Flatten,
266 impl Fn(&A) -> Sc + Send + Sync,
267 Sc,
268 >
269 where
270 Sc: Copy,
271 {
272 self.reward(Sc::one_hard())
273 }
274
275 pub fn reward_soft(
276 self,
277 ) -> ExistsConstraintBuilder<
278 S,
279 A,
280 P,
281 B,
282 K,
283 EA,
284 EP,
285 KA,
286 KB,
287 FA,
288 FP,
289 Flatten,
290 impl Fn(&A) -> Sc + Send + Sync,
291 Sc,
292 >
293 where
294 Sc: Copy,
295 {
296 self.reward(Sc::one_soft())
297 }
298}
299
300impl<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, Sc: Score> std::fmt::Debug
301 for ExistsConstraintStream<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, Sc>
302{
303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304 f.debug_struct("ExistsConstraintStream")
305 .field("mode", &self.mode)
306 .finish()
307 }
308}
309
310pub struct ExistsConstraintBuilder<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc>
311where
312 Sc: Score,
313{
314 mode: ExistenceMode,
315 extractor_a: EA,
316 extractor_parent: EP,
317 key_a: KA,
318 key_b: KB,
319 filter_a: FA,
320 filter_parent: FP,
321 flatten: Flatten,
322 impact_type: ImpactType,
323 weight: W,
324 is_hard: bool,
325 _phantom: PhantomData<(
326 fn() -> S,
327 fn() -> A,
328 fn() -> P,
329 fn() -> B,
330 fn() -> K,
331 fn() -> Sc,
332 )>,
333}
334
335impl<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc>
336 ExistsConstraintBuilder<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc>
337where
338 S: Send + Sync + 'static,
339 A: Clone + Send + Sync + 'static,
340 P: Clone + Send + Sync + 'static,
341 B: Clone + Send + Sync + 'static,
342 K: Eq + Hash + Clone + Send + Sync,
343 EA: TrackedCollectionExtract<S, Item = A>,
344 EP: TrackedCollectionExtract<S, Item = P>,
345 KA: Fn(&A) -> K + Send + Sync,
346 KB: Fn(&B) -> K + Send + Sync,
347 FA: UniFilter<S, A> + Send + Sync,
348 FP: UniFilter<S, P> + Send + Sync,
349 Flatten: FlattenExtract<P, Item = B> + Send + Sync,
350 W: Fn(&A) -> Sc + Send + Sync,
351 Sc: Score + 'static,
352{
353 pub fn named(
354 self,
355 name: &str,
356 ) -> IncrementalExistsConstraint<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc> {
357 IncrementalExistsConstraint::new(
358 ConstraintRef::new("", name),
359 self.impact_type,
360 self.mode,
361 self.extractor_a,
362 self.extractor_parent,
363 self.key_a,
364 self.key_b,
365 self.filter_a,
366 self.filter_parent,
367 self.flatten,
368 self.weight,
369 self.is_hard,
370 )
371 }
372}
373
374impl<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc: Score> std::fmt::Debug
375 for ExistsConstraintBuilder<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc>
376{
377 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378 f.debug_struct("ExistsConstraintBuilder")
379 .field("mode", &self.mode)
380 .field("impact_type", &self.impact_type)
381 .finish()
382 }
383}
384
385pub(crate) type DirectExistenceStream<S, A, B, K, EA, EP, KA, KB, FA, FP, Sc> =
386 ExistsConstraintStream<S, A, B, B, K, EA, EP, KA, KB, FA, FP, SelfFlatten, Sc>;