Skip to main content

opt_einsum_path/
helpers.rs

1//! Contains helper functions for opt_einsum testing scripts.
2
3use crate::*;
4
5/// Computes the product of the elements in indices based on the dictionary
6/// idx_dict.
7///
8/// # Parameters
9///
10/// * `indices` - Indices to base the product on.
11/// * `idx_dict` - Dictionary of index sizes
12///
13/// # Returns
14///
15/// The resulting product as an integer.
16///
17/// # Counterpart in Python
18///
19/// `opt_einsum.helpers.compute_size_by_dict`
20///
21/// # Example
22///
23/// **Using HashMap as a dictionary**
24///
25/// In this case, indices and dictionary are both passed by reference.
26///
27/// ```rust
28/// # use itertools::Itertools;
29/// # use num::ToPrimitive;
30/// # use std::collections::{BTreeMap, BTreeSet};
31/// # use opt_einsum_path::helpers::compute_size_by_dict;
32/// let indices = "abbc".chars();
33/// let idx_dict = BTreeMap::from([('a', 2), ('b', 3), ('c', 5)]);
34/// let size = compute_size_by_dict(indices, &idx_dict);
35/// assert_eq!(size.to_usize().unwrap(), 90);
36/// ```
37///
38/// Python equivalent:
39///
40/// ```python
41/// >>> opt_einsum.helpers.compute_size_by_dict('abbc', {'a': 2, 'b': 3, 'c': 5})
42/// 90
43/// ```
44pub fn compute_size_by_dict<T>(indices: impl Iterator<Item = T>, idx_dict: &SizeDictType) -> SizeType
45where
46    T: Borrow<char>,
47{
48    indices.map(|k| SizeType::from_usize(idx_dict[k.borrow()]).unwrap()).product()
49}
50
51/// Finds the contraction details for a given set of input indices, output indices, and positions of
52/// terms to contract.
53///
54/// # Parameters
55///
56/// * `positions` - Indices of the terms (from `input_sets`) to be contracted.
57/// * `input_sets` - List of sets where each set represents the indices of an input term in the
58///   einsum expression.
59/// * `output_set` - Set representing the indices of the final output of the overall einsum
60///   expression.
61///
62/// # Returns
63///
64/// A tuple containing:
65/// 1. `new_result`: The indices of the resulting intermediate tensor from the contraction.
66/// 2. `remaining`: The list of input sets after removing the contracted terms, with `new_result`
67///    appended.
68/// 3. `idx_removed`: The indices that are summed over (removed) during this contraction.
69/// 4. `idx_contract`: All indices involved in the current contraction (from the contracted terms).
70///
71/// # Counterpart in Python
72///
73/// `opt_einsum.helpers.find_contraction`
74///
75/// # Examples
76///
77/// **A simple dot product test case**
78///
79/// ```rust
80/// # use std::collections::BTreeSet;
81/// # use opt_einsum_path::helpers::find_contraction;
82/// let positions = [0, 1];
83/// let input_sets = ["ab".chars().collect(), "bc".chars().collect()];
84/// let input_sets = input_sets.iter().collect::<Vec<_>>();
85/// let output_set = "ac".chars().collect();
86/// let (new_result, remaining, idx_removed, idx_contract) =
87///     find_contraction(&positions, &input_sets, &output_set);
88/// assert_eq!(new_result, "ac".chars().collect());
89/// assert_eq!(remaining, vec!["ac".chars().collect()]);
90/// assert_eq!(idx_removed, "b".chars().collect());
91/// assert_eq!(idx_contract, "abc".chars().collect());
92/// ```
93///
94/// Python equivalent:
95///
96/// ```python
97/// >>> from opt_einsum.helpers import find_contraction
98/// >>> pos = (0, 1)
99/// >>> isets = [set('ab'), set('bc')]
100/// >>> oset = set('ac')
101/// >>> find_contraction(pos, isets, oset)
102/// ({'a', 'c'}, [{'a', 'c'}], {'b'}, {'a', 'b', 'c'})
103/// ```
104///
105/// **A more complex case with additional terms in the contraction**
106///
107/// ```rust
108/// # use std::collections::BTreeSet;
109/// # use opt_einsum_path::helpers::find_contraction;
110/// let positions = [0, 2];
111/// let input_sets = ["abd".chars().collect(), "ac".chars().collect(), "bdc".chars().collect()];
112/// let input_sets = input_sets.iter().collect::<Vec<_>>();
113/// let output_set = "ac".chars().collect();
114/// let (new_result, remaining, idx_removed, idx_contract) =
115///     find_contraction(&positions, &input_sets, &output_set);
116/// assert_eq!(new_result, "ac".chars().collect());
117/// assert_eq!(remaining, vec!["ac".chars().collect(), "ac".chars().collect()]);
118/// assert_eq!(idx_removed, "bd".chars().collect());
119/// assert_eq!(idx_contract, "abcd".chars().collect());
120/// ```
121///
122/// Python equivalent:
123///
124/// ```python
125/// >>> from opt_einsum.helpers import find_contraction
126/// >>> pos = (0, 2)
127/// >>> isets = [set('abd'), set('ac'), set('bdc')]
128/// >>> oset = set('ac')
129/// >>> find_contraction(pos, isets, oset)
130/// ({'a', 'c'}, [{'a', 'c'}, {'a', 'c'}], {'b', 'd'}, {'a', 'b', 'c', 'd'})
131/// ```
132pub fn find_contraction(
133    positions: &[usize],
134    input_sets: &[&ArrayIndexType],
135    output_set: &ArrayIndexType,
136) -> (ArrayIndexType, Vec<ArrayIndexType>, ArrayIndexType, ArrayIndexType) {
137    // To developers:
138    // - If performance is a concern, consider using `ByteSet` from crate `byte_set` instead (u8 type
139    //   string only).
140    // - Though Vec<S> is somehow faster, it is not convenient to use, compared to `BTreeSet`.
141    // - `HashSet` is not faster in small sets, and it is not ordered.
142    let positions = positions.as_ref().to_vec();
143    let mut remaining = vec![];
144    let mut idx_contract = BTreeSet::new();
145    let mut idx_remain = output_set.clone();
146    for (i, &set) in input_sets.as_ref().iter().enumerate() {
147        match positions.contains(&i) {
148            true => idx_contract.extend(set.clone()),
149            false => {
150                idx_remain.extend(set.clone());
151                remaining.push(set.clone());
152            },
153        }
154    }
155
156    let new_result = &idx_remain & &idx_contract;
157    let idx_removed = &idx_contract - &new_result;
158    remaining.push(new_result.clone());
159    (new_result, remaining, idx_removed, idx_contract)
160}
161
162/// Computes the number of FLOPS required for a contraction.
163///
164/// # Parameters
165///
166/// - `idx_contraction`: Indices involved in the contraction
167/// - `inner`: Whether this contraction requires an inner product
168/// - `num_terms`: Number of terms in the contraction
169/// - `size_dictionary`: Dictionary mapping indices to their sizes
170///
171/// # Returns
172///
173/// Total number of FLOPS
174///
175/// # Counterpart in Python
176///
177/// `opt_einsum.helpers.flop_count`
178///
179/// # Examples
180///
181/// ```rust
182/// # use std::collections::BTreeMap;
183/// # use num::ToPrimitive;
184/// # use opt_einsum_path::helpers::flop_count;
185/// # use itertools::Itertools;
186/// let mut size_dict = BTreeMap::from([('a', 2), ('b', 3), ('c', 5)]);
187/// let flops = flop_count("abc".chars(), false, 1, &size_dict);
188/// assert_eq!(flops.to_usize().unwrap(), 30);
189/// let flops = flop_count("abc".chars(), true, 2, &size_dict);
190/// assert_eq!(flops.to_usize().unwrap(), 60);
191/// ```
192///
193/// Python equivalent:
194///
195/// ```python
196/// >>> from opt_einsum.helpers import flop_count
197/// >>> flop_count('abc', False, 1, {'a': 2, 'b':3, 'c':5})
198/// 30
199/// >>> flop_count('abc', True, 2, {'a': 2, 'b':3, 'c':5})
200/// 60
201/// ```
202pub fn flop_count<T>(
203    idx_contraction: impl Iterator<Item = T>,
204    inner: bool,
205    num_terms: usize,
206    size_dictionary: &SizeDictType,
207) -> SizeType
208where
209    T: Borrow<char>,
210{
211    let overall_size = compute_size_by_dict(idx_contraction, size_dictionary);
212    // let mut op_factor = std::cmp::max(1, num_terms - 1); // may underflow
213    let mut op_factor = std::cmp::max(2, num_terms) - 1;
214    if inner {
215        op_factor += 1;
216    }
217    overall_size * SizeType::from_usize(op_factor).unwrap()
218}
219
220/// Converts a set-like structure to an `ArrayIndexType`.
221pub trait Setify {
222    type Output;
223    fn setify(self) -> Self::Output;
224}
225
226impl Setify for &str {
227    type Output = ArrayIndexType;
228    fn setify(self) -> Self::Output {
229        self.chars().collect()
230    }
231}
232
233impl<T, const N: usize> Setify for [T; N]
234where
235    T: Clone + Ord,
236{
237    type Output = BTreeSet<T>;
238    fn setify(self) -> Self::Output {
239        self.iter().cloned().collect()
240    }
241}
242
243impl<T> Setify for Vec<T>
244where
245    T: Clone + Ord,
246{
247    type Output = BTreeSet<T>;
248    fn setify(self) -> Self::Output {
249        self.into_iter().collect()
250    }
251}
252
253/// Converts some object to BTreeSet.
254pub fn setify<T>(obj: T) -> T::Output
255where
256    T: Setify,
257{
258    obj.setify()
259}
260
261#[test]
262fn playground() {
263    let positions = [0, 1];
264    let input_sets = [setify("ab"), setify("bc")];
265    let input_sets = input_sets.iter().collect::<Vec<_>>();
266    let output_set = setify("ac");
267    let (new_result, remaining, idx_removed, idx_contract) = find_contraction(&positions, &input_sets, &output_set);
268    assert_eq!(new_result, setify("ac"));
269    assert_eq!(remaining, vec![setify("ac")]);
270    assert_eq!(idx_removed, setify("b"));
271    assert_eq!(idx_contract, setify("abc"));
272}