Skip to main content

solverforge_scoring/constraint/
macros.rs

1/* Macros for reducing boilerplate in N-ary constraint implementations.
2
3The `impl_get_matches_nary!` macro generates `get_matches()` implementations
4for self-join constraints with identical structure but varying arity.
5*/
6
7/* Generates `get_matches()` implementation for N-ary self-join constraints.
8
9All N-ary constraints share the same pattern:
101. Extract entities and build key index
112. Iterate over N-tuples within each key group
123. Filter and collect DetailedConstraintMatch with EntityRefs
13
14# Usage
15
16This macro is used internally in constraint implementations:
17
18```text
19fn get_matches(&self, solution: &S) -> Vec<DetailedConstraintMatch<Sc>> {
20impl_get_matches_nary!(bi: self, solution)
21}
22```
23
24Available arities: `bi`, `tri`, `quad`, `penta`
25*/
26#[macro_export]
27macro_rules! impl_get_matches_nary {
28    // Bi-constraint: 2 entities
29    (bi: $self:expr, $solution:expr) => {{
30        use std::collections::HashMap;
31        use $crate::api::analysis::{ConstraintJustification, DetailedConstraintMatch, EntityRef};
32
33        let entities = $crate::stream::collection_extract::CollectionExtract::extract(
34            &$self.extractor,
35            $solution,
36        );
37        let cref = $self.constraint_ref.clone();
38
39        let mut temp_index: HashMap<_, Vec<usize>> = HashMap::new();
40        for (i, entity) in entities.iter().enumerate() {
41            let key = $crate::stream::key_extract::KeyExtract::extract(
42                &$self.key_extractor,
43                $solution,
44                entity,
45                i,
46            );
47            temp_index.entry(key).or_default().push(i);
48        }
49
50        let mut matches = Vec::new();
51        for indices in temp_index.values() {
52            for i in 0..indices.len() {
53                for j in (i + 1)..indices.len() {
54                    let idx_a = indices[i];
55                    let idx_b = indices[j];
56                    let a = &entities[idx_a];
57                    let b = &entities[idx_b];
58                    if ($self.filter)($solution, a, b, idx_a, idx_b) {
59                        let justification = ConstraintJustification::new(vec![
60                            EntityRef::new(a),
61                            EntityRef::new(b),
62                        ]);
63                        let score = $self.compute_score($solution, idx_a, idx_b);
64                        matches.push(DetailedConstraintMatch::new(
65                            cref.clone(),
66                            score,
67                            justification,
68                        ));
69                    }
70                }
71            }
72        }
73        matches
74    }};
75
76    // Tri-constraint: 3 entities
77    (tri: $self:expr, $solution:expr) => {{
78        use std::collections::HashMap;
79        use $crate::api::analysis::{ConstraintJustification, DetailedConstraintMatch, EntityRef};
80
81        let entities = $crate::stream::collection_extract::CollectionExtract::extract(
82            &$self.extractor,
83            $solution,
84        );
85        let cref = $self.constraint_ref.clone();
86
87        let mut temp_index: HashMap<_, Vec<usize>> = HashMap::new();
88        for (i, entity) in entities.iter().enumerate() {
89            let key = $crate::stream::key_extract::KeyExtract::extract(
90                &$self.key_extractor,
91                $solution,
92                entity,
93                i,
94            );
95            temp_index.entry(key).or_default().push(i);
96        }
97
98        let mut matches = Vec::new();
99        for indices in temp_index.values() {
100            for pos_i in 0..indices.len() {
101                for pos_j in (pos_i + 1)..indices.len() {
102                    for pos_k in (pos_j + 1)..indices.len() {
103                        let i = indices[pos_i];
104                        let j = indices[pos_j];
105                        let k = indices[pos_k];
106                        let a = &entities[i];
107                        let b = &entities[j];
108                        let c = &entities[k];
109                        if ($self.filter)($solution, a, b, c) {
110                            let justification = ConstraintJustification::new(vec![
111                                EntityRef::new(a),
112                                EntityRef::new(b),
113                                EntityRef::new(c),
114                            ]);
115                            let score = $self.compute_score($solution, i, j, k);
116                            matches.push(DetailedConstraintMatch::new(
117                                cref.clone(),
118                                score,
119                                justification,
120                            ));
121                        }
122                    }
123                }
124            }
125        }
126        matches
127    }};
128
129    // Quad-constraint: 4 entities
130    (quad: $self:expr, $solution:expr) => {{
131        use std::collections::HashMap;
132        use $crate::api::analysis::{ConstraintJustification, DetailedConstraintMatch, EntityRef};
133
134        let entities = $crate::stream::collection_extract::CollectionExtract::extract(
135            &$self.extractor,
136            $solution,
137        );
138        let cref = $self.constraint_ref.clone();
139
140        let mut temp_index: HashMap<_, Vec<usize>> = HashMap::new();
141        for (i, entity) in entities.iter().enumerate() {
142            let key = $crate::stream::key_extract::KeyExtract::extract(
143                &$self.key_extractor,
144                $solution,
145                entity,
146                i,
147            );
148            temp_index.entry(key).or_default().push(i);
149        }
150
151        let mut matches = Vec::new();
152        for indices in temp_index.values() {
153            for pos_i in 0..indices.len() {
154                for pos_j in (pos_i + 1)..indices.len() {
155                    for pos_k in (pos_j + 1)..indices.len() {
156                        for pos_l in (pos_k + 1)..indices.len() {
157                            let i = indices[pos_i];
158                            let j = indices[pos_j];
159                            let k = indices[pos_k];
160                            let l = indices[pos_l];
161                            let a = &entities[i];
162                            let b = &entities[j];
163                            let c = &entities[k];
164                            let d = &entities[l];
165                            if ($self.filter)($solution, a, b, c, d) {
166                                let justification = ConstraintJustification::new(vec![
167                                    EntityRef::new(a),
168                                    EntityRef::new(b),
169                                    EntityRef::new(c),
170                                    EntityRef::new(d),
171                                ]);
172                                let score = $self.compute_score($solution, i, j, k, l);
173                                matches.push(DetailedConstraintMatch::new(
174                                    cref.clone(),
175                                    score,
176                                    justification,
177                                ));
178                            }
179                        }
180                    }
181                }
182            }
183        }
184        matches
185    }};
186
187    // Penta-constraint: 5 entities
188    (penta: $self:expr, $solution:expr) => {{
189        use std::collections::HashMap;
190        use $crate::api::analysis::{ConstraintJustification, DetailedConstraintMatch, EntityRef};
191
192        let entities = $crate::stream::collection_extract::CollectionExtract::extract(
193            &$self.extractor,
194            $solution,
195        );
196        let cref = $self.constraint_ref.clone();
197
198        let mut temp_index: HashMap<_, Vec<usize>> = HashMap::new();
199        for (i, entity) in entities.iter().enumerate() {
200            let key = $crate::stream::key_extract::KeyExtract::extract(
201                &$self.key_extractor,
202                $solution,
203                entity,
204                i,
205            );
206            temp_index.entry(key).or_default().push(i);
207        }
208
209        let mut matches = Vec::new();
210        for indices in temp_index.values() {
211            for pos_i in 0..indices.len() {
212                for pos_j in (pos_i + 1)..indices.len() {
213                    for pos_k in (pos_j + 1)..indices.len() {
214                        for pos_l in (pos_k + 1)..indices.len() {
215                            for pos_m in (pos_l + 1)..indices.len() {
216                                let i = indices[pos_i];
217                                let j = indices[pos_j];
218                                let k = indices[pos_k];
219                                let l = indices[pos_l];
220                                let m = indices[pos_m];
221                                let a = &entities[i];
222                                let b = &entities[j];
223                                let c = &entities[k];
224                                let d = &entities[l];
225                                let e = &entities[m];
226                                if ($self.filter)($solution, a, b, c, d, e) {
227                                    let justification = ConstraintJustification::new(vec![
228                                        EntityRef::new(a),
229                                        EntityRef::new(b),
230                                        EntityRef::new(c),
231                                        EntityRef::new(d),
232                                        EntityRef::new(e),
233                                    ]);
234                                    let score = $self.compute_score($solution, i, j, k, l, m);
235                                    matches.push(DetailedConstraintMatch::new(
236                                        cref.clone(),
237                                        score,
238                                        justification,
239                                    ));
240                                }
241                            }
242                        }
243                    }
244                }
245            }
246        }
247        matches
248    }};
249}
250
251pub use impl_get_matches_nary;