def test_multi_contract(self): a = Tensor(np.random.randn(2, 3, 4), inds=[0, 1, 2], tags='red') b = Tensor(np.random.randn(3, 4, 5), inds=[1, 2, 3], tags='blue') c = Tensor(np.random.randn(5, 2, 6), inds=[3, 0, 4], tags='blue') d = tensor_contract(a, b, c) assert isinstance(d, Tensor) assert d.shape == (6, ) assert d.inds == (4, ) assert d.tags == {'red', 'blue'}
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