solverforge_scoring/stream/penta_stream.rs
1/* Zero-erasure penta-constraint stream for five-entity constraint patterns.
2
3A `PentaConstraintStream` operates on quintuples of entities and supports
4filtering, weighting, and constraint finalization. All type information
5is preserved at compile time - no Arc, no dyn.
6
7# Example
8
9```
10use solverforge_scoring::stream::ConstraintFactory;
11use solverforge_scoring::stream::joiner::equal;
12use solverforge_scoring::api::constraint_set::IncrementalConstraint;
13use solverforge_core::score::SoftScore;
14
15#[derive(Clone, Debug, Hash, PartialEq, Eq)]
16struct Task { team: u32 }
17
18#[derive(Clone)]
19struct Solution { tasks: Vec<Task> }
20
21// Penalize when five tasks are on the same team
22let constraint = ConstraintFactory::<Solution, SoftScore>::new()
23.for_each(|s: &Solution| s.tasks.as_slice())
24.join(equal(|t: &Task| t.team))
25.join(equal(|t: &Task| t.team))
26.join(equal(|t: &Task| t.team))
27.join(equal(|t: &Task| t.team))
28.penalize(SoftScore::of(1))
29.named("Team clustering");
30
31let solution = Solution {
32tasks: vec![
33Task { team: 1 },
34Task { team: 1 },
35Task { team: 1 },
36Task { team: 1 },
37Task { team: 1 },
38Task { team: 2 },
39],
40};
41
42// One quintuple on team 1: (0, 1, 2, 3, 4) = -1 penalty
43assert_eq!(constraint.evaluate(&solution), SoftScore::of(-1));
44```
45*/
46
47use crate::constraint::IncrementalPentaConstraint;
48
49super::arity_stream_macros::impl_arity_stream!(
50 penta,
51 PentaConstraintStream,
52 PentaConstraintBuilder,
53 IncrementalPentaConstraint
54);
55
56// Additional doctests for individual methods
57
58#[cfg(doctest)]
59mod doctests {
60 /* # Filter method
61
62 ```
63 use solverforge_scoring::stream::ConstraintFactory;
64 use solverforge_scoring::stream::joiner::equal;
65 use solverforge_scoring::api::constraint_set::IncrementalConstraint;
66 use solverforge_core::score::SoftScore;
67
68 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
69 struct Item { group: u32, value: i32 }
70
71 #[derive(Clone)]
72 struct Solution { items: Vec<Item> }
73
74 let constraint = ConstraintFactory::<Solution, SoftScore>::new()
75 .for_each(|s: &Solution| s.items.as_slice())
76 .join(equal(|i: &Item| i.group))
77 .join(equal(|i: &Item| i.group))
78 .join(equal(|i: &Item| i.group))
79 .join(equal(|i: &Item| i.group))
80 .filter(|a: &Item, b: &Item, c: &Item, d: &Item, e: &Item| {
81 a.value + b.value + c.value + d.value + e.value > 20
82 })
83 .penalize(SoftScore::of(1))
84 .named("High sum quintuples");
85
86 let solution = Solution {
87 items: vec![
88 Item { group: 1, value: 3 },
89 Item { group: 1, value: 4 },
90 Item { group: 1, value: 5 },
91 Item { group: 1, value: 6 },
92 Item { group: 1, value: 7 },
93 ],
94 };
95
96 // 3+4+5+6+7=25 > 20, matches
97 assert_eq!(constraint.evaluate(&solution), SoftScore::of(-1));
98 ```
99
100 # Penalize method
101
102 ```
103 use solverforge_scoring::stream::ConstraintFactory;
104 use solverforge_scoring::stream::joiner::equal;
105 use solverforge_scoring::api::constraint_set::IncrementalConstraint;
106 use solverforge_core::score::SoftScore;
107
108 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
109 struct Task { priority: u32 }
110
111 #[derive(Clone)]
112 struct Solution { tasks: Vec<Task> }
113
114 let constraint = ConstraintFactory::<Solution, SoftScore>::new()
115 .for_each(|s: &Solution| s.tasks.as_slice())
116 .join(equal(|t: &Task| t.priority))
117 .join(equal(|t: &Task| t.priority))
118 .join(equal(|t: &Task| t.priority))
119 .join(equal(|t: &Task| t.priority))
120 .penalize(SoftScore::of(5))
121 .named("Quintuple priority conflict");
122
123 let solution = Solution {
124 tasks: vec![
125 Task { priority: 1 },
126 Task { priority: 1 },
127 Task { priority: 1 },
128 Task { priority: 1 },
129 Task { priority: 1 },
130 ],
131 };
132
133 // One quintuple = -5
134 assert_eq!(constraint.evaluate(&solution), SoftScore::of(-5));
135 ```
136
137 # Penalize with dynamic weight
138
139 ```
140 use solverforge_scoring::stream::ConstraintFactory;
141 use solverforge_scoring::stream::joiner::equal;
142 use solverforge_scoring::api::constraint_set::IncrementalConstraint;
143 use solverforge_core::score::SoftScore;
144
145 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
146 struct Task { team: u32, cost: i64 }
147
148 #[derive(Clone)]
149 struct Solution { tasks: Vec<Task> }
150
151 let constraint = ConstraintFactory::<Solution, SoftScore>::new()
152 .for_each(|s: &Solution| s.tasks.as_slice())
153 .join(equal(|t: &Task| t.team))
154 .join(equal(|t: &Task| t.team))
155 .join(equal(|t: &Task| t.team))
156 .join(equal(|t: &Task| t.team))
157 .penalize_with(|a: &Task, b: &Task, c: &Task, d: &Task, e: &Task| {
158 SoftScore::of(a.cost + b.cost + c.cost + d.cost + e.cost)
159 })
160 .named("Team cost");
161
162 let solution = Solution {
163 tasks: vec![
164 Task { team: 1, cost: 1 },
165 Task { team: 1, cost: 2 },
166 Task { team: 1, cost: 3 },
167 Task { team: 1, cost: 4 },
168 Task { team: 1, cost: 5 },
169 ],
170 };
171
172 // Penalty: 1+2+3+4+5 = -15
173 assert_eq!(constraint.evaluate(&solution), SoftScore::of(-15));
174 ```
175
176 # Reward method
177
178 ```
179 use solverforge_scoring::stream::ConstraintFactory;
180 use solverforge_scoring::stream::joiner::equal;
181 use solverforge_scoring::api::constraint_set::IncrementalConstraint;
182 use solverforge_core::score::SoftScore;
183
184 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
185 struct Person { team: u32 }
186
187 #[derive(Clone)]
188 struct Solution { people: Vec<Person> }
189
190 let constraint = ConstraintFactory::<Solution, SoftScore>::new()
191 .for_each(|s: &Solution| s.people.as_slice())
192 .join(equal(|p: &Person| p.team))
193 .join(equal(|p: &Person| p.team))
194 .join(equal(|p: &Person| p.team))
195 .join(equal(|p: &Person| p.team))
196 .reward(SoftScore::of(10))
197 .named("Team synergy");
198
199 let solution = Solution {
200 people: vec![
201 Person { team: 1 },
202 Person { team: 1 },
203 Person { team: 1 },
204 Person { team: 1 },
205 Person { team: 1 },
206 ],
207 };
208
209 // One quintuple = +10
210 assert_eq!(constraint.evaluate(&solution), SoftScore::of(10));
211 ```
212
213 # named method
214
215 ```
216 use solverforge_scoring::stream::ConstraintFactory;
217 use solverforge_scoring::stream::joiner::equal;
218 use solverforge_scoring::api::constraint_set::IncrementalConstraint;
219 use solverforge_core::score::SoftScore;
220
221 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
222 struct Item { id: usize }
223
224 #[derive(Clone)]
225 struct Solution { items: Vec<Item> }
226
227 let constraint = ConstraintFactory::<Solution, SoftScore>::new()
228 .for_each(|s: &Solution| s.items.as_slice())
229 .join(equal(|i: &Item| i.id))
230 .join(equal(|i: &Item| i.id))
231 .join(equal(|i: &Item| i.id))
232 .join(equal(|i: &Item| i.id))
233 .penalize(SoftScore::of(1))
234 .named("Quintuple items");
235
236 assert_eq!(constraint.name(), "Quintuple items");
237 ```
238 */
239}