problemreductions/rules/
clustering_ilp.rs1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
9use crate::models::misc::Clustering;
10use crate::reduction;
11use crate::rules::traits::{ReduceTo, ReductionResult};
12
13#[derive(Debug, Clone)]
15pub struct ReductionClusteringToILP {
16 target: ILP<bool>,
17 num_elements: usize,
18 num_clusters: usize,
19}
20
21impl ReductionClusteringToILP {
22 fn var_index(&self, element: usize, cluster: usize) -> usize {
23 element * self.num_clusters + cluster
24 }
25}
26
27impl ReductionResult for ReductionClusteringToILP {
28 type Source = Clustering;
29 type Target = ILP<bool>;
30
31 fn target_problem(&self) -> &Self::Target {
32 &self.target
33 }
34
35 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
36 (0..self.num_elements)
37 .map(|element| {
38 (0..self.num_clusters)
39 .find(|&cluster| {
40 let idx = self.var_index(element, cluster);
41 idx < target_solution.len() && target_solution[idx] == 1
42 })
43 .unwrap_or(0)
44 })
45 .collect()
46 }
47}
48
49#[reduction(
50 overhead = {
51 num_vars = "num_elements * num_clusters",
52 num_constraints = "num_elements + num_elements * (num_elements - 1) / 2 * num_clusters",
53 }
54)]
55impl ReduceTo<ILP<bool>> for Clustering {
56 type Result = ReductionClusteringToILP;
57
58 fn reduce_to(&self) -> Self::Result {
59 let num_elements = self.num_elements();
60 let num_clusters = self.num_clusters();
61 let num_vars = num_elements * num_clusters;
62 let mut constraints = Vec::new();
63
64 let var_index =
65 |element: usize, cluster: usize| -> usize { element * num_clusters + cluster };
66
67 for element in 0..num_elements {
68 let terms: Vec<(usize, f64)> = (0..num_clusters)
69 .map(|cluster| (var_index(element, cluster), 1.0))
70 .collect();
71 constraints.push(LinearConstraint::eq(terms, 1.0));
72 }
73
74 let distances = self.distances();
75 let diameter_bound = self.diameter_bound();
76 for (i, row) in distances.iter().enumerate() {
77 for (j, &distance) in row.iter().enumerate().skip(i + 1) {
78 if distance > diameter_bound {
79 for cluster in 0..num_clusters {
80 constraints.push(LinearConstraint::le(
81 vec![(var_index(i, cluster), 1.0), (var_index(j, cluster), 1.0)],
82 1.0,
83 ));
84 }
85 }
86 }
87 }
88
89 ReductionClusteringToILP {
90 target: ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize),
91 num_elements,
92 num_clusters,
93 }
94 }
95}
96
97#[cfg(feature = "example-db")]
98pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
99 use crate::export::SolutionPair;
100
101 vec![crate::example_db::specs::RuleExampleSpec {
102 id: "clustering_to_ilp",
103 build: || {
104 let source = Clustering::new(
105 vec![
106 vec![0, 1, 3, 3],
107 vec![1, 0, 3, 3],
108 vec![3, 3, 0, 1],
109 vec![3, 3, 1, 0],
110 ],
111 2,
112 1,
113 );
114 crate::example_db::specs::rule_example_with_witness::<_, ILP<bool>>(
115 source,
116 SolutionPair {
117 source_config: vec![0, 0, 1, 1],
118 target_config: vec![1, 0, 1, 0, 0, 1, 0, 1],
119 },
120 )
121 },
122 }]
123}
124
125#[cfg(test)]
126#[path = "../unit_tests/rules/clustering_ilp.rs"]
127mod tests;