Skip to main content

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}