Ejemplo n.º 1
0
 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'}
Ejemplo n.º 2
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
Ejemplo n.º 3
0
    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