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
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}