コード例 #1
0
ファイル: tensor.py プロジェクト: CaoRX/CTL
    def sumOutLeg(self, leg, weights=None):
        """
        Sum out one leg to make a (D - 1)-dimensional tensor. Give a warning(and do nothing) if leg is not one of the current tensor, and give a warning if leg is connected to some bond(not free).

        Parameters
        ----------
        leg : Leg
            The leg to be summed out.
        weights : 1-d array, optional
            If not None, then each index on given dimension will be weighted by weights[i].

        """
        if not (leg in self.legs):
            warnings.warn(
                funcs.warningMessage(
                    "leg {} is not in tensor {}, do nothing.".format(
                        leg, self),
                    location='Tensor.sumOutLeg'), RuntimeWarning)
            return
        if leg.bond is not None:
            warnings.warn(
                funcs.warningMessage(
                    "leg {} to be summed out is connected to bond {}.".format(
                        leg, leg.bond),
                    location='Tensor.sumOutLeg'), RuntimeWarning)

        idx = self.legs.index(leg)
        # self.a = xplib.xp.sum(self.a, axis = idx)
        self.a = funcs.sumOnAxis(self.a, axis=idx, weights=weights)
        self.legs = self.legs[:idx] + self.legs[(idx + 1):]
コード例 #2
0
ファイル: tensor.py プロジェクト: CaoRX/CTL
    def sumOutLegByLabel(self, label, backward=False, weights=None):
        """
        Sum out one leg to make a (D - 1)-dimensional tensor via the label of leg. Give a warning(and do nothing) if label is not one of the current tensor.

        Parameters
        ----------
        label : str or list of str
            The label of the leg(s) to be summed out.
        backward : bool, default False
            Whether to search from backward of legs.
        weights : 1-d array, optional
            If not None, then each index on given dimension will be weighted by weights[i].

        """
        if isinstance(label, list):
            for l in label:
                self.sumOutLegByLabel(l, backward)
            return
        leg = self.getLeg(label, backward=backward)
        if leg is None:
            warnings.warn(
                funcs.warningMessage(
                    "leg {} is not in tensor {}, do nothing.".format(
                        label, self),
                    location='Tensor.sumOutLegByLabel'), RuntimeWarning)
        self.sumOutLeg(leg, weights=weights)
コード例 #3
0
ファイル: tensor.py プロジェクト: CaoRX/CTL
 def removeTensorTag(self):
     """
     Remove the tags of legs. If a leg does not contain a tag, then give a warning message.
     """
     for leg in self.legs:
         divLoc = leg.name.find('-')
         if (divLoc == -1):
             warnings.warn(
                 funcs.warningMessage(
                     warn=
                     "leg name {} of tensor {} does not contain a tensor tag."
                     .format(leg.name, self),
                     location="Tensor.removeTensorTag"))
         # assert (divLoc != -1), "Error: leg name {} does not contain a tensor tag.".format(leg.name)
         if (divLoc != -1):
             leg.name = leg.name[(divLoc + 1):]
コード例 #4
0
ファイル: tensor.py プロジェクト: CaoRX/CTL
    def bondDimension(self):
        """
        Bond dimension of the tensor. Work for the case when all dimensions are the same, otherwise, generate a warning message and return the first dimension.

        Returns
        -------
        int
            The bond dimension of this tensor.
        """

        if (not funcs.checkAllEqual(self.shape)):
            warnings.warn(
                funcs.warningMessage(
                    warn=
                    "shape of tensor does not contain the same dimesion for all legs: {}"
                    .format(self.shape),
                    location="Tensor.bondDimension"))
        return self.shape[0]
コード例 #5
0
ファイル: tensor.py プロジェクト: CaoRX/CTL
    def renameLabel(self, changeFrom, changeTo):
        """
        Rename a given label to a new name.

        Parameters
        ----------
        changeFrom, changeTo : str

        """
        legIndex = self.indexOfLabel(changeFrom)
        if (legIndex == -1):
            warnings.warn(
                funcs.warningMessage(
                    warn='leg name {} does not exist, no rename happened'.
                    format(changeFrom),
                    location='Tensor.renameLabel'), RuntimeWarning)
            return

        self.legs[legIndex].name = changeTo
コード例 #6
0
    def getChi(self, chi):
        # if chi is None: then take the maximum from bonds shared
        # otherwise, if bonds sharing larger than chi, then warning and update chi
        # otherwise, take chi
        bondChi = -1
        for i in range(self.n - 1):
            bond = shareBonds(self._tensors[i], self._tensors[i + 1])[0]
            bondChi = min(bondChi, bond.legs[0].dim)

        if (chi is None):
            return bondChi
        elif (bondChi > chi):
            warnings.warn(
                funcs.warningMessage(
                    'required chi {} is lower than real bond chi {}, set to {}.'
                    .format(chi, bondChi, bondChi),
                    location="FreeBoundaryMPS.getChi"))
            return bondChi
        else:
            return chi
コード例 #7
0
    def checkCanonical(self, excepIdx=None):
        '''
        check if the current MPS is in canonical(isometry except for excepIdx)
        if the index is not given: check with the index the last time the MPS has been canonicalized for
        !!! Note that: this will be not true when excepIdx is None if the MPS has been changed directly by self._tensors[...] = ...
        '''
        funcName = 'FreeBoundaryMPS.checkCanonical'
        assert (excepIdx is not None) or (
            self.activeIdx is not None
        ), funcs.errorMessage(
            "exception index and MPS.activeIdx cannot be None at the same time.",
            location=funcName)
        if (excepIdx is None):
            excepIdx = self.activeIdx
        assert (isinstance(excepIdx, int) and (excepIdx >= 0)
                and (excepIdx < self.n)), funcs.errorMessage(
                    "exception index must in [0, self.n), {} obtained.".format(
                        excepIdx),
                    location=funcName)
        if (self.n == 0):
            warnings.warn(
                funcs.warningMessage(
                    "number of tensors in MPS is 0, return True",
                    location=funcName))
            return True
        # print([isIsometry(tensor, ['o']) for tensor in self._tensors])
        for i in range(self.n):
            if (i == excepIdx):
                continue
            if (i == 0) or (i == self.n - 1):
                labels = ['o']
            elif (i < excepIdx):
                labels = ['l', 'o']
            else:
                labels = ['r', 'o']
            # print(i, labels, isIsometry(self._tensors[i], labels))
            if not isIsometry(self._tensors[i], labels):
                return False

        return True
コード例 #8
0
    def moveTensor(self, begIndex, endIndex, warningFlag=True):
        '''
        move then tensor at begIndex to endIndex
        '''
        funcName = 'FreeBoundaryMPS.moveTensor'
        assert (self.isIndex(begIndex)
                and self.isIndex(endIndex)), funcs.errorMessage(
                    "{} or {} is invalid index.".format(begIndex, endIndex),
                    location=funcName)

        if (begIndex == endIndex):
            if (warningFlag):
                warnings.warn(
                    funcs.warningMessage(
                        "begIndex and endIndex is equal, do nothing.",
                        location=funcName))
            return

        if (begIndex < endIndex):
            for i in range(begIndex, endIndex):
                self.swap(i, i + 1)
        else:
            for i in range(begIndex, endIndex, -1):
                self.swap(i, i - 1)
コード例 #9
0
def isIsometry(tensor, labels, eps=1e-10, warningFlag=False):
    """
    Decide if a tensor is Isometry for given labels.

    Parameters
    ----------
    tensor : Tensor

    labels : list of str
        Part of the labels in tensor
    eps : float, default 1e-10
        The upper bound of the error to be ignored when checking the result tensor is identity.
    warningFlag : bool, default False
        If True, then will give a warning message if labels contain all labels of tensor(so cannot give a decision whether it is an isometry, and return True whatever warningFlag is).
    
    Returns
    -------
    bool
        Whether the tensor is an isometry for given labels, up to eps.
    """
    funcName = 'CTL.tensor.tensorFunc.isIsometry'
    assert (tensor.labelsInTensor(labels)), funcs.errorMessage(
        "labels {} is not in {}.".format(labels, tensor), location=funcName)
    if (tensor.tensorLikeFlag):
        if (warningFlag):
            warnings.warn(
                funcs.warningMessage(
                    warn=
                    'isIsometry cannot work on tensorLike object {}, return True by default.'
                    .format(tensor),
                    location=funcName))
        return True
    mat = tensor.toMatrix(cols=labels)
    # np = tensor.xp
    iden = mat @ funcs.transposeConjugate(mat)
    return funcs.checkIdentity(iden, eps=eps)
コード例 #10
0
def merge(ta, tb, chi=None, bondName=None, renameWarning=True):
    """
    Merge the shared bonds of two tensors. If not connected, make a warning and do nothing.

    Parameters
    ----------
    ta, tb : Tensor
    
    chi : int, optional
        The upper-bound of the bond dimension of the bond after merged. If None, then no truncation.
    bondName : str, optional
        The name of bond after merging. If None, then for a list of [name1, name2, ... nameN], the name will be "{name1}|{name2}| .... |{nameN}".
    renameWarning : bool, default True
        If only one bond is shared, then the two

    Returns
    -------
    ta, tb : Tensor
        The two tensors after merging all the common bonds to one bond.

    """
    funcName = "CTL.tensor.contract.contract.truncate"

    # assert (ta.xp == tb.xp), funcs.errorMessage("Truncation cannot accept two tensors with different xp: {} and {} gotten.".format(ta.xp, tb.xp), location = funcName)

    assert (ta.tensorLikeFlag == tb.tensorLikeFlag), funcs.errorMessage(
        'two tensors to be merged must be either Tensor or TensorLike simultaneously, {} and {} obtained.'
        .format(ta, tb),
        location=funcName)

    tensorLikeFlag = ta.tensorLikeFlag

    # xp = ta.xp
    ta, tb = mergeLink(ta, tb, bondName=bondName, renameWarning=renameWarning)
    if (chi is None):
        # no need for truncation
        return ta, tb

    sb = shareBonds(ta, tb)
    # assert (len(sb) > 0), funcs.errorMessage("Truncation cannot work on two tensors without common bonds: {} and {} gotten.".format(ta, tb), location = funcName)

    # if (bondName is None):
    # 	bondNameListA = [bond.sideLeg(ta).name for bond in sb]
    # 	bondNameListB = [bond.sideLeg(tb).name for bond in sb]
    # 	bondNameA = '|'.join(bondNameListA)
    # 	bondNameB = '|'.join(bondNameListB)
    # elif (isinstance(bondName, str)):
    # 	bondNameA = bondName
    # 	bondNameB = bondName
    # else:
    # 	bondNameA, bondNameB = bondName # tuple/list

    # if (renameFlag):
    if (len(sb) == 0):
        if (renameWarning):
            warnings.warn(
                funcs.warningMessage(
                    warn=
                    'mergeLink cannot merge links between two tensors {} and {} not sharing any bond'
                    .format(ta, tb),
                    location=funcName), RuntimeWarning)
        return ta, tb

    assert (len(sb) == 1), funcs.errorMessage(
        "There should only be one common leg between ta and tb after mergeLink, {} obtained."
        .format(sb),
        location=funcName)

    legA = [bond.sideLeg(ta) for bond in sb]
    legB = [bond.sideLeg(tb) for bond in sb]

    bondNameA = legA[0].name
    bondNameB = legB[0].name

    remainLegA = ta.complementLegs(legA)
    remainLegB = tb.complementLegs(legB)

    if (not tensorLikeFlag):

        matA = ta.toMatrix(rows=None, cols=legA)
        matB = tb.toMatrix(rows=legB, cols=None)

        mat = matA @ matB

        u, s, vh = xplib.xp.linalg.svd(mat)

        chi = min(
            [chi, funcs.nonZeroElementN(s), matA.shape[0], matB.shape[1]])
        u = u[:, :chi]
        s = s[:chi]
        vh = vh[:chi]

        uOutLeg = Leg(tensor=None, dim=chi, name=bondNameA)
        vOutLeg = Leg(tensor=None, dim=chi, name=bondNameB)
        # print(legA, legB)

        sqrtS = xplib.xp.sqrt(s)
        uS = funcs.rightDiagonalProduct(u, sqrtS)
        vS = funcs.leftDiagonalProduct(vh, sqrtS)

        uTensor = Tensor(data=uS, legs=remainLegA + [uOutLeg])
        vTensor = Tensor(data=vS, legs=[vOutLeg] + remainLegB)

    else:
        chi = min([
            chi, legA[0].dim, ta.totalSize // legA[0].dim,
            tb.totalSize // legB[0].dim
        ])
        uOutLeg = Leg(tensor=None, dim=chi, name=bondNameA)
        vOutLeg = Leg(tensor=None, dim=chi, name=bondNameB)
        uTensor = Tensor(tensorLikeFlag=True, legs=remainLegA + [uOutLeg])
        vTensor = Tensor(tensorLikeFlag=True, legs=[vOutLeg] + remainLegB)

    makeLink(uOutLeg, vOutLeg)
    return uTensor, vTensor
コード例 #11
0
ファイル: link.py プロジェクト: CaoRX/CTL
def mergeLink(ta, tb, bondName=None, renameWarning=True):
    """
    Merge the links between two tensors to make a larger bond

    Parameters
    ----------
    ta, tb : Tensor
        Two tensors, bonds between which will be merged.
    bondName : None or str
        If not None, then the new bond(and two legs) will be named with bondName. Otherwise, each side should be renamed as A|B|C|...|Z, where A, B, C... are the names of merged legs.
    renameWarning : bool
        If true, and only one bond is between ta and tb, then raise a warning since we do not merge any bonds but only rename the bond.

    Returns
    -------
    ta, tb : Tensor
        The two tensors with only one bond shared.
    """

    funcName = 'CTL.tensor.contract.link.mergeLink'
    mergeLegA = []
    mergeLegB = []
    for leg in ta.legs:
        if (leg.bond is not None) and (leg.anotherSide().tensor == tb):
            mergeLegA.append(leg)
            mergeLegB.append(leg.anotherSide())

    if (len(mergeLegA) == 0):
        warnings.warn(
            funcs.warningMessage(
                warn=
                'mergeLink cannot merge links between two tensors {} and {} sharing no bonds, do nothing'
                .format(ta, tb),
                location=funcName), RuntimeWarning)
        return ta, tb

    if (len(mergeLegA) == 1):
        if (renameWarning):
            warnings.warn(
                funcs.warningMessage(
                    warn=
                    'mergeLink cannot merge links between two tensors {} and {} sharing one bond, only rename'
                    .format(ta, tb),
                    location=funcName), RuntimeWarning)
        if (bondName is not None):
            mergeLegA[0].name = bondName
            mergeLegB[0].name = bondName
        return ta, tb

    # otherwise, we need to do outProduct, and then merge
    # bondNameA = funcs.combineName(namesList = [leg.name for leg in mergeLegA], givenName = bondName)
    # bondNameB = funcs.combineName(namesList = [leg.anem for leg in mergeLegB], givenName = bondName)
    if (bondName is None):
        bondNameListA = [leg.name for leg in mergeLegA]
        bondNameListB = [leg.name for leg in mergeLegB]
        bondNameA = '|'.join(bondNameListA)
        bondNameB = '|'.join(bondNameListB)
    elif (isinstance(bondName, str)):
        bondNameA = bondName
        bondNameB = bondName
    else:
        bondNameA, bondNameB = bondName  # tuple/list

    newLegA = ta.outProduct(legList=mergeLegA, newLabel=bondNameA)
    newLegB = tb.outProduct(legList=mergeLegB, newLabel=bondNameB)

    makeLink(newLegA, newLegB)

    return ta, tb
コード例 #12
0
def createMPSFromTensor(tensor, chi=16):
    '''
    tensor is a real Tensor with n outer legs
    transfer it into an MPS with Schimdt decomposition
    after this, we can manage the tensor network decomposition by MPS network decomposition
    finally consider if tensor is only a TensorLike object
    '''

    # TODO: make this function work for tensorLike

    funcName = 'CTL.examples.MPS.createMPSFromTensor'

    legs = [leg for leg in tensor.legs]
    # xp = tensor.xp

    n = len(legs)
    assert (n > 0), funcs.errorMessage(
        "cannot create MPS from 0-D tensor {}.".format(tensor),
        location=funcName)

    if (n == 1):
        warnings.warn(
            funcs.warningMessage(
                "creating MPS for 1-D tensor {}.".format(tensor),
                location=funcName), RuntimeWarning)
        return FreeBoundaryMPS([tensor], chi=chi)

    a = xplib.xp.ravel(tensor.toTensor(labels=None))

    lastDim = -1
    tensors = []
    lastRightLeg = None
    for i in range(n - 1):
        u, v = matrixSchimdtDecomposition(a, dim=legs[i].dim, chi=chi)
        leg = legs[i]
        if (i == 0):
            dim1 = u.shape[1]
            rightLeg = Leg(None, dim=dim1, name='r')
            tensor = Tensor(shape=(leg.dim, u.shape[1]),
                            legs=[leg, rightLeg],
                            data=u)
            lastRightLeg = rightLeg
            lastDim = dim1
        else:
            dim1 = u.shape[-1]
            leftLeg = Leg(None, dim=lastDim, name='l')
            rightLeg = Leg(None, dim=dim1, name='r')
            tensor = Tensor(shape=(lastDim, leg.dim, u.shape[-1]),
                            legs=[leftLeg, leg, rightLeg],
                            data=u)

            makeLink(leftLeg, lastRightLeg)

            lastRightLeg = rightLeg
            lastDim = dim1

        tensors.append(tensor)
        a = v

    leftLeg = Leg(None, dim=lastDim, name='l')
    tensor = Tensor(shape=(lastDim, legs[-1].dim),
                    legs=[leftLeg, legs[-1]],
                    data=a)
    makeLink(leftLeg, lastRightLeg)
    tensors.append(tensor)

    # print(tensors)

    return FreeBoundaryMPS(tensorList=tensors, chi=chi)
コード例 #13
0
def contractMPS(mpsA, mpsB):
    '''
    solution 0.
    step 0. find all the connections between mpsA and mpsB(in some of o's, the same number)
    step 1. make them continuous on both mps, merge them(merge 2, canonicalize, ...?)
    step 2. use swap to move tensors to one end(tail of mpsA, head of mpsB)
    step 3. merge two tensors, and eliminate the 2-way tensor(to one side)
    Problem: how about canonicalization?
    Only one bond should exist!

    solution 1. we need to save the MPS information in tensors
    extend as MPSTensor
    problem: Schimdt will return a Tensor?
    contractTwoTensors need to maintain MPS information?
    maybe solution: to write a wrapper on these functions maintaining MPS information of Tensor

    solution 2. "merge" for all pairs after one contraction(used here)
    after the contraction, merge the new MPS(mergeMPS) with all existing MPSes
    this may increase the cost of finding merges
    but at the same time, make tensors not need to save MPS information, and for wider usage
    so in this function: no need for considering this task
    '''
    funcName = 'CTL.examples.MPS.contractMPS'
    indexA, indexB = commonLegs(mpsA, mpsB)
    # print('indexA = {}, indexB = {}'.format(indexA, indexB))
    # print('mpsA = {}, mpsB = {}'.format(mpsA, mpsB))
    assert (len(indexA) == 1), funcs.errorMessage(
        "contractMPS can only work on two MPSes sharing one bond, {} obtained."
        .format((indexA, indexB)),
        location=funcName)
    if (mpsA.chi != mpsB.chi):
        warnings.warn(
            funcs.warningMessage(
                warn=
                "chi for two MPSes are not equal: {} and {}, choose minimum for new chi."
                .format(mpsA.chi, mpsB.chi),
                location=funcName))
    indexA = indexA[0]
    indexB = indexB[0]

    mpsA.moveTensor(indexA, mpsA.n - 1, warningFlag=False)
    mpsB.moveTensor(indexB, 0, warningFlag=False)
    # print('mpsA after swap = {}'.format(mpsA))
    # print('mpsB after swap = {}'.format(mpsB))

    tensorA = mpsA.getTensor(mpsA.n - 1)
    tensorB = mpsB.getTensor(0)

    newTensor = contractTwoTensors(tensorA, tensorB)
    if (newTensor.dim == 0):
        return newTensor

    # otherwise, there must be tensors remaining in A or B
    if (mpsA.n > 1):
        newTensor = contractTwoTensors(mpsA.getTensor(-2), newTensor)
        newTensorList = mpsA._tensors[:(-2)] + [newTensor] + mpsB._tensors[1:]
    else:
        newTensor = contractTwoTensors(newTensor, mpsB.getTensor(1))
        newTensorList = mpsA._tensors[:(-1)] + [newTensor] + mpsB._tensors[2:]

    return FreeBoundaryMPS(newTensorList, chi=min(mpsA.chi, mpsB.chi))
コード例 #14
0
    def optimalContractSequence(self, bf=False, greedy=False, typicalDim=10):
        """
        Generate an order for contraction of tensors in the graph.

        Parameters
        ----------
        bf : bool, default False
            Whether to calculate the order by brute-force.
        greedy : bool, default False
            Whether to calculate the order by greedy algorithm, so the order may not be optimal. If both bf and greedy is False, then calculate with ncon technique. Priority is greedy > bf.
        typicalDim : int or None
            The typical bond dimension for cost calculation. If None, then calculate with exact dimensions.
        
        Returns
        -------
        list of length-2 tuple of ints
            Length of the returned list should be $n - 1$, where $n$ is the length of tensorList.
            Every element contains two integers a < b, means we contract a-th and b-th tensors in tensorList, and save the new tensor into a-th location.

        """

        if (greedy and (typicalDim is not None)):
            warnings.warn(
                funcs.warningMessage(
                    warn=
                    'greedy search of contract sequence, typicalDim {} has been ignored.'
                    .format(typicalDim),
                    location='TensorGraph.optimalContractSequence'))

        def lowbitI(x):
            return (x & (-x)).bit_length() - 1

        def lowbit(x):
            return (x & (-x))

        def subsetIterator(x):
            lb = lowbit(x)
            while (lb * 2 < x):
                yield lb
                nlb = lowbit(x - lb)
                lb = (lb & (~(nlb - 1))) + nlb
            return

        self.addEdgeIndex()

        edges = [[edge.index for edge in v.edges] for v in self.v]
        shapes = [[edge.weight for edge in v.edges] for v in self.v]

        n = len(self.v)

        if (not greedy):
            self.optimalCost = [None] * (1 << n)
            self.optimalSeq = [None] * (1 << n)
            self.contractRes = [None] * (1 << n)

            for i in range(n):
                self.optimalCost[(1 << i)] = 0
                self.optimalSeq[(1 << i)] = []
                self.contractRes[(1 << i)] = (edges[i], shapes[i],
                                              self.diagonalFlags[i])

        else:
            self.greedyCost = 0
            self.greedySeq = []
            self.contractRes = [None] * n
            for i in range(n):
                self.contractRes[i] = (edges[i], shapes[i],
                                       self.diagonalFlags[i])

        def getCost(tsA, tsB):
            # to deal with diagonal tensors:
            # what if we contract two diagonal tensors?
            # then it will be a new diagonal tensor with new index set
            # this means, we need to add a "diagonalFlag" to contractRes
            edgesA, shapeA, diagonalA = self.contractRes[tsA]
            edgesB, shapeB, diagonalB = self.contractRes[tsB]

            if (diagonalA and diagonalB):
                # both diagonal, only need to product
                return shapeA[0]

            diagonal = diagonalA or diagonalB

            clb = set(funcs.commonElements(edgesA, edgesB))
            res = 1
            for l, s in zip(edgesA + edgesB, shapeA + shapeB):
                if (l in clb):
                    if (not diagonal):
                        res *= xplib.xp.sqrt(s)
                    # if single diagonal: then the cost should be output shape
                else:
                    res *= s

            return int(res + 0.5)

        def getCostTypical(tsA, tsB):
            edgesA, _, diagonalA = self.contractRes[tsA]
            edgesB, _, diagonalB = self.contractRes[tsB]

            if (diagonalA and diagonalB):
                costOrder = 1
            else:
                clb = set(funcs.commonElements(edgesA, edgesB))
                if (diagonalA or diagonalB):
                    costOrder = len(edgesA) + len(edgesB) - 2 * len(clb)
                else:
                    costOrder = len(edgesA) + len(edgesB) - len(clb)
            return typicalDim**costOrder

        def isSharingBond(tsA, tsB):
            if (isinstance(tsA, int)):
                tsA = self.contractRes[tsA]
            if (isinstance(tsB, int)):
                tsB = self.contractRes[tsB]
            edgesA, _, _ = tsA
            edgesB, _, _ = tsB

            commonEdges = funcs.commonElements(edgesA, edgesB)
            return len(commonEdges) > 0

        def isDiagonalOuterProduct(tsA, tsB):
            return tsA[2] and tsB[2] and (not isSharingBond(tsA, tsB))

        def calculateContractRes(tsA, tsB):
            if (isinstance(tsA, int)):
                tsA = self.contractRes[tsA]
            if (isinstance(tsB, int)):
                tsB = self.contractRes[tsB]
            edgesA, shapeA, diagonalA = tsA
            edgesB, shapeB, diagonalB = tsB

            edges = funcs.listSymmetricDifference(edgesA, edgesB)
            shape = [None] * len(edges)

            edgeIndex = dict()
            for i in range(len(edges)):
                edgeIndex[edges[i]] = i

            for l, s in zip(edgesA + edgesB, shapeA + shapeB):
                if (l in edgeIndex):
                    shape[edgeIndex[l]] = s
            return (edges, shape, diagonalA and diagonalB)

        def getSize(tsA, tsB):
            '''
            contract self.contractRes[tsA] and self.contractRes[tsB]
            then give the size of the output tensor
            '''
            _, shape, _ = calculateContractRes(tsA, tsB)
            return funcs.tupleProduct(shape)

        costFunc = None
        if (greedy or (typicalDim is None)):
            costFunc = getCost
        else:
            costFunc = getCostTypical

        def solveSet(x):
            #print('solve_set({})'.format(x))
            if (self.optimalCost[x] is not None):
                return
            minCost = None
            minTSA = None
            minTSB = None
            localCost = -1
            for tsA in subsetIterator(x):
                tsB = x - tsA
                #print('ss = {}, tt = {}'.format(ss, tt))
                if (self.optimalCost[tsA] is None):
                    solveSet(tsA)
                if (self.optimalCost[tsB] is None):
                    solveSet(tsB)
                if (self.contractRes[x] is None):
                    self.contractRes[x] = calculateContractRes(tsA, tsB)

                localCost = costFunc(tsA, tsB)
                currCost = self.optimalCost[tsA] + self.optimalCost[
                    tsB] + localCost
                if (minCost is None) or (currCost < minCost):
                    minCost = currCost
                    minTSA = tsA
                    minTSB = tsB

            self.optimalCost[x] = minCost
            self.optimalSeq[x] = self.optimalSeq[minTSA] + self.optimalSeq[
                minTSB] + [(lowbitI(minTSA), lowbitI(minTSB))]

        def bruteForce():

            fullSet = (1 << n) - 1

            solveSet(fullSet)
            #print('minimum cost = {}'.format(self.optimalCost[full_s]))
            #print('result = {}'.format(self.contractRes[full_s]))
            return self.optimalSeq[fullSet]

        def greedySearch():
            tensorSet = list(range(n))

            while (len(tensorSet) > 1):
                minSize = -1
                minTSA = -1
                minTSB = -1

                for tsA, tsB in funcs.pairIterator(tensorSet):
                    if (not isSharingBond(tsA, tsB)):
                        continue
                    newSize = getSize(tsA, tsB)
                    if (minSize == -1) or (newSize < minSize):
                        minSize = newSize
                        minTSA, minTSB = tsA, tsB

                tsA, tsB = minTSA, minTSB
                self.greedySeq.append((tsA, tsB))
                self.greedyCost += costFunc(tsA, tsB)
                self.contractRes[tsA] = calculateContractRes(tsA, tsB)
                tensorSet.remove(tsB)

            return self.greedySeq

        def capping():
            obj_n = (1 << n)
            new_flag = [True] * obj_n
            if (typicalDim is None):
                chi_min = min([min(x) for x in shapes])
            else:
                chi_min = typicalDim
            # mu_cap = 1
            mu_old = 0
            mu_new = 1

            obj_list = [[] for i in range(n + 1)]
            obj_list[1] = [(1 << x) for x in range(n)]
            full_s = (1 << n) - 1

            def obj_iterator(c1, c2):
                if (len(obj_list[c1]) <= 0) or (len(obj_list[c2]) <= 0):
                    return
                if (c1 == c2):
                    cur1 = 1
                    cur2 = 0
                    while (cur1 < len(obj_list[c1])):
                        yield (obj_list[c1][cur2], obj_list[c1][cur1])
                        cur2 += 1
                        if (cur2 >= cur1):
                            cur1 += 1
                            cur2 = 0
                    return
                else:
                    cur1 = 0
                    cur2 = 0
                    while (cur1 < len(obj_list[c1])):
                        #print('c1 = {}, c2 = {}, cur1 = {}, cur2 = {}'.format(c1, c2, cur1, cur2))
                        yield (obj_list[c1][cur1], obj_list[c2][cur2])
                        cur2 += 1
                        if (cur2 >= len(obj_list[c2])):
                            cur1 += 1
                            cur2 = 0
                    return

            while (len(obj_list[-1]) == 0):
                #print('mu = {}'.format(mu_new))
                mu_next = mu_new
                # print('mu = {}'.format(mu_new))
                for c in range(2, n + 1):
                    for c1 in range(1, c // 2 + 1):
                        c2 = c - c1
                        for t1, t2 in obj_iterator(c1, c2):
                            #print('t1 = {}, t2 = {}'.format(t1, t2))
                            if ((t1 & t2) != 0):
                                continue
                            if (isDiagonalOuterProduct(self.contractRes[t1],
                                                       self.contractRes[t2])):
                                # diagonal outer product is banned
                                continue
                            tt = t1 | t2
                            if (self.contractRes[tt] is None):
                                self.contractRes[tt] = calculateContractRes(
                                    t1, t2)

                            if (new_flag[t1] or new_flag[t2]):
                                mu_0 = 0
                            else:
                                mu_0 = mu_old

                            mu_curr = self.optimalCost[t1] + self.optimalCost[
                                t2] + costFunc(t1, t2)
                            if (mu_curr > mu_new):
                                if (mu_next is None) or (mu_next > mu_curr):
                                    mu_next = mu_curr
                                continue

                            if (mu_curr > mu_0) and (mu_curr <= mu_new):
                                if (self.optimalCost[tt] is None):
                                    obj_list[c].append(tt)
                                    #print('append {} to {}'.format(tt, c))
                                if (self.optimalCost[tt] is None) or (
                                        self.optimalCost[tt] > mu_curr):
                                    self.optimalCost[tt] = mu_curr
                                    self.optimalSeq[tt] = self.optimalSeq[
                                        t1] + self.optimalSeq[t2] + [
                                            (lowbitI(t1), lowbitI(t2))
                                        ]
                                    new_flag[tt] = True

                mu_old = mu_new
                mu_new = max(mu_next, mu_new * chi_min)
                for c in range(n + 1):
                    for tt in obj_list[c]:
                        new_flag[tt] = False

                #print('cost of {} = {}'.format(full_s, self.optimalCost[full_s]))
                #print('length of obj_list = {}'.format([len(x) for x in obj_list]))
            # print('minimum cost = {}'.format(self.optimalCost[full_s]))
            #print('result = {}'.format(self.contractRes[full_s]))
            # print('optimal cost = {}'.format(self.optimalCost[full_s]))
            return self.optimalSeq[full_s]

        if (greedy):
            return greedySearch()
        elif (bf):
            return bruteForce()
        else:
            return capping()
コード例 #15
0
def tensorSVDDecomposition(a,
                           rows=None,
                           cols=None,
                           innerLabels=None,
                           preserveLegs=False,
                           chi=16,
                           errorOrder=2):
    """
    Decompose a tensor into two isometry tensors and one diagonal tensor, according to SVD decomposition. Namely, a = u @ s @ v.

    Parameters
    ----------
    a : Tensor

    rows, cols : None or list of str
        The labels of tensor a that form the rows and cols of the matrix to be decomposed. For more information, please check Tensor.toMatrix.
    innerLabels : None or length-2 tuple of str
        If not None, then gives the two labels for tensor u and v pointing to s, otherwise they are set to be "inner:" + auto-generated name from combined rows and cols. Note that, the legs of tensor s are always named as auto-generated labels, so that for rank-2 tensor a, the "form" will not change between a and s.
    preserveLegs(not activated yet) : bool, default False, currently always False
        If True, then the outer legs for u and v will be kept from the legs of a. This is somehow dangerous, since one leg is used twice, both in u(v) and a, so use this only when len(rows) and len(cols) is 1, and when you want to drop out tensor a.
        The usage of this parameter will lead to important changes in Tensor deduction logic. Since the usage is not found yet, we temporarily set this flag to be always False.
    chi : int, default 16
        Maximum bond dimension of inner legs.
    errorOrder : int, default 2
        The order of error in singular value decomposition. The error will be calculated as (s[chi:] ** errorOrder).sum() / (s ** errorOrder).sum().

    Returns
    -------
    d : dict of keys {"u", "s", "v", "error"}
        d["u"], d["v"] : rank-2 Tensor
        
        d["s"] : rank-2 DiagonalTensor
            a ~= d["u"] @ d["s"] @ d["v"]
        d["error"] : float
            The error from SVD process.
    """

    preserveLegs = False

    funcName = "CTL.tensor.tensorFuncs.tensorSVDDecomposition"
    aMat = a.toMatrix(rows=rows, cols=cols)
    # print('aMat = {}'.format(aMat))
    rowName = '|'.join(rows)
    colName = '|'.join(cols)

    rowLeg = None
    colLeg = None
    if preserveLegs:
        if len(rows) == 1:
            rowLeg = a.getLeg(rows[0])
        else:
            warnings.warn(
                funcs.warningMessage(
                    'cannot preserve leg object when rows is more than one legs {}'
                    .format(rows),
                    location=funcName), RuntimeWarning)

        if len(cols) == 1:
            colLeg = a.getLeg(cols[0])
        else:
            warnings.warn(
                funcs.warningMessage(
                    'cannot preserve leg object when columns is more than one legs {}'
                    .format(cols),
                    location=funcName), RuntimeWarning)
        # take the leg of row for new tensor

    if (innerLabels is not None):
        assert (
            isinstance(innerLabels, tuple) and len(innerLabels) == 2
        ), funcs.errorMessage(
            "inner labels can either be None or length-2 tuple, {} obtained.".
            format(innerLabels),
            location=funcName)
        rowLabel, colLabel = innerLabels
    else:
        rowLabel = 'inner:' + rowName
        colLabel = 'inner:' + colName

    u, s, vh, error = SVDDecomposition(aMat,
                                       chi=chi,
                                       returnSV=True,
                                       errorOrder=errorOrder)

    if (preserveLegs):
        uTensor = Tensor(data=u,
                         labels=[rowName, rowLabel],
                         legs=[rowLeg, None])
        sTensor = DiagonalTensor(data=s, labels=[rowName, colName])
        vTensor = Tensor(data=vh,
                         labels=[colLabel, colName],
                         legs=[None, colLeg])
    else:
        uTensor = Tensor(data=u, labels=[rowName, rowLabel])
        sTensor = DiagonalTensor(data=s, labels=[rowName, colName])
        # sTensor = Tensor(data = xplib.xp.diag(s), labels = [rowName, colName])
        vTensor = Tensor(data=vh, labels=[colLabel, colName])

    return {'u': uTensor, 's': sTensor, 'v': vTensor, 'error': error}