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::{CollectionExtract, FlattenExtract};
9use crate::stream::filter::UniFilter;
10use crate::stream::weighting_support::ConstraintWeight;
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 + 'static,
48 EA: CollectionExtract<S, Item = A>,
49 EP: CollectionExtract<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<W>(
106 self,
107 weight: W,
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 W: for<'w> ConstraintWeight<(&'w A,), Sc> + Send + Sync,
126 {
127 let is_hard = weight.is_hard();
128 self.into_weighted_builder(
129 ImpactType::Penalty,
130 move |a: &A| weight.score((a,)),
131 is_hard,
132 )
133 }
134
135 pub fn reward<W>(
136 self,
137 weight: W,
138 ) -> ExistsConstraintBuilder<
139 S,
140 A,
141 P,
142 B,
143 K,
144 EA,
145 EP,
146 KA,
147 KB,
148 FA,
149 FP,
150 Flatten,
151 impl Fn(&A) -> Sc + Send + Sync,
152 Sc,
153 >
154 where
155 W: for<'w> ConstraintWeight<(&'w A,), Sc> + Send + Sync,
156 {
157 let is_hard = weight.is_hard();
158 self.into_weighted_builder(ImpactType::Reward, move |a: &A| weight.score((a,)), is_hard)
159 }
160}
161
162impl<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, Sc: Score> std::fmt::Debug
163 for ExistsConstraintStream<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, Sc>
164{
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 f.debug_struct("ExistsConstraintStream")
167 .field("mode", &self.mode)
168 .finish()
169 }
170}
171
172pub struct ExistsConstraintBuilder<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc>
173where
174 Sc: Score,
175{
176 mode: ExistenceMode,
177 extractor_a: EA,
178 extractor_parent: EP,
179 key_a: KA,
180 key_b: KB,
181 filter_a: FA,
182 filter_parent: FP,
183 flatten: Flatten,
184 impact_type: ImpactType,
185 weight: W,
186 is_hard: bool,
187 _phantom: PhantomData<(
188 fn() -> S,
189 fn() -> A,
190 fn() -> P,
191 fn() -> B,
192 fn() -> K,
193 fn() -> Sc,
194 )>,
195}
196
197impl<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc>
198 ExistsConstraintBuilder<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc>
199where
200 S: Send + Sync + 'static,
201 A: Clone + Send + Sync + 'static,
202 P: Clone + Send + Sync + 'static,
203 B: Clone + Send + Sync + 'static,
204 K: Eq + Hash + Clone + Send + Sync + 'static,
205 EA: CollectionExtract<S, Item = A>,
206 EP: CollectionExtract<S, Item = P>,
207 KA: Fn(&A) -> K + Send + Sync,
208 KB: Fn(&B) -> K + Send + Sync,
209 FA: UniFilter<S, A> + Send + Sync,
210 FP: UniFilter<S, P> + Send + Sync,
211 Flatten: FlattenExtract<P, Item = B> + Send + Sync,
212 W: Fn(&A) -> Sc + Send + Sync,
213 Sc: Score + 'static,
214{
215 pub fn named(
216 self,
217 name: &str,
218 ) -> IncrementalExistsConstraint<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc> {
219 IncrementalExistsConstraint::new(
220 ConstraintRef::new("", name),
221 self.impact_type,
222 self.mode,
223 self.extractor_a,
224 self.extractor_parent,
225 self.key_a,
226 self.key_b,
227 self.filter_a,
228 self.filter_parent,
229 self.flatten,
230 self.weight,
231 self.is_hard,
232 )
233 }
234}
235
236impl<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc: Score> std::fmt::Debug
237 for ExistsConstraintBuilder<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc>
238{
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 f.debug_struct("ExistsConstraintBuilder")
241 .field("mode", &self.mode)
242 .field("impact_type", &self.impact_type)
243 .finish()
244 }
245}
246
247pub(crate) type DirectExistenceStream<S, A, B, K, EA, EP, KA, KB, FA, FP, Sc> =
248 ExistsConstraintStream<S, A, B, B, K, EA, EP, KA, KB, FA, FP, SelfFlatten, Sc>;