def test_edit_array_contraction(): cg = _array_contraction(_array_tensor_product(A, B, C, D), (1, 2, 5)) ecg = _EditArrayContraction(cg) assert ecg.to_array_contraction() == cg ecg.args_with_ind[1], ecg.args_with_ind[2] = ecg.args_with_ind[2], ecg.args_with_ind[1] assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, C, B, D), (1, 3, 4)) ci = ecg.get_new_contraction_index() new_arg = _ArgE(X) new_arg.indices = [ci, ci] ecg.args_with_ind.insert(2, new_arg) assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, C, X, B, D), (1, 3, 6), (4, 5)) assert ecg.get_contraction_indices() == [[1, 3, 6], [4, 5]] assert [[tuple(j) for j in i] for i in ecg.get_contraction_indices_to_ind_rel_pos()] == [[(0, 1), (1, 1), (3, 0)], [(2, 0), (2, 1)]] assert [list(i) for i in ecg.get_mapping_for_index(0)] == [[0, 1], [1, 1], [3, 0]] assert [list(i) for i in ecg.get_mapping_for_index(1)] == [[2, 0], [2, 1]] raises(ValueError, lambda: ecg.get_mapping_for_index(2)) ecg.args_with_ind.pop(1) assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, X, B, D), (1, 4), (2, 3)) ecg.args_with_ind[0].indices[1] = ecg.args_with_ind[1].indices[0] ecg.args_with_ind[1].indices[1] = ecg.args_with_ind[2].indices[0] assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, X, B, D), (1, 2), (3, 4)) ecg.insert_after(ecg.args_with_ind[1], _ArgE(C)) assert ecg.to_array_contraction() == _array_contraction(_array_tensor_product(A, X, C, B, D), (1, 2), (3, 6))
def _insert_candidate_into_editor(editor: _EditArrayContraction, arg_with_ind: _ArgE, candidate: _ArgE, transpose1: bool, transpose2: bool): other = candidate.element other_index: int if transpose2: other = Transpose(other) other_index = candidate.indices[0] else: other_index = candidate.indices[1] new_element = (Transpose(arg_with_ind.element) if transpose1 else arg_with_ind.element) * other editor.args_with_ind.remove(candidate) new_arge = _ArgE(new_element) return new_arge, other_index
def identify_hadamard_products(expr: Union[ArrayContraction, ArrayDiagonal]): mapping = _get_mapping_from_subranks(expr.subranks) editor: _EditArrayContraction if isinstance(expr, ArrayContraction): editor = _EditArrayContraction(expr) elif isinstance(expr, ArrayDiagonal): if isinstance(expr.expr, ArrayContraction): editor = _EditArrayContraction(expr.expr) diagonalized = ArrayContraction._push_indices_down( expr.expr.contraction_indices, expr.diagonal_indices) elif isinstance(expr.expr, ArrayTensorProduct): editor = _EditArrayContraction(None) editor.args_with_ind = [ _ArgE(arg) for i, arg in enumerate(expr.expr.args) ] diagonalized = expr.diagonal_indices else: raise NotImplementedError("not implemented") # Trick: add diagonalized indices as negative indices into the editor object: for i, e in enumerate(diagonalized): for j in e: arg_pos, rel_pos = mapping[j] editor.args_with_ind[arg_pos].indices[rel_pos] = -1 - i map_contr_to_args: Dict[FrozenSet, List[_ArgE]] = defaultdict(list) map_ind_to_inds = defaultdict(int) for arg_with_ind in editor.args_with_ind: for ind in arg_with_ind.indices: map_ind_to_inds[ind] += 1 if None in arg_with_ind.indices: continue map_contr_to_args[frozenset(arg_with_ind.indices)].append(arg_with_ind) k: FrozenSet[int] v: List[_ArgE] for k, v in map_contr_to_args.items(): if len(k) != 2: # Hadamard product only defined for matrices: continue if len(v) == 1: # Hadamard product with a single argument makes no sense: continue for ind in k: if map_ind_to_inds[ind] <= 2: # There is no other contraction, skip: continue # Check if expression is a trace: if all([map_ind_to_inds[j] == len(v) and j >= 0 for j in k]): # This is a trace continue # This is a Hadamard product: def check_transpose(x): x = [i if i >= 0 else -1 - i for i in x] return x == sorted(x) hp = hadamard_product(*[ i.element if check_transpose(i.indices) else Transpose(i.element) for i in v ]) hp_indices = v[0].indices if not check_transpose(v[0].indices): hp_indices = list(reversed(hp_indices)) editor.insert_after(v[0], _ArgE(hp, hp_indices)) for i in v: editor.args_with_ind.remove(i) # Count the ranks of the arguments: counter = 0 # Create a collector for the new diagonal indices: diag_indices = defaultdict(list) count_index_freq = Counter() for arg_with_ind in editor.args_with_ind: count_index_freq.update(Counter(arg_with_ind.indices)) free_index_count = count_index_freq[None] # Construct the inverse permutation: inv_perm1 = [] inv_perm2 = [] # Keep track of which diagonal indices have already been processed: done = set([]) # Counter for the diagonal indices: counter4 = 0 for arg_with_ind in editor.args_with_ind: # If some diagonalization axes have been removed, they should be # permuted in order to keep the permutation. # Add permutation here counter2 = 0 # counter for the indices for i in arg_with_ind.indices: if i is None: inv_perm1.append(counter4) counter2 += 1 counter4 += 1 continue if i >= 0: continue # Reconstruct the diagonal indices: diag_indices[-1 - i].append(counter + counter2) if count_index_freq[i] == 1 and i not in done: inv_perm1.append(free_index_count - 1 - i) done.add(i) elif i not in done: inv_perm2.append(free_index_count - 1 - i) done.add(i) counter2 += 1 # Remove negative indices to restore a proper editor object: arg_with_ind.indices = [ i if i is not None and i >= 0 else None for i in arg_with_ind.indices ] counter += len([i for i in arg_with_ind.indices if i is None or i < 0]) inverse_permutation = inv_perm1 + inv_perm2 permutation = _af_invert(inverse_permutation) if isinstance(expr, ArrayContraction): return editor.to_array_contraction() else: # Get the diagonal indices after the detection of HadamardProduct in the expression: diag_indices_filtered = [ tuple(v) for v in diag_indices.values() if len(v) > 1 ] expr1 = editor.to_array_contraction() expr2 = ArrayDiagonal(expr1, *diag_indices_filtered) expr3 = PermuteDims(expr2, permutation) return expr3
def identify_hadamard_products(expr: tUnion[ArrayContraction, ArrayDiagonal]): editor: _EditArrayContraction = _EditArrayContraction(expr) map_contr_to_args: tDict[FrozenSet, List[_ArgE]] = defaultdict(list) map_ind_to_inds: tDict[Optional[int], int] = defaultdict(int) for arg_with_ind in editor.args_with_ind: for ind in arg_with_ind.indices: map_ind_to_inds[ind] += 1 if None in arg_with_ind.indices: continue map_contr_to_args[frozenset(arg_with_ind.indices)].append(arg_with_ind) k: FrozenSet[int] v: List[_ArgE] for k, v in map_contr_to_args.items(): make_trace: bool = False if len(k) == 1 and next(iter(k)) >= 0 and sum( [next(iter(k)) in i for i in map_contr_to_args]) == 1: # This is a trace: the arguments are fully contracted with only one # index, and the index isn't used anywhere else: make_trace = True first_element = S.One elif len(k) != 2: # Hadamard product only defined for matrices: continue if len(v) == 1: # Hadamard product with a single argument makes no sense: continue for ind in k: if map_ind_to_inds[ind] <= 2: # There is no other contraction, skip: continue def check_transpose(x): x = [i if i >= 0 else -1 - i for i in x] return x == sorted(x) # Check if expression is a trace: if all([map_ind_to_inds[j] == len(v) and j >= 0 for j in k]) and all([j >= 0 for j in k]): # This is a trace make_trace = True first_element = v[0].element if not check_transpose(v[0].indices): first_element = first_element.T hadamard_factors = v[1:] else: hadamard_factors = v # This is a Hadamard product: hp = hadamard_product(*[ i.element if check_transpose(i.indices) else Transpose(i.element) for i in hadamard_factors ]) hp_indices = v[0].indices if not check_transpose(hadamard_factors[0].indices): hp_indices = list(reversed(hp_indices)) if make_trace: hp = Trace(first_element * hp.T)._normalize() hp_indices = [] editor.insert_after(v[0], _ArgE(hp, hp_indices)) for i in v: editor.args_with_ind.remove(i) return editor.to_array_contraction()