problemreductions/rules/
partition_openshopscheduling.rs1use crate::models::misc::{OpenShopScheduling, Partition};
4use crate::reduction;
5use crate::rules::traits::{ReduceTo, ReductionResult};
6
7#[derive(Debug, Clone)]
8pub struct ReductionPartitionToOpenShopScheduling {
9 target: OpenShopScheduling,
10}
11
12impl ReductionResult for ReductionPartitionToOpenShopScheduling {
13 type Source = Partition;
14 type Target = OpenShopScheduling;
15
16 fn target_problem(&self) -> &Self::Target {
17 &self.target
18 }
19
20 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
21 let num_elements = self.target.num_jobs().saturating_sub(1);
22 let mut source_config = vec![0; num_elements];
23 let Some(orders) = self.target.decode_orders(target_solution) else {
24 return source_config;
25 };
26 if num_elements == 0 {
27 return source_config;
28 }
29
30 let special_job = num_elements;
31 let half_sum = self.target.processing_times()[special_job][0];
32
33 let makespan_orders = &orders;
35 let n = self.target.num_jobs();
36 let m = self.target.num_machines();
37
38 let mut machine_avail = vec![0usize; m];
40 let mut job_avail = vec![0usize; n];
41 let mut start_times = vec![vec![0usize; m]; n];
42
43 let mut cursor = vec![0usize; m];
45 let total_ops = n * m;
46 for _ in 0..total_ops {
47 let mut best: Option<(usize, usize, usize)> = None; for (mi, order) in makespan_orders.iter().enumerate() {
49 if cursor[mi] < order.len() {
50 let job = order[cursor[mi]];
51 let start = machine_avail[mi].max(job_avail[job]);
52 if best.is_none_or(|(bs, _, _)| start < bs) {
53 best = Some((start, mi, job));
54 }
55 }
56 }
57 let (start, mi, job) = best.expect("schedule incomplete");
58 start_times[job][mi] = start;
59 let end = start + self.target.processing_times()[job][mi];
60 machine_avail[mi] = end;
61 job_avail[job] = end;
62 cursor[mi] += 1;
63 }
64
65 let middle_machine = (0..m)
67 .find(|&machine| start_times[special_job][machine] == half_sum)
68 .unwrap_or_else(|| {
69 let mut machines: Vec<usize> = (0..m).collect();
70 machines.sort_by_key(|&machine| (start_times[special_job][machine], machine));
71 machines[m / 2]
72 });
73 let pivot = start_times[special_job][middle_machine];
74
75 for (job, slot) in source_config.iter_mut().enumerate() {
76 let completion = start_times[job][middle_machine]
77 + self.target.processing_times()[job][middle_machine];
78 if completion <= pivot {
79 *slot = 1;
80 }
81 }
82
83 source_config
84 }
85}
86
87#[reduction(overhead = {
88 num_jobs = "num_elements + 1",
89 num_machines = "3",
90})]
91impl ReduceTo<OpenShopScheduling> for Partition {
92 type Result = ReductionPartitionToOpenShopScheduling;
93
94 fn reduce_to(&self) -> Self::Result {
95 let half_sum = self.total_sum() as usize / 2;
96 let mut processing_times: Vec<Vec<usize>> = self
97 .sizes()
98 .iter()
99 .map(|&size| vec![size as usize; 3])
100 .collect();
101 processing_times.push(vec![half_sum; 3]);
102
103 ReductionPartitionToOpenShopScheduling {
104 target: OpenShopScheduling::new(3, processing_times),
105 }
106 }
107}
108
109#[cfg(feature = "example-db")]
110pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
111 use crate::export::SolutionPair;
112
113 vec![crate::example_db::specs::RuleExampleSpec {
114 id: "partition_to_open_shop_scheduling",
115 build: || {
116 crate::example_db::specs::rule_example_with_witness::<_, OpenShopScheduling>(
117 Partition::new(vec![1, 2, 3]),
118 SolutionPair {
119 source_config: vec![0, 0, 1],
120 target_config: vec![0, 1, 2, 3, 0, 1, 2, 3, 2, 3, 0, 1],
121 },
122 )
123 },
124 }]
125}
126
127#[cfg(test)]
128#[path = "../unit_tests/rules/partition_openshopscheduling.rs"]
129mod tests;