def test_canonize_cyclic(self, dtype, block): k = MPS_rand_state(40, 10, dtype=dtype, cyclic=True) b = k.H k.add_tag('KET') b.add_tag('BRA') kb = (b | k) assert not np.allclose(k[block].H @ k[block], 1.0) assert not np.allclose(b[block].H @ b[block], 1.0) k.canonize_cyclic(block, bra=b) assert_allclose(k[block].H @ k[block], 1.0, rtol=2e-4) assert_allclose(b[block].H @ b[block], 1.0, rtol=2e-4) ii = kb.select(block, which='!any') ^ all if isinstance(block, slice): start, stop = block.start, block.stop else: start, stop = block, block + 1 assert len(kb.select_tensors(block, 'any')) == 2 * (stop - start) ul, = bonds(kb[k.site_tag(start - 1), 'BRA'], kb[k.site_tag(start), 'BRA']) ur, = bonds(kb[k.site_tag(stop - 1), 'BRA'], kb[k.site_tag(stop), 'BRA']) ll, = bonds(kb[k.site_tag(start - 1), 'KET'], kb[k.site_tag(start), 'KET']) lr, = bonds(kb[k.site_tag(stop - 1), 'KET'], kb[k.site_tag(stop), 'KET']) ii = ii.to_dense((ul, ur), (ll, lr)) assert_allclose(ii, np.eye(ii.shape[0]), rtol=0.001, atol=0.001)
def rand_tn1d_sect(n, bd, dtype=complex): mps = qtn.MPS_rand_state(n + 2, bd, dtype=dtype) mpo = qtn.MPO_rand_herm(n + 2, 5, dtype=dtype) norm = qtn.TensorNetwork(qtn.align_TN_1D(mps.H, mpo, mps)) lix = qtn.bonds(norm[0], norm[1]) rix = qtn.bonds(norm[n], norm[n + 1]) to = norm[1:n + 1] return qtn.TNLinearOperator1D(to, lix, rix, 1, n + 1)
def rand_tn1d_sect(n, bd, dtype=complex): mps = qtn.MPS_rand_state(n + 2, bd, dtype=dtype) mpo = qtn.MPO_rand_herm(n + 2, 5, dtype=dtype) norm = qtn.TensorNetwork(qtn.align_TN_1D(mps.H, mpo, mps)) # greedy not good and contracting with large bsz norm.structure_bsz = 2 lix = qtn.bonds(norm[0], norm[1]) rix = qtn.bonds(norm[n], norm[n + 1]) to = norm[1:n + 1] return qtn.TNLinearOperator1D(to, lix, rix, 1, n + 1)
def test_TNLinearOperator1D(self): p = MPS_rand_state(40, 10, dtype=complex) pp = p.H & p start, stop = 10, 30 lix = bonds(pp[start - 1], pp[start]) rix = bonds(pp[stop - 1], pp[stop]) sec = pp[start:stop] A = TNLinearOperator1D(sec, lix, rix, start, stop) B = sec.aslinearoperator(lix, rix) s1 = spla.svds(A)[1] s2 = spla.svds(B)[1] assert_allclose(s1, s2)
def main_test(): psi = beeky.QubitEncodeVector.rand(3, 3) X, Y, Z = (qu.pauli(i) for i in 'xyz') where = (3, 4, 9) #which qubits to act on numsites = len(where) dp = 2 #phys ind dimension gate = X & X & X ## take over from here ## g_xx = qtn.tensor_1d.maybe_factor_gate_into_tensor( gate, dp, numsites, where) #shape (2,2,2,2) or (2,2,2,2,2,2) site_inds = [psi.phys_ind_id.format(q) for q in where] bond_inds = [qtn.rand_uuid() for _ in range(numsites)] reindex_map = dict(zip(site_inds, bond_inds)) TG = qtn.Tensor(g_xx, inds=site_inds + bond_inds, left_inds=bond_inds, tags=['GATE']) original_ts = [psi[q] for q in where] bonds_along = [ next(iter(qtn.bonds(t1, t2))) for t1, t2 in qu.utils.pairwise(original_ts) ] triangle_gate_absorb(TG=TG, reindex_map=reindex_map, vertex_tensors=(psi[where[0]], psi[where[1]]), face_tensor=psi[where[2]], phys_inds=site_inds)
def test_cut_iter(self): psi = MPS_rand_state(10, 7, cyclic=True) pp = psi.H & psi bnds = bonds(pp[0], pp[-1]) assert sum(tn ^ all for tn in pp.cut_iter(*bnds)) == pytest.approx(1.0) assert pp ^ all == pytest.approx(1.0)
def absorb_three_body_tensor(self, TG, coos, reindex_map, phys_inds, gate_tags, restore_dummies=True, inplace=False, **compress_opts): '''Serves the same purpose as ``self.absorb_three_body_gate``, but assumes gate has already been shaped into a tensor and appropriate indices have been gathered. TG: qtn.Tensor The 3-body gate (shape [2]*8) as a tensor. coos: sequence of tuple[int, int] The (x,y)-coordinates for 3 qubit sites to hit with the gate. phys_inds: sequence of str The target qubits' physical indices "k{x},{y}" reindex_map: dict[str: str] Map `phys_inds` to the bonds between sites and gate acting on those sites. gate_tags: None or sequence of str, optional Sites acted on with ``TG`` will have these tags added to them. inplace: bool If False, will make a copy of ``self`` and act on that instead. ''' psi = self if inplace else self.copy() vertex_a, vertex_b, face_coo = coos face_qnum = psi.coo_to_qubit_map(face_coo) ## keep track of dummies' tags & neighbor tensors dummy_identities_info = {} # absorb appropriate identity tensors into vertex sites for k, vcoo in enumerate((vertex_a, vertex_b)): vertex_qnum = psi.coo_to_qubit_map(vcoo) # tag of identity to be absorbed adjacent_tag = psi.adjacent_aux_tag.format( vertex_qnum, face_qnum) # "AUX{V},{F}" # tag of vertex to absorb identity _into_ vertex_tag = psi._site_tag_id.format(*vcoo) dummy_identities_info.update({ (k, 'neighbor_tags'): tuple(t.tags for t in psi.select_neighbors(adjacent_tag)) }) tids = psi._get_tids_from_tags(tags=(vertex_tag, adjacent_tag), which='any') # pop and reattach the contracted tensors pts = [psi._pop_tensor(tid) for tid in tids] new_vertex = qtn.tensor_contract(*pts) dummy_identities_info.update({ (k, 'tags'): pts[1].tags, #dummy tags (k, 'coo'): psi.find_tensor_coo(pts[1]), #coo (x,y) # (k, 'inds'): pts[1].inds, #dummy indices }) # drop dummy's tags from vertex site new_vertex.drop_tags(pts[1].tags - pts[0].tags) psi |= new_vertex vertex_tensors = [psi[coo] for coo in (vertex_a, vertex_b)] face_tensor = psi[face_coo] # apply gate! three_body_op.triangle_gate_absorb(TG=TG, reindex_map=reindex_map, vertex_tensors=vertex_tensors, face_tensor=face_tensor, phys_inds=phys_inds, gate_tags=gate_tags, **compress_opts) if not restore_dummies: return psi # now insert new dummy identities where they used to be # for k in range(2): # vt = vertex_tensors[k] # ts_to_connect = set( # psi[tags] for tags in dummy_identities_info[(k, 'neighbor_tags')] # ) - set([vt]) # for T2 in ts_to_connect: # psi |= insert_identity_between_tensors(T1=vt, T2=T2, add_tags='TEMP') # # contract new dummies into a single identity # psi ^= 'TEMP' # # restore previous tags, and drop temporary tag # for tag in dummy_identities_info[(k, 'tags')]: # psi['TEMP'].add_tag(tag) # psi.drop_tags('TEMP') for k, vcoo in enumerate((vertex_a, vertex_b)): vtensor = psi[vcoo] vertex_ind = psi.site_ind_id.format(*vcoo) # kx,y ts_to_connect = set(psi[tags] for tags in dummy_identities_info[ (k, 'neighbor_tags')]) - set([vtensor]) dummy_coo = dummy_identities_info[(k, 'coo')] # (x,y) # free_inds = [ix for ix in vtensor.inds if # len(psi.ind_map[ix]) == 1] # bonds connecting to dummy's neighbors dummy_bonds = qu.oset.union(*(qtn.bonds(vtensor, t) for t in ts_to_connect)) # pop the vertex tensor, to split and reattach soon vtensor, = (psi._pop_tensor(x) for x in psi._get_tids_from_inds(vertex_ind)) # the dummy may not have a physical "kx,y" index dummy_phys_ix = psi.site_ind_id.format(*dummy_coo) if dummy_phys_ix in vtensor.inds: dummy_bonds |= qu.oset([dummy_phys_ix]) # split into vertex & dummy new_vertex, new_dummy = vtensor.split(left_inds=None, method='qr', get='tensors', right_inds=dummy_bonds) new_dummy.drop_tags() for t in dummy_identities_info[(k, 'tags')]: new_dummy.add_tag(t) psi |= new_vertex psi |= new_dummy return psi
def gate( self, G, coos, contract='auto_split', tags=('GATE', ), inplace=False, info=None, **compress_opts, ): ''' contract: {False, 'reduce_split', 'triangle_absorb', 'reduce_split_lr'} -False: leave gate uncontracted at sites [For 2-body ops:] -reduce_split: Absorb dummy, apply gate with `qtn.tensor_2d.reduce_split`, then reinsert dummy. (NOTE: this one seems very slow) -reduce_split_lr: leave dummy as-is, treat gate as a LR interaction. The final bonds are much smaller this way! [For 3-body ops:] -triangle_absorb: use `three_body_op.triangle_gate_absorb` to apply the 3-body gate. Assumes `coos` is ordered like ~ (vertex, vertex, face)! [For any n-body:] -auto_split: will automatically choose depending on n. n=1 -> contract = True n=2 -> contract = 'reduce_split_lr' n=3 -> contract = 'triangle_absorb' ''' check_opt("contract", contract, (False, True, 'reduce_split', 'triangle_absorb', 'reduce_split_lr', 'auto_split')) psi = self if inplace else self.copy() if is_lone_coo(coos): coos = (coos, ) else: coos = tuple(coos) numsites = len(coos) #num qubits acted on if contract == 'auto_split': contract = { 1: True, 2: 'reduce_split_lr', 3: 'triangle_absorb' }[numsites] #inds like 'k{x},{y}' site_inds = [self._site_ind_id.format(*c) for c in coos] # physical dimension, d=2 for qubits dp = self.ind_size(site_inds[0]) gate_tags = tags_to_oset(tags) G = qtn.tensor_1d.maybe_factor_gate_into_tensor(G, dp, numsites, coos) #old physical indices joined to new gate bond_inds = [qtn.rand_uuid() for _ in range(numsites)] reindex_map = dict(zip(site_inds, bond_inds)) TG = qtn.Tensor(G, inds=site_inds + bond_inds, left_inds=bond_inds, tags=gate_tags) if contract is False: #attach gates without contracting any bonds # # 'qA' 'qB' # │ │ <- site_inds # GGGGG # │╱ │╱ <- bond_inds # ──●───●── # ╱ ╱ # psi.reindex_(reindex_map) psi |= TG return psi elif (contract is True) or (numsites == 1): # # │╱ │╱ # ──GGGGG── # ╱ ╱ # psi.reindex_(reindex_map) # get the sites that used to have the physical indices site_tids = psi._get_tids_from_inds(bond_inds, which='any') # pop the sites, contract, then re-add pts = [psi._pop_tensor(tid) for tid in site_tids] psi |= qtn.tensor_contract(*pts, TG) return psi elif contract == 'triangle_absorb' and numsites == 3: # absorbs 3-body gate while preserving lattice structure. psi.absorb_three_body_tensor_(TG=TG, coos=coos, reindex_map=reindex_map, phys_inds=site_inds, gate_tags=gate_tags, **compress_opts) return psi # NOTE: this one seems very inefficient for # "next-nearest" neighbor interactions. elif contract == 'reduce_split' and numsites == 2: # First absorb identity into a site, then # restore after gate has been applied. # # 1. Absorb identity step: # # │ │ Absorb │ │ # GGGGGGG ident. GGGGGGG # │╱ ╱ │╱ ==> │╱ │╱╱ # ──●──I──●── ──●─────●─ # a ╱ ╱ ╱ b ╱ ╱╱ # # 2. Gate 'reduce_split' step: # # │ │ │ │ # GGGGG GGG │ │ # │╱ │╱ ==> ╱│ │ ╱ ==> ╱│ │ ╱ │╱ │╱ # ──●───●── ──>─●─●─<── ──>─GGG─<── ==> ──G┄┄┄G── # ╱ ╱ ╱ ╱ ╱ ╱ ╱ ╱ # <QR> <LQ> <SVD> # # 3. Reinsert identity: # # │╱ │╱╱ │╱ ╱ │╱ # ──G┄┄┄┄┄G── ==> ──G┄┄I┄┄G── # ╱ ╱╱ ╱ ╱ ╱ # (x1, y1), (x2, y2) = coos mid_coo = (int((x1 + x2) / 2), int((y1 + y2) / 2)) dummy_coo_tag = psi.site_tag_id.format(*mid_coo) # keep track of dummy identity's tags and neighbors prev_dummy_info = { 'tags': psi[dummy_coo_tag].tags, 'neighbor_tags': tuple(t.tags for t in psi.select_neighbors(dummy_coo_tag)) } which_bond = int( psi.bond_size(coos[0], mid_coo) >= psi.bond_size( coos[1], mid_coo)) if which_bond == 0: # (vertex_0 ── identity) bond is larger vertex_tag = psi.site_tag_id.format(*coos[0]) else: # (vertex_1 -- identity) bond larger vertex_tag = psi.site_tag_id.format(*coos[1]) tids = psi._get_tids_from_tags(tags=(vertex_tag, dummy_coo_tag), which='any') # pop and reattach the (vertex & identity) tensor pts = [psi._pop_tensor(tid) for tid in tids] new_vertex = qtn.tensor_contract(*pts) # new_vertex.drop_tags(prev_dummy_info['tags'] - ) new_vertex.drop_tags(pts[1].tags - pts[0].tags) psi |= new_vertex # reattach [vertex & identity] # insert 2-body gate! qtn.tensor_2d.gate_string_reduce_split_( TG=TG, where=coos, string=coos, original_ts=[psi[c] for c in coos], bonds_along=(psi.bond(*coos), ), reindex_map=reindex_map, site_ix=site_inds, info=info, **compress_opts) # now restore the dummy identity between vertices vtensor = psi[ coos[which_bond]] # the vertex we absorbed dummy into ts_to_connect = set( psi[tags] for tags in prev_dummy_info['neighbor_tags']) - set([vtensor]) for T2 in ts_to_connect: # restore previous dummy bonds psi |= qubit_networks.insert_identity_between_tensors( T1=vtensor, T2=T2, add_tags='TEMP') # contract new dummies into a single identity psi ^= 'TEMP' for t in prev_dummy_info['tags']: psi['TEMP'].add_tag(t) # restore previous dummy tags psi.drop_tags('TEMP') return psi.fuse_multibonds_() elif contract == 'reduce_split_lr' and numsites == 2: # There will be a 'dummy' identity tensor between the # sites, so the 2-body operator will look "long-range" # # │ │ # GGGGGGG # │╱ ╱ │╱ │╱ ╱ │╱ # ──●──I──●── ==> ──G┄┄I┄┄G── # ╱ ╱ ╱ ╱ ╱ ╱ # (x1, y1), (x2, y2) = coos mid_coo = (int((x1 + x2) / 2), int((y1 + y2) / 2)) dummy_coo_tag = psi.site_tag_id.format(*mid_coo) string = (coos[0], mid_coo, coos[1]) original_ts = [psi[coo] for coo in string] bonds_along = [ next(iter(qtn.bonds(t1, t2))) for t1, t2 in qu.utils.pairwise(original_ts) ] qtn.tensor_2d.gate_string_reduce_split_(TG=TG, where=coos, string=string, original_ts=original_ts, bonds_along=bonds_along, reindex_map=reindex_map, site_ix=site_inds, info=info, **compress_opts) return psi