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