Source code for ttnopt.src.TwoSiteUpdater

import numpy as np
import tensornetwork as tn
import itertools
from collections import deque, defaultdict


[docs]class TwoSiteUpdaterMixin:
[docs] def initial_flag(self): edge_ids = set(itertools.chain.from_iterable(self.psi.edges)) flag = {ind: 0 if ind not in self.psi.physical_edges else 1 for ind in edge_ids} return flag
[docs] def entanglement_entropy(self, probability): el = probability**2 / np.sum(probability**2) el = el[el > 0.0] ee = -np.sum(el * np.log(el)) return np.real(ee)
[docs] def initial_distance(self): adjacency_list = defaultdict(list) for node in self.psi.edges: child1, child2, parent = node adjacency_list[child1].append(parent) adjacency_list[parent].append(child1) adjacency_list[child2].append(parent) adjacency_list[parent].append(child2) adjacency_list[child1].append(child2) adjacency_list[child2].append(child1) for key, val in adjacency_list.items(): adjacency_list[key] = list(set(val)) distances = {self.psi.top_edge_id: 0} queue = deque([self.psi.top_edge_id]) while queue: current_edge = queue.popleft() current_distance = distances[current_edge] for neighbor in adjacency_list[current_edge]: if neighbor not in distances.keys(): distances[neighbor] = current_distance + 1 queue.append(neighbor) return distances
[docs] def candidate_edge_ids(self): child_tensor_ids = [ i for i, edge in enumerate(self.psi.edges) if edge[2] == self.psi.canonical_center_edge_id ] candidate_edge_ids = ( self.psi.edges[child_tensor_ids[0]][:2] + self.psi.edges[child_tensor_ids[1]][:2] ) candidate_edge_ids = [e for e in candidate_edge_ids if self.flag[e] == 0] return candidate_edge_ids
[docs] def local_two_tensor(self): candidate_edge_ids = self.candidate_edge_ids() max_v = np.max([self.distance[e] for e in candidate_edge_ids]) candidate_edge_ids = [ e for e in candidate_edge_ids if self.distance[e] == max_v ] edge_id = candidate_edge_ids[0] # select one for i, edge in enumerate(self.psi.edges): if edge_id == edge[2]: connected_tensor_id = i if edge_id in edge[:2]: selected_tensor_id = i child_tensor_ids = [ i for i, edge in enumerate(self.psi.edges) if edge[2] == self.psi.canonical_center_edge_id ] for child_tensor_id in child_tensor_ids: if child_tensor_id != selected_tensor_id: not_selected_tensor_id = child_tensor_id return ( edge_id, selected_tensor_id, connected_tensor_id, not_selected_tensor_id, )
[docs] def set_flag(self, not_selected_tensor_id): if ( self.flag[self.psi.edges[not_selected_tensor_id][0]] == 1 and self.flag[self.psi.edges[not_selected_tensor_id][1]] == 1 ): if self.psi.canonical_center_edge_id != self.psi.top_edge_id: self.flag[self.psi.canonical_center_edge_id] = 1 return
[docs] def contract_central_tensors(self): central_tensor_ids = self.psi.central_tensor_ids() psi1 = tn.Node(self.psi.tensors[central_tensor_ids[0]], backend=self.backend) psi2 = tn.Node(self.psi.tensors[central_tensor_ids[1]], backend=self.backend) gauge = tn.Node(self.psi.gauge_tensor, backend=self.backend) psi1[2] ^ gauge[0] gauge[1] ^ psi2[2] psi = tn.contractors.auto( [psi1, gauge, psi2], output_edge_order=[psi1[0], psi1[1], psi2[0], psi2[1]], ) return psi
[docs]class TwoSiteUpdater(TwoSiteUpdaterMixin): def __init__(self, psi): self.psi = psi self.backend = "numpy" self.flag = self.initial_flag() self.distance = self.initial_distance()
[docs] def decompose_two_tensors( self, psi, max_bond_dim, opt_structure=0, operate_degeneracy=False, epsilon=1e-8, delta=0.1, max_truncation_error=0.0, temperature=0.0, e=1e-13, ): edge_order = [0, 1, 2, 3] if opt_structure == 0: a = psi[0] b = psi[1] c = psi[2] d = psi[3] (u, s, v, _) = tn.split_node_full_svd(psi, [a, b], [c, d]) p = np.diagonal(s.get_tensor()) ind = np.min([max_bond_dim, len(p)]) if operate_degeneracy: if ind < len(p): while ind > 1: if (np.abs(p[ind] - p[ind - 1]) / (p[ind - 1] + e)) < delta: ind -= 1 else: break else: candidates = [[0, 1, 2, 3], [2, 1, 0, 3], [0, 2, 1, 3]] candidate_index = 0 ps = [] inds = [] for edges in candidates: psi_ = psi.copy() a = psi_[edges[0]] b = psi_[edges[1]] c = psi_[edges[2]] d = psi_[edges[3]] (u_, s_, v_, _) = tn.split_node_full_svd( psi_, [a, b], [c, d], ) p_ = np.diagonal(s_.get_tensor()) ps.append(p_) ind = np.min([max_bond_dim, len(p_)]) if max_truncation_error > 0.0: p_max = p_[0] for i in range(1, ind): if p_[i] / p_max < max_truncation_error: ind = i break if operate_degeneracy: if ind < len(p_): while ind > 1: if ( np.abs(p_[ind] - p_[ind - 1]) / (p_[ind - 1] + e) ) < delta: ind -= 1 else: break inds.append(ind) ees = [self.entanglement_entropy(probability=p) for p in ps] p_truncates = [p[:ind] for p, ind in zip(ps, inds)] errors = [1.0 - np.real(np.sum(p_t**2)) for p_t in p_truncates] if opt_structure == 1: if temperature == 0.0: candidate_index = np.argmin(ees) else: weights = np.array(ees) / temperature weights = np.exp(-weights) weights = np.array(weights) / np.sum(weights) candidate_index = np.random.choice(len(ees), p=weights) elif opt_structure == 2: candidate_index = np.argmin(errors) if ( np.isclose(errors[candidate_index], errors[0], atol=1e-14) or max_truncation_error > 0.0 ): candidate_index = np.argmin(ees) if np.isclose(ees[candidate_index], ees[0], atol=epsilon): candidate_index = 0 edge_order = candidates[candidate_index] p = ps[candidate_index] ind = inds[candidate_index] # Degeneracy a = psi[edge_order[0]] b = psi[edge_order[1]] c = psi[edge_order[2]] d = psi[edge_order[3]] (u, s, v, terr) = tn.split_node_full_svd( psi, [a, b], [c, d], max_singular_values=ind ) v = v.reorder_edges([v[1], v[2], v[0]]) u = u.get_tensor() v = v.get_tensor() s = s.get_tensor() p_truncate = np.diagonal(s) err = 1.0 - np.real(np.sum(p_truncate**2)) s = s / np.linalg.norm(s) return u, s, v, p, err, edge_order
[docs] def entanglement_entropy_at_physical_bond(self, psi, psi_edges): ee_at_physical_bond = {} for i, edge in enumerate(psi_edges): if edge in self.psi.physical_edges: ee_at_physical_bond[edge] = 0.0 for edge_id in ee_at_physical_bond.keys(): psi_ = psi.copy() matching_index = list({i for i, v in enumerate(psi_edges) if v == edge_id})[ 0 ] non_matching_indices = list( {i for i in range(len(psi_edges)) if i != matching_index} ) psi_dag = psi_.copy(conjugate=True) for i in non_matching_indices: psi_[i] ^ psi_dag[i] rho = tn.contractors.auto( [psi_, psi_dag], output_edge_order=[psi_[matching_index], psi_dag[matching_index]], ) (u, s, v, terr) = tn.split_node_full_svd(rho, [rho[0]], [rho[1]]) p_ = np.diagonal(s.get_tensor()) p_ = np.sqrt(p_) ee_at_physical_bond[edge_id] = self.entanglement_entropy(probability=p_) return ee_at_physical_bond
[docs] def set_ttn_properties_at_one_tensor(self, edge_id, selected_tensor_id): # update_ttn_properties self.psi.canonical_center_edge_id = edge_id out_selected_inds = [] for i, e in enumerate(self.psi.edges[selected_tensor_id]): if e == edge_id: canonical_center_ind = i else: out_selected_inds.append(i) self.psi.tensors[selected_tensor_id] = self.psi.tensors[ selected_tensor_id ].transpose( out_selected_inds + [canonical_center_ind], ) self.psi.edges[selected_tensor_id] = [ self.psi.edges[selected_tensor_id][i] for i in out_selected_inds ] + [edge_id] for i, e in enumerate(self.psi.edges[selected_tensor_id]): self.psi.edge_dims[e] = self.psi.tensors[selected_tensor_id].shape[i] return