1use std::iter::zip;
4
5use itertools::Itertools;
6use kahypar::{partition, KaHyParContext};
7use rustc_hash::FxHashMap;
8
9use crate::tensornetwork::tensor::{CompositeTensor, LeafTensor};
10
11mod partition_config;
12pub use partition_config::PartitioningStrategy;
13
14const LOG_SCALE_FACTOR: f64 = 1e5;
20
21pub fn find_partitioning(
32 tensor_network: &CompositeTensor,
33 k: i32,
34 partitioning_strategy: PartitioningStrategy,
35 min: bool,
36) -> Vec<usize> {
37 if k == 1 {
38 return vec![0; tensor_network.len()];
39 }
40
41 let num_vertices = tensor_network.len() as u32;
42 let mut context = KaHyParContext::new();
43 partitioning_strategy.apply(&mut context);
44
45 let x = if min { 1 } else { -1 };
46
47 let imbalance = 0.03;
48 let mut objective = 0;
49 let mut hyperedge_weights = vec![];
50 let mut hyperedge_indices = vec![0];
51 let mut hyperedges = vec![];
52
53 let mut edge_dict = FxHashMap::default();
54 for (tensor_id, tensor) in tensor_network.tensors().iter().enumerate() {
55 let tensor = tensor
56 .as_leaf()
57 .expect("Partitioning currently only supports one level of nesting");
58 for (leg, dim) in tensor.edges() {
59 edge_dict
60 .entry(leg)
61 .and_modify(|entry| {
62 hyperedges.push(*entry as u32);
63 hyperedges.push(tensor_id as u32);
64 hyperedge_indices.push(hyperedge_indices.last().unwrap() + 2);
65 let weight = LOG_SCALE_FACTOR * (dim as f64).log2();
68 hyperedge_weights.push(x * weight as i32);
69 *entry = tensor_id;
70 })
71 .or_insert(tensor_id);
72 }
73 }
74
75 let mut partitioning = vec![-1; num_vertices as usize];
76 partition(
77 num_vertices,
78 hyperedge_weights.len() as u32,
79 imbalance,
80 k,
81 None,
82 Some(hyperedge_weights),
83 &hyperedge_indices,
84 hyperedges.as_slice(),
85 &mut objective,
86 &mut context,
87 &mut partitioning,
88 );
89 partitioning.iter().map(|e| *e as usize).collect()
90}
91
92pub fn communication_partitioning(
101 tensors: &[(usize, LeafTensor)],
102 k: i32,
103 imbalance: f64,
104 partitioning_strategy: PartitioningStrategy,
105 min: bool,
106) -> Vec<usize> {
107 assert!(k > 1, "Partitioning only valid for more than one process");
108 let num_vertices = tensors.len() as u32;
109 let mut context = KaHyParContext::new();
110 partitioning_strategy.apply(&mut context);
111
112 let x = if min { 1 } else { -1 };
113
114 let mut objective = 0;
115 let mut hyperedge_weights = vec![];
116
117 let mut hyperedge_indices = vec![0];
118 let mut hyperedges = vec![];
119
120 let mut edge_dict = FxHashMap::default();
125 for (tensor_id, (_, tensor)) in tensors.iter().enumerate() {
126 for (leg, dim) in tensor.edges() {
127 edge_dict
128 .entry(leg)
129 .and_modify(|entry| {
130 hyperedges.push(*entry as u32);
131 hyperedges.push(tensor_id as u32);
132 hyperedge_indices.push(hyperedge_indices.last().unwrap() + 2);
133 let weight = LOG_SCALE_FACTOR * (dim as f64).log2();
136 hyperedge_weights.push(x * weight as i32);
137 *entry = tensor_id;
138 })
139 .or_insert(tensor_id);
140 }
141 }
142
143 let mut partitioning = vec![-1; num_vertices as usize];
144 partition(
145 num_vertices,
146 hyperedge_weights.len() as u32,
147 imbalance,
148 k,
149 None,
150 Some(hyperedge_weights),
151 &hyperedge_indices,
152 hyperedges.as_slice(),
153 &mut objective,
154 &mut context,
155 &mut partitioning,
156 );
157
158 partitioning.iter().map(|e| *e as usize).collect()
160}
161
162pub fn partition_tensor_network(tn: CompositeTensor, partitioning: &[usize]) -> CompositeTensor {
165 let partition_ids = partitioning.iter().unique().copied().collect_vec();
166 let partition_dict =
167 zip(partition_ids.iter().copied(), 0..partition_ids.len()).collect::<FxHashMap<_, _>>();
168
169 let mut partitions = vec![CompositeTensor::default(); partition_ids.len()];
170 for (partition_id, tensor) in zip(partitioning, tn.tensors()) {
171 partitions[partition_dict[partition_id]].push_tensor(tensor.clone());
172 }
173 CompositeTensor::new(partitions)
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 use approx::assert_abs_diff_eq;
181 use rustc_hash::FxHashMap;
182
183 use crate::tensornetwork::partitioning::partition_config::PartitioningStrategy;
184 use crate::tensornetwork::tensor::{EdgeIndex, LeafTensor};
185
186 fn setup_complex() -> (CompositeTensor, FxHashMap<EdgeIndex, u64>) {
187 let bond_dims = FxHashMap::from_iter([
188 (0, 27),
189 (1, 18),
190 (2, 12),
191 (3, 15),
192 (4, 5),
193 (5, 3),
194 (6, 18),
195 (7, 22),
196 (8, 45),
197 (9, 65),
198 (10, 5),
199 (11, 17),
200 ]);
201 (
202 CompositeTensor::new(vec![
203 LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims),
204 LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims),
205 LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims),
206 LeafTensor::new_from_map(vec![6, 8, 9], &bond_dims),
207 LeafTensor::new_from_map(vec![10, 8, 9], &bond_dims),
208 LeafTensor::new_from_map(vec![5, 1, 0], &bond_dims),
209 ]),
210 bond_dims,
211 )
212 }
213
214 #[test]
215 fn test_simple_partitioning() {
216 let (tn, bond_dims) = setup_complex();
217 let ref_tensor_1 = CompositeTensor::new(vec![
218 LeafTensor::new_from_map(vec![6, 8, 9], &bond_dims),
219 LeafTensor::new_from_map(vec![10, 8, 9], &bond_dims),
220 ]);
221 let ref_tensor_2 = CompositeTensor::new(vec![
222 LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims),
223 LeafTensor::new_from_map(vec![5, 1, 0], &bond_dims),
224 ]);
225 let ref_tensor_3 = CompositeTensor::new(vec![
226 LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims),
227 LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims),
228 ]);
229 let partitioning = find_partitioning(&tn, 3, PartitioningStrategy::MinCut, true);
230 assert_eq!(partitioning, [2, 1, 2, 0, 0, 1]);
231 let partitioned_tn = partition_tensor_network(tn, partitioning.as_slice());
232 assert_eq!(partitioned_tn.tensors().len(), 3);
233
234 assert_abs_diff_eq!(partitioned_tn.tensor(2), ref_tensor_1.as_tensor());
235 assert_abs_diff_eq!(partitioned_tn.tensor(1), ref_tensor_2.as_tensor());
236 assert_abs_diff_eq!(partitioned_tn.tensor(0), ref_tensor_3.as_tensor());
237 }
238
239 #[test]
240 fn test_single_partition() {
241 let (tn, _) = setup_complex();
242 let partitioning = find_partitioning(&tn, 1, PartitioningStrategy::MinCut, true);
243 assert_eq!(partitioning, [0, 0, 0, 0, 0, 0]);
244 }
245}