1use std::collections::BinaryHeap;
2
3use itertools::Itertools;
4use rustc_hash::FxHashMap;
5
6use crate::{
7 contractionpath::{
8 candidates::Candidate,
9 contraction_cost::{contract_op_cost_tensors, contract_size_tensors},
10 paths::{BasicContractionPathResult, ContractionPathResult, CostType, Pathfinder},
11 ssa_ordering, ContractionPath,
12 },
13 tensornetwork::tensor::{CompositeTensor, LeafTensor},
14 utils::traits::HashMapInsertNew,
15};
16
17pub struct WeightedBranchBound {
19 nbranch: Option<usize>,
20 cutoff_flops_factor: f64,
21 minimize: CostType,
22 best_flops: f64,
23 best_size: f64,
24 best_path: ContractionPath,
25 best_progress: FxHashMap<usize, f64>,
26 largest_latency: f64,
27 result_cache: FxHashMap<(usize, usize), (usize, f64, f64)>,
28 comm_cache: FxHashMap<usize, f64>,
29 tensor_cache: FxHashMap<usize, LeafTensor>,
30}
31
32impl WeightedBranchBound {
33 pub fn new(
34 nbranch: Option<usize>,
35 cutoff_flops_factor: f64,
36 latency_map: FxHashMap<usize, f64>,
37 minimize: CostType,
38 ) -> Self {
39 Self {
40 nbranch,
41 cutoff_flops_factor,
42 minimize,
43 best_flops: f64::INFINITY,
44 best_size: f64::INFINITY,
45 best_path: ContractionPath::default(),
46 best_progress: FxHashMap::default(),
47 largest_latency: Default::default(),
48 result_cache: FxHashMap::default(),
49 comm_cache: latency_map,
50 tensor_cache: FxHashMap::default(),
51 }
52 }
53
54 fn assess_candidate(
55 &mut self,
56 mut i: usize,
57 mut j: usize,
58 size: f64,
59 remaining_len: usize,
60 ) -> Option<Candidate> {
61 if self.tensor_cache[&j].size() > self.tensor_cache[&i].size() {
62 (i, j) = (j, i);
63 }
64
65 let &mut (k12, flops_12, size_12) = self.result_cache.entry((i, j)).or_insert_with(|| {
66 let k12 = self.tensor_cache.len();
67 let flops_12 = contract_op_cost_tensors(&self.tensor_cache[&i], &self.tensor_cache[&j]);
68 let size_12 = contract_size_tensors(&self.tensor_cache[&i], &self.tensor_cache[&j]);
69 let k12_tensor = &self.tensor_cache[&i] ^ &self.tensor_cache[&j];
70 self.tensor_cache.insert_new(k12, k12_tensor);
71 (k12, flops_12, size_12)
72 });
73
74 let current_flops = if let Some(total_flops) = self.comm_cache.get(&k12) {
75 *total_flops
76 } else {
77 let total_flops = flops_12 + self.comm_cache[&i].max(self.comm_cache[&j]);
78 self.comm_cache.insert(k12, total_flops);
79 total_flops
80 };
81 let current_size = size.max(size_12);
82
83 if current_flops > self.best_flops && current_size > self.best_size {
84 return None;
85 }
86 let best_flops = *self
87 .best_progress
88 .entry(remaining_len)
89 .or_insert(current_flops);
90
91 if current_flops < best_flops {
92 self.best_progress.insert(remaining_len, current_flops);
93 } else if current_flops > (self.cutoff_flops_factor * best_flops + self.largest_latency) {
94 return None;
95 }
96
97 Some(Candidate {
98 flop_cost: current_flops,
99 size_cost: current_size,
100 parent_ids: (i, j),
101 child_id: k12,
102 })
103 }
104
105 fn branch_iterate(
110 &mut self,
111 tensor: &CompositeTensor,
112 path: &[(usize, usize, usize)],
113 remaining: &[usize],
114 flops: f64,
115 size: f64,
116 ) {
117 if remaining.len() == 1 {
118 match self.minimize {
119 CostType::Flops => {
120 if self.best_flops > flops {
121 self.best_flops = flops;
122 self.best_size = size;
123 self.best_path = ssa_ordering(path, tensor.tensors().len());
124 }
125 }
126 CostType::Size => {
127 if self.best_size > size {
128 self.best_flops = flops;
129 self.best_size = size;
130 self.best_path = ssa_ordering(path, tensor.tensors().len());
131 }
132 }
133 }
134 return;
135 }
136
137 let mut candidates = BinaryHeap::with_capacity(remaining.len() * (remaining.len() - 1) / 2);
138 for pair in remaining.iter().copied().combinations(2) {
139 let candidate = self.assess_candidate(pair[0], pair[1], size, remaining.len());
140 if let Some(new_candidate) = candidate {
141 candidates.push(new_candidate);
142 }
143 }
144 let mut candidates = candidates.into_sorted_vec();
145 if let Some(limit) = self.nbranch {
146 candidates.truncate(limit);
147 }
148
149 let mut new_path = Vec::with_capacity(path.len() + 1);
150 new_path.extend_from_slice(path);
151
152 for candidate in candidates.into_iter().rev() {
153 let Candidate {
154 flop_cost,
155 size_cost,
156 parent_ids,
157 child_id,
158 } = candidate;
159 let mut new_remaining = remaining.to_vec();
160 new_remaining.retain(|e| *e != parent_ids.0 && *e != parent_ids.1);
161 new_remaining.push(child_id);
162 new_path.push((parent_ids.0, parent_ids.1, child_id));
163 self.branch_iterate(tensor, &new_path, &new_remaining, flop_cost, size_cost);
164 new_path.pop();
165 }
166 }
167}
168
169impl Pathfinder for WeightedBranchBound {
170 type Result = BasicContractionPathResult;
171
172 fn find_path(&mut self, tensor: &CompositeTensor) -> BasicContractionPathResult {
173 let tensors = tensor.tensors().clone();
174 self.result_cache.clear();
175 self.tensor_cache.clear();
176 self.largest_latency = *self
177 .comm_cache
178 .iter()
179 .max_by(|a, b| a.1.partial_cmp(b.1).expect("Tried to compare NaN"))
180 .unwrap()
181 .1;
182 let mut nested_paths = FxHashMap::default();
183 for (index, mut tensor) in tensors.into_iter().enumerate() {
185 if let Some(composite) = tensor.as_composite() {
187 let mut bb = WeightedBranchBound::new(
188 self.nbranch,
189 self.cutoff_flops_factor,
190 self.comm_cache.clone(),
191 self.minimize,
192 );
193 let result = bb.find_path(composite);
194 nested_paths.insert(index, result.ssa_path().clone());
195 tensor = composite.external_tensor().into();
196 }
197 let tensor = tensor.into_leaf().unwrap();
198 self.tensor_cache.insert_new(index, tensor);
199 }
200 let remaining = (0..tensor.tensors().len()).collect_vec();
201 self.branch_iterate(tensor, &[], &remaining, 0f64, 0f64);
202 let best_path = ContractionPath {
203 nested: nested_paths,
204 toplevel: std::mem::take(&mut self.best_path).into_simple(),
205 };
206 BasicContractionPathResult {
207 ssa_path: best_path,
208 flops: self.best_flops,
209 size: self.best_size,
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 use rustc_hash::FxHashMap;
219
220 use crate::contractionpath::paths::CostType;
221 use crate::contractionpath::paths::Pathfinder;
222 use crate::path;
223
224 fn setup_simple() -> (CompositeTensor, FxHashMap<usize, f64>) {
225 let bond_dims =
226 FxHashMap::from_iter([(0, 5), (1, 2), (2, 6), (3, 8), (4, 1), (5, 3), (6, 4)]);
227 (
228 CompositeTensor::new(vec![
229 LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims),
230 LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims),
231 LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims),
232 ]),
233 FxHashMap::from_iter([(0, 20.), (1, 40.), (2, 85.)]),
234 )
235 }
236
237 fn setup_complex() -> (CompositeTensor, FxHashMap<usize, f64>) {
238 let bond_dims = FxHashMap::from_iter([
239 (0, 27),
240 (1, 18),
241 (2, 12),
242 (3, 15),
243 (4, 5),
244 (5, 3),
245 (6, 18),
246 (7, 22),
247 (8, 45),
248 (9, 65),
249 (10, 5),
250 (11, 17),
251 ]);
252 (
253 CompositeTensor::new(vec![
254 LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims),
255 LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims),
256 LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims),
257 LeafTensor::new_from_map(vec![6, 8, 9], &bond_dims),
258 LeafTensor::new_from_map(vec![10, 8, 9], &bond_dims),
259 LeafTensor::new_from_map(vec![5, 1, 0], &bond_dims),
260 ]),
261 FxHashMap::from_iter([(0, 120.), (1, 0.), (2, 15.), (3, 15.), (4, 85.), (5, 15.)]),
262 )
263 }
264
265 #[test]
266 fn test_contract_order_simple() {
267 let (tn, latency_costs) = setup_simple();
268 let mut opt = WeightedBranchBound::new(None, 20., latency_costs, CostType::Flops);
269 let result = opt.find_path(&tn);
270
271 assert_eq!(
272 result,
273 BasicContractionPathResult {
274 ssa_path: path![(1, 0), (2, 3)],
275 flops: 640.,
276 size: 538.
277 }
278 );
279 }
280
281 #[test]
282 fn test_contract_order_complex() {
283 let (tn, latency_costs) = setup_complex();
284 let mut opt = WeightedBranchBound::new(None, 20., latency_costs, CostType::Flops);
285 let result = opt.find_path(&tn);
286
287 assert_eq!(
288 result,
289 BasicContractionPathResult {
290 ssa_path: path![(3, 4), (2, 6), (1, 5), (0, 8), (7, 9)],
291 flops: 265230.,
292 size: 89478.
293 }
294 );
295 }
296}