Skip to main content

tnc/contractionpath/
contraction_tree.rs

1use std::cell::Ref;
2use std::rc::Rc;
3
4use itertools::Itertools;
5use rustc_hash::FxHashMap;
6
7use crate::contractionpath::contraction_tree::node::{
8    child_node, parent_node, Node, NodeRef, WeakNodeRef,
9};
10use crate::contractionpath::paths::validate_path;
11use crate::contractionpath::{ContractionPath, SimplePath, SimplePathRef};
12use crate::tensornetwork::tensor::{CompositeTensor, LeafTensor};
13
14pub mod balancing;
15mod node;
16mod utils;
17
18/// Struct representing the full contraction path of a given [`Tensor`] object.
19#[derive(Default, Debug, Clone)]
20pub struct ContractionTree {
21    /// Map of node ids to the actual nodes.
22    nodes: FxHashMap<usize, NodeRef>,
23    /// Map of tree level to the node ids on this level.
24    partitions: FxHashMap<usize, Vec<usize>>,
25    /// Reference to the root node.
26    root: WeakNodeRef,
27}
28
29impl ContractionTree {
30    /// Returns a reference to the node with the given `node_id`.
31    pub fn node(&self, node_id: usize) -> Ref<'_, Node> {
32        let borrow = &self.nodes[&node_id];
33        borrow.as_ref().borrow()
34    }
35
36    /// Returns the node id of the root node, if any.
37    pub fn root_id(&self) -> Option<usize> {
38        self.root.upgrade().map(|node| node.borrow().id())
39    }
40
41    pub const fn partitions(&self) -> &FxHashMap<usize, Vec<usize>> {
42        &self.partitions
43    }
44
45    /// Populates `nodes` and `partitions` with the tree structure of the contraction
46    /// `path`.
47    fn from_contraction_path_recurse(
48        tensor: &CompositeTensor,
49        path: &ContractionPath,
50        nodes: &mut FxHashMap<usize, NodeRef>,
51        partitions: &mut FxHashMap<usize, Vec<usize>>,
52        prefix: &[usize],
53    ) {
54        let mut scratch = FxHashMap::default();
55
56        // Obtain tree structure from composite tensors
57        for (path_id, path) in path.nested.iter().sorted_by_key(|&(path_id, _)| *path_id) {
58            let composite_tensor = tensor.tensor(*path_id).as_composite().unwrap();
59            let mut new_prefix = prefix.to_owned();
60            new_prefix.push(*path_id);
61            Self::from_contraction_path_recurse(
62                composite_tensor,
63                path,
64                nodes,
65                partitions,
66                &new_prefix,
67            );
68            scratch.insert(*path_id, Rc::clone(&nodes[&(nodes.len() - 1)]));
69        }
70
71        // Add nodes for leaf tensors
72        for (tensor_idx, tensor) in tensor.tensors().iter().enumerate() {
73            if tensor.is_leaf() {
74                let mut nested_tensor_idx = prefix.to_owned();
75                nested_tensor_idx.push(tensor_idx);
76                let new_node = child_node(nodes.len(), nested_tensor_idx);
77                scratch.insert(tensor_idx, Rc::clone(&new_node));
78                nodes.insert(nodes.len(), new_node);
79            }
80        }
81
82        // Build tree based on contraction path
83        for (i_path, j_path) in &path.toplevel {
84            let i = &scratch[i_path];
85            let j = &scratch[j_path];
86            let parent = parent_node(nodes.len(), i, j);
87
88            scratch.insert(*i_path, Rc::clone(&parent));
89            nodes.insert(nodes.len(), parent);
90            scratch.remove(j_path);
91        }
92        partitions
93            .entry(prefix.len())
94            .or_default()
95            .push(nodes.len() - 1);
96    }
97
98    /// Creates a `ContractionTree` from `tensor` and contract `path`. The tree
99    /// represents all intermediate tensors and costs of given contraction path and
100    /// tensor network.
101    #[must_use]
102    pub fn from_contraction_path(tensor: &CompositeTensor, path: &ContractionPath) -> Self {
103        validate_path(path);
104        let mut nodes = FxHashMap::default();
105        let mut partitions = FxHashMap::default();
106        Self::from_contraction_path_recurse(tensor, path, &mut nodes, &mut partitions, &[]);
107        let root = Rc::downgrade(&nodes[&(nodes.len() - 1)]);
108        Self {
109            nodes,
110            partitions,
111            root,
112        }
113    }
114
115    fn leaf_ids_recurse(node: &Node, leaf_indices: &mut Vec<usize>) {
116        if node.is_leaf() {
117            leaf_indices.push(node.id());
118        } else {
119            Self::leaf_ids_recurse(&node.left_child().unwrap().as_ref().borrow(), leaf_indices);
120            Self::leaf_ids_recurse(&node.right_child().unwrap().as_ref().borrow(), leaf_indices);
121        }
122    }
123
124    /// Returns the id of all leaf nodes in subtree with root at `node_id`.
125    pub fn leaf_ids(&self, node_id: usize) -> Vec<usize> {
126        let mut leaf_indices = Vec::new();
127        let node = self.node(node_id);
128        Self::leaf_ids_recurse(&node, &mut leaf_indices);
129        leaf_indices
130    }
131
132    /// Removes subtree with root at `node_id`.
133    fn remove_subtree_recurse(&mut self, node_id: usize) {
134        if self.node(node_id).is_leaf() {
135            // Leaf nodes are not removed. We need to manually clear parent/children relations
136            let node = &self.nodes[&node_id];
137            if let Some(parent_id) = node.borrow().parent_id() {
138                if self.nodes.contains_key(&parent_id) {
139                    self.nodes[&parent_id]
140                        .borrow_mut()
141                        .remove_child(node.borrow().id());
142                }
143            }
144            node.borrow_mut().remove_parent();
145            return;
146        }
147
148        let node = self.nodes.remove(&node_id).unwrap();
149        let node = node.borrow();
150
151        if let Some(id) = node.left_child_id() {
152            self.remove_subtree_recurse(id);
153        }
154        if let Some(id) = node.right_child_id() {
155            self.remove_subtree_recurse(id);
156        }
157    }
158
159    /// Removes subtree with root at `node_id`.
160    pub(crate) fn remove_subtree(&mut self, node_id: usize) {
161        self.remove_subtree_recurse(node_id);
162    }
163
164    /// Converts a contraction path into a ContractionTree, then attaches this as a subtree at `parent_id`
165    /// The ContractionTree should already contain the leaf nodes of the
166    pub(crate) fn add_path_as_subtree(
167        &mut self,
168        path: &ContractionPath,
169        parent_id: usize,
170        leaf_tensor_indices: &[usize],
171    ) -> usize {
172        validate_path(path);
173        assert!(self.nodes.contains_key(&parent_id));
174
175        let mut index = 0;
176        // Utilize a scratch hashmap to store intermediate tensor information
177        let mut scratch = FxHashMap::default();
178
179        // Fill scratch with leaf tensors, these should already be present in self.nodes.
180        for &tensor_index in leaf_tensor_indices {
181            scratch.insert(tensor_index, Rc::clone(&self.nodes[&tensor_index]));
182        }
183
184        // Generate intermediate tensors by looping over contraction operations, fill and update scratch as needed.
185        assert!(
186            path.is_simple(),
187            "Constructor not implemented for nested Tensors"
188        );
189        for (i_path, j_path) in &path.toplevel {
190            // Always keep track of latest added tensor. Last index will be the root of the subtree.
191            index = self.next_id(index);
192            let i = &scratch[i_path];
193            let j = &scratch[j_path];
194
195            // Ensure that we are not reusing nodes that are already in another contraction path
196            assert!(
197                i.borrow().parent_id().is_none(),
198                "Tensor {i_path} is already used in another contraction"
199            );
200            assert!(
201                j.borrow().parent_id().is_none(),
202                "Tensor {j_path} is already used in another contraction"
203            );
204
205            let parent = parent_node(index, i, j);
206            scratch.insert(*i_path, Rc::clone(&parent));
207            scratch.remove(j_path);
208            // Ensure that intermediate tensor information is stored in internal HashMap for reference
209            self.nodes.insert(index, parent);
210        }
211
212        // Add the root of the subtree to the indicated node `parent_id` in larger contraction tree.
213        let new_parent = &self.nodes[&parent_id];
214        new_parent
215            .borrow_mut()
216            .add_child(Rc::downgrade(&self.nodes[&index]));
217
218        let new_child = &self.nodes[&index];
219        new_child.borrow_mut().set_parent(Rc::downgrade(new_parent));
220
221        index
222    }
223
224    fn remove_communication_path(&mut self, partition_ids: &[usize]) {
225        for partition_id in partition_ids {
226            let mut parent_id = self.node(*partition_id).parent_id();
227            while let Some(tensor_id) = parent_id {
228                parent_id = self.node(tensor_id).parent_id();
229                self.nodes.remove(&tensor_id);
230            }
231        }
232    }
233
234    fn replace_communication_path(
235        &mut self,
236        partition_ids: Vec<usize>,
237        communication_path: SimplePathRef,
238    ) {
239        // Remove all nodes involved in communication path
240        self.remove_communication_path(&partition_ids);
241
242        // Rebuild the communication-part of the tree
243        let mut communication_ids = partition_ids;
244        let mut next_id = self.next_id(0);
245        for (i, j) in communication_path {
246            let left_child = communication_ids[*i];
247            let right_child = communication_ids[*j];
248            let new_parent =
249                parent_node(next_id, &self.nodes[&left_child], &self.nodes[&right_child]);
250            self.nodes.insert(next_id, new_parent);
251
252            communication_ids[*i] = next_id;
253            next_id = self.next_id(next_id);
254        }
255
256        // Update root
257        self.root = Rc::downgrade(self.nodes.iter().max_by_key(|entry| entry.0).unwrap().1);
258    }
259
260    fn tree_weights_recurse(
261        node: &Node,
262        tn: &CompositeTensor,
263        weights: &mut FxHashMap<usize, f64>,
264        scratch: &mut FxHashMap<usize, LeafTensor>,
265        cost_function: fn(&LeafTensor, &LeafTensor) -> f64,
266    ) {
267        if node.is_leaf() {
268            let Some(tensor_index) = &node.tensor_index() else {
269                panic!("All leaf nodes should have a tensor index")
270            };
271            weights.insert(node.id(), 0f64);
272            scratch.insert(
273                node.id(),
274                tn.nested_tensor(tensor_index).as_leaf().unwrap().clone(),
275            );
276            return;
277        }
278
279        let left_child = &node.left_child().unwrap();
280        let right_child = &node.right_child().unwrap();
281        let left_ref = left_child.as_ref().borrow();
282        let right_ref = right_child.as_ref().borrow();
283
284        // Recurse first because weights of leaves are needed for further computation.
285        Self::tree_weights_recurse(&left_ref, tn, weights, scratch, cost_function);
286        Self::tree_weights_recurse(&right_ref, tn, weights, scratch, cost_function);
287
288        let t1 = &scratch[&left_ref.id()];
289        let t2 = &scratch[&right_ref.id()];
290
291        let cost = weights[&left_ref.id()] + weights[&right_ref.id()] + cost_function(t1, t2);
292
293        weights.insert(node.id(), cost);
294        scratch.insert(node.id(), t1 ^ t2);
295    }
296
297    /// Returns `HashMap` storing resultant tensor and its respective contraction costs calculated via `cost_function`.
298    ///
299    /// # Arguments
300    /// * `node_id` - root of Node to start calculating contraction costs
301    /// * `tn` - [`Tensor`] object containing bond dimension and leaf node information
302    /// * `cost_function` - cost function returning contraction cost
303    pub fn tree_weights(
304        &self,
305        node_id: usize,
306        tn: &CompositeTensor,
307        cost_function: fn(&LeafTensor, &LeafTensor) -> f64,
308    ) -> FxHashMap<usize, f64> {
309        let mut weights = FxHashMap::default();
310        let mut scratch = FxHashMap::default();
311        let node = self.node(node_id);
312        Self::tree_weights_recurse(&node, tn, &mut weights, &mut scratch, cost_function);
313        weights
314    }
315
316    /// Populates given vector with contractions path of contraction tree starting at `node`.
317    ///
318    /// # Arguments
319    /// * `node` - pointer to Node object
320    /// * `path` - vec to store contraction path in
321    /// * `replace` - if set to `true` returns replace path, otherwise, returns in SSA format
322    fn to_contraction_path_recurse(node: &Node, path: &mut SimplePath, replace: bool) -> usize {
323        if node.is_leaf() {
324            return node.id();
325        }
326
327        // Get children
328        let (Some(left_child), Some(right_child)) = (node.left_child(), node.right_child()) else {
329            panic!("All parents should have two children")
330        };
331
332        // Get right and left child tensor ids
333        let mut t1_id =
334            Self::to_contraction_path_recurse(&left_child.as_ref().borrow(), path, replace);
335        let mut t2_id =
336            Self::to_contraction_path_recurse(&right_child.as_ref().borrow(), path, replace);
337        if t2_id < t1_id {
338            (t1_id, t2_id) = (t2_id, t1_id);
339        }
340
341        // Add pair to path
342        path.push((t1_id, t2_id));
343
344        // Return id of contracted tensor
345        if replace {
346            t1_id
347        } else {
348            node.id()
349        }
350    }
351
352    /// Populates given vector with contractions path of contraction tree starting at `node_id`.
353    /// # Arguments
354    /// * `node` - pointer to Node object
355    /// * `replace` - if set to `true` returns replace path, otherwise, returns in SSA format
356    pub fn to_flat_contraction_path(&self, node_id: usize, replace: bool) -> SimplePath {
357        let node = self.node(node_id);
358        let mut path = Vec::new();
359        Self::to_contraction_path_recurse(&node, &mut path, replace);
360        path
361    }
362
363    fn next_id(&self, mut init: usize) -> usize {
364        while self.nodes.contains_key(&init) {
365            init += 1;
366        }
367        init
368    }
369
370    /// Returns intermediate [`Tensor`] object corresponding to `node_id`.
371    ///
372    /// # Arguments
373    /// * `node_id` - id of Node corresponding to [`Tensor`] of interest
374    /// * `tensor` - tensor containing bond dimension and leaf node information
375    ///
376    /// # Returns
377    /// Empty tensor with legs (dimensions) of data after fully contracted.
378    pub fn tensor(&self, node_id: usize, tensor: &CompositeTensor) -> LeafTensor {
379        let leaf_nodes = self.leaf_ids(node_id);
380        let mut new_tensor = LeafTensor::default();
381
382        for leaf_id in leaf_nodes {
383            new_tensor = &new_tensor
384                ^ tensor
385                    .nested_tensor(self.node(leaf_id).tensor_index().as_ref().unwrap())
386                    .as_leaf()
387                    .unwrap();
388        }
389        new_tensor
390    }
391}
392
393fn populate_subtree_tensor_map_recursive(
394    contraction_tree: &ContractionTree,
395    node_id: usize,
396    node_tensor_map: &mut FxHashMap<usize, LeafTensor>,
397    tensor_network: &CompositeTensor,
398    height_limit: Option<usize>,
399) -> (LeafTensor, usize) {
400    let node = contraction_tree.node(node_id);
401
402    if node.is_leaf() {
403        let tensor_index = node.tensor_index().unwrap();
404        let t = tensor_network
405            .nested_tensor(tensor_index)
406            .as_leaf()
407            .unwrap();
408        node_tensor_map.insert(node.id(), t.clone());
409        (t.clone(), 0)
410    } else {
411        let (t1, new_height1) = populate_subtree_tensor_map_recursive(
412            contraction_tree,
413            node.left_child_id().unwrap(),
414            node_tensor_map,
415            tensor_network,
416            height_limit,
417        );
418        let (t2, new_height2) = populate_subtree_tensor_map_recursive(
419            contraction_tree,
420            node.right_child_id().unwrap(),
421            node_tensor_map,
422            tensor_network,
423            height_limit,
424        );
425        let t12 = &t1 ^ &t2;
426        if let Some(height_limit) = height_limit {
427            if new_height1 < height_limit && new_height2 < height_limit {
428                node_tensor_map.insert(node.id(), t12.clone());
429            }
430        } else {
431            node_tensor_map.insert(node.id(), t12.clone());
432        }
433
434        (t12, new_height1.max(new_height2) + 1)
435    }
436}
437
438/// Populates `node_tensor_map` with all intermediate and leaf node ids and corresponding [`Tensor`] object, with root at `node_id`.
439/// Only inserts Tensors with up to `height_limit` number of contractions.
440///
441/// # Arguments
442/// * `contraction_tree` - [`ContractionTree`] object
443/// * `node_id` - root of subtree to examine
444/// * `node_tensor_map` - empty HashMap to populate
445/// * `tensor_network` - [`Tensor`] object containing bond dimension and leaf node information
446///
447///
448/// # Returns
449/// Populated HashMap mapping intermediate node ids up to `height_limit` to Tensor objects.
450fn populate_subtree_tensor_map(
451    contraction_tree: &ContractionTree,
452    node_id: usize,
453    tensor_network: &CompositeTensor,
454    height_limit: Option<usize>,
455) -> FxHashMap<usize, LeafTensor> {
456    let mut node_tensor_map = FxHashMap::default();
457    let _ = populate_subtree_tensor_map_recursive(
458        contraction_tree,
459        node_id,
460        &mut node_tensor_map,
461        tensor_network,
462        height_limit,
463    );
464    node_tensor_map
465}
466
467/// Populates `node_tensor_map` with all leaf node ids and corresponding [`Tensor`] object, with root at `node_id`.
468///
469/// # Arguments
470/// * `contraction_tree` - [`ContractionTree`] object
471/// * `node_id` - root of subtree to examine
472/// * `tensor_network` - [`Tensor`] object containing bond dimension and leaf node information
473///
474/// # Returns
475/// Populated HashMap mapping leaf node ids to Tensor objects.
476fn populate_leaf_node_tensor_map(
477    contraction_tree: &ContractionTree,
478    node_id: usize,
479    tensor_network: &CompositeTensor,
480) -> FxHashMap<usize, LeafTensor> {
481    let mut node_tensor_map = FxHashMap::default();
482    for leaf_node_id in contraction_tree.leaf_ids(node_id) {
483        node_tensor_map.insert(
484            leaf_node_id,
485            contraction_tree.tensor(leaf_node_id, tensor_network),
486        );
487    }
488    node_tensor_map
489}
490
491#[cfg(test)]
492mod tests {
493    use super::*;
494
495    use std::cell::RefCell;
496    use std::iter::zip;
497    use std::rc::Weak;
498
499    use itertools::Itertools;
500
501    use crate::contractionpath::contraction_cost::contract_cost_tensors;
502    use crate::contractionpath::contraction_tree::node::{child_node, parent_node};
503    use crate::contractionpath::contraction_tree::{ContractionTree, Node};
504    use crate::contractionpath::ssa_replace_ordering;
505    use crate::path;
506    use crate::tensornetwork::tensor::{EdgeIndex, Tensor};
507
508    fn setup_simple() -> (CompositeTensor, ContractionPath, FxHashMap<EdgeIndex, u64>) {
509        let bond_dims =
510            FxHashMap::from_iter([(0, 5), (1, 2), (2, 6), (3, 8), (4, 1), (5, 3), (6, 4)]);
511        (
512            CompositeTensor::new(vec![
513                LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims),
514                LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims),
515                LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims),
516            ]),
517            path![(0, 1), (2, 0)],
518            bond_dims,
519        )
520    }
521
522    fn setup_complex() -> (CompositeTensor, ContractionPath, FxHashMap<EdgeIndex, u64>) {
523        let bond_dims = FxHashMap::from_iter([
524            (0, 27),
525            (1, 18),
526            (2, 12),
527            (3, 15),
528            (4, 5),
529            (5, 3),
530            (6, 18),
531            (7, 22),
532            (8, 45),
533            (9, 65),
534            (10, 5),
535        ]);
536        (
537            CompositeTensor::new(vec![
538                LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims),
539                LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims),
540                LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims),
541                LeafTensor::new_from_map(vec![6, 8, 9], &bond_dims),
542                LeafTensor::new_from_map(vec![10, 8, 9], &bond_dims),
543                LeafTensor::new_from_map(vec![5, 1, 0], &bond_dims),
544            ]),
545            path![(1, 5), (0, 1), (3, 4), (2, 3), (0, 2)],
546            bond_dims,
547        )
548    }
549
550    fn setup_unbalanced() -> (CompositeTensor, ContractionPath) {
551        let bond_dims = FxHashMap::from_iter([
552            (0, 27),
553            (1, 18),
554            (2, 12),
555            (3, 15),
556            (4, 5),
557            (5, 3),
558            (6, 18),
559            (7, 22),
560            (8, 45),
561            (9, 65),
562            (10, 5),
563            (11, 17),
564        ]);
565        (
566            CompositeTensor::new(vec![
567                LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims),
568                LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims),
569                LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims),
570                LeafTensor::new_from_map(vec![6, 8, 9], &bond_dims),
571                LeafTensor::new_from_map(vec![10, 8, 9], &bond_dims),
572                LeafTensor::new_from_map(vec![5, 1, 0], &bond_dims),
573            ]),
574            path![(0, 1), (2, 0), (3, 2), (4, 3), (5, 4)],
575        )
576    }
577
578    fn setup_nested() -> (CompositeTensor, ContractionPath) {
579        let bond_dims = FxHashMap::from_iter([
580            (0, 27),
581            (1, 18),
582            (2, 12),
583            (3, 15),
584            (4, 5),
585            (5, 3),
586            (6, 18),
587            (7, 22),
588            (8, 45),
589            (9, 65),
590            (10, 5),
591            (11, 17),
592        ]);
593
594        let t0 = LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims);
595        let t1 = LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims);
596        let t2 = LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims);
597        let t3 = LeafTensor::new_from_map(vec![6, 8, 9], &bond_dims);
598        let t4 = LeafTensor::new_from_map(vec![5, 1, 0], &bond_dims);
599        let t5 = LeafTensor::new_from_map(vec![10, 8, 9], &bond_dims);
600
601        let t01 = CompositeTensor::new(vec![t0, t1]);
602        let t23 = CompositeTensor::new(vec![t2, t3]);
603        let t45 = CompositeTensor::new(vec![t4, t5]);
604        let tensor_network = CompositeTensor::new(vec![t01, t23, t45]);
605        (
606            tensor_network,
607            path![{(0, [(0, 1)]), (1, [(0, 1)]), (2, [(0, 1)])}, (0, 1), (0, 2)],
608        )
609    }
610
611    fn setup_double_nested() -> (CompositeTensor, ContractionPath) {
612        let bond_dims = FxHashMap::from_iter([
613            (0, 27),
614            (1, 18),
615            (2, 12),
616            (3, 15),
617            (4, 5),
618            (5, 3),
619            (6, 18),
620            (7, 22),
621            (8, 45),
622            (9, 65),
623            (10, 5),
624            (11, 17),
625        ]);
626
627        let t0 = LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims);
628        let t1 = LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims);
629        let t2 = LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims);
630        let t3 = LeafTensor::new_from_map(vec![6, 8, 9], &bond_dims);
631        let t4 = LeafTensor::new_from_map(vec![5, 1, 0], &bond_dims);
632        let t5 = LeafTensor::new_from_map(vec![10, 8, 9], &bond_dims);
633
634        let t01 = CompositeTensor::new(vec![t0, t1]);
635        let t01: Tensor = t01.into();
636        let t012 = CompositeTensor::new(vec![t01, t2.into()]);
637        let t34 = CompositeTensor::new(vec![t3, t4]);
638        let t34: Tensor = t34.into();
639        let t345 = CompositeTensor::new(vec![t34, t5.into()]);
640        let tensor_network = CompositeTensor::new(vec![t012, t345]);
641        (
642            tensor_network,
643            path![
644                {
645                (0, [{(0, [(0, 1)])}, (0, 1)]),
646                (1, [{(0, [(0, 1)])}, (0, 1)]),
647                },
648                (0, 1)
649            ],
650        )
651    }
652
653    impl PartialEq for Node {
654        fn eq(&self, other: &Self) -> bool {
655            self.id() == other.id()
656                && self.left_child_id() == other.left_child_id()
657                && self.right_child_id() == other.right_child_id()
658                && self.parent_id() == other.parent_id()
659                && self.tensor_index() == other.tensor_index()
660        }
661    }
662
663    #[test]
664    fn test_from_contraction_path_simple() {
665        let (tensor, path, _) = setup_simple();
666        let ContractionTree { nodes, root, .. } =
667            ContractionTree::from_contraction_path(&tensor, &path);
668
669        let node0 = child_node(0, vec![0]);
670        let node1 = child_node(1, vec![1]);
671        let node2 = child_node(2, vec![2]);
672
673        let node3 = parent_node(3, &node0, &node1);
674        let node4 = parent_node(4, &node2, &node3);
675
676        let ref_root = Rc::clone(&node4);
677        let ref_nodes = [node0, node1, node2, node3, node4];
678
679        for (key, ref_node) in ref_nodes.iter().enumerate() {
680            let node = &nodes[&key];
681            assert_eq!(node, ref_node);
682        }
683        assert_eq!(root.upgrade().unwrap(), ref_root);
684    }
685
686    #[test]
687    fn test_from_contraction_path_complex() {
688        let (tensor, path, _) = setup_complex();
689        let ContractionTree { nodes, root, .. } =
690            ContractionTree::from_contraction_path(&tensor, &path);
691
692        let node0 = child_node(0, vec![0]);
693        let node1 = child_node(1, vec![1]);
694        let node2 = child_node(2, vec![2]);
695        let node3 = child_node(3, vec![3]);
696        let node4 = child_node(4, vec![4]);
697        let node5 = child_node(5, vec![5]);
698
699        let node6 = parent_node(6, &node1, &node5);
700        let node7 = parent_node(7, &node0, &node6);
701        let node8 = parent_node(8, &node3, &node4);
702        let node9 = parent_node(9, &node2, &node8);
703        let node10 = parent_node(10, &node7, &node9);
704
705        let ref_root = Rc::clone(&node10);
706        let ref_nodes = [
707            node0, node1, node2, node3, node4, node5, node6, node7, node8, node9, node10,
708        ];
709
710        for (key, ref_node) in ref_nodes.iter().enumerate() {
711            let node = &nodes[&key];
712            assert_eq!(node, ref_node);
713        }
714        assert_eq!(root.upgrade().unwrap(), ref_root);
715    }
716
717    #[test]
718    fn test_from_contraction_path_nested() {
719        let (tensor, path) = setup_nested();
720        let ContractionTree { nodes, root, .. } =
721            ContractionTree::from_contraction_path(&tensor, &path);
722
723        let node0 = child_node(0, vec![0, 0]);
724        let node1 = child_node(1, vec![0, 1]);
725        let node3 = child_node(3, vec![1, 0]);
726        let node4 = child_node(4, vec![1, 1]);
727        let node6 = child_node(6, vec![2, 0]);
728        let node7 = child_node(7, vec![2, 1]);
729
730        let node2 = parent_node(2, &node0, &node1);
731        let node5 = parent_node(5, &node3, &node4);
732        let node8 = parent_node(8, &node6, &node7);
733        let node9 = parent_node(9, &node2, &node5);
734        let node10 = parent_node(10, &node9, &node8);
735
736        let ref_root = Rc::clone(&node10);
737        let ref_nodes = [
738            node0, node1, node2, node3, node4, node5, node6, node7, node8, node9, node10,
739        ];
740
741        for (key, ref_node) in ref_nodes.iter().enumerate() {
742            let node = &nodes[&key];
743            assert_eq!(node, ref_node);
744        }
745        assert_eq!(root.upgrade().unwrap(), ref_root);
746    }
747
748    #[test]
749    fn test_from_contraction_path_double_nested() {
750        let (tensor, path) = setup_double_nested();
751        let ContractionTree { nodes, root, .. } =
752            ContractionTree::from_contraction_path(&tensor, &path);
753
754        let node0 = child_node(0, vec![0, 0, 0]);
755        let node1 = child_node(1, vec![0, 0, 1]);
756        let node3 = child_node(3, vec![0, 1]);
757        let node5 = child_node(5, vec![1, 0, 0]);
758        let node6 = child_node(6, vec![1, 0, 1]);
759        let node8 = child_node(8, vec![1, 1]);
760
761        let node2 = parent_node(2, &node0, &node1);
762        let node4 = parent_node(4, &node2, &node3);
763        let node7 = parent_node(7, &node5, &node6);
764        let node9 = parent_node(9, &node7, &node8);
765        let node10 = parent_node(10, &node4, &node9);
766
767        let ref_root = Rc::clone(&node10);
768        let ref_nodes = [
769            node0, node1, node2, node3, node4, node5, node6, node7, node8, node9, node10,
770        ];
771
772        for (key, ref_node) in ref_nodes.iter().enumerate() {
773            let node = &nodes[&key];
774            assert_eq!(node, ref_node);
775        }
776        assert_eq!(root.upgrade().unwrap(), ref_root);
777    }
778
779    #[test]
780    fn test_leaf_ids_simple() {
781        let (tn, path, _) = setup_simple();
782        let tree = ContractionTree::from_contraction_path(&tn, &path);
783
784        assert_eq!(tree.leaf_ids(4), vec![2, 0, 1]);
785        assert_eq!(tree.leaf_ids(3), vec![0, 1]);
786        assert_eq!(tree.leaf_ids(2), vec![2]);
787    }
788
789    #[test]
790    fn test_leaf_ids_complex() {
791        let (tn, path, _) = setup_complex();
792        let tree = ContractionTree::from_contraction_path(&tn, &path);
793
794        assert_eq!(tree.leaf_ids(10), vec![0, 1, 5, 2, 3, 4]);
795        assert_eq!(tree.leaf_ids(9), vec![2, 3, 4]);
796        assert_eq!(tree.leaf_ids(8), vec![3, 4]);
797        assert_eq!(tree.leaf_ids(7), vec![0, 1, 5]);
798        assert_eq!(tree.leaf_ids(6), vec![1, 5]);
799        assert_eq!(tree.leaf_ids(3), vec![3]);
800    }
801
802    #[test]
803    fn test_leaf_ids_nested() {
804        let (tn, path) = setup_nested();
805        let tree = ContractionTree::from_contraction_path(&tn, &path);
806        assert_eq!(tree.leaf_ids(10), vec![0, 1, 3, 4, 6, 7]);
807        assert_eq!(tree.leaf_ids(9), vec![0, 1, 3, 4]);
808        assert_eq!(tree.leaf_ids(8), vec![6, 7]);
809        assert_eq!(tree.leaf_ids(5), vec![3, 4]);
810        assert_eq!(tree.leaf_ids(2), vec![0, 1]);
811    }
812
813    #[test]
814    fn test_leaf_ids_double_nested() {
815        let (tn, path) = setup_double_nested();
816        let tree = ContractionTree::from_contraction_path(&tn, &path);
817
818        assert_eq!(tree.leaf_ids(10), vec![0, 1, 3, 5, 6, 8]);
819        assert_eq!(tree.leaf_ids(9), vec![5, 6, 8]);
820        assert_eq!(tree.leaf_ids(7), vec![5, 6]);
821        assert_eq!(tree.leaf_ids(4), vec![0, 1, 3]);
822        assert_eq!(tree.leaf_ids(2), vec![0, 1]);
823    }
824
825    #[test]
826    fn test_remove_subtree() {
827        let (tn, path) = setup_nested();
828        let mut tree = ContractionTree::from_contraction_path(&tn, &path);
829
830        tree.remove_subtree(8);
831
832        let ContractionTree { nodes, root, .. } = tree;
833
834        let node0 = child_node(0, vec![0, 0]);
835        let node1 = child_node(1, vec![0, 1]);
836        let node3 = child_node(3, vec![1, 0]);
837        let node4 = child_node(4, vec![1, 1]);
838        let node6 = child_node(6, vec![2, 0]);
839        let node7 = child_node(7, vec![2, 1]);
840        let node2 = parent_node(2, &node0, &node1);
841        let node5 = parent_node(5, &node3, &node4);
842        let node9 = parent_node(9, &node2, &node5);
843        let node10 = Rc::new(RefCell::new(Node::new(
844            10,
845            Rc::downgrade(&node9),
846            Weak::new(),
847            Weak::new(),
848            None,
849        )));
850        node9.borrow_mut().set_parent(Rc::downgrade(&node10));
851
852        let ref_root = Rc::clone(&node10);
853        let ref_nodes = [
854            node0, node1, node2, node3, node4, node5, node6, node7, node9, node10,
855        ];
856        let mut range = (0..8).collect_vec();
857        range.extend(9..11);
858        for (key, ref_node) in zip(range.iter(), ref_nodes.iter()) {
859            let node = &nodes[key];
860            assert_eq!(node, ref_node);
861        }
862        assert_eq!(root.upgrade().unwrap(), ref_root);
863    }
864
865    #[test]
866    fn test_remove_trivial_subtree() {
867        let (tensor, path) = setup_nested();
868        let mut tree = ContractionTree::from_contraction_path(&tensor, &path);
869
870        tree.remove_subtree(7);
871
872        let ContractionTree { nodes, root, .. } = tree;
873
874        let node0 = child_node(0, vec![0, 0]);
875        let node1 = child_node(1, vec![0, 1]);
876        let node3 = child_node(3, vec![1, 0]);
877        let node4 = child_node(4, vec![1, 1]);
878        let node6 = child_node(6, vec![2, 0]);
879        let node7 = child_node(7, vec![2, 1]);
880        let node2 = parent_node(2, &node0, &node1);
881        let node5 = parent_node(5, &node3, &node4);
882        let node8 = Rc::new(RefCell::new(Node::new(
883            8,
884            Rc::downgrade(&node6),
885            Weak::new(),
886            Weak::new(),
887            None,
888        )));
889        let node9 = parent_node(9, &node2, &node5);
890        let node10 = parent_node(10, &node9, &node8);
891        node6.borrow_mut().set_parent(Rc::downgrade(&node8));
892
893        let ref_root = Rc::clone(&node10);
894        let ref_nodes = [
895            node0, node1, node2, node3, node4, node5, node6, node7, node8, node9, node10,
896        ];
897
898        for (key, ref_node) in ref_nodes.iter().enumerate() {
899            let node = &nodes[&key];
900            assert_eq!(node, ref_node);
901        }
902        assert_eq!(root.upgrade().unwrap(), ref_root);
903    }
904
905    #[test]
906    fn test_tree_weights_simple() {
907        let (tensor, path, _) = setup_simple();
908        let tree = ContractionTree::from_contraction_path(&tensor, &path);
909        let ref_weights = FxHashMap::from_iter([(1, 0.), (0, 0.), (2, 0.), (3, 3820.), (4, 4540.)]);
910        let weights = tree.tree_weights(4, &tensor, contract_cost_tensors);
911
912        assert_eq!(weights, ref_weights);
913        let ref_weights = FxHashMap::from_iter([(1, 0.), (0, 0.), (3, 3820.)]);
914        let weights = tree.tree_weights(3, &tensor, contract_cost_tensors);
915        assert_eq!(weights, ref_weights);
916
917        assert_eq!(weights, ref_weights);
918        let ref_weights = FxHashMap::from_iter([(2, 0.)]);
919        let weights = tree.tree_weights(2, &tensor, contract_cost_tensors);
920        assert_eq!(weights, ref_weights);
921    }
922
923    #[test]
924    fn test_tree_weights_complex() {
925        let (tensor, path, _) = setup_complex();
926        let tree = ContractionTree::from_contraction_path(&tensor, &path);
927        let ref_weights = FxHashMap::from_iter([
928            (0, 0.),
929            (1, 0.),
930            (2, 0.),
931            (3, 0.),
932            (4, 0.),
933            (5, 0.),
934            (6, 2098440.),
935            (7, 2120010.),
936            (8, 2105820.),
937            (9, 2116470.),
938            (10, 4237070.),
939        ]);
940        let weights = tree.tree_weights(10, &tensor, contract_cost_tensors);
941
942        assert_eq!(weights, ref_weights);
943    }
944
945    #[test]
946    fn test_to_contraction_path_simple() {
947        let (tensor, ref_path, _) = setup_simple();
948        let tree = ContractionTree::from_contraction_path(&tensor, &ref_path);
949        let path = tree.to_flat_contraction_path(4, false);
950        let path = ssa_replace_ordering(&ContractionPath::simple(path));
951        assert_eq!(path, ref_path);
952    }
953
954    #[test]
955    fn test_to_contraction_path_complex() {
956        let (tensor, ref_path, _) = setup_complex();
957        let tree = ContractionTree::from_contraction_path(&tensor, &ref_path);
958        let path = tree.to_flat_contraction_path(10, false);
959        let path = ssa_replace_ordering(&ContractionPath::simple(path));
960        assert_eq!(path, ref_path);
961    }
962
963    #[test]
964    fn test_to_contraction_path_unbalanced() {
965        let (tensor, ref_path) = setup_unbalanced();
966        let tree = ContractionTree::from_contraction_path(&tensor, &ref_path);
967        let path = tree.to_flat_contraction_path(10, false);
968        let path = ssa_replace_ordering(&ContractionPath::simple(path));
969        assert_eq!(path, ref_path);
970    }
971
972    #[test]
973    fn test_populate_subtree_tensor_map_simple() {
974        let (tensor, ref_path, bond_dims) = setup_simple();
975        let tree = ContractionTree::from_contraction_path(&tensor, &ref_path);
976        let mut node_tensor_map = FxHashMap::default();
977        populate_subtree_tensor_map_recursive(&tree, 4, &mut node_tensor_map, &tensor, None);
978
979        let ref_node_tensor_map = FxHashMap::from_iter([
980            (0, LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims)),
981            (1, LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims)),
982            (2, LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims)),
983            (3, LeafTensor::new_from_map(vec![4, 0, 1], &bond_dims)),
984            (4, LeafTensor::new_from_map(vec![5, 6, 0, 1], &bond_dims)),
985        ]);
986
987        for (key, value) in ref_node_tensor_map {
988            assert_eq!(node_tensor_map[&key].legs(), value.legs());
989        }
990    }
991
992    #[test]
993    fn test_populate_subtree_tensor_map_complex() {
994        let (tensor, ref_path, bond_dims) = setup_complex();
995        let tree = ContractionTree::from_contraction_path(&tensor, &ref_path);
996        let mut node_tensor_map = FxHashMap::default();
997        populate_subtree_tensor_map_recursive(&tree, 10, &mut node_tensor_map, &tensor, None);
998
999        let ref_node_tensor_map = FxHashMap::from_iter([
1000            (0, LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims)),
1001            (1, LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims)),
1002            (2, LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims)),
1003            (3, LeafTensor::new_from_map(vec![6, 8, 9], &bond_dims)),
1004            (4, LeafTensor::new_from_map(vec![10, 8, 9], &bond_dims)),
1005            (5, LeafTensor::new_from_map(vec![5, 1, 0], &bond_dims)),
1006            (6, LeafTensor::new_from_map(vec![3, 2, 5], &bond_dims)),
1007            (7, LeafTensor::new_from_map(vec![4, 5], &bond_dims)),
1008            (8, LeafTensor::new_from_map(vec![6, 10], &bond_dims)),
1009            (9, LeafTensor::new_from_map(vec![4, 5, 10], &bond_dims)),
1010            (10, LeafTensor::new_from_map(vec![10], &bond_dims)),
1011        ]);
1012
1013        for (key, value) in ref_node_tensor_map {
1014            assert_eq!(node_tensor_map[&key].legs(), value.legs());
1015        }
1016    }
1017
1018    #[test]
1019    fn test_populate_subtree_tensor_map_height_limit() {
1020        let (tensor, ref_path, bond_dims) = setup_complex();
1021        let tree = ContractionTree::from_contraction_path(&tensor, &ref_path);
1022        let node_tensor_map = populate_subtree_tensor_map(&tree, 10, &tensor, Some(1));
1023
1024        let ref_node_tensor_map = FxHashMap::from_iter([
1025            (0, LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims)),
1026            (1, LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims)),
1027            (2, LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims)),
1028            (3, LeafTensor::new_from_map(vec![6, 8, 9], &bond_dims)),
1029            (4, LeafTensor::new_from_map(vec![10, 8, 9], &bond_dims)),
1030            (5, LeafTensor::new_from_map(vec![5, 1, 0], &bond_dims)),
1031            (6, LeafTensor::new_from_map(vec![3, 2, 5], &bond_dims)),
1032            (8, LeafTensor::new_from_map(vec![6, 10], &bond_dims)),
1033        ]);
1034
1035        for (key, value) in ref_node_tensor_map {
1036            assert_eq!(node_tensor_map[&key].legs(), value.legs());
1037        }
1038    }
1039
1040    #[test]
1041    fn test_populate_leaf_node_tensor_map_simple() {
1042        let (tensor, ref_path, bond_dims) = setup_simple();
1043        let tree = ContractionTree::from_contraction_path(&tensor, &ref_path);
1044
1045        let node_tensor_map = populate_leaf_node_tensor_map(&tree, 4, &tensor);
1046
1047        let ref_node_tensor_map = FxHashMap::from_iter([
1048            (0, LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims)),
1049            (1, LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims)),
1050            (2, LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims)),
1051        ]);
1052
1053        for (key, value) in ref_node_tensor_map {
1054            assert_eq!(node_tensor_map[&key].legs(), value.legs());
1055        }
1056    }
1057
1058    #[test]
1059    fn test_populate_leaf_node_tensor_map_complex() {
1060        let (tensor, ref_path, bond_dims) = setup_complex();
1061        let tree = ContractionTree::from_contraction_path(&tensor, &ref_path);
1062        let node_tensor_map = populate_subtree_tensor_map(&tree, 10, &tensor, None);
1063
1064        let ref_node_tensor_map = FxHashMap::from_iter([
1065            (0, LeafTensor::new_from_map(vec![4, 3, 2], &bond_dims)),
1066            (1, LeafTensor::new_from_map(vec![0, 1, 3, 2], &bond_dims)),
1067            (2, LeafTensor::new_from_map(vec![4, 5, 6], &bond_dims)),
1068            (3, LeafTensor::new_from_map(vec![6, 8, 9], &bond_dims)),
1069            (4, LeafTensor::new_from_map(vec![10, 8, 9], &bond_dims)),
1070        ]);
1071
1072        for (key, value) in ref_node_tensor_map {
1073            assert_eq!(node_tensor_map[&key].legs(), value.legs());
1074        }
1075    }
1076
1077    #[test]
1078    fn test_add_path_as_subtree() {
1079        let (tensor, path, _) = setup_complex();
1080
1081        let mut complex_tree = ContractionTree::from_contraction_path(&tensor, &path);
1082        complex_tree.remove_subtree(9);
1083        let new_path = path![(4, 2), (4, 3)];
1084
1085        complex_tree.add_path_as_subtree(&new_path, 10, &[3, 4, 2]);
1086
1087        let ContractionTree { nodes, root, .. } = complex_tree;
1088
1089        let node0 = child_node(0, vec![0]);
1090        let node1 = child_node(1, vec![1]);
1091        let node2 = child_node(2, vec![2]);
1092        let node3 = child_node(3, vec![3]);
1093        let node4 = child_node(4, vec![4]);
1094        let node5 = child_node(5, vec![5]);
1095        let node6 = parent_node(6, &node1, &node5);
1096        let node7 = parent_node(7, &node0, &node6);
1097        let node8 = parent_node(8, &node4, &node2);
1098        let node9 = parent_node(9, &node8, &node3);
1099        let node10 = parent_node(10, &node7, &node9);
1100
1101        let ref_root = Rc::clone(&node10);
1102        let ref_nodes = [
1103            node0, node1, node2, node3, node4, node5, node6, node7, node8, node9, node10,
1104        ];
1105
1106        for (key, ref_node) in ref_nodes.iter().enumerate() {
1107            let node = &nodes[&key];
1108            assert_eq!(node, ref_node);
1109        }
1110        assert_eq!(root.upgrade().unwrap(), ref_root);
1111    }
1112
1113    #[test]
1114    #[should_panic = "Tensor 2 is already used in another contraction"]
1115    fn test_add_path_as_subtree_invalid_path() {
1116        let (tensor, path, _) = setup_complex();
1117
1118        let mut complex_tree = ContractionTree::from_contraction_path(&tensor, &path);
1119        complex_tree.remove_subtree(8);
1120        let new_path = path![(4, 2), (4, 3)];
1121
1122        complex_tree.add_path_as_subtree(&new_path, 9, &[3, 4, 2]);
1123    }
1124
1125    #[test]
1126    fn test_remove_communication_path() {
1127        let (tensor, path) = setup_nested();
1128        let mut complex_tree = ContractionTree::from_contraction_path(&tensor, &path);
1129        let partition_ids = vec![2, 5, 8];
1130        complex_tree.remove_communication_path(&partition_ids);
1131        assert!(!complex_tree.nodes.contains_key(&9));
1132        assert!(!complex_tree.nodes.contains_key(&10));
1133        assert!(complex_tree.root_id().is_none());
1134    }
1135
1136    #[test]
1137    fn test_replace_communication_path() {
1138        let (tensor, path) = setup_nested();
1139        let mut complex_tree = ContractionTree::from_contraction_path(&tensor, &path);
1140        let partition_ids = vec![2, 5, 8];
1141        complex_tree.replace_communication_path(partition_ids, &[(0, 2), (1, 0)]);
1142
1143        let ContractionTree { nodes, root, .. } = complex_tree;
1144
1145        let node2 = child_node(2, vec![]);
1146        let node5 = child_node(5, vec![]);
1147        let node8 = child_node(8, vec![]);
1148        let node9 = parent_node(9, &node2, &node8);
1149        let node10 = parent_node(10, &node5, &node9);
1150
1151        assert_eq!(nodes[&9], node9);
1152        assert_eq!(nodes[&10], node10);
1153        assert_eq!(root.upgrade().unwrap(), node10);
1154    }
1155}