(
        self,
        inputs_: List[ArrayIndexType],
        output_: ArrayIndexType,
        size_dict_: Dict[str, int],
        memory_limit: Optional[int] = None,
    )
return _tree_to_sequence(tree)
**Parameters:**

        - **inputs** - *(list)* List of sets that represent the lhs side of the einsum subscript
        - **output** - *(set)* Set that represents the rhs side of the overall einsum subscript
        - **size_dict** - *(dictionary)* Dictionary of index sizes
        - **memory_limit** - *(int)* The maximum number of elements in a temporary array

        **Returns:**

        - **path** - *(list)* The contraction order (a list of tuples of ints).

        **Examples:**

        ```python
        n_in = 3  # exponential scaling
        n_out = 2 # linear scaling
        s = dict()
        i_all = []
        for _ in range(n_out):
            i = [set() for _ in range(n_in)]
            for j in range(n_in):
                for k in range(j+1, n_in):
                    c = oe.get_symbol(len(s))
                    i[j].add(c)
                    i[k].add(c)
                    s[c] = 2
            i_all.extend(i)
        o = DynamicProgramming()
        o(i_all, set(), s)
        #> [(1, 2), (0, 4), (1, 2), (0, 2), (0, 1)]
        ```
**Parameters:**
** Parameters : **
def __call__(
        self,
        inputs_: List[ArrayIndexType],
        output_: ArrayIndexType,
        size_dict_: Dict[str, int],
        memory_limit: Optional[int] = None,
    ) -> PathType:
        """
        **Parameters:**

        - **inputs** - *(list)* List of sets that represent the lhs side of the einsum subscript
        - **output** - *(set)* Set that represents the rhs side of the overall einsum subscript
        - **size_dict** - *(dictionary)* Dictionary of index sizes
        - **memory_limit** - *(int)* The maximum number of elements in a temporary array

        **Returns:**

        - **path** - *(list)* The contraction order (a list of tuples of ints).

        **Examples:**

        ```python
        n_in = 3  # exponential scaling
        n_out = 2 # linear scaling
        s = dict()
        i_all = []
        for _ in range(n_out):
            i = [set() for _ in range(n_in)]
            for j in range(n_in):
                for k in range(j+1, n_in):
                    c = oe.get_symbol(len(s))
                    i[j].add(c)
                    i[k].add(c)
                    s[c] = 2
            i_all.extend(i)
        o = DynamicProgramming()
        o(i_all, set(), s)
        #> [(1, 2), (0, 4), (1, 2), (0, 2), (0, 1)]
        ```
        """
        ind_counts = Counter(itertools.chain(*inputs_, output_))
        all_inds = tuple(ind_counts)

        # convert all indices to integers (makes set operations ~10 % faster)
        symbol2int = {c: j for j, c in enumerate(all_inds)}
        inputs = [frozenset(symbol2int[c] for c in i) for i in inputs_]
        output = frozenset(symbol2int[c] for c in output_)
        size_dict_canonical = {symbol2int[c]: v for c, v in size_dict_.items() if c in symbol2int}
        size_dict = [size_dict_canonical[j] for j in range(len(size_dict_canonical))]
        naive_cost = len(inputs) * functools.reduce(operator.mul, size_dict)

        inputs, inputs_done, inputs_contractions = _dp_parse_out_single_term_ops(inputs, all_inds, ind_counts)

        if not inputs:
            # nothing left to do after single axis reductions!
            return _tree_to_sequence(simple_tree_tuple(inputs_done))

        # a list of all necessary contraction expressions for each of the
        # disconnected subgraphs and their size
        subgraph_contractions = inputs_done
        subgraph_contractions_size = [1] * len(inputs_done)

        if self.search_outer:
            # optimize everything together if we are considering outer products
            subgraphs = [frozenset(range(len(inputs)))]
        else:
            subgraphs = _find_disconnected_subgraphs(inputs, output)

        # the bitmap set of all tensors is computed as it is needed to
        # compute set differences: s1 - s2 transforms into
        # s1 & (all_tensors ^ s2)
        all_tensors = (1 << len(inputs)) - 1

        for g in subgraphs:

            # dynamic programming approach to compute x[n] for subgraph g;
            # x[n][set of n tensors] = (indices, cost, contraction)
            # the set of n tensors is represented by a bitmap: if bit j is 1,
            # tensor j is in the set, e.g. 0b100101 = {0,2,5}; set unions
            # (intersections) can then be computed by bitwise or (and);
            x: List[Any] = [None] * 2 + [dict() for j in range(len(g) - 1)]  # type: ignore
            x[1] = OrderedDict((1 << j, (inputs[j], 0, inputs_contractions[j])) for j in g)

            # convert set of tensors g to a bitmap set:
            bitmap_g = functools.reduce(lambda x, y: x | y, (1 << j for j in g))

            # try to find contraction with cost <= cost_cap and increase
            # cost_cap successively if no such contraction is found;
            # this is a major performance improvement; start with product of
            # output index dimensions as initial cost_cap
            subgraph_inds = frozenset.union(*_bitmap_select(bitmap_g, inputs))
            if self.cost_cap is True:
                cost_cap = helpers.compute_size_by_dict(subgraph_inds & output, size_dict)
            elif self.cost_cap is False:
                cost_cap = float("inf")  # type: ignore
            else:
                cost_cap = self.cost_cap
            # set the factor to increase the cost by each iteration (ensure > 1)
            if len(subgraph_inds) == 0:
                cost_increment = 2
            else:
                cost_increment = max(min(map(size_dict.__getitem__, subgraph_inds)), 2)

            while len(x[-1]) == 0:
                for n in range(2, len(x[1]) + 1):
                    xn = x[n]

                    # try to combine solutions from x[m] and x[n-m]
                    for m in range(1, n // 2 + 1):
                        for s1, (i1, cost1, contract1) in x[m].items():
                            for s2, (i2, cost2, contract2) in x[n - m].items():

                                # can only merge if s1 and s2 are disjoint
                                # and avoid e.g. s1={0}, s2={1} and s1={1}, s2={0}
                                if (not s1 & s2) and (m != n - m or s1 < s2):
                                    i1_cut_i2_wo_output = (i1 & i2) - output

                                    # maybe ignore outer products:
                                    if self._check_outer(i1_cut_i2_wo_output):

                                        i1_union_i2 = i1 | i2
                                        self._check_contraction(
                                            cost1,
                                            cost2,
                                            i1_union_i2,
                                            size_dict,
                                            cost_cap,
                                            s1,
                                            s2,
                                            xn,
                                            bitmap_g,
                                            all_tensors,
                                            inputs,
                                            i1_cut_i2_wo_output,
                                            memory_limit,
                                            contract1,
                                            contract2,
                                        )

                if (cost_cap > naive_cost) and (len(x[-1]) == 0):
                    raise RuntimeError("No contraction found for given `memory_limit`.")

                # increase cost cap for next iteration:
                cost_cap = cost_increment * cost_cap

            i, cost, contraction = list(x[-1].values())[0]
            subgraph_contractions.append(contraction)
            subgraph_contractions_size.append(helpers.compute_size_by_dict(i, size_dict))

        # sort the subgraph contractions by the size of the subgraphs in
        # ascending order (will give the cheapest contractions); note that
        # outer products should be performed pairwise (to use BLAS functions)
        subgraph_contractions = [
            subgraph_contractions[j]
            for j in sorted(
                range(len(subgraph_contractions_size)),
                key=subgraph_contractions_size.__getitem__,
            )
        ]

        # build the final contraction tree
        tree = simple_tree_tuple(subgraph_contractions)
        return _tree_to_sequence(tree)
def __call__ ( self , inputs_ : List [ ArrayIndexType ] , output_ : ArrayIndexType , size_dict_ : Dict [ str , int ] , memory_limit : Optional [ int ] = None , ) -> PathType : ind_counts = Counter ( itertools . chain ( * inputs_ , output_ ) ) all_inds = tuple ( ind_counts ) # convert all indices to integers (makes set operations ~10 % faster) symbol2int = { c : j for j , c in enumerate ( all_inds ) } inputs = [ frozenset ( symbol2int [ c ] for c in i ) for i in inputs_ ] output = frozenset ( symbol2int [ c ] for c in output_ ) size_dict_canonical = { symbol2int [ c ] : v for c , v in size_dict_ . items ( ) if c in symbol2int } size_dict = [ size_dict_canonical [ j ] for j in range ( len ( size_dict_canonical ) ) ] naive_cost = len ( inputs ) * functools . reduce ( operator . mul , size_dict ) inputs , inputs_done , inputs_contractions = _dp_parse_out_single_term_ops ( inputs , all_inds , ind_counts ) if not inputs : # nothing left to do after single axis reductions! return _tree_to_sequence ( simple_tree_tuple ( inputs_done ) ) # a list of all necessary contraction expressions for each of the # disconnected subgraphs and their size subgraph_contractions = inputs_done subgraph_contractions_size = [ 1 ] * len ( inputs_done ) if self . search_outer : # optimize everything together if we are considering outer products subgraphs = [ frozenset ( range ( len ( inputs ) ) ) ] else : subgraphs = _find_disconnected_subgraphs ( inputs , output ) # the bitmap set of all tensors is computed as it is needed to # compute set differences: s1 - s2 transforms into # s1 & (all_tensors ^ s2) all_tensors = ( 1 << len ( inputs ) ) - 1 for g in subgraphs : # dynamic programming approach to compute x[n] for subgraph g; # x[n][set of n tensors] = (indices, cost, contraction) # the set of n tensors is represented by a bitmap: if bit j is 1, # tensor j is in the set, e.g. 0b100101 = {0,2,5}; set unions # (intersections) can then be computed by bitwise or (and); x : List [ Any ] = [ None ] * 2 + [ dict ( ) for j in range ( len ( g ) - 1 ) ] # type: ignore x [ 1 ] = OrderedDict ( ( 1 << j , ( inputs [ j ] , 0 , inputs_contractions [ j ] ) ) for j in g ) # convert set of tensors g to a bitmap set: bitmap_g = functools . reduce ( lambda x , y : x | y , ( 1 << j for j in g ) ) # try to find contraction with cost <= cost_cap and increase # cost_cap successively if no such contraction is found; # this is a major performance improvement; start with product of # output index dimensions as initial cost_cap subgraph_inds = frozenset . union ( * _bitmap_select ( bitmap_g , inputs ) ) if self . cost_cap is True : cost_cap = helpers . compute_size_by_dict ( subgraph_inds & output , size_dict ) elif self . cost_cap is False : cost_cap = float ( "inf" ) # type: ignore else : cost_cap = self . cost_cap # set the factor to increase the cost by each iteration (ensure > 1) if len ( subgraph_inds ) == 0 : cost_increment = 2 else : cost_increment = max ( min ( map ( size_dict . __getitem__ , subgraph_inds ) ) , 2 ) while len ( x [ - 1 ] ) == 0 : for n in range ( 2 , len ( x [ 1 ] ) + 1 ) : xn = x [ n ] # try to combine solutions from x[m] and x[n-m] for m in range ( 1 , n // 2 + 1 ) : for s1 , ( i1 , cost1 , contract1 ) in x [ m ] . items ( ) : for s2 , ( i2 , cost2 , contract2 ) in x [ n - m ] . items ( ) : # can only merge if s1 and s2 are disjoint # and avoid e.g. s1={0}, s2={1} and s1={1}, s2={0} if ( not s1 & s2 ) and ( m != n - m or s1 < s2 ) : i1_cut_i2_wo_output = ( i1 & i2 ) - output # maybe ignore outer products: if self . _check_outer ( i1_cut_i2_wo_output ) : i1_union_i2 = i1 | i2 self . _check_contraction ( cost1 , cost2 , i1_union_i2 , size_dict , cost_cap , s1 , s2 , xn , bitmap_g , all_tensors , inputs , i1_cut_i2_wo_output , memory_limit , contract1 , contract2 , ) if ( cost_cap > naive_cost ) and ( len ( x [ - 1 ] ) == 0 ) : raise RuntimeError ( "No contraction found for given `memory_limit`." ) # increase cost cap for next iteration: cost_cap = cost_increment * cost_cap i , cost , contraction = list ( x [ - 1 ] . values ( ) ) [ 0 ] subgraph_contractions . append ( contraction ) subgraph_contractions_size . append ( helpers . compute_size_by_dict ( i , size_dict ) ) # sort the subgraph contractions by the size of the subgraphs in # ascending order (will give the cheapest contractions); note that # outer products should be performed pairwise (to use BLAS functions) subgraph_contractions = [ subgraph_contractions [ j ] for j in sorted ( range ( len ( subgraph_contractions_size ) ) , key = subgraph_contractions_size . __getitem__ , ) ] # build the final contraction tree tree = simple_tree_tuple ( subgraph_contractions ) return _tree_to_sequence ( tree )