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 = ($self.key_extractor)(entity);
37            temp_index.entry(key).or_default().push(i);
38        }
39
40        let mut matches = Vec::new();
41        for indices in temp_index.values() {
42            for i in 0..indices.len() {
43                for j in (i + 1)..indices.len() {
44                    let idx_a = indices[i];
45                    let idx_b = indices[j];
46                    let a = &entities[idx_a];
47                    let b = &entities[idx_b];
48                    if ($self.filter)(a, b) {
49                        let justification = ConstraintJustification::new(vec![
50                            EntityRef::new(a),
51                            EntityRef::new(b),
52                        ]);
53                        let score = $self.compute_score(a, b);
54                        matches.push(DetailedConstraintMatch::new(
55                            cref.clone(),
56                            score,
57                            justification,
58                        ));
59                    }
60                }
61            }
62        }
63        matches
64    }};
65
66    // Tri-constraint: 3 entities
67    (tri: $self:expr, $solution:expr) => {{
68        use std::collections::HashMap;
69        use $crate::api::analysis::{ConstraintJustification, DetailedConstraintMatch, EntityRef};
70
71        let entities = ($self.extractor)($solution);
72        let cref = $self.constraint_ref.clone();
73
74        let mut temp_index: HashMap<_, Vec<usize>> = HashMap::new();
75        for (i, entity) in entities.iter().enumerate() {
76            let key = ($self.key_extractor)(entity);
77            temp_index.entry(key).or_default().push(i);
78        }
79
80        let mut matches = Vec::new();
81        for indices in temp_index.values() {
82            for pos_i in 0..indices.len() {
83                for pos_j in (pos_i + 1)..indices.len() {
84                    for pos_k in (pos_j + 1)..indices.len() {
85                        let i = indices[pos_i];
86                        let j = indices[pos_j];
87                        let k = indices[pos_k];
88                        let a = &entities[i];
89                        let b = &entities[j];
90                        let c = &entities[k];
91                        if ($self.filter)(a, b, c) {
92                            let justification = ConstraintJustification::new(vec![
93                                EntityRef::new(a),
94                                EntityRef::new(b),
95                                EntityRef::new(c),
96                            ]);
97                            let score = $self.compute_score(a, b, c);
98                            matches.push(DetailedConstraintMatch::new(
99                                cref.clone(),
100                                score,
101                                justification,
102                            ));
103                        }
104                    }
105                }
106            }
107        }
108        matches
109    }};
110
111    // Quad-constraint: 4 entities
112    (quad: $self:expr, $solution:expr) => {{
113        use std::collections::HashMap;
114        use $crate::api::analysis::{ConstraintJustification, DetailedConstraintMatch, EntityRef};
115
116        let entities = ($self.extractor)($solution);
117        let cref = $self.constraint_ref.clone();
118
119        let mut temp_index: HashMap<_, Vec<usize>> = HashMap::new();
120        for (i, entity) in entities.iter().enumerate() {
121            let key = ($self.key_extractor)(entity);
122            temp_index.entry(key).or_default().push(i);
123        }
124
125        let mut matches = Vec::new();
126        for indices in temp_index.values() {
127            for pos_i in 0..indices.len() {
128                for pos_j in (pos_i + 1)..indices.len() {
129                    for pos_k in (pos_j + 1)..indices.len() {
130                        for pos_l in (pos_k + 1)..indices.len() {
131                            let i = indices[pos_i];
132                            let j = indices[pos_j];
133                            let k = indices[pos_k];
134                            let l = indices[pos_l];
135                            let a = &entities[i];
136                            let b = &entities[j];
137                            let c = &entities[k];
138                            let d = &entities[l];
139                            if ($self.filter)(a, b, c, d) {
140                                let justification = ConstraintJustification::new(vec![
141                                    EntityRef::new(a),
142                                    EntityRef::new(b),
143                                    EntityRef::new(c),
144                                    EntityRef::new(d),
145                                ]);
146                                let score = $self.compute_score(a, b, c, d);
147                                matches.push(DetailedConstraintMatch::new(
148                                    cref.clone(),
149                                    score,
150                                    justification,
151                                ));
152                            }
153                        }
154                    }
155                }
156            }
157        }
158        matches
159    }};
160
161    // Penta-constraint: 5 entities
162    (penta: $self:expr, $solution:expr) => {{
163        use std::collections::HashMap;
164        use $crate::api::analysis::{ConstraintJustification, DetailedConstraintMatch, EntityRef};
165
166        let entities = ($self.extractor)($solution);
167        let cref = $self.constraint_ref.clone();
168
169        let mut temp_index: HashMap<_, Vec<usize>> = HashMap::new();
170        for (i, entity) in entities.iter().enumerate() {
171            let key = ($self.key_extractor)(entity);
172            temp_index.entry(key).or_default().push(i);
173        }
174
175        let mut matches = Vec::new();
176        for indices in temp_index.values() {
177            for pos_i in 0..indices.len() {
178                for pos_j in (pos_i + 1)..indices.len() {
179                    for pos_k in (pos_j + 1)..indices.len() {
180                        for pos_l in (pos_k + 1)..indices.len() {
181                            for pos_m in (pos_l + 1)..indices.len() {
182                                let i = indices[pos_i];
183                                let j = indices[pos_j];
184                                let k = indices[pos_k];
185                                let l = indices[pos_l];
186                                let m = indices[pos_m];
187                                let a = &entities[i];
188                                let b = &entities[j];
189                                let c = &entities[k];
190                                let d = &entities[l];
191                                let e = &entities[m];
192                                if ($self.filter)(a, b, c, d, e) {
193                                    let justification = ConstraintJustification::new(vec![
194                                        EntityRef::new(a),
195                                        EntityRef::new(b),
196                                        EntityRef::new(c),
197                                        EntityRef::new(d),
198                                        EntityRef::new(e),
199                                    ]);
200                                    let score = $self.compute_score(a, b, c, d, e);
201                                    matches.push(DetailedConstraintMatch::new(
202                                        cref.clone(),
203                                        score,
204                                        justification,
205                                    ));
206                                }
207                            }
208                        }
209                    }
210                }
211            }
212        }
213        matches
214    }};
215}
216
217pub use impl_get_matches_nary;