1use core::f64;
4use std::rc::Rc;
5
6use itertools::Itertools;
7use log::debug;
8use rand::seq::IndexedRandom;
9use rand::{rngs::StdRng, Rng};
10use rustc_hash::FxHashMap;
11
12use crate::contractionpath::communication_schemes::CommunicationScheme;
13use crate::contractionpath::contraction_cost::communication_path_op_costs;
14use crate::contractionpath::contraction_tree::{
15 utils::{characterize_partition, subtree_contraction_path},
16 ContractionTree,
17};
18use crate::contractionpath::paths::validate_path;
19use crate::contractionpath::{ContractionPath, SimplePath};
20use crate::tensornetwork::tensor::{CompositeTensor, LeafTensor, TensorIndex, TensorType};
21
22mod balancing_schemes;
23
24pub use balancing_schemes::BalancingScheme;
25
26#[derive(Debug, Clone, Copy)]
27pub struct BalanceSettings<R>
28where
29 R: Rng,
30{
31 random_balance: Option<(usize, R)>,
34 rebalance_depth: usize,
35 iterations: usize,
36 objective_function: fn(&LeafTensor, &LeafTensor) -> f64,
37 communication_scheme: CommunicationScheme,
38 balancing_scheme: BalancingScheme,
39 memory_limit: Option<f64>,
40}
41
42impl BalanceSettings<StdRng> {
43 pub fn new(
44 rebalance_depth: usize,
45 iterations: usize,
46 objective_function: fn(&LeafTensor, &LeafTensor) -> f64,
47 communication_scheme: CommunicationScheme,
48 balancing_scheme: BalancingScheme,
49 memory_limit: Option<f64>,
50 ) -> Self {
51 BalanceSettings::<StdRng> {
52 random_balance: None,
53 rebalance_depth,
54 iterations,
55 objective_function,
56 communication_scheme,
57 balancing_scheme,
58 memory_limit,
59 }
60 }
61}
62
63impl<R> BalanceSettings<R>
64where
65 R: Rng,
66{
67 pub fn new_random(
68 random_balance: Option<(usize, R)>,
69 rebalance_depth: usize,
70 iterations: usize,
71 objective_function: fn(&LeafTensor, &LeafTensor) -> f64,
72 communication_scheme: CommunicationScheme,
73 balancing_scheme: BalancingScheme,
74 memory_limit: Option<f64>,
75 ) -> Self {
76 BalanceSettings::<R> {
77 random_balance,
78 rebalance_depth,
79 iterations,
80 objective_function,
81 communication_scheme,
82 balancing_scheme,
83 memory_limit,
84 }
85 }
86}
87
88#[derive(Debug, Clone)]
89pub(crate) struct PartitionData {
90 pub id: usize,
91 pub flop_cost: f64,
92 pub mem_cost: f64,
93 pub contraction: SimplePath,
94 pub local_tensor: LeafTensor,
95}
96
97pub fn balance_partitions_iter<R>(
99 tensor_network: &CompositeTensor,
100 path: &ContractionPath,
101 mut balance_settings: BalanceSettings<R>,
102 rng: &mut R,
103) -> (usize, CompositeTensor, ContractionPath, Vec<(f64, f64)>)
104where
105 R: Rng,
106{
107 let mut contraction_tree = ContractionTree::from_contraction_path(tensor_network, path);
108
109 let BalanceSettings {
110 rebalance_depth,
111 iterations,
112 balancing_scheme,
113 communication_scheme,
114 memory_limit,
115 ..
116 } = balance_settings;
117 let mut partition_data =
118 characterize_partition(&contraction_tree, rebalance_depth, tensor_network);
119
120 assert!(partition_data.len() > 1);
121
122 let partition_number = partition_data.len();
123
124 let (partition_tensors, partition_costs): (Vec<_>, Vec<_>) = partition_data
125 .iter()
126 .map(
127 |PartitionData {
128 local_tensor,
129 flop_cost,
130 ..
131 }| (local_tensor.clone(), *flop_cost),
132 )
133 .collect();
134
135 let ((mut best_cost, sum_cost), _) = communication_path_op_costs(
136 &partition_tensors,
137 &path.toplevel,
138 true,
139 Some(&partition_costs),
140 );
141
142 let mut max_costs = Vec::with_capacity(iterations + 1);
143 max_costs.push((best_cost, sum_cost));
144
145 let mut best_iteration = 0;
146 let mut best_contraction_path = path.to_owned();
147 let mut best_tn = tensor_network.clone();
148
149 for iteration in 1..=iterations {
150 debug!("Balancing iteration {iteration} with balancing scheme {balancing_scheme:?}, communication scheme {communication_scheme:?}");
151
152 let (nested_paths, new_tensor_network) = balance_partitions(
154 &mut partition_data,
155 &mut contraction_tree,
156 tensor_network,
157 &mut balance_settings,
158 iteration,
159 );
160
161 assert_eq!(nested_paths.len(), partition_number, "Tensors lost!");
162
163 let communication_path = communicate_partitions(
166 &partition_data,
167 &mut contraction_tree,
168 &new_tensor_network,
169 &balance_settings,
170 Some(rng),
171 );
172
173 let (partition_tensors, partition_costs): (Vec<_>, Vec<_>) = partition_data
174 .iter()
175 .map(
176 |PartitionData {
177 local_tensor,
178 flop_cost,
179 ..
180 }| (local_tensor.clone(), *flop_cost),
181 )
182 .collect();
183
184 let ((flop_cost, sum_cost), mem_cost) = communication_path_op_costs(
185 &partition_tensors,
186 &communication_path,
187 true,
188 Some(&partition_costs),
189 );
190
191 let new_path = ContractionPath {
192 nested: nested_paths,
193 toplevel: path.toplevel.clone(),
194 };
195 validate_path(&new_path);
196
197 max_costs.push((flop_cost, sum_cost));
198 if memory_limit.is_some_and(|limit| mem_cost > limit) {
199 break;
200 }
201 if flop_cost < best_cost {
202 best_cost = flop_cost;
203 best_iteration = iteration;
204 best_tn = new_tensor_network;
205 best_contraction_path = new_path;
206 }
207 }
208
209 (best_iteration, best_tn, best_contraction_path, max_costs)
210}
211
212fn communicate_partitions<R>(
213 partition_data: &[PartitionData],
214 contraction_tree: &mut ContractionTree,
215 tensor_network: &CompositeTensor,
216 balance_settings: &BalanceSettings<R>,
217 rng: Option<&mut R>,
218) -> SimplePath
219where
220 R: Rng,
221{
222 let communication_scheme = balance_settings.communication_scheme;
223 let children_tensors = tensor_network
224 .tensors()
225 .iter()
226 .map(|t| match t.kind() {
227 TensorType::Composite => t.as_composite().unwrap().external_tensor(),
228 TensorType::Leaf => t.as_leaf().unwrap().clone(),
229 })
230 .collect_vec();
231 let latency_map = partition_data
232 .iter()
233 .enumerate()
234 .map(|(i, partition)| (i, partition.flop_cost))
235 .collect::<FxHashMap<_, _>>();
236
237 let partition_ids = partition_data
238 .iter()
239 .map(|partition| partition.id)
240 .collect_vec();
241 let communication_path =
242 communication_scheme.communication_path(&children_tensors, &latency_map, rng);
243
244 contraction_tree.replace_communication_path(partition_ids, &communication_path);
245
246 communication_path
247}
248
249fn balance_partitions<R>(
250 partition_data: &mut [PartitionData],
251 contraction_tree: &mut ContractionTree,
252 tensor_network: &CompositeTensor,
253 balance_settings: &mut BalanceSettings<R>,
254 iteration: usize,
255) -> (FxHashMap<TensorIndex, ContractionPath>, CompositeTensor)
256where
257 R: Rng,
258{
259 let BalanceSettings {
260 ref mut random_balance,
261 rebalance_depth,
262 objective_function,
263 balancing_scheme,
264 ..
265 } = balance_settings;
266 if tensor_network.total_num_tensors() < 3 {
268 panic!("No rebalancing undertaken, as tn is too small (< 3 tensors)");
270 }
271 assert!(partition_data.len() > 1);
273
274 partition_data.sort_unstable_by(|a, b| a.flop_cost.total_cmp(&b.flop_cost));
275
276 let shifted_nodes = match balancing_scheme {
277 BalancingScheme::BestWorst => balancing_schemes::best_worst(
278 partition_data,
279 contraction_tree,
280 random_balance,
281 *objective_function,
282 tensor_network,
283 ),
284 BalancingScheme::Tensor => balancing_schemes::best_tensor(
285 partition_data,
286 contraction_tree,
287 random_balance,
288 *objective_function,
289 tensor_network,
290 ),
291 BalancingScheme::Tensors => balancing_schemes::best_tensors(
292 partition_data,
293 contraction_tree,
294 random_balance,
295 *objective_function,
296 tensor_network,
297 ),
298 BalancingScheme::AlternatingTensors => {
299 if iteration % 2 == 1 {
300 balancing_schemes::tensors_odd(
301 partition_data,
302 contraction_tree,
303 random_balance,
304 *objective_function,
305 tensor_network,
306 )
307 } else {
308 balancing_schemes::tensors_even(
309 partition_data,
310 contraction_tree,
311 random_balance,
312 *objective_function,
313 tensor_network,
314 )
315 }
316 }
317 BalancingScheme::IntermediateTensors { height_limit } => {
318 balancing_schemes::best_intermediate_tensors(
319 partition_data,
320 contraction_tree,
321 random_balance,
322 *objective_function,
323 tensor_network,
324 *height_limit,
325 )
326 }
327 BalancingScheme::AlternatingIntermediateTensors { height_limit } => {
328 if iteration % 2 == 1 {
329 balancing_schemes::intermediate_tensors_odd(
330 partition_data,
331 contraction_tree,
332 random_balance,
333 *objective_function,
334 tensor_network,
335 *height_limit,
336 )
337 } else {
338 balancing_schemes::intermediate_tensors_even(
339 partition_data,
340 contraction_tree,
341 random_balance,
342 *objective_function,
343 tensor_network,
344 *height_limit,
345 )
346 }
347 }
348 BalancingScheme::AlternatingTreeTensors { height_limit } => {
349 if iteration % 2 == 1 {
350 balancing_schemes::tree_tensors_odd(
351 partition_data,
352 contraction_tree,
353 *objective_function,
354 tensor_network,
355 *height_limit,
356 )
357 } else {
358 balancing_schemes::tree_tensors_even(
359 partition_data,
360 contraction_tree,
361 *objective_function,
362 tensor_network,
363 *height_limit,
364 )
365 }
366 }
367 };
368 let mut shifted_indices = FxHashMap::default();
369 for shift in shifted_nodes {
370 let shifted_from_id = *shifted_indices
371 .get(&shift.from_subtree_id)
372 .unwrap_or(&shift.from_subtree_id);
373
374 let shifted_to_id = *shifted_indices
375 .get(&shift.to_subtree_id)
376 .unwrap_or(&shift.to_subtree_id);
377
378 let (
379 larger_id,
380 larger_contraction,
381 larger_subtree_flop_cost,
382 larger_subtree_mem_cost,
383 smaller_id,
384 smaller_contraction,
385 smaller_subtree_flop_cost,
386 smaller_subtree_mem_cost,
387 ) = shift_node_between_subtrees(
388 contraction_tree,
389 *rebalance_depth,
390 shifted_from_id,
391 shifted_to_id,
392 shift.moved_leaf_ids,
393 tensor_network,
394 );
395 shifted_indices.insert(shift.from_subtree_id, larger_id);
396 shifted_indices.insert(shift.to_subtree_id, smaller_id);
397
398 let larger_tensor = contraction_tree.tensor(larger_id, tensor_network);
399 let smaller_tensor = contraction_tree.tensor(smaller_id, tensor_network);
400
401 for PartitionData {
403 id,
404 flop_cost,
405 mem_cost,
406 contraction: subtree_contraction,
407 local_tensor,
408 } in partition_data.iter_mut()
409 {
410 if *id == shifted_from_id {
411 *id = larger_id;
412 subtree_contraction.clone_from(&larger_contraction);
413 *flop_cost = larger_subtree_flop_cost;
414 *mem_cost = larger_subtree_mem_cost;
415 *local_tensor = larger_tensor.clone();
416 } else if *id == shifted_to_id {
417 *id = smaller_id;
418 subtree_contraction.clone_from(&smaller_contraction);
419 *flop_cost = smaller_subtree_flop_cost;
420 *mem_cost = smaller_subtree_mem_cost;
421 *local_tensor = smaller_tensor.clone();
422 }
423 }
424 }
425
426 partition_data.sort_unstable_by(
427 |PartitionData {
428 flop_cost: cost_a, ..
429 },
430 PartitionData {
431 flop_cost: cost_b, ..
432 }| { cost_a.total_cmp(cost_b) },
433 );
434
435 let mut rebalanced_paths = FxHashMap::default();
436 let (partition_tensors, partition_ids): (Vec<_>, Vec<_>) = partition_data
437 .iter()
438 .enumerate()
439 .map(
440 |(
441 i,
442 PartitionData {
443 id,
444 contraction: subtree_contraction,
445 ..
446 },
447 )| {
448 rebalanced_paths.insert(i, ContractionPath::simple(subtree_contraction.clone()));
449 let leaf_ids = contraction_tree.leaf_ids(*id);
450 let leaf_tensors = leaf_ids
451 .iter()
452 .map(|node_id| {
453 let nested_indices = contraction_tree
454 .node(*node_id)
455 .tensor_index()
456 .cloned()
457 .unwrap();
458 tensor_network.nested_tensor(&nested_indices).clone()
459 })
460 .collect_vec();
461 let child_tensor = CompositeTensor::new(leaf_tensors);
462 (child_tensor, *id)
463 },
464 )
465 .collect();
466
467 contraction_tree
468 .partitions
469 .insert(*rebalance_depth, partition_ids);
470
471 let updated_tn = CompositeTensor::new(partition_tensors);
472 (rebalanced_paths, updated_tn)
473}
474
475fn find_rebalance_node<R>(
483 random_balance: &mut Option<(usize, R)>,
484 larger_subtree_nodes: &FxHashMap<usize, LeafTensor>,
485 smaller_subtree_nodes: &FxHashMap<usize, LeafTensor>,
486 objective_function: fn(&LeafTensor, &LeafTensor) -> f64,
487) -> (usize, f64)
488where
489 R: Rng,
490{
491 let node_comparison = larger_subtree_nodes
492 .iter()
493 .cartesian_product(smaller_subtree_nodes.iter())
494 .map(|((larger_node_id, larger_tensor), (_, smaller_tensor))| {
495 (
496 *larger_node_id,
497 objective_function(larger_tensor, smaller_tensor),
498 )
499 });
500 if let Some((options_considered, ref mut rng)) = random_balance {
501 let node_options = node_comparison
502 .sorted_unstable_by(|a, b| b.1.total_cmp(&a.1))
503 .take(*options_considered)
504 .collect_vec();
505 let max = node_options.first().unwrap().1;
506 *node_options
508 .choose_weighted(rng, |node_option| node_option.1 / max)
509 .unwrap()
510 } else {
511 node_comparison.max_by(|a, b| a.1.total_cmp(&b.1)).unwrap()
512 }
513}
514
515fn shift_node_between_subtrees(
518 contraction_tree: &mut ContractionTree,
519 rebalance_depth: usize,
520 larger_subtree_id: usize,
521 smaller_subtree_id: usize,
522 rebalanced_nodes: Vec<usize>,
523 tensor_network: &CompositeTensor,
524) -> (usize, SimplePath, f64, f64, usize, SimplePath, f64, f64) {
525 let larger_subtree_parent_id = contraction_tree
527 .node(larger_subtree_id)
528 .parent_id()
529 .unwrap();
530 let smaller_subtree_parent_id = contraction_tree
531 .node(smaller_subtree_id)
532 .parent_id()
533 .unwrap();
534
535 let mut larger_subtree_leaf_nodes = contraction_tree.leaf_ids(larger_subtree_id);
536 let mut smaller_subtree_leaf_nodes = contraction_tree.leaf_ids(smaller_subtree_id);
537
538 assert!(rebalanced_nodes
540 .iter()
541 .all(|node| !smaller_subtree_leaf_nodes.contains(node)));
542 assert!(rebalanced_nodes
543 .iter()
544 .all(|node| larger_subtree_leaf_nodes.contains(node)));
545
546 larger_subtree_leaf_nodes.retain(|leaf| !rebalanced_nodes.contains(leaf));
548 smaller_subtree_leaf_nodes.extend(rebalanced_nodes);
549
550 let (updated_larger_path, local_larger_path, larger_flop_cost, larger_mem_cost) =
552 subtree_contraction_path(&larger_subtree_leaf_nodes, contraction_tree, tensor_network);
553
554 let (updated_smaller_path, local_smaller_path, smaller_flop_cost, smaller_mem_cost) =
555 subtree_contraction_path(
556 &smaller_subtree_leaf_nodes,
557 contraction_tree,
558 tensor_network,
559 );
560
561 contraction_tree.remove_subtree(larger_subtree_id);
563
564 let new_larger_subtree_id = if updated_larger_path.is_empty() {
565 contraction_tree.nodes[&larger_subtree_leaf_nodes[0]]
567 .borrow_mut()
568 .set_parent(Rc::downgrade(
569 &contraction_tree.nodes[&larger_subtree_parent_id],
570 ));
571 contraction_tree.nodes[&larger_subtree_parent_id]
572 .borrow_mut()
573 .add_child(Rc::downgrade(
574 &contraction_tree.nodes[&larger_subtree_leaf_nodes[0]],
575 ));
576 larger_subtree_leaf_nodes[0]
577 } else {
578 contraction_tree.add_path_as_subtree(
579 &ContractionPath::simple(updated_larger_path),
580 larger_subtree_parent_id,
581 &larger_subtree_leaf_nodes,
582 )
583 };
584
585 contraction_tree.remove_subtree(smaller_subtree_id);
587 let new_smaller_subtree_id = contraction_tree.add_path_as_subtree(
589 &ContractionPath::simple(updated_smaller_path),
590 smaller_subtree_parent_id,
591 &smaller_subtree_leaf_nodes,
592 );
593
594 let partition = contraction_tree
596 .partitions
597 .get_mut(&rebalance_depth)
598 .unwrap();
599 partition.retain(|&e| e != smaller_subtree_id && e != larger_subtree_id);
600 partition.push(new_larger_subtree_id);
601 partition.push(new_smaller_subtree_id);
602
603 (
604 new_larger_subtree_id,
605 local_larger_path,
606 larger_flop_cost,
607 larger_mem_cost,
608 new_smaller_subtree_id,
609 local_smaller_path,
610 smaller_flop_cost,
611 smaller_mem_cost,
612 )
613}
614
615#[cfg(test)]
616mod tests {
617 use super::*;
618
619 use std::rc::Rc;
620
621 use rand::{rngs::StdRng, SeedableRng};
622 use rustc_hash::FxHashMap;
623
624 use crate::contractionpath::contraction_tree::{
625 balancing::find_rebalance_node,
626 node::{child_node, parent_node},
627 ContractionTree,
628 };
629 use crate::path;
630
631 fn setup_complex() -> (ContractionTree, CompositeTensor) {
632 let bond_dims = FxHashMap::from_iter([
633 (0, 27),
634 (1, 18),
635 (2, 12),
636 (3, 15),
637 (4, 5),
638 (5, 3),
639 (6, 18),
640 (7, 22),
641 (8, 45),
642 (9, 65),
643 (10, 5),
644 ]);
645 let (tensor, contraction_path) = (
646 CompositeTensor::new(vec![
647 LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims),
648 LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims),
649 LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims),
650 LeafTensor::new_from_map(vec![6, 8, 9], &bond_dims),
651 LeafTensor::new_from_map(vec![10, 8, 9], &bond_dims),
652 LeafTensor::new_from_map(vec![5, 1, 0], &bond_dims),
653 ]),
654 path![(1, 5), (0, 1), (3, 4), (2, 3), (0, 2)],
655 );
656 (
657 ContractionTree::from_contraction_path(&tensor, &contraction_path),
658 tensor,
659 )
660 }
661
662 #[test]
663 fn test_shift_leaf_node_between_subtrees() {
664 let (mut tree, tensor) = setup_complex();
665 tree.partitions.entry(1).or_insert_with(|| vec![9, 7]);
666 shift_node_between_subtrees(&mut tree, 1, 9, 7, vec![3], &tensor);
667
668 let ContractionTree { nodes, root, .. } = tree;
669
670 let node0 = child_node(0, vec![0]);
671 let node1 = child_node(1, vec![1]);
672 let node2 = child_node(2, vec![2]);
673 let node3 = child_node(3, vec![3]);
674 let node4 = child_node(4, vec![4]);
675 let node5 = child_node(5, vec![5]);
676
677 let node6 = parent_node(6, &node1, &node5);
678 let node7 = parent_node(7, &node6, &node0);
679 let node8 = parent_node(8, &node2, &node4);
680 let node9 = parent_node(9, &node7, &node3);
681 let node10 = parent_node(10, &node9, &node8);
682
683 let ref_root = Rc::clone(&node10);
684 let ref_nodes = [
685 node0, node1, node2, node3, node4, node5, node6, node7, node8, node9, node10,
686 ];
687
688 for (key, ref_node) in ref_nodes.iter().enumerate() {
689 let node = &nodes[&key];
690 assert_eq!(node, ref_node);
691 }
692 assert_eq!(root.upgrade().unwrap(), ref_root);
693 }
694
695 #[test]
696 fn test_shift_subtree_between_subtrees() {
697 let (mut tree, tensor) = setup_complex();
698 tree.partitions.entry(1).or_insert_with(|| vec![9, 7]);
699 shift_node_between_subtrees(&mut tree, 1, 9, 7, vec![2, 3], &tensor);
700
701 let ContractionTree { nodes, root, .. } = tree;
702
703 let node0 = child_node(0, vec![0]);
704 let node1 = child_node(1, vec![1]);
705 let node2 = child_node(2, vec![2]);
706 let node3 = child_node(3, vec![3]);
707 let node4 = child_node(4, vec![4]);
708 let node5 = child_node(5, vec![5]);
709
710 let node6 = parent_node(6, &node1, &node5);
711 let node7 = parent_node(7, &node2, &node3);
712 let node8 = parent_node(8, &node6, &node0);
713 let node9 = parent_node(9, &node8, &node7);
714 let node10 = parent_node(10, &node9, &node4);
715
716 let ref_root = Rc::clone(&node10);
717 let ref_nodes = [
718 node0, node1, node2, node3, node4, node5, node6, node7, node8, node9, node10,
719 ];
720
721 for (key, ref_node) in ref_nodes.iter().enumerate() {
722 let node = &nodes[&key];
723 assert_eq!(node, ref_node);
724 }
725
726 assert_eq!(root.upgrade().unwrap(), ref_root);
727 }
728
729 fn custom_weight_function(a: &LeafTensor, b: &LeafTensor) -> f64 {
730 (a & b).legs().len() as f64
731 }
732
733 #[test]
734 fn test_find_rebalance_node() {
735 let bond_dims =
736 FxHashMap::from_iter([(0, 2), (1, 1), (2, 3), (3, 5), (4, 3), (5, 8), (6, 7)]);
737 let larger_hash = FxHashMap::from_iter([
738 (0, LeafTensor::new_from_map(vec![0, 1, 2], &bond_dims)),
739 (1, LeafTensor::new_from_map(vec![1, 2, 3], &bond_dims)),
740 (2, LeafTensor::new_from_map(vec![3, 4, 5], &bond_dims)),
741 ]);
742
743 let smaller_hash =
744 FxHashMap::from_iter([(3, LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims))]);
745
746 let ref_balanced_node = 2;
747 let (node_id, cost) = find_rebalance_node::<StdRng>(
748 &mut None,
749 &larger_hash,
750 &smaller_hash,
751 custom_weight_function,
752 );
753 assert_eq!(cost, 2.);
754 assert_eq!(node_id, ref_balanced_node);
755 }
756
757 #[test]
758 fn test_find_random_rebalance_node() {
759 let bond_dims =
760 FxHashMap::from_iter([(0, 2), (1, 1), (2, 3), (3, 5), (4, 3), (5, 8), (6, 7)]);
761 let larger_hash = FxHashMap::from_iter([
762 (0, LeafTensor::new_from_map(vec![0, 1, 2], &bond_dims)),
763 (1, LeafTensor::new_from_map(vec![1, 2, 6], &bond_dims)),
764 (2, LeafTensor::new_from_map(vec![3, 4, 5], &bond_dims)),
765 ]);
766
767 let smaller_hash =
768 FxHashMap::from_iter([(3, LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims))]);
769
770 let ref_balanced_node = 1;
771 let (node_id, cost) = find_rebalance_node(
772 &mut Some((2, StdRng::seed_from_u64(1))),
773 &larger_hash,
774 &smaller_hash,
775 custom_weight_function,
776 );
777 assert_eq!(cost, 1.);
778 assert_eq!(node_id, ref_balanced_node);
779 }
780}