Skip to main content

solverforge_scoring/constraint/
macros.rs

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