graco/
activities.rs

1use crate::petgraph::graph::Graph;
2use crate::petgraph::prelude::*;
3use std::fmt;
4
5#[derive(Debug, Clone, Copy)]
6pub struct Resource {
7    /// Name of the resource
8    pub id: u32,
9    /// Number of times this resource is reusable
10    pub reusability: u16,
11}
12
13#[derive(Debug, Clone)]
14pub struct Activity {
15    /// Earliest start (get the latest start by adding the slack time)
16    pub earliest_start: u32,
17    /// Earliest finish (get the latest finish by adding the slack time)
18    pub earliest_finish: u32,
19    /// Slack time, i.e. difference between the earliest and latest start or, equally, end
20    pub slack: u32,
21    /// Vector of resources
22    pub resources: Vec<Resource>,
23    /// An activity is a dummy activity if it is the very first or very last
24    pub dummy: bool,
25}
26
27impl fmt::Display for Activity {
28    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29        if self.dummy {
30            write!(
31                f,
32                "dur: {}\t#r: {}",
33                self.earliest_finish - self.earliest_start,
34                self.resources.len()
35            )
36        } else if self.resources.len() > 1 {
37            write!(
38                f,
39                "dur: {}\t#r: {}\n({} .. {})",
40                self.earliest_finish - self.earliest_start,
41                self.resources.len(),
42                self.resources[0].id,
43                self.resources[self.resources.len() - 1].id
44            )
45        } else {
46            write!(
47                f,
48                "dur: {}\t#r: {}\n({})",
49                self.earliest_finish - self.earliest_start,
50                self.resources.len(),
51                self.resources[0].id
52            )
53        }
54    }
55}
56
57impl Activity {
58    pub fn start() -> Activity {
59        Activity {
60            earliest_start: 0,
61            earliest_finish: 0,
62            slack: 0,
63            resources: vec![],
64            dummy: true,
65        }
66    }
67
68    pub fn from_earliest_start(start: u32, duration: u32, resources: Vec<Resource>) -> Activity {
69        Activity {
70            earliest_start: start,
71            earliest_finish: start + duration,
72            slack: 0,
73            resources: resources.clone(),
74            dummy: false,
75        }
76    }
77
78    pub fn duration(&self) -> u32 {
79        self.earliest_finish - self.earliest_start
80    }
81}
82
83#[derive(Clone, Copy)]
84pub enum BuildUntil {
85    Exhaustion,
86    UntilNActivities(usize),
87}
88
89#[derive(Clone, Copy)]
90pub enum ResAlloc {
91    /// Activities require exactly two resources
92    Single,
93    /// Activities require exactly one resource
94    Dual,
95}
96
97fn place_dual_resource_activities<V: Copy, W: Copy, T: fmt::Display>(
98    mut activities: &mut Graph<Activity, T, Directed>,
99    resources: Vec<Resource>,
100    prev_node: NodeIndex<u32>,
101    activity_duration: u32,
102    edge_weighting: W,
103    activity_valid: V,
104    condition: BuildUntil,
105) where
106    V: Fn(Activity, Activity) -> bool,
107    W: Fn(Activity, Activity) -> T,
108{
109    let mut available_resources = resources.clone();
110
111    let get_node_uid = |res_1: Resource, res_2: Resource| {
112        if res_1.id > res_2.id {
113            (res_1.id, res_2.id)
114        } else {
115            (res_2.id, res_1.id)
116        }
117    };
118
119    let mut node_id: Option<(u32, u32)> = None;
120    // Use the current node resources
121    let prev_activity = activities[prev_node].clone();
122    if !prev_activity.dummy {
123        // We are using this
124        let res_1 = prev_activity.resources[0];
125        let res_2 = prev_activity.resources[1];
126        for res in &mut available_resources {
127            if res.id == res_1.id || res.id == res_2.id {
128                res.reusability -= 1;
129            }
130        }
131
132        node_id = Some(get_node_uid(res_1, res_2));
133    }
134
135    let cur_time = prev_activity.earliest_finish;
136    let mut added_nodes = Vec::new();
137
138    for i in 0..available_resources.len() {
139        if available_resources[i].reusability == 0 {
140            continue; // This resource has been exhausted
141        }
142
143        for j in 0..available_resources.len() {
144            if i == j {
145                continue;
146            }
147
148            if let BuildUntil::UntilNActivities(max_num) = condition {
149                if activities.node_count() >= max_num {
150                    break;
151                }
152            }
153
154            if available_resources[j].reusability == 0 {
155                continue; // This resource has been exhausted
156            }
157
158            if let Some(prev_id) = node_id {
159                // Check that the node we are about to add is not the same as the previous node
160                if prev_id == get_node_uid(available_resources[i], available_resources[j]) {
161                    continue;
162                }
163            }
164
165            let new_node_id = get_node_uid(available_resources[i], available_resources[j]);
166            if added_nodes.contains(&new_node_id) {
167                continue;
168            }
169            added_nodes.push(new_node_id);
170
171            // New activity looks like it can fit, let's use it
172            let new_activity = Activity::from_earliest_start(
173                cur_time,
174                activity_duration,
175                vec![available_resources[i], available_resources[j]],
176            );
177
178            // Check that this activity can be placed after the previous one
179            if !activity_valid(prev_activity.clone(), new_activity.clone()) {
180                continue;
181            }
182
183            let new_node = activities.add_node(new_activity.clone());
184
185            activities.add_edge(
186                prev_node,
187                new_node,
188                edge_weighting(prev_activity.clone(), new_activity),
189            );
190
191            place_dual_resource_activities(
192                &mut activities,
193                available_resources.clone(),
194                new_node,
195                activity_duration,
196                edge_weighting,
197                activity_valid,
198                condition,
199            );
200        }
201    }
202}
203
204fn place_sgl_resource_activities<V: Copy, W: Copy, T: fmt::Display>(
205    mut activities: &mut Graph<Activity, T, Directed>,
206    resources: Vec<Resource>,
207    prev_node: NodeIndex<u32>,
208    activity_duration: u32,
209    edge_weighting: W,
210    activity_valid: V,
211    condition: BuildUntil,
212) where
213    V: Fn(Activity, Activity) -> bool,
214    W: Fn(Activity, Activity) -> T,
215{
216    let mut available_resources = resources.clone();
217
218    let mut node_id: Option<u32> = None;
219    // Use the current node resources
220    let prev_activity = activities[prev_node].clone();
221    if !prev_activity.dummy {
222        // We are using this
223        let res_1 = prev_activity.resources[0];
224        for res in &mut available_resources {
225            if res.id == res_1.id {
226                res.reusability -= 1;
227                break;
228            }
229        }
230
231        node_id = Some(res_1.id);
232    }
233
234    let cur_time = prev_activity.earliest_finish;
235    let mut added_nodes = Vec::new();
236
237    for resource in available_resources.clone() {
238        if resource.reusability == 0 {
239            continue; // This resource has been exhausted
240        }
241
242        if let BuildUntil::UntilNActivities(max_num) = condition {
243            if activities.node_count() >= max_num {
244                break;
245            }
246        }
247
248        if let Some(prev_id) = node_id {
249            // Check that the node we are about to add is not the same as the previous node
250            if prev_id == resource.id {
251                continue;
252            }
253        }
254
255        let new_node_id = resource.id;
256        if added_nodes.contains(&new_node_id) {
257            continue;
258        }
259        added_nodes.push(new_node_id);
260
261        // New activity looks like it can fit, let's use it
262        let new_activity =
263            Activity::from_earliest_start(cur_time, activity_duration, vec![resource]);
264
265        // Check that this activity can be placed after the previous one
266        if !activity_valid(prev_activity.clone(), new_activity.clone()) {
267            continue;
268        }
269
270        let new_node = activities.add_node(new_activity.clone());
271
272        activities.add_edge(
273            prev_node,
274            new_node,
275            edge_weighting(prev_activity.clone(), new_activity),
276        );
277
278        place_sgl_resource_activities(
279            &mut activities,
280            available_resources.clone(),
281            new_node,
282            activity_duration,
283            edge_weighting,
284            activity_valid,
285            condition,
286        );
287    }
288}
289
290pub fn build_equal_activities<V: Copy, W: Copy, T: fmt::Display>(
291    resources: Vec<Resource>,
292    activity_duration: u32,
293    edge_weighting: W,
294    activity_valid: V,
295    condition: BuildUntil,
296    alloc: ResAlloc,
297) -> Graph<Activity, T, Directed>
298where
299    V: Fn(Activity, Activity) -> bool,
300    W: Fn(Activity, Activity) -> T,
301{
302    let mut activities: Graph<Activity, T, Directed> = Graph::new();
303    let start_node = activities.add_node(Activity::start());
304
305    match alloc {
306        ResAlloc::Dual => place_dual_resource_activities(
307            &mut activities,
308            resources,
309            start_node,
310            activity_duration,
311            edge_weighting,
312            activity_valid,
313            condition,
314        ),
315        ResAlloc::Single => place_sgl_resource_activities(
316            &mut activities,
317            resources,
318            start_node,
319            activity_duration,
320            edge_weighting,
321            activity_valid,
322            condition,
323        ),
324    };
325
326    activities
327}
328
329#[test]
330fn test_builder_dual_resource() {
331    use crate::io::export_dot;
332    let sc1 = Resource {
333        id: 1,
334        reusability: 5,
335    };
336    let sc2 = Resource {
337        id: 2,
338        reusability: 5,
339    };
340    let sc3 = Resource {
341        id: 3,
342        reusability: 5,
343    };
344
345    let always_true = |_, _| true;
346
347    let activities = build_equal_activities(
348        vec![sc1, sc2, sc3],
349        60,
350        always_true,
351        always_true,
352        BuildUntil::Exhaustion,
353        ResAlloc::Dual,
354    );
355    assert_eq!(activities.node_count(), 298);
356    assert_eq!(activities.edge_count(), 297);
357    export_dot(&activities, "dotgraphs/test_builder_exhaustion_dual.dot");
358
359    // Test max nodes
360    let activities = build_equal_activities(
361        vec![sc1, sc2, sc3],
362        60,
363        always_true,
364        always_true,
365        BuildUntil::UntilNActivities(100),
366        ResAlloc::Dual,
367    );
368    assert_eq!(activities.node_count(), 100);
369    assert_eq!(activities.edge_count(), 99);
370    export_dot(&activities, "dotgraphs/test_builder_untilN_dual.dot");
371}
372
373#[test]
374fn test_builder_sgl_resource() {
375    use crate::io::export_dot;
376    let sc1 = Resource {
377        id: 1,
378        reusability: 1,
379    };
380    let sc2 = Resource {
381        id: 2,
382        reusability: 1,
383    };
384    let sc3 = Resource {
385        id: 3,
386        reusability: 1,
387    };
388
389    let always_true = |_, _| true;
390
391    let activities = build_equal_activities(
392        vec![sc1, sc2, sc3],
393        60,
394        always_true,
395        always_true,
396        BuildUntil::Exhaustion,
397        ResAlloc::Single,
398    );
399    export_dot(&activities, "dotgraphs/test_builder_exhaustion_single.dot");
400
401    let activities = build_equal_activities(
402        vec![sc1, sc2, sc3],
403        60,
404        always_true,
405        always_true,
406        BuildUntil::UntilNActivities(25),
407        ResAlloc::Single,
408    );
409    export_dot(&activities, "dotgraphs/test_builder_untilN_single.dot");
410}