Source code for entanglish.EntangCase

from entanglish.DenMat import *
from entanglish.DenMatPertTheory import *
from entanglish.MaxEntangState import *
import numpy as np
import itertools as it


[docs]class EntangCase: """ This is an abstract class meant to be overridden. It is the parent class of classes PureStEnt and SquashedEnt, which calculate quantum entanglement for pure and mixed states using various methods. This class contains methods useful to all of its children. For example, it contains methods that construct an entanglement profile data structure and print it. Attributes ---------- approx : str approx used to calculate the (natural) log of a density matrix that appears in the definition of the von Neumann entropy. This parameter can be either 'eigen', 'pade', 'pert'. num_bstrap_steps : int number bootstrap steps used in perturbation theory. Only used if approx = 'pert' num_row_axes : int number of row axes, same as number of qudits, equal to len( row_shape) of a DenMat verbose : bool """
[docs] def __init__(self, num_row_axes, approx='eigen', num_bstrap_steps=1, verbose=False): """ Constructor Parameters ---------- num_row_axes : int approx : str num_bstrap_steps : int verbose : bool Returns ------- """ self.num_row_axes = num_row_axes self.approx = approx assert approx in ['eigen', 'pade', 'pert'] self.num_bstrap_steps = num_bstrap_steps assert num_bstrap_steps > 0 self.verbose = verbose
[docs] def mirror(self, x): """ Given an element of the list range(self.num_row_axes), this method returns the mirror image of x with respect to the center of the list. Parameters ---------- x : int Returns ------- int """ assert 0 <= x < self.num_row_axes return self.num_row_axes - 1 - x
[docs] def mirror_many(self, xx): """ This applies the method self.mirror() to each element of a list, set, tuple Parameters ---------- xx : tuple[int]|set[int]|list[int] Returns ------- tuple[int]|set[int]|list[int] """ if isinstance(xx, list): return list([self.mirror(k) for k in xx]) elif isinstance(xx, set): return set([self.mirror(k) for k in xx]) elif isinstance(xx, tuple): return tuple([self.mirror(k) for k in xx]) else: assert False
[docs] def get_entang(self, axes_subset): """ This is an abstract method that should be overridden by the children of the class. Parameters ---------- axes_subset : set[int] the entanglement is calculated for parts axes_subset and its complement Returns ------- float """ assert False
[docs] @staticmethod def check_max_entang_st(st): """ This method checks that the object st of class MaxEntangState does indeed carry maximal entanglement. The entanglement is calculated 3 different ways (von Neumann entropy of 2 partial traces of density matrix, and from known value) and the 3 values are checked to agree. Parameters ---------- st : MaxEntangState Returns ------- None """ dm = DenMat(st.num_rows, st.row_shape) st_vec = st.get_st_vec() dm.set_arr_from_st_vec(st_vec) ent1 = dm.get_partial_tr(set(st.y_axes)).get_entropy() ent2 = dm.get_partial_tr(set(st.x_axes)).get_entropy() ent3 = np.log(st.num_vals_min) assert abs(ent1 - ent2) < 1e-6 and abs(ent1 - ent3) < 1e-6, \ str(ent1) + ', ' + str(ent2) + ', ' + str(ent3)
[docs] @staticmethod def get_max_entag(row_shape, x_axes, y_axes): """ This method returns the maximum possible entanglement with parts x_axes, y_axes. x_axes, y_axes give a bi-partition of range( len( row_shape)). If num_vals_min = min(num_vals_x, num_vals_y), then max-entang is the log of num_vals_min. Parameters ---------- row_shape : tuple[int] x_axes : list{int] y_axes : list{int] Returns ------- float """ assert len(row_shape) == len(set(x_axes).union(set(y_axes))) num_axes_x = len(x_axes) num_axes_y = len(y_axes) row_shape_x = [row_shape[k] for k in x_axes] row_shape_y = [row_shape[k] for k in y_axes] num_vals_x = np.product(np.array(row_shape_x)) num_vals_y = np.product(np.array(row_shape_y)) num_vals_min = min(num_vals_x, num_vals_y) return np.log(num_vals_min)
[docs] def get_entang_profile(self): """ This method constructs a dictionary that we call an entanglement profile. Given a state with num_row_axes qudits, this method calculates a (bipartite) entanglement for each possible bi-partition of range( num_row_axes). By a bi-partition we mean two nonempty disjoint subsets whose union is range(num_row_axes). An entanglement profile is a dictionary mapping bi-partition half-size to a dictionary that maps each bi-partition of that half-size to its entanglement. Returns ------- dict[int, dict[tuple[int], float]] """ # 5 axes, max_comb_len=2= 5//2 # 4 axes, max_comb_len=2= 4//2 max_comb_len = self.num_row_axes//2 all_axes = list(range(0, self.num_row_axes)) entang_profile = {} for comb_len in range(1, max_comb_len+1): entang_profile[comb_len] = {} for traced_axes in it.combinations(all_axes, comb_len): entang_profile[comb_len][tuple(sorted(traced_axes))] = \ self.get_entang(set(traced_axes)) return entang_profile
[docs] def print_entang_profiles(self, profile_list, row_shape=None): """ This method takes as input a list of entanglement profiles. Each element of that list can be generated with the method get_entang_profile(). The method prints, side by side, all the entanglement profiles in the input profile list. If a row_shape is provided, the last column of the profile is the maximum entanglement. Parameters ---------- profile_list : list[dict[int, dict[tuple[int], float]]] row_shape : tuple[int] Returns ------- None """ # 5 axes, max_comb_len=2= 5//2 # 4 axes, max_comb_len=2= 4//2 max_comb_len = self.num_row_axes//2 assert all(len(profile_list[k]) == max_comb_len for k in range(len(profile_list))) all_lines = '' for comb_len in range(1, max_comb_len+1): all_lines += 'bi-partition half-size=' + str(comb_len) + '\n' for traced_axes in profile_list[0][comb_len].keys(): comb = list(traced_axes) comb_c = [k for k in range(self.num_row_axes) if k not in traced_axes] line = '(' + str(comb)[1:-1] \ + ' | ' \ + str(comb_c)[1:-1] + ")" line += ' :\t' for pf_index, pf in enumerate(profile_list): line += "{:8.5f}".format(pf[comb_len][traced_axes]) if pf_index != len(profile_list)-1: line += ", " else: if row_shape: line += ', max-entang=' + \ "{:8.5f}".format(EntangCase.get_max_entag( row_shape, comb, comb_c)) + '\n' else: line += '\n' all_lines += line print(all_lines)
[docs] def sqrt(self, dm, approx=None): """ This method is a simple wrapper for dm.sqrt() so that all usages inside the class utilize approx=self.approx if approx!=None. Parameters ---------- dm : DenMat approx : str Returns ------- DenMat """ if approx is None: approx = self.approx dm_out = None if approx in ['eigen', 'pade']: dm_out = dm.sqrt(approx=approx) elif approx == 'pert': esys = DenMatPertTheory.do_bstrap_with_separable_dm0( dm, self.num_bstrap_steps) dm_out = DenMat.get_fun_of_dm_from_eigen_sys( dm.num_rows, dm.row_shape, esys, np.sqrt) else: assert False return dm_out
[docs] def exp(self, dm, approx=None): """ This method is a simple wrapper for dm.exp() so that all usages inside the class utilize approx=self.approx if approx!=None. Parameters ---------- dm : DenMat approx : str Returns ------- DenMat """ if approx is None: approx = self.approx dm_out = None if approx in ['eigen', 'pade']: dm_out = dm.exp(approx) elif approx == 'pert': esys = DenMatPertTheory.do_bstrap_with_separable_dm0( dm, self.num_bstrap_steps) dm_out = DenMat.get_fun_of_dm_from_eigen_sys( dm.num_rows, dm.row_shape, esys, np.exp) else: assert False return dm_out
[docs] def log(self, dm, approx=None, eps=None, clip_to_zero=False): """ This method is a simple wrapper for dm.log() so that all usages inside the class utilize approx=self.approx if approx!=None. Parameters ---------- dm : DenMat approx : str|None eps : float | None clip_to_zero : bool Returns ------- DenMat """ if approx is None: approx = self.approx dm_out = None if approx in ['eigen', 'pade']: dm_out = dm.log(approx=approx, eps=eps, clip_to_zero=clip_to_zero) elif approx == 'pert': esys = DenMatPertTheory.do_bstrap_with_separable_dm0( dm, self.num_bstrap_steps) if eps is None: fun = np.log else: fun = ut.clipped_log_of_vec dm_out = DenMat.get_fun_of_dm_from_eigen_sys( dm.num_rows, dm.row_shape, esys, fun, eps=eps, clip_to_zero=clip_to_zero) else: assert False return dm_out
if __name__ == "__main__": from entanglish.DenMat import * from entanglish.SymNupState import * from entanglish.PureStEnt import * def main(): num_qbits = 5 num_up = 2 dm = DenMat(1 << num_qbits, tuple([2]*num_qbits)) st = SymNupState(num_up, num_qbits) st_vec = st.get_st_vec() dm.set_arr_from_st_vec(st_vec) ecase = PureStEnt(dm, 'eigen') pf = ecase.get_entang_profile() # print(',,...', pf) ecase.print_entang_profiles([pf, pf], dm.row_shape) main()