def canonize_column(self, j, sweep, xrange=None, **canonize_opts): check_opt('sweep', sweep, ('up', 'down')) if xrange is None: xrange = (0, self._Lx - 1) if sweep == 'down': for i in range(min(xrange), max(xrange), +1): self.canonize_between((i, j), (i + 1, j), **canonize_opts) else: for i in range(max(xrange), min(xrange), -1): self.canonize_between((i, j), (i - 1, j), **canonize_opts)
def canonize_row(self, i, sweep, yrange=None, **canonize_opts): check_opt('sweep', sweep, ('right', 'left')) if yrange is None: yrange = (0, self._Ly - 1) if sweep == 'right': for j in range(min(yrange), max(yrange), +1): self.canonize_between((i, j), (i, j + 1), **canonize_opts) else: for j in range(max(yrange), min(yrange), -1): self.canonize_between((i, j), (i, j - 1), **canonize_opts)
def compress_row(self, i, sweep, yrange=None, **compress_opts): check_opt('sweep', sweep, ('right', 'left')) compress_opts.setdefault('absorb', 'right') if yrange is None: yrange = (0, self._Ly - 1) if sweep == 'right': for j in range(min(yrange), max(yrange), +1): self.compress_between((i, j), (i, j + 1), **compress_opts) else: for j in range(max(yrange), min(yrange), -1): self.compress_between((i, j), (i, j - 1), **compress_opts)
def compress_column(self, j, sweep, xrange=None, **compress_opts): check_opt('sweep', sweep, ('up', 'down')) if xrange is None: xrange = (0, self._Lx - 1) if sweep == 'down': compress_opts.setdefault('absorb', 'right') for i in range(min(xrange), max(xrange), +1): self.compress_between((i, j), (i + 1, j), **compress_opts) else: compress_opts.setdefault('absorb', 'left') for i in range(max(xrange), min(xrange), -1): self.compress_between((i - 1, j), (i, j), **compress_opts)
def find_tensor_coo(self, t, get='coo'): '''Get the coo of the given tensor `t`. Important: assumes coo tag is of the form "...x,y", e.g. "Sx,y". get: {'coo', 'tag', 'ind'} -'coo': returns tuple[int] -'tag': returns tag string e.g. "Sx,y" -'ind': returns index string e.g. "kx,y" ''' check_opt("get", get, ('coo', 'tag', 'ind')) p = re.compile(self.site_tag_id.format('\d', '\d')) coo_tag, = (s for s in t.tags if p.match(s)) coo = int(coo_tag[-3]), int( coo_tag[-1]) # This assumes tag is ~ "...x,y" return { 'coo': coo, 'tag': coo_tag, 'ind': self.site_ind_id.format(*coo) }[get]
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