from copy import deepcopy
from typing import Dict, Tuple
import numpy as np
import tensornetwork as tn
from ttnopt.src.Hamiltonian import Hamiltonian
from ttnopt.src.PhysicsEngine import PhysicsEngine
from ttnopt.src.TTN import TreeTensorNetwork
[docs]class GroundStateSearch(PhysicsEngine):
"""A class for ground state search algorithm based on DMRG.
Args:
psi (TreeTensorNetwork): The quantum state.
hamiltonians (Hamiltonian): Hamiltonian which is list of Observable.
init_bond_dim (int, optional): Initial bond dimension. Defaults to 4.
max_bond_dim (int, optional): Maximum bond dimension. Defaults to 16.
"""
[docs] def __init__(
self,
psi: TreeTensorNetwork,
hamiltonian: Hamiltonian,
init_bond_dim: int = 4,
max_bond_dim: int = 16,
energy_degeneracy_threshold: float = 1e-13,
entanglement_degeneracy_threshold: float = 0.1,
):
"""Initialize a DMRG object.
Args:
psi : The quantum state.
hamiltonians : The Hamiltonian.
init_bond_dim : Initial bond dimension.
max_bond_dim : Maximum bond dimension.
truncation_error : Maximum truncation error.
"""
self.energy: Dict[int, float] = {}
self.entanglement: Dict[int, float] = {}
self.error: Dict[int, float] = {}
self.one_site_expval: Dict[int, Dict[str, float]] = {}
self.two_site_expval: Dict[Tuple[int, int], Dict[str, float]] = {}
super().__init__(
psi,
hamiltonian,
init_bond_dim,
max_bond_dim,
energy_degeneracy_threshold,
entanglement_degeneracy_threshold,
)
[docs] def run(
self,
opt_structure: int = 0,
energy_convergence_threshold: float = 1e-8,
entanglement_convergence_threshold: float = 1e-8,
max_num_sweep: int = 10,
converged_count: int = 2,
eval_onesite_expval: bool = False,
eval_twosite_expval: bool = False,
temperature: float = 0.0,
tau: int = 0,
):
"""Run DMRG algorithm.
Args:
opt_structure (bool, optional): If optimize the tree structure or not. Defaults to False.
energy_convergence_threshold (float, optional): Energy threshold for convergence. Defaults to 1e-8.
entanglement_convergence_threshold (float, optional): Entanglement entropy threshold for automatic optimization. Defaults to 1e-8.
converged_count (int, optional): Converged count. Defaults to 1.
eval_onesite_expval (bool): If evaluate one-site expectation value or not.
eval_twosite_expval (bool): If evaluate two-site expectation value or not.
"""
energy_at_edge: Dict[int, float] = {}
_energy_at_edge: Dict[int, float] = {}
ee_at_edge: Dict[int, float] = {}
_ee_at_edge: Dict[int, float] = {}
_error_at_edge: Dict[int, float] = {}
onesite_expval: Dict[int, Dict[str, float]] = {}
twosite_expval: Dict[Tuple[int, int], Dict[str, float]] = {}
edges, _edges = deepcopy(self.psi.edges), deepcopy(self.psi.edges)
converged_num = 0
if tau == 0:
tau = max_num_sweep // 2 + 1
for sweep_num in range(max_num_sweep):
temp = temperature * (2 ** (-sweep_num / tau))
if converged_num > converged_count:
break
energy_at_edge = deepcopy(_energy_at_edge)
ee_at_edge = deepcopy(_ee_at_edge)
edges = deepcopy(_edges)
self.distance = self.initial_distance()
self.flag = self.initial_flag()
(
_edge_id,
_selected_tensor_id,
_connected_tensor_id,
_not_selected_tensor_id,
) = self.local_two_tensor()
print("Sweep count: " + str(sweep_num + 1))
while True:
edge_id = _edge_id
selected_tensor_id = _selected_tensor_id
connected_tensor_id = _connected_tensor_id
not_selected_tensor_id = _not_selected_tensor_id
# absorb gauge tensor
iso = tn.Node(self.psi.tensors[selected_tensor_id])
gauge = tn.Node(self.psi.gauge_tensor)
iso[2] ^ gauge[0]
iso = tn.contractors.auto(
[iso, gauge], output_edge_order=[iso[0], iso[1], gauge[1]]
)
self.psi.tensors[selected_tensor_id] = iso.get_tensor()
self.set_ttn_properties_at_one_tensor(edge_id, selected_tensor_id)
self._set_edge_spin(not_selected_tensor_id)
self._set_block_hamiltonian(not_selected_tensor_id)
ground_state_order = [selected_tensor_id, connected_tensor_id]
ground_state, energy = self.lanczos(ground_state_order)
psi_edges = (
self.psi.edges[selected_tensor_id][:2]
+ self.psi.edges[connected_tensor_id][:2]
)
u, s, v, probability, error, edge_order = self.decompose_two_tensors(
ground_state,
self.max_bond_dim,
opt_structure=opt_structure,
temperature=temp,
operate_degeneracy=True,
epsilon=entanglement_convergence_threshold,
delta=self.entanglement_degeneracy_threshold,
)
self.psi.tensors[selected_tensor_id] = u
self.psi.tensors[connected_tensor_id] = v
self.psi.gauge_tensor = s
(
self.psi.edges[selected_tensor_id][0],
self.psi.edges[selected_tensor_id][1],
) = (
psi_edges[edge_order[0]],
psi_edges[edge_order[1]],
)
(
self.psi.edges[connected_tensor_id][0],
self.psi.edges[connected_tensor_id][1],
) = (
psi_edges[edge_order[2]],
psi_edges[edge_order[3]],
)
self.distance = self.initial_distance()
_energy_at_edge[self.psi.canonical_center_edge_id] = energy
print(energy)
ee = self.entanglement_entropy(probability)
_ee_at_edge[self.psi.canonical_center_edge_id] = ee
ee_dict = self.entanglement_entropy_at_physical_bond(
ground_state, psi_edges
)
for key in ee_dict.keys():
_ee_at_edge[key] = ee_dict[key]
_error_at_edge[self.psi.canonical_center_edge_id] = error
if self.candidate_edge_ids() == []:
break
(
_edge_id,
_selected_tensor_id,
_connected_tensor_id,
_not_selected_tensor_id,
) = self.local_two_tensor()
self.set_flag(_not_selected_tensor_id)
# eval expval
if self.flag[_not_selected_tensor_id]:
if eval_onesite_expval:
onesite_expval_dict = self.expval_onesite(
_not_selected_tensor_id,
ground_state,
ground_state_order,
)
for key in onesite_expval_dict.keys():
onesite_expval[key] = onesite_expval_dict[key]
if eval_twosite_expval:
twosite_expval_dict = self.expval_twosite(
_not_selected_tensor_id,
ground_state,
ground_state_order,
)
for key in twosite_expval_dict.keys():
twosite_expval[key] = twosite_expval_dict[key]
if eval_onesite_expval:
for i in self.psi.central_tensor_ids():
onesite_expval_dict = self.expval_onesite(
i, ground_state, ground_state_order
)
for key in onesite_expval_dict.keys():
onesite_expval[key] = onesite_expval_dict[key]
if eval_twosite_expval:
for i in self.psi.central_tensor_ids():
twosite_expval_dict = self.expval_twosite(
i, ground_state, ground_state_order
)
for key in twosite_expval_dict.keys():
twosite_expval[key] = twosite_expval_dict[key]
twosite_expval_dict = self.expval_twosite_origin(
twosite_expval.keys(), ground_state, ground_state_order
)
for key in twosite_expval_dict.keys():
twosite_expval[key] = twosite_expval_dict[key]
_edges = deepcopy(self.psi.edges)
sweep_num += 1
if sweep_num > 2:
diff_energy = [
np.abs(1 - _energy_at_edge[key] / energy_at_edge[key])
for key in energy_at_edge.keys()
]
diff_ee = [
np.abs(ee_at_edge[key] - _ee_at_edge[key])
for key in ee_at_edge.keys()
]
if all(
[
set(edge[:2]) == set(_edge[:2]) and edge[2] == _edge[2]
for edge, _edge in zip(edges, _edges)
]
):
if all(
[
energy < energy_convergence_threshold
for energy in diff_energy
]
) and all(
[ee < entanglement_convergence_threshold for ee in diff_ee]
):
converged_num += 1
print("Converged")
self.energy = _energy_at_edge
self.entanglement = _ee_at_edge
self.error = _error_at_edge
self.one_site_expval = onesite_expval
self.two_site_expval = twosite_expval
return 0