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):]
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)
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):]
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]
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
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
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
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)
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)
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
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
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)
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))
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()
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}